Friday, April 6, 2018

Arduino push buttons: debounce, and short vs. long press

A typical Arduino setup may include multiple push buttons having different functions, depending on whether they are pressed for a short or longer time. Furthermore, you want to avoid that these buttons bounce, meaning that they give multiple on/off signals when pressed or when released.

Here is a small piece of code that handles all of the above: It can read a number of different buttons, debounces them, and distinguishes between a short or a long press. Of course, you could also debounce the buttons by adding a small capacitor - but why would you add hardware when you can easily do it in code?

One contact of each button is connected to GND, and the other one to an Arduino pin which is specified in the initialization. In the initialization (the code before the "setup"), you specify the number of buttons used in your project, and the Arduino pins to which they are connected.
// -----------------------------------------------------------------
// --- push buttons: this example is made for five push buttons
const byte maxButton = 5;             // number of buttons
const byte pinPushButton[maxButton] = {2,3,4,5,A2};  // the Arduino pins 
In the "setup", you set the pinMode for the push button pins to "input" and enable the pull-up resistors for those pins.
// ------------------------------------------------------------------------
// --- Setup
// --------------------------------------------------------------------------
void setup() {
  // --- initialize push button pins as inputs with pull-ups
  for (byte i=0; i<maxButton; i++) {
    pinMode(pinPushButton[i], INPUT_PULLUP);
  }
}   
In the following, I assume that your code is written without any longer delays, and your main loop is frequently executed. In my Arduino sketches, the main loop is usually executed more than 10,000 times per second. But it is not necessary to check the buttons that frequent, so I reduced the number of checks to 500 per second. In time intervals of 2 milliseconds I loop over all buttons, and, for each button, call the code to check if a short or a long push was detected. Then I am then executing the code that performs the corresponding actions.
// --------------------------------------------------------------------------
// --- Main loop
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void loop() {

  static unsigned long previousTime = 0;  
  const byte timeInterval = 2;   // pick a short time interval, you don't have to 
                                 // check the button with 10kHz
  byte a[maxButton] = {};        // array to store the latest readings

  // - check all buttons
  if ((millis()-previousTime) > timeInterval) {
    previousTime = millis();
    for (byte i=0; i<maxButton; i++) {
      a[i] = checkButtons(i);  
    }

    if (a[0] == 1)  [your code, when button 0 is short-pushed];
    if (a[0] == 2)  [your code, when button 0 is long-pushed];
    if (a[1] == 1)  [your code, when button 1 is short-pushed];
    if (a[1] == 2)  [your code, when button 1 is long-pushed];
    if (a[2] == 1)  [your code, when button 2 is short-pushed];
    if (a[2] == 2)  [your code, when button 2 is long-pushed];
    if (a[3] == 1)  [your code, when button 3 is short-pushed];
    if (a[3] == 2)  [your code, when button 3 is long-pushed];
    if (a[4] == 1)  [your code, when button 4 is short-pushed];
    if (a[4] == 2)  [your code, when button 4 is long-pushed];
  }

} 
That last item is the main one: the subroutine "checkButtons([button-number])" which, after debouncing the button, determines if it was push for a short time (<400ms long="" or="">400ms). The 400ms is hard-coded. It worked well for me (and my daughter's robot), but you may modify it according to your personal preferences. The subroutine returns zero if the button was not pressed, one in case of a short press, and two for a long press. This is what the code does: The "state" variables for all buttons are initialized as zero. If it is detected that a button (that was not pressed before) was pressed, its "state" variable is set to 1, and the current time is stored in "previousTime". If a button was pressed before (meaning its "state" is 1), the code waits 100ms (this takes care of the debouncing) After 100ms, it checks if the button was released before 400ms expired - if yes, this was a short press. After the 400ms expired (and a short press was not detected, it must have been a long press. In that case, the code waits until the button is released. Afterwards, the code waits another 200ms before it is ready to accept any new presses.

// -----------------------------------------------------------------
// ---------------------------------------------------------------------------------
// --- check one button for short or long push
// ---------------------------------------------------------------------------------
//     returns: 0-none  1-short  2-long
//
byte checkButtons(byte buttonNo) {
  const unsigned long timeDebounce = 100; // time to debounce 
  const unsigned long timeLong = 400;    // minimum time for Long press 
  const unsigned long timeBreak = 200;   // time interval after button release, 
                                         //  before ready for next press 
  static byte state[maxButton] = {};     // this initializes all elements to zero
  static unsigned long previousTime[maxButton] = {};  // this initializes all elements to zero
  byte r = 0;

  r = 0;      // 0:not  1:short  2:long

  if (state[buttonNo] == 0) {             // --- no button has been pressed - check if 
    if (digitalRead(pinPushButton[buttonNo]) == LOW) {
      previousTime[buttonNo] = millis();
      state[buttonNo] = 1;
    }
  } else if (state[buttonNo] == 1) {  // --- button was pressed - check for how long
    if ( (millis()-previousTime[buttonNo]) > timeDebounce) {
      if ( (millis()-previousTime[buttonNo]) < timeLong) {
        if ( digitalRead(pinPushButton[buttonNo]) == HIGH) { // released -> short press
          previousTime[buttonNo] = millis();
          state[buttonNo] = 3;
          r = 1;
        }
      } else {                        // it was a long press
        state[buttonNo] = 2;
        r = 2;
      }
    }
  } else if (state[buttonNo] == 2) {  // --- wait for long button press to end
    if (digitalRead(pinPushButton[buttonNo]) == HIGH) {
      previousTime[buttonNo] = millis();
      state[buttonNo] = 3;
    }
  } else if (state[buttonNo] == 3) {  // --- wait a little while after previous button press
    if ( (millis()-previousTime[buttonNo]) > timeBreak) {
      state[buttonNo] = 0;
    }
  }
  return r;
} 
That's it! The above code works nicely in a few of my recent projects. In a few cases it allowed me to use less buttons, and therefore fewer Arduino pins. In one case it helped me to add functionality to a project that was built using a single button.
Note: Two typos in the code were fixed on July 10, 2018 - now it works correctly
2nd Note: Another bug was in the code that was fixed on July 23, 2018. Now it really works beautifully in my projects, distinguishing between short and long button pushes.

2 comments:

Anonymous said...

Hey, I just wanted to say fine work here. I needed a 'long press' solution and didn't want to reinvent the wheel. Tried a few but none came even close to the elegance and clean operation of yours.

Cheers.

Markus said...

Thank you for the compliment. I'm glad it worked for you.