Debouncing on Multiple Input Pins, both directions

I was looking for some help on Debouncing on multiple inputs and found this:

Debouncing on Multiple Input Pins

The solution put up by @PerryBebbington was great. I changed a couple lines from his code and made it debounce the state change, not just the press. I'm using this for reading switches.

/* Simple button debounce for 4 buttons. Increments a count and sends the updated count to the serial monitor once per button press */
/* Tested on Mega 2560 */

#define noOfButtons 4     //Exactly what it says; must be the same as the number of elements in buttonPins
#define bounceDelay 40    //Minimum delay before regarding a button as being pressed and debounced
#define minButtonPress 3  //Number of times the button has to be detected as pressed before the press is considered to be valid

const int buttonPins[] = {48, 49, 50, 51};      // Input pins to use, connect buttons between these pins and 0V
uint32_t previousMillis[noOfButtons];       // Timers to time out bounce duration for each button
uint8_t pressCount[noOfButtons];            // Counts the number of times the button is detected as pressed, when this count reaches minButtonPress button is regared as debounced 
uint8_t buttonStates[noOfButtons];          //state of buttons, used to see value, and to see if changed

void setup() {
  uint8_t i;
  uint32_t baudrate = 9600;
  Serial.begin(baudrate);
  Serial.println("");
  Serial.print("Serial port connected: ");
  Serial.println(baudrate);

  //setup buttonStates Array, populate with HIGH
  for (i = 0; i < noOfButtons; ++i) {
    buttonStates[i] = HIGH;
  }

  for (i = 0; i < noOfButtons; ++i) {
    pinMode(buttonPins[i], INPUT_PULLUP);

  }
}

void loop() {
  debounce();
  delay(10);     //Your other code goes here instead of this delay. DO NOT leave this delay here, it's ONLY for demonstration.
}

void debounce() {
  uint8_t i;
  uint32_t currentMillis = millis();
  for (i = 0; i < noOfButtons; ++i) {
    uint8_t thisReading = digitalRead(buttonPins[i]);
    if (thisReading == buttonStates[i]) {             //Input is unchanged, button not changed or in the middle of bouncing and happens to be back to previous state
        previousMillis[i] = currentMillis;        //Set previousMillis to millis to reset timeout
        pressCount[i] = 0;                        //Set the number of times the button has been detected as pressed to 0
      } else {
      if (currentMillis - previousMillis[i] > bounceDelay) {
        previousMillis[i] = currentMillis;        //Set previousMillis to millis to reset timeout
        ++pressCount[i];
        if (pressCount[i] == minButtonPress) {
          buttonStates[i] = thisReading;
          doStuff(i);                             //Button has been debounced. Call function to do whatever you want done.
        }
      }
    }
  }
}

// Function to do whatever you want done once the button has been debounced.
// In this example it increments a counter and send the count to the serial monitor.
// Put your own functions here to do whatever you like.
void doStuff(uint8_t buttonNumber) {
  
  Serial.print("Button ");
  Serial.print(buttonNumber);
  Serial.print("  Current Value: ");
  Serial.println(buttonStates[buttonNumber]);
}
1 Like

What’s the question ?
Do you have extra buttons (up down etc )?

What means

debounce the state change

I have never heard of a bouncing state change, nor seen any code for debouncing them.

Button input gets debounced so we can handle clean edges, clean edges are used to detect state changes.

If you would post your version and @PerryBebbington's version, perhaps we could see what you are needing to call debouncing the state change.

Please make it easy for us to see the difference you wrought.

a7

I found the code listed in the link in my original post. The URL is:

The code written by @PerryBebbington is the first response.

I'm reading the inputs from 2 of these switches:

When holding down the switch down (and reading LOW), it will sometimes momentarily read HIGH.
I wanted a way to make sure it stayed HIGH for a few milliseconds before acting on the button being released and reading a HIGH state. Whether or not this is technically "debouncing", I'm not sure.

I had a problem, and I couldn't find any other code that solved my specific problem. When I was able to solve it, I figured I'd post it here so that someone in the future could find the code and use it. If you don't have a use for this code, that's fine.

