how can I simplify button inputs without allowing bouncing & not using delay()

I have 11 buttons on a single project and based on my limited knowledge of coding I feel like it can easily be simplified without introducing a problem with bouncing and also without adding delays.

My full code will be below but the overall logic I use today is to set a variable for the current button state and the previous button state for each button. Then per loop() set the current state = digitalRead of my button.

I then compare the current state to the previous state and if it is different I perform an action. This is getting very long with having 11 buttons and am looking for how I can simplify.

Code was to long to post with description. Added as comment.

Scan you switches every 50ms, this is almost always enough to cope with switch bounce.

This is placed in loop()

//****************************
  //time to check the switches ?  every 50ms
  if (currentMillis - switchMillis > 50)
  {
    //restart the TIMER
    switchMillis = currentMillis;

    checkSwitches();
  }

No matter how I try and post my code it says its over 9000 char even when I check that its under that limit… So I cut out 2 parts that shouldnt matter when it comes to understanding my setup. I cut out the #defines of the pins

int passwordPos1UpState = 0; // current state of the 1st pos up button
int passwordPos2UpState = 0;
int passwordPos3UpState = 0;
int passwordPos4UpState = 0;
int passwordPos5UpState = 0;
int passwordPos1DownState = 0; // current state of the 1st pos Down button
int passwordPos2DownState = 0;
int passwordPos3DownState = 0;
int passwordPos4DownState = 0;
int passwordPos5DownState = 0;
int passwordSubmitState = 0; // current state of the submit button
int lastPasswordPos1UpState = 0; // previous state of the 1st pos up button
int lastPasswordPos2UpState = 0;
int lastPasswordPos3UpState = 0;
int lastPasswordPos4UpState = 0;
int lastPasswordPos5UpState = 0;
int lastPasswordPos1DownState = 0; // previous state of the 1st pos Down button
int lastPasswordPos2DownState = 0;
int lastPasswordPos3DownState = 0;
int lastPasswordPos4DownState = 0;
int lastPasswordPos5DownState = 0;

int lastPasswordSubmitState = 0; // previous state of the submit button

char displayVals[5] = {0,0,0,0,0};

char positionVals[5][6] = {
 {0,0,0,0,0,0},
 {0,0,0,0,0,0},
 {0,0,0,0,0,0},
 {0,0,0,0,0,0},
 {0,0,0,0,0,0}
};

int currentPositionPos[] = {0,0,0,0,0};

char validWords[35][5] = {
 {'a','b','o','u','t'},
 {'a','f','t','e','r'},
 {'a','g','a','i','n'},
 {'b','e','l','o','w'},
 {'c','o','u','l','d'},
 {'e','v','e','r','y'},
 {'f','i','r','s','t'},
 {'f','o','u','n','d'},
 {'g','r','e','a','t'},
 {'h','o','u','s','e'},
 {'l','a','r','g','e'},
 {'l','e','a','r','n'},
 {'n','e','v','e','r'},
 {'o','t','h','e','r'},
 {'p','l','a','c','e'},
 {'p','l','a','n','t'},
 {'p','o','i','n','t'},
 {'r','i','g','h','t'},
 {'s','m','a','l','l'},
 {'s','o','u','n','d'},
 {'s','p','e','l','l'},
 {'s','t','i','l','l'},
 {'s','t','u','d','y'},
 {'t','h','e','i','r'},
 {'t','h','e','r','e'},
 {'t','h','e','s','e'},
 {'t','h','i','n','g'},
 {'t','h','i','n','k'},
 {'t','h','r','e','e'},
 {'w','a','t','e','r'},
 {'w','h','e','r','e'},
 {'w','h','i','c','h'},
 {'w','o','r','l','d'},
 {'w','o','u','l','d'},
 {'w','r','i','t','e'}
};

