My button debounce version

Hello,

Just for a matter of testing myself of finding a new way to debounce a button press.

I've studied the example in Arduino examples and learned how it works.

I'm posting this thread to share my experience and get the tips and support as always :)

This is my code:

#define BUTTON    2
#define LED       13

bool ledState    = HIGH;
bool readingLock = LOW;

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;



void setup() {
  Serial.begin(9600);
  pinMode(BUTTON, INPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, ledState);
}

void loop() {

  if(digitalRead(BUTTON) && !readingLock){  
    readingLock = 1;
    ledState = !ledState;
    digitalWrite(LED, ledState);    
    lastDebounceTime = millis();
  }

  if (((millis() - lastDebounceTime) > debounceDelay) && (readingLock)){ 
    if(!digitalRead(BUTTON)){  
      readingLock = 0;
    }
  }
}

And this is Arduino examples version:

const uint8_t buttonPin = 2;
const uint8_t ledPin = 13;

bool ledState = HIGH;
bool buttonState = 0;
bool lastButtonState = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, ledState);
}

void loop() {
  int reading = digitalRead(buttonPin); 
  
  if (reading != lastButtonState) {
    Serial.println("button is pressed");
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == HIGH) {
        ledState = !ledState;
      }
    }
  }

  digitalWrite(ledPin, ledState);
  lastButtonState = reading;
}

So what you think ? I've tested mine and it works like a charm !

bool ledState = HIGH;
bool buttonState = 0;
bool lastButtonState = LOW;

just think about it and ask yourself ... why? ;)

noiasca: bool ledState = HIGH; bool buttonState = 0; bool lastButtonState = LOW;

just think about it and ask yourself ... why? ;)

The arrangement of processing these variables is to ensure that the toggle of the press is secured from bouncing signals.

Thats not the point. You are using bool. Thats fine. But why are you mixing HIGH, 0, and LOW. Or to be even more precise: why not

bool buttonState = LOW;

to make it even more readable and nice looking.

noiasca:
Thats not the point.
You are using bool. Thats fine.
But why are you mixing HIGH, 0, and LOW. Or to be even more precise:
why not

bool buttonState = LOW;

to make it even more readable and nice looking.

Yep :slight_smile:

I wanted to prove to myself that LOW is the same as 0 looool

You’re right I really have to put them all in one order.

You can basically do debounce two ways: 1) Do not accept a change, except if last for more than a specific time. 2) Report it immediately when a input changes and lock out the input for some time.

In both cases a time about 10ms will work for good switches, for cheaper switches a bit longer may be needed.

I often use the first one for switches and inputs from "unstable" devices, usually implemented as a interrupt routine.

For encoders the second one is better and the timeout has to fairly short or the rotationnel speed is too limited.

HKJ-lygte: You can basically do debounce two ways: 1) Do not accept a change, except if last for more than a specific time. 2) Report it immediately when a input changes and lock out the input for some time.

In both cases a time about 10ms will work for good switches, for cheaper switches a bit longer may be needed.

I often use the first one for switches and inputs from "unstable" devices, usually implemented as a interrupt routine.

For encoders the second one is better and the timeout has to fairly short or the rotationnel speed is too limited.

Thank you so much for sharing your experience :)

Two cases seems really interesting to think about the reasons and the actual situations.

Next step you should try: put it in a separate function, ...

wolfrose:
Two cases seems really interesting to think about the reasons and the actual situations.

Exactly.

The first algorithm works well in noise environments even with long wires that pick up noise.

The second algorithm works well with encoders, but also with switches close to the processor, where you do not have to worry about false detections.

For lazy programmers (That is me) the second algorithm is usually the easiest to implement and it is good enough in most cases (Noisy signals being the exception). And if you have encoders it is the best solution.

noiasca:
Next step you should try: put it in a separate function, …

Absolutely …

I’ve done some functions for projects in the diploma program and wrote a function to manage the functions.

In the following code, I’ve done some functions that process IR sensor input signals but I’ve not developed a function that solves the bounce problems.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include <stdio.h>
#include <Arduino.h>
#include "I2C_m.h"
#include "arduino_multitask.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// variables
static byte ir1_tri, ir2_tri;
byte masks[]={0xDE,0xDD,0xF3,0xEB,0x9B,0x5B};
byte red_light1_flag,red_light2_flag,red_light3_flag;
byte units1,tens1,units2,tens2,units3,tens3,digit_switch;
int8_t counter1,counter2,counter3,mask_cnt;
//static

