Two-State button debouncer

This function implements software debouncing for a two-state button. It responds to a short press and a long press and identifies between the two examples. This information is returned encoded in an integer, whereby 0 means no activity, 1 means a short press has been detected and 2 indicates a long press has occurred.

The press times are defined as constants in the beginning of the function. as the time to debounce and for a quick press is 50ms or more. The time for a long press is 2 seconds. These values are easily changed to fine tune for your hardware/situation.

External to the function, the only requirements are that the button pin be defined. In addition, a second Pin is defined as a ground pin, usually adjacent to the button pin, to allow both wires to the button to run side by side. This first pin should be defined as "INPUT_ONLY". The second pin should be defined as an "OUTPUT", and a digital write should set the pin LOW.

It has been written so that there are no intrinsic timing loops. This means your sketch can continue processing while the button function continues to pause for debouncing or waiting for a long press.

#define buttonPin 12
#define groundPin 11


void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
  pinMode(buttonPin,INPUT_PULLUP);
  pinMode(groundPin,OUTPUT);
  digitalWrite(groundPin,LOW);
}


void loop() {

  while (true) {

    switch (checkButton()) {
      case 0:
        break;
      case 1:
        Serial.println();Serial.println("Short press");
        break;
      case 2:
        Serial.println();Serial.println("L-o-n-g press");
        break;
    }   
  }
}


int checkButton() {
  
   
const int  stateNormal=0; // no button activity
const int  stateShort=1;  // short button press
const int  stateLong=2;   // long button press
const long longDelta=2000; // hold 2 seconds for a long press
const long debounceDelta=50; // debounce time of 50ms

static int lastButtonStatus=HIGH; // HIGH indicates the button is NOT pressed
static long longTime=0, shortTime=0; // future times to determine is button has been poressed a short or long time
int buttonStatus;                       // button state Pressed/LOW; Open/HIGH
boolean Released=true, Transition=false;  // various button states
boolean timeoutShort=false, timeoutLong=false; // flags for the state of the presses

buttonStatus=digitalRead(buttonPin); // read the button state on the pin "buttonPin"
timeoutShort=(millis()>shortTime); // calculate the current time states for the button presses
timeoutLong =(millis()>longTime);

if (buttonStatus != lastButtonStatus) { // reset the timeouts if the button state changed
  longTime =millis()+longDelta;
  shortTime=millis()+debounceDelta;
  }

Transition=(buttonStatus != lastButtonStatus);
Released = (Transition && (buttonStatus == HIGH)); // for input pullup circuit
lastButtonStatus = buttonStatus; // save the button status

if ( ! Transition) return stateNormal; // if there has not been a transition, return the normal state
if (timeoutLong && Released) { // long timeout has occurred and the button was just released
      return stateLong;  
  } else if (timeoutShort && Released){ // short timeout has occurred (and not long timeout) and button ws just released
      return stateShort; 
   } else { // else there is no change in status, return the normal state
      return stateNormal;
  }
}

@djsfantasi:

Awesome, thanks. I don't have a need for it right now, but I am writing some stuff using 4 buttons. Just this morning I was wondering if I should use a long press for some functions. Talk about serendipity. :slight_smile:

With multiple buttons, can I use just the one groundPin for all the other buttons on their own Data pin?

As in...
#define buttonOnePin 12
#define buttonTwoPin 13
#define buttonThreePin 14
#define groundPin 11

Strange, I thought I had replied to this earlier but I don't see it. So if you see two replies...

In short, the answer to your question is yes. Only one ground source is necessary.

Note that it does NOT have to be a data pin; you can connect ground to one of the Arduino's ground pins. I used a data pin because I had a cable terminated in two male pins spaced 0.1" apart. The technique of using a data pin for ground or Vcc is one I've used before.

Note that for four buttons, you might need four copies of the routine. This is because it uses static variables to maintain state information between calls. In order to generalize the routine, one could redefine several of these variables as arrays and index to the appropriate value for the appropriate button. Thus, only one routine would be necessary.

djsfantasi:
The technique of using a data pin for ground or Vcc is one I've used before.

The I/O pins have very limited current capability - 40mA absolute max. I would not use more than 20mA.

There are also additional limitations on the combined current through the several pins on one port.

...R