The simplest button debounce solution

The simplest button debounce function

Recently I came across a piece of code which is nearly 50 lines long, plus several global variables, a state machine, all it does is to debounce two buttons. Button debounce does not to be this complicate, in this article I will show you probably the simplest button debounce solution.

The needs for debouncing a button is a well-understood problem in the embedded development world. Buttons (A.K.A. Pushbuttons, or switches) often generate spurious open/close transitions when pressed, causing by its mechanical design and material it used. These transitions may be read by an MCU as multiple presses and therefore need to be “debounced” either with some extra hardware or some software solution.

The idea of button debouncing is simple, when a button is pressed, you will wait for it to reach a stable state and only then take the button input as the ultimate state, whether it is an ON or an OFF.

Debounce shouldn’t be this complicate

Recently I saw this piece of code when I working on a project which requires me to port some code from one MCU platform to another platform.

switch (debounceState)
{
  case DEBOUNCE_STATE_IDLE:
    // No valid switch press
    switchStatus = SWITCH_NONE;

    switchValue = readSwitch();

    // If either switch is pressed
    if (switchValue != SWITCH_NONE)
    {
      // Keep track of the pressed switch
      switchMask = switchValue;
      // Intialize debounce counter
      lastDebounceTime = millis();
      // Proceed to check validity of button press
      debounceState = DEBOUNCE_STATE_CHECK;
    }
    break;

  case DEBOUNCE_STATE_CHECK:
    switchValue = readSwitch();
    if (switchValue == switchMask)
    {
      // If minimum debounce period is completed
      if ((millis() - lastDebounceTime) > DEBOUNCE_INTERVAL)
      {
        // Valid switch press
        switchStatus = switchMask;
        // Proceed to wait for button release
        debounceState = DEBOUNCE_STATE_RELEASE;
      }
    }
    // False trigger
    else
    {
      // Reinitialize button debounce state machine
      debounceState = DEBOUNCE_STATE_IDLE;
    }
    break;

  case DEBOUNCE_STATE_RELEASE:
    switchValue = readSwitch();
    if (switchValue == SWITCH_NONE)
    {
      // Reinitialize button debounce state machine
      debounceState = DEBOUNCE_STATE_IDLE;
    }
    break;
}

This nearly 50 lines of code uses a state machine to determine the state of two buttons, it is heavily commented because without those comments, it getting difficult to know what exactly is going on with the logic. It requires a few global variables and some set up to make it work.

typedef	enum SWITCH
{
  SWITCH_NONE,
  SWITCH_START_STOP,
  SWITCH_LF_RF
} switch_t;
switch_t switchStatus;
switch_t switchValue;
switch_t switchMask;

typedef enum DEBOUNCE_STATE
{
  DEBOUNCE_STATE_IDLE,
  DEBOUNCE_STATE_CHECK,
  DEBOUNCE_STATE_RELEASE
} debounceState_t;
debounceState_t debounceState;

I don’t like what I saw, how can someone write such complicate software for a simple task of button debouncing? I decided to replace it with a much much simpler solution. But before doing so, I was also curious of what is the “best practise” type of the code used in the Arduino community for button debouncing? So I found this code from Arduino.cc tutorials website, this code has been around for quite sometime, was written by some David Mellis in 2006, and later been modified/enhanced multiple times, one of the person who modified the code is Lady Ada (Limor Fried – the founder/owner of Adafruit). So this code is consider the best practise of code for debouncing and well circulated and quote in various Arduino tutorials.

/*
  Debounce

  Each time the input pin goes from LOW to HIGH (e.g. because of a push-button
  press), the output pin is toggled from LOW to HIGH or HIGH to LOW. There's a
  minimum delay between toggles to debounce the circuit (i.e. to ignore noise).

  The circuit:
  - LED attached from pin 13 to ground through 220 ohm resistor
  - pushbutton attached from pin 2 to +5V
  - 10 kilohm resistor attached from pin 2 to ground

  - Note: On most Arduino boards, there is already an LED on the board connected
    to pin 13, so you don't need any extra components for this example.

  created 21 Nov 2006
  by David A. Mellis
  modified 30 Aug 2011
  by Limor Fried
  modified 28 Dec 2012
  by Mike Walters
  modified 30 Aug 2016
  by Arturo Guadalupi

  This example code is in the public domain.

  https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce
*/

// constants won't change. They're used here to set pin numbers:
const int buttonPin = 2;    // the number of the pushbutton pin
const int ledPin = 13;      // the number of the LED pin

// Variables will change:
int ledState = HIGH;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading 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 = 50;    // the debounce time; increase if the output flickers

void setup() {
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);

  // set initial LED state
  digitalWrite(ledPin, ledState);
}

void loop() {
  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // 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 (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading 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 (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        ledState = !ledState;
      }
    }
  }

  // set the LED:
  digitalWrite(ledPin, ledState);

  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = reading;
}

