Trouble with push button debouncing

Hello all and thank you in advance for any help!
This is my first real arduino-project. I am building a little darts "game station" for my sisal dartboard. Currently it consists of four 7segment screens, seven LEDs, a matrix keyboard and three push-buttons.I tried to make everything as adaptable and "generalized" as possible, to be able to change the design and add functionalities later.
Everything is working fine so far, but I can't seem to debounce the buttons properly. They still behave very much undebounced. For whatever reason I didn't want to use a library for the buttons, because I wanted to do it by myself but here I am asking for help anyway :slight_smile:

The button function is called butR. Any help would be appreciated, thank you!

#include <Arduino.h> 
#include <TM1637Display.h> 
#include <Keypad.h>
/////////////////////////////////////////////////////////////// 7 SEGMENT
const uint8_t clockPin = 48;
const uint8_t DIO [] = {10,11,12,13};
int currentNum [] = {301,301,301,301};
const uint8_t displayNum = 4;

TM1637Display displays[4]={TM1637Display (clockPin, DIO[0]),  // define display objects
                           TM1637Display (clockPin, DIO[1]),
                           TM1637Display (clockPin, DIO[2]),
                           TM1637Display (clockPin, DIO[3])                                                      
                          } ;
unsigned long nowDisp []= {0,0,0,0};
unsigned long beforeDisp []= {0,0,0,0};
bool dispBlink [] = {false,false,false,false};
const int intervalDisp = 650;
          
/////////////////////////////////////////////////////////////// KEYBOARD
int inputString;
const byte ROWS = 4;
const byte COLS = 4;
byte rowPins[ROWS] = {5, 4, 3, 2};
byte colPins[COLS] = {9, 8, 7, 6};

char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
  };
Keypad kpd = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
/////////////////////////////////////////////////////////////// LEDS
const uint8_t pLED[] = {40,42,44,46}; //40=1;42=2;44=3;46=4
const uint8_t gLED[] = {38,36}; // 38=301; 36=501
const uint8_t pLEDNum = 4; 
const uint8_t gLEDNum = 2;
const int intervalLED = 650;

unsigned long nowGLED []= {0,0};
unsigned long beforeGLED []= {0,0};
unsigned long nowPLED []= {0,0,0,0};
unsigned long beforePLED []= {0,0,0,0};
int pLEDState [] = {LOW,LOW,LOW,LOW};
int gLEDState [] = {LOW,LOW};
/////////////////////////////////////////////////////////////// BUTTONS
const uint8_t button [] = {53,50,51};
const uint8_t buttonNum = 3;
const int buttonDelay = 40;

unsigned long lastDebounceTime []= {0,0,0};
int lastButtonState [] = {LOW,LOW,LOW};
int buttonState [] = {LOW,LOW,LOW};
/////////////////////////////////////////////////////////////// BUZZER
const uint8_t buzzerPin(25);

/////////////////////////////////////////////////////////////// GAME
uint8_t gStage = 0;
uint8_t gMode = 0;                    // 0 = 301; 1 = 501
int maxScore[] = {301,501};
uint8_t maxpNum = 4;                  // max Players
uint8_t pNum = 1;                     // 1 = 1, 2 = 2, 3 = 3
int pScore [] = { 301,301,301,301};   // initialise as 301
const uint8_t gNum = 2;



///////////////////////////////////////////////////////////////   SETUP
///////////////////////////////////////////////////////////////
void setup()
{
                                      
  for (int i = 0;i < displayNum; i++){
    displays[i].setBrightness(0x0f);
  }

  for (int i = 0;i < pLEDNum; i++){
    pinMode(pLED[i],OUTPUT);
  }

  for (int i = 0;i < gLEDNum; i++){
    pinMode(gLED[i],OUTPUT);
  }

    for (int i = 0;i<buttonNum; i++){
    pinMode(button[i],INPUT_PULLUP);
  }
  
pinMode(buzzerPin,OUTPUT);

initialise();

Serial.begin(9600);
}

///////////////////////////////////////////////////////////////   LOOP
///////////////////////////////////////////////////////////////  
void loop()
{
  
gSelect();
reStart();


while (gStage == 1){
reStart(); 
if (gStage == 1){
      // ADD DISPLAY BLINK HERE
  }
  if(butR(0)==1){
    pNum++;
    if (pNum > maxpNum){                        // TOGGLE BETWEEN PLAYER NUM
      pNum = 1;
      dispsOff();
      }

      
  }
for (int i = 0; i < pNum; i++){
blinkDisp(i,0);   
if(butR(1)== 1){
gStage = 2;
  }


}
}

/////// REWRITE BLINK FOR LEDs and SCREENS: use only one timer for all blinking operations?
////// select more than one players, then restart --> one display stays on (hopefully debouncing issue);















}
/////////////////////////////////////////////////////////////// FUNCTIONS
///////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////// LEDs
void pLEDon (int x){                  // player 1 = 1 ...
  digitalWrite(pLED[x-1],HIGH);
}

