Example code for multi-button checker with debouncing

6button

If you have a lot of button inputs for a project, keeping track of them (whether they’re pressed, just pressed or just released) and debouncing can get a bit hairy. here is some sample code that will keep track of as many buttons as you’d like. The example shows 6. To change the pins or number of buttons, just put them in the array called “buttons” and the rest of the code will automatically adjust. (The code is in Arduino-ese but its pretty much just straight up C)
Enjoy!

 
#define DEBOUNCE 10  // button debouncer, how many ms to debounce, 5+ ms is usually plenty

// here is where we define the buttons that we'll use. button "1" is the first, button "6" is the 6th, etc
byte buttons[] = {14, 15, 16, 17, 18, 19}; // the analog 0-5 pins are also known as 14-19
// This handy macro lets us determine how big the array up above is, by checking the size
#define NUMBUTTONS sizeof(buttons)
// we will track if a button is just pressed, just released, or 'currently pressed' 
byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];

void setup() {
  byte i;
  
  // set up serial port
  Serial.begin(9600);
  Serial.print("Button checker with ");
  Serial.print(NUMBUTTONS, DEC);
  Serial.println(" buttons");

  // pin13 LED
  pinMode(13, OUTPUT);
 
  // Make input & enable pull-up resistors on switch pins
  for (i=0; i< NUMBUTTONS; i++) {
    pinMode(buttons[i], INPUT);
    digitalWrite(buttons[i], HIGH);
  }
}

void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  static long lasttime;
  byte index;

  if (millis() < lasttime) {
     // we wrapped around, lets just try again
     lasttime = millis();
  }
  
  if ((lasttime + DEBOUNCE) > millis()) {
    // not enough time has passed to debounce
    return; 
  }
  // ok we have waited DEBOUNCE milliseconds, lets reset the timer
  lasttime = millis();
  
  for (index = 0; index < NUMBUTTONS; index++) {
    justpressed[index] = 0;       // when we start, we clear out the "just" indicators
    justreleased[index] = 0;
     
    currentstate[index] = digitalRead(buttons[index]);   // read the button
    
    /*     
    Serial.print(index, DEC);
    Serial.print(": cstate=");
    Serial.print(currentstate[index], DEC);
    Serial.print(", pstate=");
    Serial.print(previousstate[index], DEC);
    Serial.print(", press=");
    */
    
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
          // just pressed
          justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
          // just released
          justreleased[index] = 1;
      }
      pressed[index] = !currentstate[index];  // remember, digital HIGH means NOT pressed
    }
    //Serial.println(pressed[index], DEC);
    previousstate[index] = currentstate[index];   // keep a running tally of the buttons
  }
}


void loop() {
  check_switches();      // when we check the switches we'll get the current state
  
  for (byte i = 0; i < NUMBUTTONS; i++) {
    if (justpressed[i]) {
      Serial.print(i, DEC);
      Serial.println(" Just pressed"); 
      // remember, check_switches() will CLEAR the 'just pressed' flag
    }
    if (justreleased[i]) {
      Serial.print(i, DEC);
      Serial.println(" Just released");
      // remember, check_switches() will CLEAR the 'just pressed' flag
    }
    if (pressed[i]) {
      Serial.print(i, DEC);
      Serial.println(" pressed");
      // is the button pressed down at this moment
    }
  }
}

if you want, you can even run the button checker in the background, which can make for a very easy interface. Remember that you’ll need to clear “just pressed”, etc. after checking or it will be “stuck” on

 
#define DEBOUNCE 10  // button debouncer, how many ms to debounce, 5+ ms is usually plenty

// here is where we define the buttons that we'll use. button "1" is the first, button "6" is the 6th, etc
byte buttons[] = {14, 15, 16, 17, 18, 19}; // the analog 0-5 pins are also known as 14-19
// This handy macro lets us determine how big the array up above is, by checking the size
#define NUMBUTTONS sizeof(buttons)
// we will track if a button is just pressed, just released, or 'currently pressed' 
volatile byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];

void setup() {
  byte i;
  
  // set up serial port
  Serial.begin(9600);
  Serial.print("Button checker with ");
  Serial.print(NUMBUTTONS, DEC);
  Serial.println(" buttons");

  // pin13 LED
  pinMode(13, OUTPUT);
 
  // Make input & enable pull-up resistors on switch pins
  for (i=0; i< NUMBUTTONS; i++) {
    pinMode(buttons[i], INPUT);
    digitalWrite(buttons[i], HIGH);
  }

  // Run timer2 interrupt every 15 ms 
  TCCR2A = 0;
  TCCR2B = 1<<CS22 | 1<<CS21 | 1<<CS20;

  //Timer2 Overflow Interrupt Enable
  TIMSK2 |= 1<<TOIE2;

}

