problem debouncing multiple buttons with interrupts

Thank you in advance for any help.

I'm trying to debounce multiple buttons using interrupts. I'm also displaying the info on a GLCD.

I have the buttons setup to go into the interrupt pin and a adc pin and setup a resistor ladder for all the buttons. There are four buttons two for increasing and decreasing a optionspotx and two for increasing and decreasing optionspoty. The glcd also displays the adc value on the screen too. Also ledpin13 lights up when i press a button so i know the microcontroller sees a button push.

Here is my circuit

-The problem is that it doesn't read my first button at all (adc value between 0 and 100)
-When optionspotx goes to negative then back to 0 it goes 1,11,21,31
-Sometimes when i press one button it reads the others, (might be debouncing issue)

Is this the best to have multiple buttons and a screen? If there is another way I'm open to suggestions.
Here is my sketch

/*00000000      Libraries included    00000000*/

#include <SerialGraphicLCD.h>
#include <SoftwareSerial.h>

/*00000000      GLCD screen size  00000000*/

#define maxX 127//159 
#define maxY 63 //127

/*00000000      variables for PINS    00000000*/

//Bup Bdown Bleft Bright Bcenter But1 But2 But3
const int ledPin13 = 13;       // the number of the LED pin

/*00000000      Table for ADC storage    00000000*/

volatile int adcval;

/*00000000    variables for debounce    00000000*/

long debouncing_time = 200; //Debouncing Time in Milliseconds
volatile unsigned long last_micros;
volatile int state = LOW;
volatile int buttonstate;

/*00000000    variables for menus    00000000*/

int menustack[6]={1,0,0,0,0,0}; //array acting as stack for menu porder when hopping from one menu to the other.
                               // set array size to number of menus desired
int currentmenuspot=1;
int prevmenuspot=1;
int menustackspot=0;
int menunumber;

int menupage=0; //0=Mainmenu 1=AirQ 2=TEMP 3=HEART 4=MORE
int optionrow[4][4]={{11,12,13,14},{21,22,23,24},{31,32,33,34},{41,42,43,44}}; //2D array as a map for menu options 
int optionspotx=0;
int optionspoty=0;

int enterbuttons[4]={0,0,0,0}; //debounce puts a 1 to be later tested if button was pressed 1=pressed 0=not pressed

/*00000000    LCD class declaration    00000000*/
LCD LCD;
/*00000000    SETUP    00000000*/

void setup()
{
  pinMode(buttonPin[0], INPUT);     //button on pin 4
  pinMode(buttonPin[1], INPUT);     //button on pin 5
  pinMode(buttonPin[2], INPUT);     //button on pin 6
  pinMode(buttonPin[3], INPUT);     //button on pin 7
  pinMode(buttonPin[4], INPUT);     //button on pin 8
  pinMode(buttonPin[5], INPUT);     //button on pin 9
  pinMode(buttonPin[6], INPUT);     //button on pin 10
  pinMode(buttonPin[7], INPUT);     //button on pin 11
  
  //interrupt
  attachInterrupt(0, debounce, FALLING);
  
 // pin 13 on board LED
  pinMode(ledPin13, OUTPUT);  
  
  LCD.setBacklight(20);
  LCD.clearScreen();

  LCD.setHome();
  LCD.printStr(" Program starts in "); //displays "program is starting in 10/9/8 etc"
    
  for(int i = 5; i >= 0; i--) //splash screen
    {
      LCD.setX(113);
      LCD.setY(0);
      LCD.printNum(i);
      delay(500);
    } 
  LCD.clearScreen(); 
  delay(500);
}

/*00000000    LOOP    00000000*/

void loop()
{
selectmove(); //compares adc values to determine apporiate action
optionrowdisplay(); //display info on glcd
}

/*00000000    displays stuff on glcd     00000000*/