void setup() 
{
  Serial.begin(9600);
  pinMode(PIN_PASSWORD_LED_FIN, OUTPUT);
  pinMode(PIN_PASSWORD_BUTTON_1, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_2, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_3, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_4, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_5, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_6, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_7, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_8, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_9, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_10, INPUT);
  pinMode(PIN_PASSWORD_BUTTON_SUBMIT, INPUT);
  
  randomSeed(analogRead(0));
  genCorrectLetters();
  genRandLetters(0);
  genRandLetters(1);
  genRandLetters(2);
  genRandLetters(3);
  genRandLetters(4);
  setInitDisplayLetters(0);
  setInitDisplayLetters(1);
  setInitDisplayLetters(2);
  setInitDisplayLetters(3);
  setInitDisplayLetters(4);
  
  Serial.println("Display Letters");
  for(int i = 0; i < 5; i++)
  {
    Serial.print(displayVals[i]);
  }
  Serial.println("");
  Serial.println("Correct Letters");
  for(int i = 0; i < 5; i++)
  {
    Serial.print(positionVals[i][0]);
  }
  Serial.println("");
  Serial.println("POS1 Letters");
  for(int i = 0; i < 6; i++)
  {
    Serial.print(positionVals[0][i]);
  }
  Serial.println("");
  Serial.println("POS2 Letters");
  for(int i = 0; i < 6; i++)
  {
    Serial.print(positionVals[1][i]);
  }
  Serial.println("");
  Serial.println("POS3 Letters");
  for(int i = 0; i < 6; i++)
  {
    Serial.print(positionVals[2][i]);
  }
  Serial.println("");
  Serial.println("POS4 Letters");
  for(int i = 0; i < 6; i++)
  {
    Serial.print(positionVals[3][i]);
  }
  Serial.println("");
  Serial.println("POS5 Letters");
  for(int i = 0; i < 6; i++)
  {
    Serial.print(positionVals[4][i]);
  }
  Serial.println("");
}

void setInitDisplayLetters(int p) 
{
  byte randomLetter = random(0,6);
  displayVals[p] = positionVals[p][randomLetter];
  currentPositionPos[p] = randomLetter;
}

void setDisplayLetters(int bp, bool pm)
{
  int currentLetterPos = currentPositionPos[bp];
  int nextLetterPos;
  if (pm==true) {
    if (currentLetterPos==5) {
      nextLetterPos = 0;
      currentPositionPos[bp] = 0;
    } else {
      nextLetterPos = currentLetterPos++;
      currentPositionPos[bp]++;
    }
  } else if (pm==false) {
    if (currentLetterPos==0) {
      nextLetterPos = 5;
      currentPositionPos[bp] = 5;
    } else {
      nextLetterPos = currentLetterPos--;
      currentPositionPos[bp]--;
    }
  }
  displayVals[bp] = positionVals[bp][nextLetterPos];
  updatePasswordDisplay();
}

void updatePasswordDisplay()
{
  for(int i = 0; i < 5; i++)
  {
    Serial.print(displayVals[i]);
  }
  Serial.println("");
}

void genCorrectLetters()
{
  byte randomWord = random(0,35);
  int positionPopulated = 0;
  while (positionPopulated<5)
  {
    char letter = validWords[randomWord][positionPopulated];
    positionVals[positionPopulated][0] = letter;
    positionPopulated ++;
  }
}

void genRandLetters(int p)
{
  int generated=1;
  while (generated<6)
  {
     byte randomValue = random(0, 26);
     char letter = randomValue + 'a';
     positionVals[p][generated] = letter;
     generated ++;
  }
}