SIGNAL(TIMER2_OVF_vect) {
  check_switches();
}

void check_switches()
{
  static byte previousstate[NUMBUTTONS];
  static byte currentstate[NUMBUTTONS];
  static long lasttime;
  byte index;

  if (millis() < lasttime) {
     // we wrapped around, lets just try again
     lasttime = millis();
  }
  
  if ((lasttime + DEBOUNCE) > millis()) {
    // not enough time has passed to debounce
    return; 
  }
  // ok we have waited DEBOUNCE milliseconds, lets reset the timer
  lasttime = millis();
  
  for (index = 0; index < NUMBUTTONS; index++) {
     
    currentstate[index] = digitalRead(buttons[index]);   // read the button
    
    /*     
    Serial.print(index, DEC);
    Serial.print(": cstate=");
    Serial.print(currentstate[index], DEC);
    Serial.print(", pstate=");
    Serial.print(previousstate[index], DEC);
    Serial.print(", press=");
    */
    
    if (currentstate[index] == previousstate[index]) {
      if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
          // just pressed
          justpressed[index] = 1;
      }
      else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
          // just released
          justreleased[index] = 1;
      }
      pressed[index] = !currentstate[index];  // remember, digital HIGH means NOT pressed
    }
    //Serial.println(pressed[index], DEC);
    previousstate[index] = currentstate[index];   // keep a running tally of the buttons
  }
}


void loop() {
  for (byte i = 0; i < NUMBUTTONS; i++) {
    if (justpressed[i]) {
      justpressed[i] = 0;
      Serial.print(i, DEC);
      Serial.println(" Just pressed"); 
      // remember, check_switches() will CLEAR the 'just pressed' flag
    }
    if (justreleased[i]) {
      justreleased[i] = 0;
      Serial.print(i, DEC);
      Serial.println(" Just released");
      // remember, check_switches() will CLEAR the 'just pressed' flag
    }
    if (pressed[i]) {
      Serial.print(i, DEC);
      Serial.println(" pressed");
      // is the button pressed down at this moment
    }
  }
}


Adafruit publishes a wide range of writing and video content, including interviews and reporting on the maker market and the wider technology world. Our standards page is intended as a guide to best practices that Adafruit uses, as well as an outline of the ethical standards Adafruit aspires to. While Adafruit is not an independent journalistic institution, Adafruit strives to be a fair, informative, and positive voice within the community – check it out here: adafruit.com/editorialstandards

Join Adafruit on Mastodon

Adafruit is on Mastodon, join in! adafruit.com/mastodon

Stop breadboarding and soldering – start making immediately! Adafruit’s Circuit Playground is jam-packed with LEDs, sensors, buttons, alligator clip pads and more. Build projects with Circuit Playground in a few minutes with the drag-and-drop MakeCode programming site, learn computer science using the CS Discoveries class on code.org, jump into CircuitPython to learn Python and hardware together, TinyGO, or even use the Arduino IDE. Circuit Playground Express is the newest and best Circuit Playground board, with support for CircuitPython, MakeCode, and Arduino. It has a powerful processor, 10 NeoPixels, mini speaker, InfraRed receive and transmit, two buttons, a switch, 14 alligator clip pads, and lots of sensors: capacitive touch, IR proximity, temperature, light, motion and sound. A whole wide world of electronics and coding is waiting for you, and it fits in the palm of your hand.

Have an amazing project to share? The Electronics Show and Tell is every Wednesday at 7pm ET! To join, head over to YouTube and check out the show’s live chat – we’ll post the link there.

Join us every Wednesday night at 8pm ET for Ask an Engineer!

Join over 36,000+ makers on Adafruit’s Discord channels and be part of the community! http://adafru.it/discord

CircuitPython – The easiest way to program microcontrollers – CircuitPython.org


Maker Business — “Packaging” chips in the US

Wearables — Enclosures help fight body humidity in costumes

Electronics — Transformers: More than meets the eye!

Python for Microcontrollers — Python on Microcontrollers Newsletter: Silicon Labs introduces CircuitPython support, and more! #CircuitPython #Python #micropython @ThePSF @Raspberry_Pi

Adafruit IoT Monthly — Guardian Robot, Weather-wise Umbrella Stand, and more!

Microsoft MakeCode — MakeCode Thank You!

EYE on NPI — Maxim’s Himalaya uSLIC Step-Down Power Module #EyeOnNPI @maximintegrated @digikey