// blink
unsigned long blink_st_ms;
unsigned long blink_cu_ms;
unsigned long blink_pd = 1000;

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// IR speed radar set
unsigned long speed_1_time;
unsigned long speed_2_time;
// IR speed radar reset
unsigned long speed_reset_st_ms;
unsigned long speed_reset_cu_ms;
unsigned long speed_reset_pd = 2000;

// traffic_lights 3-way
unsigned long traffic_lights_st_ms;
unsigned long traffic_lights_cu_ms;
unsigned long traffic_lights_pd = 1000;

// red_light_counters
unsigned long red_cnt_st_ms;
unsigned long red_cnt_cu_ms;
unsigned long red_cnt_pd = 10;

// red_light_traffic_violation
unsigned long red_set_st_ms;
unsigned long red_set_cu_ms;
unsigned long red_set_pd = 10;

unsigned long red_reset_st_ms;
unsigned long red_reset_cu_ms;
unsigned long red_reset_pd = 100;
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Multiplexing 2 digit 7-segment with 74LS47
unsigned long ref_d_start;
unsigned long ref_d_current;
unsigned long ref_d_pd = 100;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// initializations
void pins_init(void){
 
 // 7-seg data lines
 DDRB=0xFF;
 //////////////////////////////////////////////////////////////////
 // traffic_saher
 pinMode(STREET1_RED_IR, INPUT);
 pinMode(STREET2_RED_IR, INPUT);
 pinMode(STREET3_RED_IR, INPUT);
 
 // speed sensors
 pinMode(SPEED_RADAR_IR1, INPUT);
 pinMode(SPEED_RADAR_IR2, INPUT);
 
 pinMode(STREET1_RED_FLASH, OUTPUT);
 pinMode(STREET2_RED_FLASH, OUTPUT);
 pinMode(STREET3_RED_FLASH, OUTPUT);
 pinMode(SAHER_SPEED_FLASH, OUTPUT);
 pinMode(RED3, OUTPUT);
}