@lastchancename , there is no question. I'm just posting some code that I found, and tweaked, and solved my very specific problem.

@alto777 , Like I said, I don't know if this is technically debouncing. I just know it solved my very specific problem. If you have a different term for it, we can call it that.

look this over


const byte buttonPins[] = {48, 49, 50, 51};
const int  Nbut = sizeof(buttonPins);

byte buttonState [Nbut];
int  stuff       [Nbut];

// -----------------------------------------------------------------------------
void loop()
{
    for (unsigned i = 0; i < Nbut; ++i) {
        byte but = digitalRead (buttonPins[i]);
        if (buttonState [i] != but)  {      // state change
            buttonState [i] = but;
            delay (20);                     // debounce
            if (LOW == but)  {              // pressed
                Serial.println (++stuff [i]);
            }
        }
    }
}
// -----------------------------------------------------------------------------
void setup()
{
    uint32_t baudrate = 9600;
    Serial.begin (baudrate);
    Serial.println (baudrate);

    for (unsigned i = 0; i < Nbut; ++i) {
        pinMode (buttonPins[i], INPUT_PULLUP);
        buttonState [i] = digitalRead (buttonPins [i]);
    }
}

As far as I can tell, you have no problem, you just stopped by to share. But basically just shared @PerryBebbington's code, and said something that still makes no sense about state change debouncing.

Is that correct?

This

When holding down the switch down… it will sometimes momentarily read HIGH

Yes, but this should only happen right when you digitalRead() the switch. The hole point of the debouncing code is to eliminate that, so this should not have been a software issue.

As far as I can tell, you did not post the code with your modifications. So I have no idea what was wrong and why what you did fixed it and that's OK, you happy I'm happy.

a7

OK now I am curious about the switch you linked, and exactly how you wired it. I'm not able at this time to grab into the details, but I wonder if you tested this with a regular switch with the same switch functionality and got the same bad results.

a7

If you read a pin regularly (like when bit 9 or 10 of micros() changes) and store the state in bit 0 a byte that you left-shifted just before, the bits of that byte will show a pin-change history. When bit 7 is one state and bits 0 to 6 are the other, that is record of a transition and following 7 periods of stability -- aka debounce.
It doesn't have to be that many. fewer bits can be used but when I read every 512 micros I call 3.5 ms enough and if I read every ms then transition plus 7ms stable isn't bad either.
So when my function in loop() sees history == 127 or 128, those are release and press. I don't need to subtract times and compare, reading on time (micros() since millis() is +/- 1) is all the timing necessary and a port read can handle 6 pins at once on an Uno, 8 on a Mega.
The code is very lean and the intervals entirely tunable.
To port read and update 6 histories takes less time than a single Arduino digitalRead().
One function can handle many pin-history values and whatever process compares those to 127 or 128 doesn't get tied down in details, it's down to a simple compare.

@alto777 , yes I just wanted to share. no questions.
I only changed a couple lines from the original code.
Specifically