This code is much simpler, easier to understand compare to the previous one. With some effort, it could be re-factor into a function and change some of the global variables into `static` scope to create a self-contained, re-usable debounce() function. But it is still not as simple as it should be.

The simplest debounce function

The simplest button debounce function that I came across many years ago (and use it ever since in my projects) is the one written by Jack Ganssle in his part 2 of the “A guide to debouncing” article. My version is slight different from Ganssle’s original code, I made it works for Arduino, further simplify the code a little bit, and uses a different value for determine the debounced state.

bool debounce() {
  static uint16_t state = 0;
  state = (state<<1) | digitalRead(btn) | 0xfe00;
  return (state == 0xff00);
}

All you need is a button connect between a GPIO pin and ground. There is no other component required, you will need to setup the GPIO pin with internal resistor to INPUT_PULLUP so the GPIO pin remain HIGH when the button is not been pressed. We write a simple sketch to test the debounce function by toggling the on-board LED every time the button is pressed:

#define btn 2  //assuming we use D2 on Arduino

void setup() {
  pinMode(btn, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (debounced()) {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
}

When the button is pressed, the button pull the GPIO pin to ground and produce a 0. The value is added into the state through OR | operator, and shift up through shift << operator each time the button pin is read, the 0xfe00 is a bit mask that mask-out the higher byte, and sort of means that we only care if the lower byte produce a consecutive stream of 0.

When you think of the bouncing problem, the button is in an unstable mode for the initial a few microseconds when it is pressed, and could produce a stream of either 0 or 1 during that few microseconds. If our little function could detect a pattern consists of a stream of 0 after 8 times of reading the button pin, the state | 0xff00 would produce a true, in another word, the button has reached to a stable state and no longer bouncing around.

This debounce code is easy to understand if you are coming from hardware background or you had experience of using shift-register before. That is because if you think of each read of GPIO pin as a digital clocking signal, the lower 4-bit of the state as a 4-bit serial-in, parrallel-out shift-register, the output value of true can be produced with an NAND gate as shown below. Of course no one will use this elaborated hardware for debouncing purpose, but the circuit illustrated the algorithm used in our debounce() function to achieve button debouncing.

A shift-register and a NAND gate as a button debounce
The debounce function behaves like this shift-register and a NAND gate

This is a simple and elegant debounce function. Just 5 lines of code, easy to understand and self-contain. But it can only handle one button.

Handling multiple buttons

It is not difficult to convert this function to support multiple buttons. I create a simple Arduino library by wrapping the function, the state variable and button setup in a C++ class.

button.h

#ifndef button_h
#define button_h

#include "Arduino.h"

class Button
{
  private:
    uint8_t btn;
    uint16_t state;
  public:
    void begin(uint8_t button) {
      btn = button;
      state = 0;
      pinMode(btn, INPUT_PULLUP);
    }
    bool debounce() {
      state = (state<<1) | digitalRead(btn) | 0xfe00;
      return (state == 0xff00);
    }
};
#endif

The code can be downloaded as an Arduino Library from https://github.com/e-tinkers/button.

You can test the library by adding one more button to Arduino and connect it to D3, and we will write a sketch to using the switch connected to D2 to turn on the built-in LED, and the switch connected to D3 to turn off the LED.

#include "button.h"

Button btn1;
Button btn2;

void setup() {
  btn1.begin(2);
  btn2.begin(3);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (btn1.debounce()) {
    digitalWrite(LED_BUILTIN, HIGH);
  }
  if (btn2.debounce()) {
    digitalWrite(LED_BUILTIN, LOW);
  }
}

Summary

Button debounce is a well-understood problem in embedded development, and debouncing function has been around since the early day of MCU. Some of the debouncing techniques that widely circulated around the Internet as “best practise” are not necessary the best or simplest button debounce solution. I hope this article will show you the simplest button debounce function that you could use in your Arduino program. For those who are new to Arduino or embedded programming, I would highly recommend to read “A guide to debouncing”.

4 comments by readers

  1. For switch debouncing, sampling at about 10 to 20 times per second is enough.

    You need to look for edges so you need to take the differential. dButton/dt !

    I read the uP port and xor it with the last read. This can read multiple buttons.

    For touch sensors that read analogue levels, I found fricton works.
    Instead of the differential, only update the last read if it is significantly different to the last read!

    http://www.dougrice.plus.com/hp/Theory/Index.htm has some notes

  2. I had a project using 3 pushbuttons.
    Using your library I was able to successfully get it to work, and it was so much better than any of the other debounce programs I spent hours trying to get to work.

    #include “button.h”
    Button onPin; // the name of the On pushbutton pin
    Button resetPin; // the name of the Reset pushbutton pin
    Button disconPin; // the name of the Disconnect pushbutton pin

    Much thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.