New Products – Adafruit Industries – Makers, hackers, artists, designers and engineers! — #NewProds 7/19/23 Feat. Adafruit Matrix Portal S3 CircuitPython Powered Internet Display!

Get the only spam-free daily newsletter about wearables, running a "maker business", electronic tips and more! Subscribe at AdafruitDaily.com !



26 Comments

  1. Looks good and thanks for the code, but why not use one of the libraries? I had this issue once and solved it with the Bounce library [http://www.arduino.cc/playground/Code/Bounce]

  2. anthony, if you read the code you’ll see it also takes care of “just pressed” and “just released” which is pretty useful for better control of the interface!

  3. At a glance, it looks like the “debouncing” here is just delaying longer between samplings. (check_switches returns 9 times, then reads them on the 10th). If so, then this isn’t really debouncing; it’s just a busy way of reducing the sample frequency. Real software debouncing would read the buttons every cycle and use a counter for each button to ensure that the value was consistent over several consecutive samples. Only after maybe 3-5 consecutive readings in a new state would it declare that the button state had changed. Did I miss something?

  4. I rarely see example code that uses interrupts. Is there any reason why the loop() method is better?

  5. kevin, if you read code ‘at a glance’ you may miss some important stuff 😉
    there are two readings, ~10 ms apart. the button state is only stable if BOTH readings are correct. Switches bounce for about 1 ms maybe 3 at worst. Since we dont have lots of cycles, this method works quite well. If you have cycles to kill, you can sit there and stare at the pin to see if it bounces. but the chance of someone pressing a button twice in 10 ms is pretty slim

  6. Alex, interrupts are harder to understand for many.

  7. Nice. Debouncing can be tricky. Thanks for the code.

  8. Ah, right you are. Sorry about that. 🙂

  9. I went for a 10-word buffer where each word in the buffer represents a single button state read. The debounce state is the logical AND of each “column” of bits, so that a keydown will be instantly recognized and will last a minimum of 10 cycles through the debounce buffer, and a keyup will not be registered until all 10 column bits are empty. It’s a more robust algorithm than the timer based one as it relies of many samples rather than the time between (possibly noisy) samples.

    More on this technique, and the limitations of the timer based ones, at the venerable Ganssle’s website: http://www.ganssle.com/debouncing.pdf

  10. limey, sure you can grab as much data as you want, but 10ms between samples is plenty if your noise is limited to about 1ms long per press. (hell, even if its noisy for 5ms its fine). Its just sluggish 🙂

    As your friend who wrote that PDF says:

    Consider the simplest of all debouncing strategies: read the switch once every 500 msec
    or so, and set a flag indicating the input’s state. No reasonable switch will bounce that
    long. A read during the initial bounce period returns a zero or a one indicating the
    switch’s indeterminate state. No matter how we interpret the data (i.e., switch on or off)
    the result is meaningful. The slow read rate keeps the routine from deducing that bounces
    are multiple switch closures. One downside, though, is slow response.

  11. I can never seem to get my switches to bounce. Every time I throw them at the floor they just break.

  12. Oh, and the keyup and keydown events are just the EXCLUSIVE-ORs between the old key state and the new key state:

    old_keystate = keystate;
    keystate = read_keystate();
    old_keydown = keydown;
    old_keyup = keyup;

    // if a bit has been set since last time, it’s a keydown
    keydown = (old_keystate ^ keystate) & keystate;

    // if a bit has changed that wasn’t in the old keystate, it’s a keyup.
    keyup = (old_keystate ^ keystate) & old_keystate;

    No need for all these if-then-else clauses.

  13. @ladyada

    I didn’t say 1ms, I said 10 entries in the buffer. We’re reading our keys at 1KHz, so we get 100 key read a second. Plenty good enough, even for our twitchy performance midi device. We measured our key bounce off our arcade buttons using a sampling scope and found it to be a worst case of 1ms, so we use a bounce value of 3ms just ot be safe.

  14. OK, so i came across a tad dickish in that last post. I did not mean to. I apologise.

    I’m just a bit enthusiastic about having found a simpler, more robust bit-twiddling debounce algorithm. Please, look into it, I use it and like it’s simplicity (especially as there’s almost nothing to the interrupt service routine).

  15. limey, i think you’re reading the comments differently than their intent. your method is just peachy. i was pointing out the ‘limitations’ of timer based methods. which is, as your friend pointed out, its sluggish.

    we did a similar method to yours for the x0xb0x, with 32 buttons we had to keep track of a lot of inputs!

    edit: dont feel bad, we all get a little excited with bitwise operators. XOR! NAND! 😀

  16. Hi,
    A little off-topic, but where do you get your buttons and breadboards?[
    [looks like the breadboard is the one from your store, actually]
    The mini buttons from Sparkfun (https://www.sparkfun.com/commerce/product_info.php?products_id=97) and the breadboards from Pololu (http://www.pololu.com/catalog/product/352) don’t play well (they pop out at the slightest nudge), did you have to modify the buttons at all?

    Thanks,
    Jonathan

  17. Some excellent articles on switch bounce characteristics and debouncing by Jack Ganssle (this are pretty much considered definitive works on the topic).

    http://www.embedded.com/columns/breakpoint/18400810?_requestid=717996 (Characteristics of bounce)

    http://www.embedded.com/columns/breakpoint/18902552?_requestid=717950 (Hardware debounce menthods)

    http://www.embedded.com/columns/breakpoint/22100235?_requestid=717943 (Software debounce methods)

    http://www.mikrocontroller.net/attachment/12660/C_TAST.C (A short implementation).

    These algorithms do not support the just-pressed and just-released flags that yours does. That’s a nice touch.

    –jc

  18. Nice code! I am very confused by your hardware technical stuff.. Hopefully reading more of your comments and discussions will help.. I’m so accustomed to high-level programming!

    Anyways, this is good stuff. Thanks :).

  19. Hello LadyAda!
    As one of the contributors to the arduino Bounce library, I am always interested in how folks approach de-bouncing (and re-bouncing). Thank you for sharing your approach!
    My immediate criticism to your technique is that you “steal” a hardware interrupt from your project, for a relatively simple requirement. In my experience; hardware resources like interrupts on an arduino are scarce, while CPU cycles are cheap and available. In the bounce library, we made a conscious decision NOT to use an interrupt, to allow our users more freedom to handle things that need finer timer resolution than a button press rarely ever needs.
    The bounce library provides everything your example does: press detection, release detection, current state and arrays of button objects. But the Bounce library also provides re-bouncing. Re-bouncing gives the button

  20. The bounce library provides everything your example does: press detection, release detection, current state and arrays of button objects. But the Bounce library also provides re-bouncing. Re-bouncing gives the button auto-repeat capability. The user can press and hold a button and the library will repeatedly fire the button press event if desired. Re-bouncing allows the programmer to respond to repeated button press events, rather than constantly polling the buttons state

  21. Alex,
    The arduino only has two digital interrupts. I use them for meters that have a pulse (reed switch) output while i use debouncing and a loop for inputs that only rarely change

    cheers

    Diarmuid

  22. Eric, please look over the code i posted again. The code does not require any interrupts.

    If the Bounce library does detect “just pressed” and “just released” events, you may want to update the description at http://www.arduino.cc/playground/Code/Bounce because that is not mentioned anywhere

  23. Hey LadyAda!
    Sorry I didn’t get back sooner… Yes! I was confused by your second code submission which uses an interrupt to service the check_switches() routine. On second reading, I see that you can call check_switches() in the program loop, and avoid using an interrupt altogether. Sweet!

    As to the Bounce library documentation, yes – it could be made more obvious that the Bounce.update() member returns true if a state change has occurred – making it simple to do something like this:

    if ( button.update() ) Serial.print( button.read() ? “Button Was Pressed” : “Button Was Released” );

    Eric

  24. Just in case anyone else wandered over from the Arduino.cc Button example to here, do note that LadyAda has a different wiring setup that uses the Arduino’s internal resistors and she is using the analog inputs rather than the digital. The provided photo illustrates this well, but only if you are paying attention (I wasn’t!).

    @Jonathon – Yeah, I have the Sparkfun microswitches too. I have tried various leg bending strategies as well as some frustrated brute force smashing to get them to seat securely in my breadboard. Alas, nothing has worked yet.

    @LadyAda – As always, thanks for publishing your work for the world to learn from. One of your tutorials was my very first!

  25. Is it possible for us to control the switch by supplying the high (+5v) signal to the input instead of the low (Gnd) signal as stated in the example?

    What are the changes required to do so?

    I got a project, that use lots of coding in the loop line.
    As the other lines are executed, (as i’m using subroutine),
    i’m afraid that the button checking code inside the loop will not be read at the moment the buttons are pressed.

    Is there any other method for me to do achieve the interrupt instead using the physical interrupt?

    I got 8 buttons for this project that require action taken for every single input.

  26. for some weird reason i can not get this to work, even with no buttons connected i get "0 pressed" the whole time!!!! non-stop

Sorry, the comment form is closed at this time.