void loop() 
{
  passwordPos1UpState = digitalRead(PIN_PASSWORD_BUTTON_1);
  passwordPos2UpState = digitalRead(PIN_PASSWORD_BUTTON_2);
  passwordPos3UpState = digitalRead(PIN_PASSWORD_BUTTON_3);
  passwordPos4UpState = digitalRead(PIN_PASSWORD_BUTTON_4);
  passwordPos5UpState = digitalRead(PIN_PASSWORD_BUTTON_5);
  passwordPos1DownState = digitalRead(PIN_PASSWORD_BUTTON_6);
  passwordPos2DownState = digitalRead(PIN_PASSWORD_BUTTON_7);
  passwordPos3DownState = digitalRead(PIN_PASSWORD_BUTTON_8);
  passwordPos4DownState = digitalRead(PIN_PASSWORD_BUTTON_9);
  passwordPos5DownState = digitalRead(PIN_PASSWORD_BUTTON_10);
  passwordSubmitState = digitalRead(PIN_PASSWORD_BUTTON_SUBMIT);
  
//UP BUTTONS//
// compare passwordPos1UpState to previous state
  if (passwordPos1UpState != lastPasswordPos1UpState) {
    if (passwordPos1UpState == HIGH) {
      setDisplayLetters(0, true);
    }
  }
  lastPasswordPos1UpState = passwordPos1UpState;

// compare passwordPos2UpState to previous state
  if (passwordPos2UpState != lastPasswordPos2UpState) {
    if (passwordPos2UpState == HIGH) {
      setDisplayLetters(1, true);
    }
  }
  lastPasswordPos2UpState = passwordPos2UpState;

// compare passwordPos3UpState to previous state
  if (passwordPos3UpState != lastPasswordPos3UpState) {
    if (passwordPos3UpState == HIGH) {
      setDisplayLetters(2, true);
    }
  }
  lastPasswordPos3UpState = passwordPos3UpState;
  
// compare passwordPos4UpState to previous state
  if (passwordPos4UpState != lastPasswordPos4UpState) {
    if (passwordPos4UpState == HIGH) {
      setDisplayLetters(3, true);
    }
  }
  lastPasswordPos4UpState = passwordPos4UpState;
  
// compare passwordPos5UpState to previous state
  if (passwordPos5UpState != lastPasswordPos5UpState) {
    if (passwordPos5UpState == HIGH) {
      setDisplayLetters(4, true);
    }
  }
  lastPasswordPos5UpState = passwordPos5UpState;

//DOWN BUTTONS//
// compare passwordPos1DownState to previous state
  if (passwordPos1DownState != lastPasswordPos1DownState) {
    if (passwordPos1DownState == HIGH) {
      setDisplayLetters(0, false);
    }
  }
  lastPasswordPos1DownState = passwordPos1DownState;

// compare passwordPos2DownState to previous state
  if (passwordPos2DownState != lastPasswordPos2DownState) {
    if (passwordPos2DownState == HIGH) {
      setDisplayLetters(1, false);
    }
  }
  lastPasswordPos2DownState = passwordPos2DownState;

// compare passwordPos3DownState to previous state
  if (passwordPos3DownState != lastPasswordPos3DownState) {
    if (passwordPos3DownState == HIGH) {
      setDisplayLetters(2, false);
    }
  }
  lastPasswordPos3DownState = passwordPos3DownState;
  
// compare passwordPos4DownState to previous state
  if (passwordPos4DownState != lastPasswordPos4DownState) {
    if (passwordPos4DownState == HIGH) {
      setDisplayLetters(3, false);
    }
  }
  lastPasswordPos4DownState = passwordPos4DownState;
  
// compare passwordPos5DownState to previous state
  if (passwordPos5DownState != lastPasswordPos5DownState) {
    if (passwordPos5DownState == HIGH) {
      setDisplayLetters(4, false);
    }
  }
  lastPasswordPos5DownState = passwordPos5DownState;
}

You can use a class that implements the debouncing, and represent each button with an instance of that class.

This is the one I’m using, for example:
Button.ino

Pieter

You can use this sketch as a skeleton.

The ‘structure’ handles all your switch definitions.

//***************************************************************************
//  SwitchFilterinStructure.ino
//  LarryD
//  
//
//  Version   YY/MM/DD   Comments
//  1.00      19/08/14   Running code
//
//
//***************************************************************************
//Simple demonstration showing one way to handle switch scanning and how to filter 
//noise on a pin input. In this case, a switch change less than 25ms is ignored.
//With a switch sample rate of 25ms, input signals > 50ms are guaranteed to be captured.
//Signals 25-50ms might be captured, with signals < 25ms guaranteed not to be captured.
//***************************************************************************

#define noCHANGE               0
#define wasPUSHED              1
#define wasRELEASED            2

#define PUSHED                 LOW  // +5V---Internal Pullup---PIN---[switch]---GND

//***************************************************************
//Switch 'structure' definition
struct defineSwitch
{
  const byte    pin;           //physical pin being used
  const byte    pushedLevel;   //pin level when the switch is pushed
  byte          lastState;     //last state the switch was in
  byte          counter;       //switch filtering count
  unsigned long pushedMillis;  //how long the switch was pressed for
};

//define all switches here
defineSwitch    modeSwitch           = {5, PUSHED, !PUSHED, 0, 0}; 

//***************************************************************

const byte      heartBeatLED         = 13;
const byte      myLED                = 12;

byte            testCounter;

//Timing stuff
unsigned long   currentMillis;
unsigned long   switchMillis;
unsigned long   heartBeatMillis;

const unsigned long heartBeatDelay   = 250UL; //toggle LED every 1/4 second
const unsigned long switchDelay      = 25UL;  //read switch(es) every 25ms

//                              s e t u p ( ) 
//***************************************************************************
void setup()
{
  Serial.begin(9600);

  pinMode (heartBeatLED, OUTPUT);
  pinMode (myLED, OUTPUT);

  pinMode (modeSwitch.pin, INPUT_PULLUP);
  modeSwitch.lastState = digitalRead(modeSwitch.pin);

} //END of                      s e t u p ( )