void pLEDoff (int x){                 // player 1 = 1 ...
  digitalWrite(pLED[x-1],LOW);
}

void gLEDon (int x){                  // 0 = 301, 1 = 501
digitalWrite(gLED[x],HIGH);
}

void gLEDoff (int x){                 // 0 = 301, 1 = 501
digitalWrite(gLED[x],LOW);
}

void blinkGLED(int x, int y){         // x = 0 (301), 1(501); y = either 0 (standard blink), or custom timer
    nowGLED[x] = millis(); // start blinking timer for LEDs#
    int timer;
    if ( y > 0){
      timer = y;
    } else{
      timer = intervalLED;
    }
      if (nowGLED[x] - beforeGLED[x] > timer){
        beforeGLED[x] = nowGLED[x];
        if(gLEDState[x] == LOW){
          gLEDState[x] = HIGH;
        } else {
          gLEDState[x] = LOW;
        }
      digitalWrite(gLED[x],gLEDState[x]);
      }
    }

void blinkPLED(int x, int y){         // x = 1-4; y = either 0 (standard blink), or custom timer
    nowPLED[x] = millis(); // start blinking timer for LEDs#
    int timer;
    if ( y > 0){
      timer = y;
    } else{
      timer = intervalLED;
    }
      if (nowPLED[x] - beforePLED[x] > timer){
        beforePLED[x] = nowPLED[x];
        if(pLEDState[x] == LOW){
          pLEDState[x] = HIGH;
        } else {
          pLEDState[x] = LOW;
        }
      digitalWrite(pLED[x],pLEDState[x]);
      }
    }

/////////////////////////////////////////////////////////////// BUTTONS



uint8_t butR (int x){
uint8_t reading = digitalRead(button[x]);
uint8_t output = 0;
if (reading != lastButtonState[x]) {
     lastDebounceTime[x] = millis();
}
if ((millis() - lastDebounceTime[x]) > buttonDelay) {
  if (reading != buttonState[x]) {
    buttonState[x] = reading;
    if (buttonState[x] == HIGH){
      return output;
    }
//      if (buttonState[x] == LOW){
//        return buttonState[x];         
//      }
    }
  }
 }  
 


/////////////////////////////////////////////////////////////// DISPLAYS
void dispsOff (){
 for (int i = 0; i < displayNum;i++){     // clear all 7segment displays
  displays[i].clear();
 }  
}

void blinkDisp (int x, int y){
    nowDisp[x] = millis(); // start blinking timer for 7SEGMENT
    int timer;
    if ( y > 0){
      timer = y;
    } else{
      timer = intervalDisp;
    }
      if (nowDisp[x] - beforeDisp[x] > timer){
        beforeDisp[x] = nowDisp[x];
        dispBlink[x]=!dispBlink[x];
      }
      if (dispBlink[x]== true){
        displays[x].showNumberDec(currentNum[x]);
      }else{
        displays[x].clear();
        }
}
/////////////////////////////////////////////////////////////// GAME
void initialise(){
 dispsOff();                                  // clear all 7segment displays 
 gMode = 0;
 pNum = 1;   

  


 for (int i = 0; i < displayNum; i++){              // set currentNum to default (301)
  currentNum[i] = 301;
  }
 
 for (int i = 0; i < maxpNum;i++){            // initialise as 301
  pScore[i] = 301;    
 }

 for (int i = 0; i < gNum; i++){              // turn off game LEDs
  gLEDoff(i);
  } 
 for (int i = 0; i < pNum; i++){              // turn off player LEDs
  pLEDoff(i);
  } 


}

void gSelect(){                               // SELECT GAME TYPE

 while (gStage==0){                           // GAME SELECTION PHASE 
 blinkGLED(gMode,400);
 if(butR(0)==1){
  gMode++;
  if (gMode > gNum-1){                        // TOGGLE BETWEEN GAMES
    gMode = 0;
  }
  for (int i = 0; i < gNum; i++){
    gLEDoff(i);
  }
 }
 if(butR(1) == 1){                            // PRESS CONFIRM TO CONFIRM GAME CHOICE
    for (int i = 0; i < gNum; i++){
      gLEDoff(i);
    }
    gLEDon(gMode);

    for (int i = 0; i < maxpNum;i++){        // SET SCORE TO GAME TYPE
      if (gMode == 0){
        pScore[i] = 301;
      }
      if (gMode == 1){
        pScore[i] = 501;
      }
      }    
    for (int i = 0; i < maxpNum; i++){
    currentNum[i] = maxScore[gMode];
    }
    gStage = 1;                              // SELECTION PHASE ENDED      
 }
} 
}

