Execute while a button is pressed

I would like to execute an instruction only while a button is held in. I need to do this for several buttons in my program but I am stuck as to the most efficient way to do it. At present, I debounce the button and write its state to a variable. I am stuck as to how I can manage these buttons in my code?


int LHTopLimit; 
int LHObsLimit;
int LHBotLimit; 
int RHTopLimit; 
int RHObsLimit; 
int RHBotLimit; 
int UPButton; 
int DWNButton; 
int LEDPIN; 

byte buttons[] = {2,3,4,5,6,7,8,9}; // pin numbers of the buttons that we'll use
#define NUMBUTTONS sizeof(buttons)
#define LEDPIN 13
int buttonState[NUMBUTTONS];
int lastButtonState[NUMBUTTONS];
boolean buttonIsPressed[NUMBUTTONS];  
// the following variables are unsigned longs because the time, measured in
// milliseconds, will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 50; // the debounce time; increase if the output flickers
int ledState = HIGH;

int names[] = {LHTopLimit, LHObsLimit, LHBotLimit, RHTopLimit, RHObsLimit, RHBotLimit, UPButton, DWNButton};
int *ptr = names;
#define NAMBUTTONS sizeof(names)
int currentNames;
int j;

void setup() {
  Serial.begin(9600);
  // define pins:
  for (int i=0; i<(NUMBUTTONS-1); i++) {
    pinMode(i, INPUT);
    lastButtonState[i]=LOW;
    buttonIsPressed[i]=false; 
  }
}

void loop()
{
  LHTopLimit=false;
  check_buttons();
  action();
  action2();
 
}

void check_buttons() {
  for (int currentButton=0; currentButton<NUMBUTTONS; currentButton++) {
    // read the state of the switch into a local variable:
    int reading = digitalRead(buttons[currentButton]); // check to see if you just pressed the button and you've waited long enough since the last press to ignore any noise:
    // If the switch changed, due to noise or pressing then reset the debouncing timer
    if (reading != lastButtonState[currentButton]) { lastDebounceTime = millis(); }
    // whatever the reading is at, it's been there for longer than the debounce delay, so take it as the actual current state:
    if ((millis() - lastDebounceTime) > debounceDelay) {
      // if the button state has changed:
      if (reading != buttonState[currentButton]) {
        buttonState[currentButton] = reading;
        if (buttonState[currentButton]==HIGH) { //pushing down on the button
          buttonIsPressed[currentButton]=true; // set your flag for the adjustment function
        }
      }
    }
    // save the reading.  Next time through the loop, it'll be the lastButtonState:
    lastButtonState[currentButton] = reading;
  }
}

void action() {

  for (int currentButton=0; currentButton<NUMBUTTONS; currentButton++) {
    if (buttonIsPressed[currentButton]) {
      // Serial.print("button "); Serial.println(buttons[currentButton]); 
      switch (buttons[currentButton]) {
      case 2:
        LHTopLimit=1;
        break;
      case 3:
        LHObsLimit=1;
        break;
      case 4:
        LHBotLimit=1;
        break;
      case 5:
        RHTopLimit=1;
        break;
      case 6:
        RHObsLimit=1;
        break;
      case 7:
        RHBotLimit=1;
        break;
      case 8:
        UPButton=1;
        break;
      case 9:
        DWNButton=1;
        break;
  }
      buttonIsPressed[currentButton]=false; //reset the button
  }
}
}

void action2() {
  Serial.println(LHTopLimit);
  
}


Your debounce code is correct for a single button only. For a multiple buttons you need to keep a separate lastDebounceTime for each button.

Please be more specific - what is your problem exactly?

Use a button library - it will make your life much easier

Toggle or EasyButton or Bounce2, ...

Many to choose from (I like Toggle)

look this over

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

It is not a debounce

what makes you say that?
this approach works fine

It should check all the buttons in the buttons array,