//                               l o o p ( )
//***************************************************************************
void loop()
{
  //record the Arduino time
  currentMillis = millis();

  //***************************
  //The ‘Heart Beat LED’, should toggle every heartBeatDelay milliseconds if code is non-blocking
  if (currentMillis - heartBeatMillis >= heartBeatDelay)
  {
    //reset timing
    heartBeatMillis = heartBeatMillis + heartBeatDelay;

    //Toggle heartBeatLED
    digitalWrite(heartBeatLED, !digitalRead(heartBeatLED));
  }

  //***************************
  checkSwitches();

  //***************************
  // Other non-blocking code
  //***************************

} //END of                        l o o p  ( )

//                       c h e c k S w i t c h e s ( )
//***************************************************************************
void checkSwitches()
{
  //***************************
  //Time to check the switches?
  if (currentMillis - switchMillis < switchDelay)
  {
    //it is not time yet
    return;
  }

  //restart the TIMER
  switchMillis = switchMillis + switchDelay;

  //***************************         >>>>----->  m o d e S w i t c h
  switch (checkThisSwitch(modeSwitch))
  {
    case wasPUSHED:
      {
        testCounter++;
        Serial.println(testCounter);

        //toggle the LED
        digitalWrite(myLED, !digitalRead(myLED));
      }
      break;

    case wasRELEASED:
      {
        //the time the switch 'remained' pressed
        Serial.println(modeSwitch.pushedMillis);
        Serial.println();
      }
      break;
  }

  //***************************
  //Other switches go here
  //***************************

} //END of                c h e c k S w i t c h e s ( )

//                     c h e c k T h i s S w i t c h ( )
//***************************************************************************
//handles switch change in state, timing and filtering
byte checkThisSwitch(defineSwitch &SWITCH)
{
  //***************************
  byte currentState = digitalRead(SWITCH.pin);

  //has the switch changed state ?
  if (SWITCH.lastState == currentState)
  {
    //no change, reset the filter counter
    SWITCH.counter = 0;

    //there was no change detected
    return noCHANGE;
  }

  //there was a switch change in state
  SWITCH.counter++;

  //was this the 2nd sequential time we detected the change
  if (SWITCH.counter > 1)
  {
    //a valid switch change was detected
    //get ready for the next 2 filter samples
    SWITCH.counter = 0;

    //update the switch state 
    SWITCH.lastState = currentState;

    //was the switch pushed ?
    if (currentState == SWITCH.pushedLevel)
    {
      //record the time the switch was pushed
      SWITCH.pushedMillis = currentMillis;

      return wasPUSHED;
    }

    else
    {
      //the switch was released
      //save the time the switch 'remained' pressed
      SWITCH.pushedMillis = currentMillis - SWITCH.pushedMillis;

      return wasRELEASED;
    }

  } //END of if(SWITCH.counter > 1)

  //there was no valid change detected
  return noCHANGE;

} //END of              c h e c k T h i s S w i t c h ( )

//***************************************************************************

See The simple way to program for multiple buttons

cwegrecki:
No matter how I try and post my code it says its over 9000 char even when I check that its under that limit...

In that case, you are supposed to add it as an attachment.

larryd:
You can use this sketch as a skeleton.

The ‘structure’ handles all your switch definitions.

//***************************************************************************

//  SwitchFilterinStructure.ino
//  LarryD
// 
//
//  Version  YY/MM/DD  Comments
//  1.00      19/08/14  Running code
//
//
//***************************************************************************
//Simple demonstration showing one way to handle switch scanning and how to filter
//noise on a pin input. In this case, a switch change less than 25ms is ignored.
//With a switch sample rate of 25ms, input signals > 50ms are guaranteed to be captured.
//Signals 25-50ms might be captured, with signals < 25ms guaranteed not to be captured.
//***************************************************************************

#define noCHANGE              0
#define wasPUSHED              1
#define wasRELEASED            2

#define PUSHED                LOW  // +5V—Internal Pullup—PIN—[switch]—GND

//***************************************************************
//Switch ‘structure’ definition
struct defineSwitch
{
  const byte    pin;          //physical pin being used
  const byte    pushedLevel;  //pin level when the switch is pushed
  byte          lastState;    //last state the switch was in
  byte          counter;      //switch filtering count
  unsigned long pushedMillis;  //how long the switch was pressed for
};

//define all switches here
defineSwitch    modeSwitch          = {5, PUSHED, !PUSHED, 0, 0};

//***************************************************************

const byte      heartBeatLED        = 13;
const byte      myLED                = 12;

byte            testCounter;

//Timing stuff
unsigned long  currentMillis;
unsigned long  switchMillis;
unsigned long  heartBeatMillis;

const unsigned long heartBeatDelay  = 250UL; //toggle LED every 1/4 second
const unsigned long switchDelay      = 25UL;  //read switch(es) every 25ms

