How to add debounce on more than one button?

I am making a keypad using the pro micro as an HID device for the game "osu!"

I am adding up to 6 buttons on each individual pin. I am using a pull-down resistor.

I used the debounce library and made my own code from it. Is there anyway to optimize the code such that it reduces space like using an array for example?

Here is the code for one button. I use this function for all the buttons and put them in a loop.

#include "Keyboard.h"
const byte button1Pin = 2;    // the number of the pushbutton pin
const byte button2Pin = 3;


// Variables will change:
byte button1State;            // the current reading1 from the input pin
byte button2State;
byte lastButtonState[7] = {LOW, LOW, LOW, LOW, LOW, LOW};   // the previous reading1 from the input pin

// 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.
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 10;    // the debounce time; increase if the output flickers


void button1()
{
  // read the state of the switch into a local variable:
  int reading1 = digitalRead(button1Pin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading1 != lastButtonState[0]) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading1 is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading1 != button1State) {
      button1State = reading1;
      // only toggle the LED if the new button state is HIGH
      if (button1State == HIGH) // HIGH because when pressing the button, 5v will connect from the button to the pin. also the resistor is connected to the ground so when button is pressed, the path to least resistance is the volts
      {
        Keyboard.press('x');
      }
      else
      {
        Keyboard.release('x');
      }
    }
  }

for game controller make hardware debouncer and use interrupts in software

I don't have the materials for making a hardware debounce sadly.

use a button library :slight_smile:

The MoToButtons class of the MobaTools library debounces and manages up to 32 buttons in one instance.
If you don't need anything else of MobaTools, you can copy MoToButtons.h into your sketch directory. It contains everything you need. Or you install the complete library by means of the library manager.

Are you talking about using multiple buttons on an analog input pin?

1 Like

(johnwasser, I asked myself the question, but it seems that gale_electronics use digital pins).

To optimize the code, if my approach suits you, the 6 keys test is done like this: (this is an example for demo, the code must be adapted a bit in reality)

if (button1 & pressed) {action...}
if (button2 & pressed) {action...}

if (button1 & released) {action...}

You need pullups (use the internal ones, see setup), and the buttons are wired between the pin and the ground (for security, I add a 1 k resistor between the pin and the button)

#define button1 4                                               // binary value of the pushbutton pin 2
#define button2 8                                               // binary value of the pushbutton pin 3
#define button3 16                                              // binary value of the pushbutton pin 4
#define button4 32                                              // binary value of the pushbutton pin 5
#define button5 64                                              // binary value of the pushbutton pin 6
#define button6 128                                             // binary value of the pushbutton pin 7

byte Down, Stat, Up, kkk;                                       // variables for 6 keys together (max 16)
    
void setup() {                                                  // INITIALIZATION CODE 
  PORTD |= button1|button2|button3|button4|button5|button6;     // PD7..2 with pullups (pin7....pin2)
  Serial.begin(115200);                                         // (9600 is the default serial-monitor)
}

void loop() {                                                   // MAIN CODE 
  detectButtons();                                              // is a button pressed or released ?
  if (button1 & Down) Serial.println("KEY 1 PRESSED+++");       // your treatment  
  if (button1 & Up)   Serial.println("Key 1 released--");       // ...
}  


void detectButtons (void) {                                     // debounce, state, and front detections
  delay(20);                                                    // delay for demo...
  byte iii = ~PIND & 0xFC;                                      // reading PORTD pin 7..2 (with pullups)
  byte jjj = Stat;                                              // 
  Stat     = iii & kkk;                                         // while key is maintained pressed
  Down     = (jjj ^ Stat) & Stat;                               // when key is just pressed down
  Up       = (iii ^ jjj) & jjj;                                 // when key is released
  kkk      = iii;                                               // key configuration stored for next use
}

Yikes! They are using Direct Port Access to read pins 2 through 7. That's not "6 buttons on each individual pin". That's like setting pins 2 through 7 to "INPUT_PULLUP" and using digitalRead() on each one.

Yes, of course, johnwasser!

gale_electronics uses digital pins 2 and 3 in his example, it doesn't seem to be 6 buttons on an analog pin (with resistors), so I condensed the code (with a direct digital reading) since he wanted to optimize the size.

(but maybe I'm wrong)?

an array will not reduce "space" but you can rid of your duplicated code.

step zero:
arrays start at 0 not 1.

step 1 you want some arrays - make some arrays

byte buttonState[7];
const byte buttonPin[7] {2, 3, 4, 5, 6, 7, 8};
byte lastButtonState[7] = {LOW, LOW, LOW, LOW, LOW, LOW};
unsigned long lastDebounceTime[7]; 

adopt your function and handover the index (starting with 0!!!)

pseudo code - untested:

void button(byte i)
{
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin[i]);
  if (reading != lastButtonState[i]) {
    lastDebounceTime[i] = millis();
  }
  if ((millis() - lastDebounceTime[i]) > debounceDelay) {
    if (reading != buttonState[i]) {
      buttonState[i] = reading;
      if (buttonState[i] == HIGH) // HIGH because when pressing the button, 5v will connect from the button to the pin. also the resistor is connected to the ground so when button is pressed, the path to least resistance is the volts
      {
        Keyboard.press('x');
      }
      else
      {
        Keyboard.release('x');
      }
    }
  }
}