void reStart(){
 if (butR(2) == 1){
  gStage = 0;
  initialise();
  delay(100);
  } 
}

void pSelect(){

}

There are many many tutorials on software debouncing of momentary switches (aka buttons) on the net.

If I may, I will suggest that you may want to try hardware debounce. Wire a 0.1uF ceramic cap across the switch (from input to ground). I have found that that will debounce 95% of switches and if hardware debounce does not work it is because the switch is garbage. I usually get flamed for suggesting this, but it has been used for debounce for many years and it works.

2 Likes

ok. you make debounce if key is not same but no debounce if the same.
you store lastDebounceTime but it seems not very much useful , next "if()" gives FALSE and skips everything after that?

UPD: I see. You copied this from Debounce reference at Arduino.cc, but there it was the loop() function, so it checks always if time elapsed.

That is where most of my capacitors go! It is also a lot easier then debugging debouncing code. So here is my fire extinguisher to put out the flames!

1 Like

Please explain in detail.

Come on. (sorry, it's been one of those days...) There is nothing complicated or mysterious about software debouncing. In its simplest form (which by the way, works very well):

repeat
{
  read key
  if the timeout period has expired
    {
    register the key
    initiate the timeout
    }
}

That's it. No more, nada, nothing strange or mysterious.
The only thing, you can implement the timeout with a fixed delay, or by arming a timer if you want it to be non-blocking.

Once you have a clear idea of the logic, the code is easy to write and understand.

Yes, I know! The ones I have found all use the same logic, which makes sense to me, but I can't seem to implement it properly.
Thank you for your advice, I have ordered a box of capacitors because of your suggestion. If it can be done on the hardware side of things, I'm happy.

No worries, I know it's a very basic operation and I was prepared for much snarkier answers than a simple "come on" haha.
I think I understand the logic but am unable to implement it properly, however.

V

Very true, didn't think about that! I changed it so that the button-reading-function is now at the beginning of every loop and stores the current button values in a global variable.

void butR (){
  for (int x = 0; x < buttonNum;x++){
  int reading = digitalRead(button[x]);  
 if (reading != lastButtonState[x]) {
      lastDebounceTime[x] = millis();
 }
  if ((millis() - lastDebounceTime[x]) > buttonDelay) {
    if (reading != buttonState[x]) {
      buttonState[x] = reading;
      if (buttonState[x] == HIGH) {
        butP[x] = true;
        }else{
          butP[x] = false;
        }
    }}
  lastButtonState[x] = reading;
}
}

I see a problem already!

A statement like that is a non-starter because without knowing why you are unable, it is impossible to help. It's especially mysterious that you understand it and can't implement it. That implies maybe some specific implementation problems - but what are they? You see our dilemma?

How do you even know that it works/doesn't work? You posted no information about the behaviour or testing that you did. Just like, "here's the code". No response to requests like #5. Really, you are not helping us help.

I'm not going to take your code as a Mensa puzzle to discover from it, what it does. Not without more feedback from you, anyway...

        }
    }}

Your formatting is sloppy. I call this the "flock of seagulls". There are conventions that you should follow for indentation and the IDE will auto format to some extent using ctrl-T.

As @anon57585045 has reminded me, I haven't answered your post, sorry!
The "device" has three buttons: "select", "confirm" and "restart". I am currently working on the "select game type" and "select nr. of players" stages. When your are selecting the game type, pressing the "select" button toggles between 301 and 501. You confirm your choice with "confirm" and then select the number of players in the same manner.
What is happening is, that when I press the select button, sometimes it toggles, other times it does not and toggles back. When I press the confirm button, it already "confirms" the second selection for the number of players.

Of course I am in over my head, but that's the fun part for me! Eventually I'll get it going somehow, I'm sure. I tried to familiarize myself with each component on its own before I started on assembling the whole thing.

Enable internal pullup with:
pinMode(pin, INPUT_PULLUP);

1 Like

I hope you haven't changed this. Lets have a look under the hood:

void butR () {
  for (int x = 0; x < buttonNum; x++) {
    int reading = digitalRead(button[x]);
    if (reading != lastButtonState[x]) {
      lastDebounceTime[x] = millis();
    }
    if ((millis() - lastDebounceTime[x]) > buttonDelay) {
      if (reading != buttonState[x]) {
        buttonState[x] = reading;
        if (buttonState[x] == HIGH) {
          butP[x] = true;
        } else {
          butP[x] = false;
        }
      }
    }
    lastButtonState[x] = reading;
  }
}

I corrected the flock of seagulls with ctrl-T. Here is the pseudocode, simplified to look at only one key:

reading = button state
if reading is not the same as the last state
  { save a timestamp (i.e. reset timer) }
if timer is expired
  {
  if reading is not the same as current state
    {
     current state = reading
     output = reading
    }
  }
last state = reading

Does this make sense?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.