//                              s e t u p ( )
//***************************************************************************
void setup()
{
  Serial.begin(9600);

pinMode (heartBeatLED, OUTPUT);
  pinMode (myLED, OUTPUT);

pinMode (modeSwitch.pin, INPUT_PULLUP);
  modeSwitch.lastState = digitalRead(modeSwitch.pin);

} //END of                      s e t u p ( )

//                              l o o p ( )
//***************************************************************************
void loop()
{
  //record the Arduino time
  currentMillis = millis();

//***************************
  //The ‘Heart Beat LED’, should toggle every heartBeatDelay milliseconds if code is non-blocking
  if (currentMillis - heartBeatMillis >= heartBeatDelay)
  {
    //reset timing
    heartBeatMillis = heartBeatMillis + heartBeatDelay;

//Toggle heartBeatLED
    digitalWrite(heartBeatLED, !digitalRead(heartBeatLED));
  }

//***************************
  checkSwitches();

//***************************
  // Other non-blocking code
  //***************************

} //END of                        l o o p  ( )

//                      c h e c k S w i t c h e s ( )
//***************************************************************************
void checkSwitches()
{
  //***************************
  //Time to check the switches?
  if (currentMillis - switchMillis < switchDelay)
  {
    //it is not time yet
    return;
  }

//restart the TIMER
  switchMillis = switchMillis + switchDelay;

//***************************        >>>>----->  m o d e S w i t c h
  switch (checkThisSwitch(modeSwitch))
  {
    case wasPUSHED:
      {
        testCounter++;
        Serial.println(testCounter);

//toggle the LED
        digitalWrite(myLED, !digitalRead(myLED));
      }
      break;

case wasRELEASED:
      {
        //the time the switch ‘remained’ pressed
        Serial.println(modeSwitch.pushedMillis);
        Serial.println();
      }
      break;
  }

//***************************
  //Other switches go here
  //***************************

} //END of                c h e c k S w i t c h e s ( )

//                    c h e c k T h i s S w i t c h ( )
//***************************************************************************
//handles switch change in state, timing and filtering
byte checkThisSwitch(defineSwitch &SWITCH)
{
  //***************************
  byte currentState = digitalRead(SWITCH.pin);

//has the switch changed state ?
  if (SWITCH.lastState == currentState)
  {
    //no change, reset the filter counter
    SWITCH.counter = 0;

//there was no change detected
    return noCHANGE;
  }

//there was a switch change in state
  SWITCH.counter++;

//was this the 2nd sequential time we detected the change
  if (SWITCH.counter > 1)
  {
    //a valid switch change was detected
    //get ready for the next 2 filter samples
    SWITCH.counter = 0;

//update the switch state
    SWITCH.lastState = currentState;

//was the switch pushed ?
    if (currentState == SWITCH.pushedLevel)
    {
      //record the time the switch was pushed
      SWITCH.pushedMillis = currentMillis;

return wasPUSHED;
    }

else
    {
      //the switch was released
      //save the time the switch ‘remained’ pressed
      SWITCH.pushedMillis = currentMillis - SWITCH.pushedMillis;

return wasRELEASED;
    }

} //END of if(SWITCH.counter > 1)

//there was no valid change detected
  return noCHANGE;

} //END of              c h e c k T h i s S w i t c h ( )

//***************************************************************************

What an interesting way to store those values. I would never have thought of storing it that way but it surely does make it much easier to add more buttons as needed without rewriting the entire thing or it getting infinitely longer with each new button. For example right now I have 11 but by the end will have close to 20 buttons total. So really neat method. Thanks for sharing.

My favorite:

void loop()
{
  unsigned long currentMillis = millis();
  static unsigned long lastStateChangeTime = 0;
  const unsigned debounceIntervsl = 10;

  boolean buttonName_isPushed = digitalRead(ButtonName_DIPin) == LOW; // INPUT_PULLUP, active LOW
  static boolean buttonName_wasPushed = false;

  if (buttonName_isPushed != buttonName_wasPushed && currentMillis - lastStateChangeTime >= debounceInterval)
  {
    lastStateChangeTime = currentMillis;
    buttonName_wasPushed = buttonName_isPushed;
    if (buttonName_isPushed)
    {
      // Button just pushed
    }
    else
    {
      // Button just released
    }
  }
}

You can use the same lastStateChangeTime for all buttons. For a bunch of buttons that are treated as a set I would make a global const byte array of pin numbers, a global boolean array of 'isPushed', and a global boolean array of 'wasPushed'.