uint8_t buttonStates[noOfButtons];          //state of buttons, used to see value, and to see if changed

  if (thisReading == buttonStates[i]) {             //Input is unchanged, button not changed or in the middle of bouncing and happens to be back to previous state
  

If you don't like the code, or my post, just ignore it.

@GoForSmoke , your solution sounds more elegant than what I have. Can you share your code?

No need to get snippy. I'm just curious, and thanks for sharing.


Please post your complete sketch! The lines you quote with zero context do not show how you solved the problem you were having.

TIA

a7

You may enjoy diving deeper. This does, and describes several software solutions,

See Listing 2. and the accompanying explanation.

HTH

a7

1 Like

Using 4 buttons on a Mega2560, we want them all on the same 8 pin port.

Here is a pin map of the mega2560 showing digital pin numbers in brown with associated port/pin in yellow and features, the upper left legend tells color codes. Pins 48-51 are on the bottom part.


pins 42 to 49 are D42 to D49 are all on Port L with 49 as PL0.
Pins 50 and 51 are on Port B. For faster&easier, could the buttons be on pins 49 (PL0), 48 (PL1), 47 (PL2) and 46 (PL3)? In that order the 1st port pin is 49 and the 4th is 46. That's the way the board traces fit, I guess and WHY we need the frikking pin map!
I dunno your project so maybe you want different pins, if they are all on the same port the code will be simpler and the Mega has many ports!

If we use port L, pins 0 to 3, we will be reading the chip register PINL directly and the low 4 bits will have pin status for all 4 buttons. The order of the pins won't make the code smaller but will make it a bit easier to follow. No matter what board you use, choosing pins is necessary and know that the Mega, like the Uno and ordinary Nano and many Arduinos uses an ATmega family chip, our code will take advantage of AVR hardware... other chip families like AMD chips will have some differences to work out.
This is all for speed and less code. We can just stick with convenient pin holes, it means slower and more code is all.

Whatcha say? I don't have any Mega2560 ready examples, this is off the cuff.

Also if you do this, you can't have any delay()s in the sketch as they would BLOCK on-time pin reads. You will be using time-based code instead... things can wait but not stop everything to do it! Using delay() stops everything. It Wastes 16000 cpu cycles per ms of delay, it's Dick and Jane level code. I do have a 2 sketch demo about that with much less 'elegant' code, you bet!

If N bits are on one port, then the technique of

vertical counter debounce

is worth looking into.

Here's a competent article with code and explanations

I have some demos of that, I think. I'll check when I am in the lab.

The code makes sense if you've ever looked into how to make addition circuits using logic gates, in other words a technique with roots in hardware similar to the origins of the bit-shifting algorithm - which itself is entirely amenable to a parallel port-at-a-time implementation.

It can get addictive. Then one day, you may just throw in the towel and find a decent library, if only so your real code isn't cluttered up with debouncing, nor the process steps coming ked with debouncing making both harder to discern.

a7

Have you thought about using the keypad library, I believe it includes debouncing?

is there a valid reason for this appllication that a simple delay is inadequate to debounce an input?

i understand that timers may be required in a real-time applicaiton where a 20 delay impacts response. Is this that kind of real-time application?

#define DebounceMsec    10
int
chkButtons ()
{
    unsigned long  msec = millis ();

    But *b = & buts [0];
    for (unsigned n = 0; n < sizeof(pinsBut); n++, b++)  {
        if (b->msecLst && (msec - b->msecLst) < DebounceMsec)
            continue;                  // ignore button until timeout

        b->msecLst = 0;

        byte but = digitalRead (pinsBut [n]);

        if (b->state != but)  {
            b->state = but;
            b->msecLst  = msec ? msec : 1;      // make sure non-zero

            if (On == but)
                return n;
        }
    }
    return -1;
}

It depends on how responsive you want your code to be and how many things you want to do "at once". Using delay() cripples sketches and robs their cycles.

Key reason to not use delay(10) is that is completely wastes 160000 cycles when so many opcodes execute in 1 or 2 cycles, that in 320 cycles (20 usecs) void loop() may do several useful steps then a delay(1) in the same loop() can change the number of times a pin is read and something useful done from 50KHz to less than 1KHz. THAT is what Blocking does in code. When I started adding loop counters my guess-so went poof.

I have 2 sketches from earlier this year just for someone who wanted beginner simple, if that's what you want. The delay() using sketch isn't stuttery or lame. It's only limited in how far it can be taken.

// BlockedSketchLedAndButton v1.1 by GoForSmoke 03/13/24 
// for 1.1 a lot of comments have been added.

// This sketch is not PERFECT. 
// It explores doing 2 things "at once" with delay-code.
// How responsive the button acts says how well it works.
// this began with a simple Blink With Delay Sketch.
// This sketch adds an ON/OFF toggle button with delay debounce.

// compiles, untested so far
// expect: ground pin 7 to toggle blink off/on
// expect this to take a while to notice button action.
// expect the button response to be quicker when blink is OFF.
// this is a Before Sketch. The After Sketch does not Block Execution.

// note for Beginners
// Arduino variable type byte can be 0 to 255, easy to type.
// variable type unsigned int can be 0 to 65535, Arduino name is word.
// Where an int uses 16 bits of RAM, Arduino byte uses only 8.
// When I only need a byte, I don't use more on Arduinos.

const byte debounce = 10; // ms delay to ignore contact bouncing.
const byte blinkPin = 13; // LED pin to blink
const byte buttonPin = 7; // the name says it

byte buttonStateNow;  // pin state may be 0 (LOW) or not-0 (HIGH).
byte buttonStatePrev; // see if pin state changed from Prev to Now.
byte blinkState;  // the LED is 0 (LOW) or not-0 (HIGH)
// using 0 for LOW and not-0 for HIGH gives options not used here
// and that is the way the chip works. 
// When you have 8 bits for one T/F... they can ALL be used.

unsigned int blinkInterval = 500; // so far the blink ON time = OFF time
const unsigned int interval = 500; // to restore the working value
// the button works by making blinkInterval = 0 or = interval

void setup() 
{
  pinMode( blinkPin, OUTPUT ); // LOW by default
  // blinkState = 0; // by default
  pinMode( buttonPin, INPUT_PULLUP );  // up is 1, down is 0
  buttonStateNow = buttonStatePrev = 1; // start values initialized
}

void blink() // can be turned OFF and ON through blinkInterval
{
  if ( blinkInterval > 0 ) // button toggled to ON by default
  {
    blinkState = !blinkState; // 0 <==> 1
    digitalWrite( blinkPin, blinkState ); // led blink
    delay( blinkInterval );
  }
}


void button() // works by changing blinkInterval, OFF turns LED OFF.
{
  buttonStateNow = digitalRead( buttonPin );

  // button press detect, state was 1, now 0
  if (( buttonStateNow == 0 ) && ( buttonStatePrev == 1 )) // press detected
  {
    delay( debounce ); // contact switch changes are briefly DIRTY

    if ( blinkInterval > 0 )  // if blink is ON, turn it OFF
    {
      blinkInterval = 0; // stop blinking
      blinkState = 0;
      digitalWrite( blinkPin, blinkState );
    }
    else    // if blink is OFF, turn it ON
    {
      blinkInterval = interval; // blink interval restored
    }
  }
  // button release detect, state was 0, now 1
  else
  {
    if (( buttonStateNow == 1 ) && ( buttonStatePrev == 0 )) // release detected
    {
      delay( debounce ); // DIRTY on both press and release, a few ms.
    }
  }

  buttonStatePrev = buttonStateNow;  // button done, make Prev = Now
}

void loop()
{
  button();  // which comes first doesn't really matter
  blink();   // try running blink first if yer not sure!
}

same sketch unblocked

// NonBlockedSketchLedAndButton v1.1 by GoForSmoke 03/15/24
// compiles, untested so far
// expect: ground pin 7 to toggle blink off/on

// this example has loop counter added, loop() speed averages ~89057 Hz 
// loop counter code commented out, can be un-commented. 06/23/24


const byte blinkPin = 13; // Uno board LED pin13

const byte buttonPin = 7; // a jumper from pin 7 to GND = pressed
byte buttonStateNow, buttonStatePrev; // to compare what is to what was
const byte debounce = 10; // ms delay while the contacts settle, dirty signal
unsigned int debounceStart; // interval is debounce above

byte blinkState;  // led13 0=OFF, not-0=ON
unsigned long blinkStart, blinkInterval = 500; // ON/OFF times are equal
const unsigned long interval = 500; // so blinkInterval can reset after change

/*  Loop Counter code commented OUT
extern volatile unsigned long timer0_millis; // might be faster than millis()
void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set) // 1 second
  {
//    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
//    Serial.write('\t');
    Serial.println( count ); // 32-bit binary into decimal text, load of cycles!
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set; //  changes 0<==>1 
  }
}
*/

void setup()
{
  Serial.begin( 115200 );
  pinMode( blinkPin, OUTPUT ); // LOW by default
  // blinkState is 0 by default
  pinMode( buttonPin, INPUT_PULLUP ); // pin reads 0 when the button to ground is down
  buttonStateNow = buttonStatePrev = 1;  // start with button up as last pin read
}

void blink()  // only blinks when blinkInterval > 0
{
  static unsigned long waitStart, waitMs; // time values, always unsigned
  if ( blinkInterval == 0 ) // here is where the blink is stopped if true
  {
    return; // let loop() run so that this can check later.
  }

  if ( waitMs > 0 ) // if-timer runs when set then turns itself off
  {
    // working with time, End Time - Start Time = Elapsed Time
    // that formula always works even across rollover (no problem).
    if ( millis() - waitStart >= waitMs )  // see if wait is over
    {
      waitMs = 0; // wait is over, turn wait off!
    }
  }
  else
  {
    blinkState = !blinkState; // != is NOT =, toggle 0 <==> 1
    digitalWrite( blinkPin, blinkState ); 
    waitMs = blinkInterval; 
    waitStart = millis();
  }
  return;
}


void button()  // this works, it's not the quickest but is simpler than quicker code!
{
  static byte State = 0;  //  local variable only the function uses
  static unsigned long waitStart, waitMs;  // local vars

  if ( waitMs > 0 ) // if-timer for delays set in this function
  {
    if ( millis() - waitStart >= waitMs ) // check if elapsed time >= wait time
    {
      waitMs = 0;
    }
  }
  else // no timer delay this time
  {
    switch ( State )  // now we run the state machine!
    {
      case 0 : // looking for change from button WAS up and is NOW down.
        buttonStateNow = digitalRead( buttonPin );
        if ( buttonStateNow == 0 && buttonStatePrev == 1 ) // press detected
        {
          waitMs = debounce;  // time to spend ingnoring button bounce
          waitStart = millis();  // setting start time to NOW
          State = 1;
        }
        else if ( buttonStateNow == 1 && buttonStatePrev == 0 ) // release detected
        {
          waitMs = debounce; // setting the interval of ignoring the pin
          waitStart = millis();  // when the blink is to start... is NOW
        }
        buttonStatePrev = buttonStateNow;  // done with the read, make it previous
        break; // leave the switch-case

      case 1 :
        if ( blinkInterval > 0 )  // if the blink is still active
        {
          blinkInterval = 0; // stop blinking
          digitalWrite( blinkPin, LOW ); // the led may be ON or OFF, make sure it's OFF
        }
        else // the blink must be OFF
        {
          blinkInterval = interval; // blink at interval, restarts blinking
        }
        State = 0; // so now it goes back to waiting for button press
        break; // exit the switch-case
    }
  }
  return;
}

void loop() // this will run so fast the order of functions makes no difference
{
 // LoopCounter(); // the function runs as a task, the optimizer will inline the code.
  button();
  blink();
}

There is a lesson about unblocking as a technique if you get that far. What has delay() can be converted to work and play with other tasks.

@GoForSmoke , I wasn't aware about the different ports, and pins grouped to ports. I'll have to look into this more. For the moment though, I'm stuck with the pins I have. I already have a few devices out in the field.

And as far as the pin map, I like to keep all the pin numbers, and some variables listed in a properties file, for easy reference (and ease of changing). That's just a preference of mine.
I sometimes go months without putting any of this project together, and having all the pins listed in a properties file helps when I need to assemble some again in the future (always be kind to your future self)

Okay, we'll go a little slower and use a bit more code, it's so little extra cycles you'd need 100 buttons to see maybe 1 ms difference.

You're fine with no delay() code is the critical part, I'll go with Arduino digital reads and keep the read history bytes. I found a 2018 demo of using read history to debounce multiple buttons, see if you like it. It's for 2 buttons but easy to change and the code should work as is. The whole add-a-sketch series was made to show how with non-blocking code we can add tasks till the load and resources fill capacity and they still run together.

// add-a-sketch_buttons 2018 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 6/2018 by GFS. Compiled on Arduino IDE 1.6.9.
/*  Button Debounce Example

  --- for this example connect a button between pin 7 and GND
  --- or stick a jumper in pin 7 and while holding the board steady
  --- tap the free end onto the grounded USB port box to press.

  Yes I'm using a 16 bit micros timer to time fractions of millis as micros.
  The button reader only reads 1 button per call so as to not block void loop().
  Each button has a history byte that holds the last 8 reads with 256 possible
  states but only 4 of them being significant.
  0 is the button held down
  255 is the button left up
  127 is the buton transitioning from up to down, button just released.
  128 is the button transititioning from down to up, button just pressed.
  everything else is to be ignored as bounce.

  For multiple buttons on 1:1 pins the between-reads time is reduced and each
  pin is read in turn. This demo makes pin 0 be ON-RESET/OFF and pin 1 adjusts
  blink time.
*/

// multi_buttons vars
const byte buttons = 2;
byte buttonIndex, lastIndex; // only process 1 button every waitButtonTime micros
byte buttonPin[ buttons ] = { 6, 7 };
byte buttonHistory[ buttons ];
word markButtonTime;        // 16-bit micros timers
const word waitButtonTime = 250; // micros, 20 to 500 / more to fewer buttons.
// type word as micros can time across 65.535 millis before rollover, can be a few late

// added sketch task, on-off blinker vars
byte ledState, ledPin = 13; // use byte for small values, int cost 2 bytes
word startBlink, waitBlink; // 16 bit millis is good to time 65.535 seconds


void multiButtonsTask()
{ // read twice per milli, bits 0 to 6 all same sets the state
  if ( word( micros()) - markButtonTime >= waitButtonTime ) // read occaisoinally
  {
    buttonHistory[ buttonIndex ] <<= 1; // if you don't know <<= look it up in the Arduino Reference
    // keep a browser open to that page when you use the IDE.
    buttonHistory[ buttonIndex ] += digitalRead( buttonPin[ buttonIndex ] ); // read history streams through buttonHistory
    markButtonTime = micros(); // gets the low 16 bits of micros(), time to 60 ms + margin
  }

  // ++buttonIndex pre-increments buttonIndex before comparing to buttons
  if ( ++buttonIndex >= buttons )   buttonIndex = 0;
}

void OnOffBlinker() // only blinks if there's a wait time, can be switched on/off
{
  if ( waitBlink > 0 ) // this is the on/off switch
  {
    // word( millis()) gets the low 16 bits of the 32-bit millis() return.
    if ( word( millis()) - startBlink >= waitBlink ) // difference in time by subtracting start from end
    {
      ledState = !ledState;  // ! is NOT: not_0/true becomes 0/false else 0 becomes 1.
      digitalWrite( ledPin, ledState ); // the led changes state.
      startBlink += waitBlink; // next blink starts when it should, where diff > wait.
    }
  }
  else if ( ledState > 0 ) // waitBlink == 0 turns blinking off
  {
    digitalWrite( ledPin, ledState = LOW ); //  make sure the led is OFF
  } // yes, you can set a variable during calculation in C, the write here is LOW.
}


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Button Debounce Example, free by GoForSmoke\n" ));
  Serial.println( F( "This code shows use of struct, union, bit fields and 16 bit timers." ));
  Serial.println( F( "It can work with many buttons, uses structs to hold button data." ));
  Serial.println( F( "Button data structs can be arrayed even for matrix arrangements." ));
  Serial.println( F( "\n-- for this example connect a button between pin 7 and GND" ));
  Serial.println( F( "--- or stick a jumper in pin 7 and while holding the board steady" ));
  Serial.println( F( "--- tap the free end onto the grounded USB port box to press." ));

  pinMode( ledPin, OUTPUT );

  for ( byte i = 0; i < buttons; i++ )
  {
    pinMode( buttonPin[ i ], INPUT_PULLUP );
    buttonHistory[ i ] = 255;
  }
};