step 2
bundle the arrays into a structure

step 3
make a class with member function

step 42
never start variable numbering with 1 again :wink:

Nice. Now write one without a honking delay.

You’ll want to have a timer on each input; since you appear to enjoy getting down with the code, I suggest you implement vertical counters - it would fit right in with you byte-wide approach.

a7

1 Like

alto777, how do you make a debounce function without delay?

you should have learned that from the IDE examples:
https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce

Hi noiasca, in this IDE example, I read:
"how to debounce an input, which means checking twice in a short period of time"
"Short period of time" isn't a delay ?

you asked

The IDE example debounces without a delay. Or do you see a delay() in the sketch?

your new question

no a short period is not a delay.
The microocontroller can use this short period of time to do other things. For example to blink a led without delay.

This is how we write non blocking code: using millis() - avoiding delay().

1 Like

No problem! I made it clear that my first example was a quick demo, where I used the delay() function for convenience, to display the activated keys on the terminal-series. If you want the non-blocking version, it's not too difficult:

#define button1 4                                               // binary value of the pushbutton pin 2
#define button2 8                                               // binary value of the pushbutton pin 3
#define button3 16                                              // binary value of the pushbutton pin 4
#define button4 32                                              // binary value of the pushbutton pin 5
#define button5 64                                              // binary value of the pushbutton pin 6
#define button6 128                                             // binary value of the pushbutton pin 7
#define allButtons 4 + 8 + 16 + 32 + 64 + 128                   // binary value of all buttons together

byte Down, Stat, Up, tempo;                                     // variables for 6 keys together (max 16)
unsigned long previousmillis;                                   // debounce period     
    
void setup() {                                                  // INITIALIZATION CODE 
  PORTD |= allButtons;                                          // PD7..2 with pullups (pin7....pin2)
  Serial.begin(115200);                                         // (9600 is the default serial-monitor)
}

void loop() {                                                   // MAIN CODE 
  detectButtons();                                              // is a button pressed or released ?
}  



void detectButtons (void) {                                     // debounce, state, and front detections
  if (millis() - previousmillis > 20) {                         // xx ms delay for debounce, usually 10 or 20 (ms)
    previousmillis = millis();                                  //

    byte iii = ~PIND & allButtons;                              // reading PORTD pin 7..2 (with pullups)
    byte jjj = Stat;                                            // 
    Stat     = iii & tempo;                                     // while key is maintained pressed
    Down     = (jjj ^ Stat) & Stat;                             // when key is just pressed down
    Up       = (iii ^ jjj) & jjj;                               // when key is released
    tempo    = iii;                                             // key configuration stored for next use
    
    if (button1 & Down) Serial.println("KEY 1 PRESSED+++");     // your treatment  
    if (button1 & Up)   Serial.println("Key 1 released--");     // ...  
  }  
}


Closer. It is still not as good as you can do; you really need a timing mechanism on each input line.

This is illustrated in the code from @noiasca's #10 above.

It was untested code - there were a few thinkos and maybe a show-stopper, I fixed it up like so

void button(byte i)
{
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin[i]);

  if ((millis() - lastDebounceTime[i]) > debounceDelay) {
    if (reading != lastButtonState[i]) {

      lastDebounceTime[i] = millis();

      lastButtonState[i] = reading;
      if (lastButtonState[i] == LOW) // LOW because I wired it to use the internal pull up resistors.
      {
//        Keyboard.press('x');
        Serial.print(i); Serial.println(" pressed");
      }
      else
      {
//        Keyboard.release('x');
        Serial.print(i); Serial.println(" released");
      }
    }
  }
}

and threw it into the wokwi where it seems to function.

To understand why you need want a timer on each input, crank up the debounce interval you've coded to 500 ms or so and play with the buttons...

And since you've done a broadside approach to multiple pins using direct port and bit manipulation, I thought the vertical counters would be perfect.

a7

2 Likes

alto777

And since you've done a broadside approach to multiple pins using direct port and bit manipulation, I thought the vertical counters would be perfect.

Show us how you add vertical counters on my code, because I'm not sure I understand the benefit of it. Of course, there are many ways to manage buttons, but the one I use works perfectly and it can't be shorter! (that's the purpose of this particular code : to be short, 6 lines for 1 to 16 independent buttons). Why do you want to add counters on each bit ?

To understand why you need want a timer on each input, crank up the debounce interval you've coded to 500 ms or so and play with the buttons...

I'm trying to visualize what would happen with a 500ms (or even longer) debounce interval, but it wouldn't change the way the buttons work, they would be detected in the same way, independently of each other. Try it in a wokwi! (I don't know how to do it yet ; but wokwi and real time are not very compatible! )

I'm still trying to figure out how to reply to a particular writer, with his name at the beginning of the message
OK, I just figured it out

Yeah I put them on a digital pin since three pins are already occupied by the rgb led