void functions_init(void){
 digit_switch = 0;
 mask_cnt = 0;
 blink_st_ms = millis();
 traffic_lights_st_ms = millis(); 
 red_cnt_st_ms = millis();
 red_set_st_ms = millis();
 ref_d_start = millis();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// multitasking code

void multitasking_manager(void){
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// sensors tasks
 blink_pin13();

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// traffic_lights tasks 
 speed_radar_set(); 
 speed_reset();
 traffic_lights_mask_write();
 red_set();
 if(red_light1_flag||red_light2_flag||red_light3_flag)red_reset(); 
 display_refresh();

}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void display_refresh(void){
 switch(digit_switch){
 ref_d_current = millis();
 if(ref_d_current - ref_d_start >= ref_d_pd){ 
 case 0:
 ioexp8_write(0xFF,2);PORTB = (units1%10);ioexp8_write(0xFE,2);
 ioexp8_write(0xFF,2);PORTB = (tens1%10);ioexp8_write(0xFD,2);
 digit_switch = 1;
 ref_d_start = ref_d_current;
 break;
 case 1:
 ioexp8_write(0xFF,2); PORTB = (units2%10); ioexp8_write(0xFB,2);
 ioexp8_write(0xFF,2); PORTB = (tens2%10); ioexp8_write(0xF7,2);
 digit_switch = 2;
 ref_d_start = ref_d_current; 
 break;
 case 2:
 ioexp8_write(0xFF,2);PORTB = (units3%10);ioexp8_write(0xEF,2);
 ioexp8_write(0xFF,2);PORTB = (tens3%10);ioexp8_write(0xDF,2);
 digit_switch = 0;
 ref_d_start = ref_d_current;
 break;
 }
 }
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// traffic light 3-way MASKING/TIMIN
void traffic_lights_mask_write(void){ 
 traffic_lights_cu_ms = millis();
 if(traffic_lights_cu_ms - traffic_lights_st_ms >= traffic_lights_pd){
 if(mask_cnt > 5) mask_cnt = 0;
 
 ioexp8_write(masks[mask_cnt],1);
 if(mask_cnt == 4 || mask_cnt == 5)digitalWrite(RED3,HIGH); 
 else digitalWrite(RED3,LOW); 
 
 if(mask_cnt == 2) counter1 = 4 * (traffic_lights_pd/1000);
 if(mask_cnt == 0) counter2 = 2 * (traffic_lights_pd/1000);
 if(mask_cnt == 4) counter2 = 4 * (traffic_lights_pd/1000);
 if(mask_cnt == 0) counter3 = 4 * (traffic_lights_pd/1000); 
 
 tens1 = counter1 / 10;
 units1 = counter1 % 10;  
 tens2 = counter2 / 10;
 units2 = counter2 % 10;  
 tens3 = counter3 / 10;
 units3 = counter3 % 10;

 if(counter1) counter1--; else counter1 = 0;
 if(counter2) counter2--; else counter2 = 0;
 if(counter3) counter3--; else counter3 = 0;

 mask_cnt++;
 
 traffic_lights_st_ms = millis();
 }
}


void red_set(void){
 red_set_cu_ms = millis();
 if(red_set_cu_ms - red_set_st_ms >= red_set_pd){ 
 if((!digitalRead(STREET1_RED_IR)) && (mask_cnt>2) && (mask_cnt<=6)){
 digitalWrite(STREET1_RED_FLASH,HIGH);
 red_light1_flag = 1;
 }
 
 if((!digitalRead(STREET2_RED_IR)) && ((mask_cnt>=0) && (mask_cnt<=2))||((mask_cnt>4) && (mask_cnt<=6))){
 digitalWrite(STREET2_RED_FLASH,HIGH); 
 red_light2_flag = 1;
 } 
 
 if((!digitalRead(STREET3_RED_IR)) && ((mask_cnt>0) && (mask_cnt<=4))){
 digitalWrite(STREET3_RED_FLASH,HIGH); 
 red_light3_flag = 1; 
 }
 red_set_st_ms = red_set_cu_ms; 
 }
} 
 
void red_reset(void){
 red_reset_cu_ms = millis();
 if(red_reset_cu_ms - red_reset_st_ms >= red_reset_pd){
 if(red_light1_flag){
 digitalWrite(STREET1_RED_FLASH,LOW);
 red_light1_flag = 0; 
 }
 
 if(red_light2_flag){
 digitalWrite(STREET2_RED_FLASH,LOW);
 red_light2_flag = 0;
 }

 if(red_light3_flag){
 digitalWrite(STREET3_RED_FLASH,LOW);
 red_light3_flag = 0; 
 }
 red_reset_st_ms = red_reset_cu_ms;
 }
}

void ioexp8_write(uint8_t data, uint8_t exp_no){
  if(exp_no == 1)I2C_start(IOEXP1_WR);
  if(exp_no == 2)I2C_start(IOEXP2_WR);
  I2C_tx(data);     
  I2C_stop();
}

uint8_t ioexp8_read(uint8_t exp_no){
  uint8_t data;
  if(exp_no == 1)I2C_start(IOEXP1_RD);
  if(exp_no == 2)I2C_start(IOEXP2_RD);
  data = I2C_rx();
  Serial.println(data, HEX);
  I2C_stop();
  return data;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void blink_pin13(void){
 blink_cu_ms = millis();
 if(blink_cu_ms - blink_st_ms >= blink_pd){
 digitalWrite(13, !digitalRead(13)); 
 blink_st_ms = blink_cu_ms;
 }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void speed_radar_set(void)
{
 if(!digitalRead(SPEED_RADAR_IR1) && (ir1_tri==0) && (ir2_tri==0)){
 speed_1_time = millis();
 ir1_tri=1;
 }
 
 if(!digitalRead(SPEED_RADAR_IR2) && (ir1_tri==1)){
 speed_2_time = millis();
 ir2_tri=1;
 }
 
 if(ir1_tri==1&&ir2_tri==1){
 ir1_tri=0;
 Serial.println(speed_2_time - speed_1_time);
 if((speed_2_time - speed_1_time)<1000)
 digitalWrite(SAHER_SPEED_FLASH,HIGH);
 }
}

void speed_reset(void){
 speed_reset_cu_ms = millis();
 if(speed_reset_cu_ms - speed_reset_st_ms >= speed_reset_pd){
 ir2_tri=0;
 digitalWrite(SAHER_SPEED_FLASH,LOW);
 speed_reset_st_ms = speed_reset_cu_ms;
 }
}

Thanks for sharing, ain’t it fun.

FYI
Unless your hardware is located where there is a lot of electrical noise, there is really no need to look for a minimum contact closure amount of time.

Just scan you switches every 50ms and look for a change in state.


This might be of some interest to you:
Nick Gammon’s ‘Switch Manager’

http://gammon.com.au/Arduino/SwitchManager.zip

Yes the noise, now I understand the reason for now taking the action in the first place and instead wait for 50ms then check for a change.

That’s really clever.

If you want really "ironclad" de-bouncing, this may interest you:

// Radio Buttons!
const int led1Pin =  3;    // LED pin number
const int button1 =  2;
const int led2Pin =  5;
const int button2 =  4;
const int led3Pin =  6;
const int button3 =  7;
const int led4Pin =  9;
const int button4 =  8;
char bstate1 = 0;
char bstate2 = 0;
char bstate3 = 0;
char bstate4 = 0;
unsigned long bcount1 = 0; // button debounce timer.  Replicate as necessary.
unsigned long bcount2 = 0;
unsigned long bcount3 = 0;
unsigned long bcount4 = 0;


// Have we completed the specified interval since last confirmed event?
// "marker" chooses which counter to check
// Routines by Paul__B of Arduino Forum
boolean timeout(unsigned long *marker, unsigned long interval) {
  if (millis() - *marker >= interval) {
    *marker += interval;    // move on ready for next interval
    return true;       
  }
  else return false;
}

// Deal with a button read; true if button pressed and debounced is a new event
// Uses reading of button input, debounce store, state store and debounce interval.
// Routines by Paul__B of Arduino Forum
boolean butndown(char button, unsigned long *marker, char *butnstate, unsigned long interval) {
  switch (*butnstate) {               // Odd states if was pressed, >= 2 if debounce in progress
  case 0: // Button up so far,
    if (button == HIGH) return false; // Nothing happening!
    else {
      *butnstate = 2;                 // record that is now pressed
      *marker = millis();             // note when was pressed
      return false;                   // and move on
    }

  case 1: // Button down so far,
    if (button == LOW) return false; // Nothing happening!
    else {
      *butnstate = 3;                 // record that is now released
      *marker = millis();             // note when was released
      return false;                   // and move on
    }

  case 2: // Button was up, now down.
    if (button == HIGH) {
      *butnstate = 0;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else {
      if (millis() - *marker >= interval) {
        *butnstate = 1;               // jackpot!  update the state
        return true;                  // because we have the desired event!
      }
      else
        return false;                 // not done yet; just move on
    }

  case 3: // Button was down, now up.
    if (button == LOW) {
      *butnstate = 1;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else {
      if (millis() - *marker >= interval) {
        *butnstate = 0;               // Debounced; update the state
        return false;                 // but it is not the event we want
      }
      else
        return false;                 // not done yet; just move on
    }
  default:                            // Error; recover anyway
    { 
      *butnstate = 0;
      return false;                   // Definitely false!
    }
  }
}

void setup() {
  pinMode(led1Pin, OUTPUT);     
  pinMode(button1, INPUT);
  pinMode(led2Pin, OUTPUT);     
  pinMode(button2, INPUT);     
  pinMode(led3Pin, OUTPUT);     
  pinMode(button3, INPUT);     
  pinMode(led4Pin, OUTPUT);     
  pinMode(button4, INPUT);       
  digitalWrite (led1Pin, LOW);
  digitalWrite (led2Pin, LOW);
  digitalWrite (led3Pin, LOW);
  digitalWrite (led4Pin, LOW);
}

void loop() {
  // Select LED if button debounced
  if (butndown(digitalRead(button1), &bcount1, &bstate1, 10UL )) {
    digitalWrite (led1Pin, HIGH);
    digitalWrite (led2Pin, LOW);
    digitalWrite (led3Pin, LOW);
    digitalWrite (led4Pin, LOW);
    delay(500);
  }
  // Select LED if button debounced
  if (butndown(digitalRead(button2), &bcount2, &bstate2, 10UL )) {
    digitalWrite (led1Pin, LOW);
    digitalWrite (led2Pin, HIGH);
    digitalWrite (led3Pin, LOW);
    digitalWrite (led4Pin, LOW);
    delay(500);
  }
  // Select LED if button debounced
  if (butndown(digitalRead(button3), &bcount3, &bstate3, 10UL )) {
    digitalWrite (led1Pin, LOW);
    digitalWrite (led2Pin, LOW);
    digitalWrite (led3Pin, HIGH);
    digitalWrite (led4Pin, LOW);
    delay(500);
  }
  if (butndown(digitalRead(button4), &bcount4, &bstate4, 10UL )) {
    // Select LED if button debounced
    digitalWrite (led1Pin, LOW);
    digitalWrite (led2Pin, LOW);
    digitalWrite (led3Pin, LOW);
    digitalWrite (led4Pin, HIGH);
    delay(500);     
  }
}

There is usually no reason to do separate debounce for each button, combing all buttons in a single byte (or more if needed) and debounce them together with one timer.

byte lastButtonState=0;
uint32_t lockoutTimer=0;

boolean buttonPress() {
  if (millis()<lockoutTimer) return false;

// Build a byte with the current button status
  byte currentButtonState=0;
  currentButtonState|=digitalRead(buttonPin1)?0x01:0;
  currentButtonState|=digitalRead(buttonPin2)?0x02:0;
  currentButtonState|=digitalRead(buttonPin3)?0x04:0;
  currentButtonState|=digitalRead(buttonPin4)?0x08:0;
// Check if buttons has changed and report true in that case
  if (currentButtonState!=lastButtonState) {
    lastButtonState=currentButtonState;
    lockoutTimer=millis()+30; // 30ms lockout
    return true;    
  } else {
    return false;
  } 
}

The above is a very simple debounce code, it will report true each time a button is pressed or released, then lastButtonState must be checked for the actual status. It do not report what button has changed state, but keeping another copy of the lastButtonState and XOR it with the new state will show that.

Due to the way I have made the timer it will only work for 49 days, then the Arduino must be reset. Coding it slightly different will fix that, but is not as easy to read.

Paul__B: If you want really "ironclad" de-bouncing, this may interest you:

What does ironclad mean ?

I copied your code and tried to understand it, it seems really good; I liked your way of debouncing the buttons.

Really thank ! I learned a lot in this thread so far.

HKJ-lygte: There is usually no reason to do separate debounce for each button, combing all buttons in a single byte (or more if needed) and debounce them together with one timer.

The above is a very simple debounce code, it will report true each time a button is pressed or released, then lastButtonState must be checked for the actual status. It do not report what button has changed state, but keeping another copy of the lastButtonState and XOR it with the new state will show that.

Due to the way I have made the timer it will only work for 49 days, then the Arduino must be reset. Coding it slightly different will fix that, but is not as easy to read.

wow in your code, you can mask out a lot of pins, and name them in defs section.

wolfrose: wow in your code, you can mask out a lot of pins, and name them in defs section.

As shown it supports 8 buttons, using uint16_t or uint32_t for the two state variables would allow 16 or 32 buttons, without doing any more coding except testing the actual input state.

wolfrose: What does ironclad mean ?

It means extremely well protected. :sunglasses:

Specifically it re-checks to see that a button has adopted a new state for every pass through the loop() until millis() has advanced the specified interval, so it only registers a button press if the button state has been entirely stable for that period rather than merely checking that the button had changed state at the end of the interval as well as the beginning.

Since the code must always read millis() on each pass through the loop() in order to determine when the interval has passed, it is not a great deal more involved to review that the state has maintained the change as well.

It is certainly appropriate to debounce switches in groups of eight or sixteen (or 64) at a time, as you necessarily would for a keyboard; generally the group would correspond to an 8-bit port and be read by a full port read rather than bit by bit. If doing this, you might be comparing three state values: the previous stable value, the last read value and the current value and re-start the timer each time the current value either reverts to the previous stable value or differs from the last read value.

*Consider pinMode(pin, INPUT_Pullup)*

Something to consider when deploying buttons in the real-world is to invert the state of the pin monitoring the button. In other words, configure the default state of your unpushed button to HIGH by setting your pinMode to HIGH using "INPUT_PULLUP".

Connect one side of your button to the HIGH Input pin and the other side to ground. When the button is pressed it shorts the pin to ground (with very low current).

It may sound counter intuitive at first, but using this method has several advantages. Noisy circuits can't compete with a hard short to ground, so you don't get false positives. Most importantly though, you won't be exposing a positive voltage to a remote button (thus eliminating the risk of a blue smoke failure).

If you can't use 'INPUT_PULLUP' (ex: using shift registers as inputs) you can employ a similar technique using 10K+ resistors to pull an unpushed button HIGH before monitoring it for a grounded (pushed) state.

Worth wrapping your hear around, for sure!

Bit'