void loop()
{
  multiButtonsTask();
  /*
    0 is the button held down
    255 is the button left up
    127 is the buton changing from up to down, button just released.
    128 is the button changing from down to up, button just pressed.
    everything else is to be ignored as bounce.
  */

  if ( buttonIndex != lastIndex )
  {
    lastIndex = buttonIndex;

    switch ( buttonHistory[ buttonIndex ] ) // buttonHistory does not change as fast as this runs
    {
      case 128 : // pin is HIGH in bit 7, LOW for 7 reads, up to down detected
        buttonHistory[ buttonIndex ] = 0; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  press detected     " ));
        Serial.println( millis());
        if ( buttonIndex == 0 )
        {
          if ( waitBlink == 0 ) // toggle action tied to button press
          {
            waitBlink = 500; // makes the blinking start
            startBlink = millis(); // gets the low 16 bits
          }
          else // press button 2 changes the blink rate
          {
            waitBlink = 0; // makes the blinking stop
          }
        }
        else
        {
          if (( waitBlink -= 100 ) < 100 ) // setting a var in an expression is okay C
          {
            waitBlink = 1000;
          }
        }
        break;
      case 127 : // pin is LOW in bit 7, HIGH for 7 reads, down to up detected
        buttonHistory[ buttonIndex ] = 255; // change detected, make it into no change now
        Serial.print( F( "button " ));
        Serial.print( buttonIndex );
        Serial.print( F( "  release detected   " ));
        Serial.println( millis());
        break;
    }
  }

  OnOffBlinker();
}