void optionrowdisplay()
{
  LCD.setHome();
  LCD.printStr("x spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(00);
  LCD.printNum(optionspotx);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(9);
  LCD.printStr("y spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(9);
  LCD.printNum(optionspoty);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(18);
  LCD.printStr("center but ");
  delay(100);
  
  LCD.setX(70);
  LCD.setY(18);
  LCD.printNum(enterbuttons[1]);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(27);
  LCD.printStr("button 1 ");
  delay(100);
  
  LCD.setX(70);
  LCD.setY(27);
  LCD.printNum(enterbuttons[0]);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(36);
  LCD.printStr("ADC value ");
  delay(100);
  
  LCD.setX(70);
  LCD.setY(36);
  LCD.printNum(adcval);
  delay(100);
}

void selectmove()
{
  
if(buttonstate==true)//interrupt debounce changes this to true
  {
  if(adcval>0 && adcval<100)
    {optionspoty=optionspoty+1;}
  else if(adcval>225 && adcval<300)
    {optionspoty=optionspoty-1;}
  else if(adcval>325 && adcval<380)
    {optionspotx=optionspotx+1;}
  else if(adcval>390 && adcval<450)
    {optionspotx=optionspotx-1;}
  buttonstate=false;//resets 
  }
}

/*00000000   debounce functions    00000000*/

void debounce() 
{
  if(((long)(micros() - last_micros) >= debouncing_time * 1000) | last_micros==0)
  {
    digitalWrite(13, state);
    state = !state;
    last_micros = micros();
    adcval = analogRead(A0); //adc resistor ladder
    buttonstate=true;        //for activating selectmove
    
  }
}/code]

Can you draw up a schematic and post it?

I see three yellow wire ends. One connects to an Analog input pin, and assuming that the other end of that connects to the top (picture-wise) of the ladder network, I am left wondering where the other one connects to.

I see a red wire on the other end of the ladder, and it looks like it connects to 3V, but I'm not sure.

In any case, when that top switch is pressed, it looks like the voltage on the analog pin will be zero, since it is running directly to GND through the switch.

Personally, I would use digital pins (or analog pins used as digital pins, if you have a need for all the digital pins.

Thank for your reply. Here is the circuit diagram.

Yigiter007:
Thank for your reply. Here is the circuit diagram.

Your main problem is that your ladder does not do what you think it does. Every button press will make the junction between two of the resistors go to GND. This means that the 3.3V is effectively removed from any influence on the analog input, and the voltage on A0 will be GND, or VERY close to it. If you have a multimeter, measure the voltage on A0 while you press the buttons, and you'll see what I mean.

The other problem I think you might run into is that even if the circuit was rewired to give you four different levels, one or more of those levels may not be enough to trigger the interrupt. As I mentioned before, I would use digital, not analog, to sense the switches. You can put diodes into the circuit to trigger an interrupt from any button press.

Attached is a schematic showing one way to get four discrete analog levels. from 4 switches, just in case you want to go analog.

Thanks for the reply. So I'm taking your advice and going with a digital input. I put a Red LED as a diode like you said to prevent voltage from going to the other buttons once one button is pressed. The Arduino sees the button push and ledpin13 turns on and off. But internally the sketch isn't recording the button pushes into the variables I assigned it to increment/decrement when a button is pushed.

Notes
Interrupt pin is internally pulled up to 5V
Multimeter readings
when button is pushed digital pin goes from 3.65V to 0V
interrupt pin is pulled up to 5V and drops to 3.8V when button is pushed
Red LED lights up when button is pushed
LEDpin13 will sometimes flicker instead of toggling aka staying on or staying off continuously.
When sketch first starts up it registers and increments optionspot7 once then never again

Here is the circuit diagram

Here is the code sketch

//v5 clone modified ith interrupts

/*00000000      Libraries included    00000000*/

#include <SerialGraphicLCD.h>
#include <SoftwareSerial.h>

/*00000000      GLCD screen size  00000000*/

#define maxX 127//159 
#define maxY 63 //127

/*00000000      variables for PINS    00000000*/
//Bup Bdown Bleft Bright Bcenter But1 But2 But3
const int ledPin13 = 13;       // the number of the LED pin
/*00000000    variables for debounce    00000000*/
long debouncing_time = 25; //Debouncing Time in Milliseconds
volatile unsigned long last_micros;
volatile int state = LOW;
volatile int buttonstate5;
volatile int buttonstate6;
volatile int buttonstate7;
volatile int buttonstate8;
/*00000000    variables for menus    00000000*/
int optionspotx=0;
int optionspoty=0;
/*00000000    LCD class declaration    00000000*/
LCD LCD;
/*00000000    SETUP    00000000*/

void setup()
{
  pinMode(5, INPUT);     //button on pin 5
  attachInterrupt(0, debounce, FALLING); //interrupt
  pinMode(ledPin13, OUTPUT);   // pin 13 on board LED
  
  LCD.setBacklight(20);
  LCD.clearScreen();
  LCD.setHome();
  LCD.printStr(" Program starts in "); //displays "program is starting in 10/9/8 etc"
    
  for(int i = 5; i >= 0; i--) //splash screen
    {
      LCD.setX(113);
      LCD.setY(0);
      LCD.printNum(i);
      delay(500);
    } 
  LCD.clearScreen(); 
  delay(200);
}

/*00000000    LOOP    00000000*/

void loop()
{
optionrowdisplay(); //display info on glcd  
}

/*00000000    displays stuff on glcd     00000000*/

void optionrowdisplay()
{
  LCD.setHome();
  LCD.printStr("x spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(00);
  LCD.printNum(optionspotx);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(9);
  LCD.printStr("y spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(9);
  LCD.printNum(optionspoty);
  delay(100);
  
}


/*00000000   debounce functions    00000000*/

void debounce() 
{
  if(((long)(micros() - last_micros) >= debouncing_time * 1000) | last_micros==0)
  {
    digitalWrite(13, state);
    state = !state;
    last_micros = micros();
    buttontest();

  }
}

void buttontest()
{
    if(digitalRead(5)==LOW)
    {optionspoty=optionspotx+1;}
    /*
    if(digitalRead(6)==HIGH)
    {buttonstate6=true;}
    
    if(digitalRead(7)==HIGH)
    {buttonstate7=true;}
    
    if(digitalRead(8)==HIGH)
    {buttonstate8=true;}
    */
}

This circuit would use the full ADC range in equal steps. The 10K ensures a stronger pullup and stable signal when no buttons are pressed. This would be the adcval ranges with 100 count gaps in between:

if(adcval>150 && adcval<250)        // button1
    {optionspoty=optionspoty+1;}
  else if(adcval>350 && adcval<450) // button2
    {optionspoty=optionspoty-1;}
  else if(adcval>550 && adcval<650) // button3
    {optionspotx=optionspotx+1;}
  else if(adcval>750 && adcval<850) // button4
    {optionspotx=optionspotx-1;}
  buttonstate=false;//resets 
  }

Edit: You could shift the ranges higher by 20 counts to account for the 10K pullup resistor.

Yigiter007:
But internally the sketch isn't recording the button pushes into the variables I assigned it to increment/decrement when a button is pushed.

    if(digitalRead(5)==LOW)
    {optionspoty=optionspotx+1;}

This isn't right. When you press the button on pin 5, you will assign x + 1 to the y variable. This is probably why you think the variable gets updated once, then never again., It's actually getting updated with the same value each time the button is pressed. I gather your reference to optionspot7 is actually a fingo (a fingo is a typo caused by your finger hitting a nearby button), and that you meant optionspoty.

Notes
Interrupt pin is internally pulled up to 5V
LEDpin13 will sometimes flicker instead of toggling aka staying on or staying off continuously.
When sketch first starts up it registers and increments optionspot7 once then never again

All millis() and micros() variables should be unsigned long. If they are not unsigned, the math will be in error when the variable goes negative.

Thanks for the reply. Sorry for my delayed reply I lost my board for a while. What a error I made thank you for catching that. I fixed it and now it registers correctly to the correct variable. The only problem now is that when i push the button sometimes it will go up by two at a time. I think it might be the structure of the button, I'm using larger version of the standard push buttons (Momentary Pushbutton Switch - 12mm Square - COM-09190 - SparkFun Electronics). Should I change the change the debounce time to fix this?

Also Thank you Dlloyd I'm going to save that resistor ladder for when I dont have any digital inputs left or I have a lot of buttons. Thanks. :slight_smile:

This setup doesn't work for multiple buttons since the Interrupt pin is pulled high when ever any other button is pressed it pulls the other buttons low too.

I got multiple buttons to work, since the interrupt is set high, I set the digital pin to high, and put both on the same of the button. Buttons still jumps the variables by two sometimes

I tried changing the variables so that they are all unsigned longs, but it still changes the variable by two when the button is only pressed once. The LedPin13 will light once and the variable will increase twice. So i think its something in the code.

Yigiter007:
I tried changing the variables so that they are all unsigned longs, but it still changes the variable by two when the button is only pressed once. The LedPin13 will light once and the variable will increase twice. So i think its something in the code.

Switches bounce. One press will give you multiple closures. You need to debounce them.

See Nick Gammon's switch tutorial at Gammon Forum : Electronics : Microprocessors : Switches tutorial

So here is an update on my project so far with the new changes.

The code is here

/*00000000      Libraries included    00000000*/
#include <SerialGraphicLCD.h>
#include <SoftwareSerial.h>
/*00000000      GLCD screen size  00000000*/
#define maxX 127//159 
#define maxY 63 //127

/*00000000      variables for PINS    00000000*/
//Bup Bdown Bleft Bright Bcenter But1 But2 But3
const int ledPin13 = 13;       // the number of the LED pin

/*00000000    variables for debounce    00000000*/
unsigned long debouncing_time = 200; //Debouncing Time in Milliseconds
volatile unsigned long last_micros;
volatile int state = LOW;

/*00000000    variables for menus    00000000*/
int optionspotx=0;
int optionspoty=0;
int enterbutton=0;
int backbutton=0;

/*00000000    LCD class declaration    00000000*/
LCD LCD;

/*00000000    SETUP    00000000*/

void setup()
{
  pinMode(5, INPUT_PULLUP);     //button on pin 5
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  
  attachInterrupt(0, debounce, FALLING); //interrupt
  
  pinMode(ledPin13, OUTPUT);   // pin 13 on board LED
  
  //splash screen
  LCD.setBacklight(20);
  LCD.clearScreen();
  LCD.setHome();
  LCD.printStr(" Program starts in "); //displays "program is starting in 5/4/3/2/1 etc"
    for(int i = 5; i >= 0; i--) 
      {
        LCD.setX(113);
        LCD.setY(0);
        LCD.printNum(i);
        delay(500);
      } 
  LCD.clearScreen(); 
  delay(200);
}

/*00000000    LOOP    00000000*/

void loop()
{
optionrowdisplay(); //display info on glcd  
}

/*00000000    displays stuff on glcd     00000000*/

void optionrowdisplay()
{
  LCD.setHome();
  LCD.printStr("x spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(00);
  LCD.printNum(optionspotx);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(9);
  LCD.printStr("y spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(9);
  LCD.printNum(optionspoty);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(18);
  LCD.printStr("enter button");
  delay(100);
  
  LCD.setX(80);
  LCD.setY(18);
  LCD.printNum(enterbutton); //counter
  delay(100);
  
  LCD.setX(0);
  LCD.setY(27);
  LCD.printStr("back button");
  delay(100);
  
  LCD.setX(80);
  LCD.setY(27);
  LCD.printNum(backbutton); //counter
  delay(100);
}

/*00000000   debounce functions    00000000*/

void debounce() 
{
  if(((long)(micros() - last_micros) >= debouncing_time * 1000) | last_micros==0)
  {
    digitalWrite(13, state);
    state = !state;
    last_micros = micros();
    buttontest();
  }
}

void buttontest()
{
    if(digitalRead(5)==LOW)
    {optionspotx=optionspotx+1;}
    
    if(digitalRead(6)==LOW)
    {optionspoty=optionspoty+1;}
    
    if(digitalRead(7)==LOW)
    {enterbutton=enterbutton+1;}
    
    if(digitalRead(8)==LOW)
    {backbutton=backbutton+1;}    
}

The circuit diagram is here

The only issue I have is when I push a button sometimes it will increment the variable twice.

I looked at your link lar3ry
I have the debouncing code in my sketch, and I tried changing the debounce time, but it didn't help.
Since the pins are being pulled up and the interrupt is set to "falling" where would i pull the capacitor to hardware debounce.

The only issue I have is when I push a button sometimes it will increment the variable twice.

  if(((long)(micros() - last_micros) >= debouncing_time * 1000) | last_micros==0)

Logical or (||) and bitwise or (|) are not interchangeable. You are NOT comparing bits.

I changed it to

if(((long)(micros() - last_micros) >= debouncing_time * 1000) || last_micros==0)

but it doesn't fix the issue it still jumps by 2 sometimes.

unsigned long debouncing_time = 200; //Debouncing Time in Milliseconds

Read this comment. Read the rest of the code that involves this variable. Fix the comment.

Then, think about fixing the value or the code.

PaulS I'm not sure what i need to fix. I know that this value changes how long to "wait" for the button noise to pass. The number is multiplied by 1000 in my debounce function so its more of a coefficient. I tried lengthening the number thinking it wasn't long enough and I tried shortening thinking it was too long and neither fixed the my variable jumping.

I also found a arduino library called debounce that was made for buttons. (found here GitHub - thomasfredericks/Bounce2: Debouncing library for Arduino and Wiring)

I'm trying to use it with my pulled up inputs, but its giving me the same issues, but worse. With this it will jump by bigger and random number 2 then 3 then 9 then 10 then 2 again, etc.

It might be easier to use, but maybe I'm using it wrong. I'm also not sure whether if the library is using interrupts and if I need to stop using my interrupt. I'm also not sure if I need to attach the function to my interrupt pin or my digital input pin.

Here is my code

//

/*00000000      Libraries included    00000000*/
#include <SerialGraphicLCD.h>
#include <SoftwareSerial.h>
#include <Bounce2.h>
/*00000000      GLCD screen size  00000000*/
#define maxX 127//159 
#define maxY 63 //127

/*00000000      variables for PINS    00000000*/
//Bup Bdown Bleft Bright Bcenter But1 But2 But3
const int ledPin13 = 13;       // the number of the LED pin

/*00000000    variables for debounce    00000000*/
long debouncing_time = 100; //Debouncing Time in Milliseconds
long last_micros;
int state = LOW;
//volatile unsigned
/*00000000    variables for menus    00000000*/
volatile int optionspotx=0;
volatile int optionspoty=0;
volatile int enterbutton=0;
volatile int backbutton=0;

/*00000000    LCD class declaration    00000000*/
LCD LCD;

/*0000000    Instantiate a Bounce object    00000000*/
Bounce debouncer = Bounce(); 

/*00000000    SETUP    00000000*/

void setup()
{
  pinMode(5, INPUT_PULLUP);     //button on pin 5
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  
    // After setting up the button, setup debouncer
  debouncer.attach(0);
  debouncer.interval(5);
  
  attachInterrupt(0, debounceAPP, FALLING); //interrupt
  
  pinMode(ledPin13, OUTPUT);   // pin 13 on board LED
  
  //splash screen
  LCD.setBacklight(20);
  LCD.clearScreen();
  LCD.setHome();
  LCD.printStr(" Program starts in "); //displays "program is starting in 5/4/3/2/1 etc"
    for(int i = 5; i >= 0; i--) 
      {
        LCD.setX(113);
        LCD.setY(0);
        LCD.printNum(i);
        delay(500);
      } 
  LCD.clearScreen(); 
  delay(200);
}

/*00000000    LOOP    00000000*/

void loop()
{
optionrowdisplay(); //display info on glcd  

}

void debounceAPP()
{
 debouncer.update();  // Update the debouncer

 int value = debouncer.read(); // Get the update value\
 
   if( value == HIGH) 
       {
         digitalWrite(13, state);
         state = !state;
         buttontest();
       }
}

/*00000000    displays stuff on glcd     00000000*/

void optionrowdisplay()
{
  LCD.setHome();
  LCD.printStr("x spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(00);
  LCD.printNum(optionspotx);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(9);
  LCD.printStr("y spot ");
  delay(100);
  
  LCD.setX(50);
  LCD.setY(9);
  LCD.printNum(optionspoty);
  delay(100);
  
  LCD.setX(0);
  LCD.setY(18);
  LCD.printStr("enter button");
  delay(100);
  
  LCD.setX(80);
  LCD.setY(18);
  LCD.printNum(enterbutton); //counter
  delay(100);
  
  LCD.setX(0);
  LCD.setY(27);
  LCD.printStr("back button");
  delay(100);
  
  LCD.setX(80);
  LCD.setY(27);
  LCD.printNum(backbutton); //counter
  delay(100);
}


void buttontest()
{
    if(digitalRead(5)==LOW)
    {optionspotx=optionspotx+1;}
    
    if(digitalRead(6)==LOW)
    {optionspoty=optionspoty+1;}
    
    if(digitalRead(7)==LOW)
    {enterbutton=enterbutton+1;}
    
    if(digitalRead(8)==LOW)
    {backbutton=backbutton+1;}    
}

Each button has a pin set to INPUT_PULLUP?

Suppose you could detect and debounce the lot and act on them all without using an interrupt or delay()?
Why no interrupt is because debounce takes more than a milli which is slower than you need an interrupt for.

I have a one button debounce with blinking led that could easily handle 4 buttons without a lot of work.
For my example, the buttons could send a line of serial or affect the blink rate.
I haven't even tried and I'm already sure! Any bets I can't? :stuck_out_tongue:
Here's the bet: if I can't then anyone who takes it gets karma and if I can then anyone who takes it gives me karma.

Here's the 1 button code, an expanded version of our beloved BlinkWithoutDelay.

// All you need to do this example is an Arduino and a jumper in pin 2.
// Push and let go the button once, led blinks. Again, led turns off.
// Button is debounced and wired directly to ground or, the button can 
// be a jumper from pin 2 grounded on USB connector. Tested with jumper.
// By GoForSmoke for free public use. Using Arduino 1.0.5-r2

/*
This sketch uses millis() timing but only the low bytes. Unsigned math
 lets this work just as well as with all 32 bits.
 This sketch has 3 separated code blocks. 
 --- 1 for the button that changes buttonState only after a debounced change.
 --- 1 that processes changes in buttonState to control led blink.
 --- 1 that blinks the led when it's supposed to.
 Each code block could be replaced by another with minimal change to the others.
 More blocks can be added, they don't have to deal with the led but they could.
 If you want to merge sketches then here is one strategy to do it.
 */

// Look Ma, no extra libraries!

const byte ledPin =  13;      // this is the onboard led pin
const byte buttonPin = 2;


byte ledPinState = 0;
byte blinkLed = 0; // led stays off when 0, blinks when 1
const word ledBlinkMs = 1000U; // blink interval
word ledBlinkNowMs = 0U; // low 16 bits of millis()
word ledBlinkStartMs = 0U; // 16 bit blink (on or off) time ms

// button variables
byte buttonRead;  // wired for pullup -- if down then LOW, if up then HIGH 
byte lastButtonRead = HIGH; // so debounce knows previous read
byte checkDebounce = 0; // only checks decounce after every button pin change
byte lastButtonState = 0; // last stable button state
byte buttonState = 0;  // stable button state
// 0 = button is up after debounce
// 1 = button is down after debounce
// button debounce timing variables
const byte debounceMs = 10; // debounced when reads for debounceMs ms are the same
byte debounceMillsLowByte = 0; // only need to count to 10, don't need 32 bits
byte msNow = 0; // don't worry, unsigned rollover makes it work 


byte processState = 0;  // the button gets pushed and released twice
// 0 = 1st press has not happened, led blink is OFF, looking for press
// 1 = 1st press is down, led blink is ON, looking for release
// 2 = 1st push relesased, led blink stays ON, looking for 2nd press
// 3 = 2nd press is down, led blink is OFF, looking for release
// there is no state 4, 2nd push release changes state to 0


void setup() 
{
  Serial.begin( 57600 );

  pinMode( ledPin, OUTPUT );  // default is LOW 
  pinMode( buttonPin, INPUT_PULLUP ); // my button connects to ground, not 5V
  // however that means that the pin is LOW when the button is pressed.
}

void loop() // make sure that loop() runs fast and stays in the "now".
{

  // BUTTON CODE BLOCK, it handles debouncing. 
  // the task is to set the variable buttonState when a Stable Button State is reached.
  // other sensor code could change the same variable if desired

  // read the pin which may be changing fast and often as the jumper contacts the housing
  buttonRead = digitalRead( buttonPin ); // momentary state

  // msNow gets the low byte of millis() to time the very short debounce time
  msNow = (byte) ( millis() & 0xFF ); // don't worry, unsigned rollover makes it work 

  if ( buttonRead != lastButtonRead )
  {
    debounceMillsLowByte = msNow;
    checkDebounce = 1;
  }
  else if ( checkDebounce )
  { 
    if ( msNow - debounceMillsLowByte >= debounceMs ) // stable button state achieved
    {
      buttonState = !buttonRead; // mission accomplished, button is stable
      // note that buttonState is opposite buttonRead
      checkDebounce = 0; // stop debounce checking until pin change
    }
  }
  lastButtonRead = buttonRead;
  //
  // End of the BUTTON CODE BLOCK

  //==================================================================================

  // LED CONTROL CODE BLOCK that uses buttonState and processState
  if ( lastButtonState != buttonState )
  {
    lastButtonState = buttonState;

    // debug type prints -- also testing some loading, does it alter the blink much?
    Serial.println( F( "============================================================" ));    
    Serial.print( F( "processState " ));
    Serial.print( processState );
    Serial.print( F( "  buttonState " ));
    Serial.println( buttonState );
    Serial.println( F( "============================================================" ));    

    switch ( processState )
    {
    case 0: // 0 = 1st press has not happened, led is OFF, looking for press
      if ( buttonState == 1 ) // button is pressed 
      {
        processState = 1;
        blinkLed = 1;
        ledPinState = 0;
        ledBlinkStartMs = (word) ( millis() & 0xFFFF )- ledBlinkMs; 
      }
      break; // note that until the 1st press, this case runs over and over

    case 1: // 1 = 1st press is down, led is ON, looking for release
      if ( buttonState == 0 ) // button is released 
      {
        processState = 2;
      }
      break; // note that until the 1st release, this case runs over and over

    case 2: // 2 = 1st push relesased, led stays ON, looking for 2nd press
      if ( buttonState == 1 ) // button is pressed 
      {
        processState = 3;
        blinkLed = 0;
      }
      break; // note that until the 2nd press, this case runs over and over

    case 3: // 3 = 2nd press is down, led is OFF, looking for release
      if ( buttonState == 0 ) // button is released 
      {
        processState = 0; // back to the start
      }
      break; // note that until the 2nd release, this case runs over and over
    } 
  }
  // End of the LED CONTROL CODE

  //==================================================================================

  // LED BLINK CODE BLOCK
  if ( blinkLed )
  { 
    ledBlinkNowMs = (word) ( millis() & 0xFFFF );

    if ( ledBlinkNowMs - ledBlinkStartMs >= ledBlinkMs )
    { 
      ledPinState = !ledPinState;
      digitalWrite(ledPin, ledPinState );
      ledBlinkStartMs = ledBlinkNowMs;
    }
  }
  else
  {
    ledPinState = LOW;
    digitalWrite(ledPin, LOW );
  }
  // End of the LED BLINK CODE

  //==================================================================================

  // Want to add serial commands and args input? 
  // this is a good spot. Serial is so slow it can usually go last.
}

Still no takers?

Okay then, here's interrupt free BWD with 8 debounced buttons.
When you're watching for a 200 ms timeout, you don't need interrupts.

I didn't use buttons. I used jumpers for extra dirty contact bounce.
The button pins are 4 through 11.
Pin 4, button 0, slows the blink, doubles time to a maximum of 4000 ms and reports the press
Pin 5, button 1, speeds the blink, halves time to a minimum 0f 10 ms and reports the press.
Pin 11, button 7, toggles the blink on and off and reports press and release as well as process status.
All the others report when pressed or released.

This sketch is ready to modify/add to.
There's plenty of room, avrdude reports it uses 4472 bytes of flash.
Free RAM on the test UNO at start is 1751 bytes. I might be able to skin that down a few.

It's not beginner friendly. It uses bits to keep track of 5 different states per button.
That's 8 bytes used where 40 would do. Silly me. :stuck_out_tongue:

Annnnnnnnd it won't fit in this post or the next.
I guess I could remove the comments and make it even less user friendly. There's lots of comments.
Or maybe it's just someone up there saying don't bother.