void check_buttons() {
  for (int currentButton=0; currentButton<NUMBUTTONS; currentButton++) {

and it returns which button has been pressed in the serial monitor.

The issue is that once the button is released, the state is cleared before this loop is finished.

buttonIsPressed[currentButton]=false; //reset the button

I would like to execute some code in another loop further down in the program only while a button is held down.

I would assume I need to write each buttons state to another variable and clear it further down in the program.

The question is how can I do it without having any break in the state of the button unless it is released?

I don’t want to clear it at the bottom and then write it again at the top as this would result in an constant high low cycle.

I would be grateful if you had some ideas of how I can impliment this in an efficient manner. I am trying to learn how to be a bit more efficient or clever with the code, without getting too carried away to the point I can’t understand it?

time the button press and repeat the operation after the button long enough

The point of debounce is to read the button twice with a slight delay and compare the data. And you don't read it the second time, you just wait.

the new read is compared to the current state set by the previous read.

somewhat hesitant to post this because of it's use of funciton pointers, but it also demonstrates

  • use of a struct to describe each command
  • each cmd is defined by a function (using a ptr to that func)
  • each cmd has 2 arguments, one which is void *, a ptr to an undefined type that can be cast within the func
  • each cmd also has a button state and a msec value

while a cmd can provide an LED pin value, the cmd doesn't provide a convenient way to know that, so LEDs are configured outside the cmds

cmd uses the msec value to recognize that a button is being held down allowing the cmd to be repeated. This also provides a delay to ignore bounces.

hopefully you see how the program is data driven, minizing the logic and allowing new cmds to be easily added, as well as new cmd functions

the code only has 3 buttons because my multifunciton board only has 3 buttons. Hopefully is demonstates enough for you to customize for your needs

assume you can ask questions

int LhTopLimit;

const byte PinLeds [] = { 13, 12, 11 };
const int  NLed       = sizeof(PinLeds);

// -----------------------------------------------------------------------------
void incVar (
    int     val,
    void   *adr )
{
    Serial.print   ("    ");
    Serial.println (*(int *)adr += val);        // cast void* to int*
}

// ---------------------------------------------------------
void setVar (
    int     val,
    void   *adr )
{
    Serial.print   ("    ");
    Serial.println (*(int *)adr  = val);        // cast void* to int*
}

// ---------------------------------------------------------
void toggle (
    int     pin,
    void   *_unused_ )
{
    digitalWrite (pin, ! digitalRead (pin));
}

// ---------------------------------------------------------
struct ButCmd {
    const byte Pin;
    void (*func) (int, void*);      // pointer to function
    int         arg1;
    void       *arg2;               // undefined type
    const char *desc;

    byte            butState;
    unsigned long   msec;
}

cmds [] = {
    { A1, toggle, 13, (void*) 0,           "LED-13" },
    { A2, setVar,  1, (void*) & LhTopLimit, "LhTopLimit" },
    { A3, incVar, -1, (void*) & LhTopLimit, "LhTopLimit--" },
};
const int Ncmd = sizeof (cmds)/sizeof(ButCmd);

// -----------------------------------------------------------------------------

void setup ()
{
    Serial.begin (9600);

    for (int i = 0; i < NLed; i++)
        pinMode (PinLeds [i], OUTPUT);

    for (int i = 0; i < Ncmd; i++) {
        pinMode (cmds [i].Pin, INPUT_PULLUP);
        cmds [i].butState = digitalRead (cmds [i].Pin);
    }
}

// -----------------------------------------------------------------------------
const unsigned long MsecTmout = 500;

void loop ()
{
    unsigned long msec = millis ();

    for (int i = 0; i < Ncmd; i++) {
        if (msec - cmds [i].msec < MsecTmout)
            continue;

        byte but = digitalRead (cmds [i].Pin);
        if (cmds [i].butState != but)
            cmds [i].butState  = but;

        if (LOW == but)  {
            cmds [i].msec      = msec;
            Serial.println (cmds [i].desc);
            (*cmds [i].func) (cmds [i].arg1,cmds [i].arg2);
        }

        cmds [i].msec      = msec;
    }
}

Do you want to “execute an instruction” just once after detecting that the button is held in, or repeatedly “execute an instruction” whilst the button is held in?

If the latter, then maybe you don’t need to debounce. E.G. if you just wanted a LED to be on whilst you were pushing a button, there’d be no need to debounce.

Why do you debounce buttons?

Debouncing buttons is not simple, so don't do it unless you need to, and from your description so far, you don't need to.

This would appear to work, but at the pressing down time the LED would be switched on and off rapidly a number of times, then remain on, and at the button release time the LED would be switched off and on rapidly a number of times, then remain off.

Which may be acceptable.

If not, then state change detection and operating when the state says the button is being held down will be necessary.

State change detection needs some kind of treatment of the behaviour of a pushbutton, a crude but effective global debounce can be achieved by inserting a delay in the loop() function. If a slow running loop is acceptable, we done.

Next least complicated is examining the buttons only once every so often using a millis()-based timer.

This is modified @LarryD's heartbeat code, I'm lazy and it works, so:

  //Is it time to read the buttons?
  if (millis() - lastButtonPeek >= buttonPeekInterval)
  {
    //Restart this TIMER.
    lastButtonPeek = millis();

 //Do the thing you only wanna do periodically...

// here read switches and update their corresponding states
// right here. code here runs every buttonPeekInterval milliseconds:

//... 

  }

This assumes

const unsigned long buttonPeekInterval = 20;
unsigned long lastButtonPeek;

a7

Exactly :slight_smile:. That’s why I chose an LED as an example. Maybe I should have clarified by saying “An LED which is used as an indicator, and nothing more”. Maybe your clarification was for the OP’s benefit. I first came across the need to debounce switches when I was teaching myself about 74 series TTL counters as a teenager. I discovered that my switch to the clock input caused multiple clocks. A lesson never to be forgotten. I still have a clear memory of the lightbulb moment when I worked out why it was happening, even though it was 50+ years ago.

When your void loop() is fast you can check 1 button per loop() and still check 10’s of buttons per millisecond… when debounce takes a few ms.

Use millis() or micros() to time debounce because delay( 1 ) wastes 16000 cycles where in a fast loop, the average loop takes a few 100 cycles to run.

When you have a fast loop() running (nothing blocks execution) you can put functions inside of loop() that run as independent tasks, run in parallel without having to be structured in with everything else.

I run my buttons that way and I check 1 button per loop() and all I do is check pin state and update a byte (per pin) that keeps the last 8 states as bits. Other functions can use the state byte to determine if a button has just been pressed, is held down pressed, has just been released, is left up, or … everything else is bounce. My button pins are always mode INPUT_PULLUP so state 0 is closed contact and 1 is open contact.

Bits 10000000 means button was up then down for 7 reads stable, “just” pressed!

Bits 00000000 means held down for the last 8 reads.

Bits 01111111 means button was down then up for 7 reads stable, “just” released!

Bits 11111111 means left up for the last 8 reads.

Everything else is change that has not stabilized, not debounced.

Updating pin history takes a few cycles is all, but it happens a lot. Comparing that byte to a particular condition takes 1 cycle. This is lean and mean.

When the button handling code is not weaved into the part that decides what to do, both parts get easier to debug.

An indicator for eyes that can’t keep up at 24 FPS or faster.

To know the true stable state of the button.

why? the simplest approach is a delay() to ignore the bounces after the initial change in state. Using a timer is not that difficult either (see above)

presumably to change the value of one of his limit variables instead of repeatedly pressing a button