It can be faster though it's already very fast.

See how your 4 button hardware can fit the demo first?
maybe mod it so each button does something different like to blink speed or even add some other leds if it helps get a feel of what the code does.

I expect the buttons handler will be what you want most.

Note that the handler task is made to run asynchronous to what uses the buttons, so no requirement to this-then-that synchronize what each part does. When the process is ready, the history latest update should be there. IMO Real Time Code should always be asynchronous since events in reality come whenever they do.

Counting, either directly, or with vertical counter or implicitly with the shift register, means waiting for N readings agreeing before deciding a switch is open or closed.

Since switches don't show continuity unless they are on their way to being fully stable and closed, we can react sooner.

I think @GoForSmoke's implementation of the shift register technique could be made asymmetric, so the switch press would be recognized at the first LOW reading from the switch, and the release could be where we need to chill until agreement.

I only g0t as far with the original to slow the code way down, and watch the contents of one of the history bytes

void printHistory(unsigned char the)
{
  byte mask = 0x80;
  for (int ii = 0; ii < 8; ii++, mask >>= 1)
    Serial.print(the & mask ? "x " : ". ");

  Serial.println("");
}

which is fun because you can watch the little ones marching in, maybe they'll fill up the entire line, maybe not.

I tried reducing the number of buttons to zero one (!) in these tests, it did not like having only one button.

This vertical counter has the adjustment

unsigned char debounce(unsigned char sample)
{
    static unsigned char state, cnt0, cnt1;
    unsigned char delta;

    delta = sample ^ state;
    cnt1 = (cnt1 ^ cnt0) & (delta & sample);
    cnt0 = ~cnt0 & (delta & sample);
    state ^= (delta & ~(cnt0 | cnt1));

    return state;
}

and is found on

toward the end, after a good explanation of vertical counting.

If a slower interrupt or polling cycle is used, there is no need for counting in any way, the previous state is compared to the current state and transitions are deduced.

This is the same effect as simply throttling the loop, so bounces are happening at an entirely different time scale, except of course it does not block.


Here

at Listing 2 (sry, code is a picture, gack!), is the shift register method. He uses 16 bits, and a mask that can be changed to allow for how many consecutive identical readings it takes to call a switch stable. I think @GoForSmoke's implementation could use the same trick if we wanted, say, to need only 3 or 4 tests.

In any case, all these are somewhat tricky to understand, so reading at the sites I link will give a different perspective that may help with the Aha! part.

Here's where @groundFungus would say you can do any of this in hardware with one capacitor, particularly in the case of (some?) AVR input architectures that have hysteresis on the switching threshold.

I still use software. :expressionless:

a7

1 Like