Debounce multiple push buttons, switches, relays or digital signals

Can anyone break this?

[i]below this line will include latest updates[/i]

I was inspired to create this debouncer after reading this great debouncing guide, especially referring to the last plot of the microswitch on page 8, where it says “I found it usually generated a pulse train guaranteed to play havoc with simple filter code. There’s no high speed hash, just hard-to-eliminate solid ones and zeroes.”

This code monitors and cleans up any number of input signals (10 shown).
Edit: Updated - see reply#5

/*==========================================================================
• get stable readings on any number of input pins in buttons[] array
• no time-out to set after which false readings could occur
• minimum response 8+ ms (varies with signal quality)
• non blocking, no interrupts, fast response
• uses pattern recognition
• filters random noise to prevent false triggering
• detects rising and falling edges on normally high or normally low signals
• use on push buttons, switches, relays or digital inputs

buttonState:  ______|________|_|_|_|??|????????|_|_|_|____________|_|________

buttonRead:   _______________________|????????????????????????|________________

buttonState:  ????|????????????|?|?|?|__|_________|_|??|?|?|??????????????????|???

buttonRead:   ????????????????????????????????|___________________|???????????????

====================================================================dlloyd*/

// constants
const byte buttons[] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // inputs
const byte qty = sizeof(buttons);
const byte led = 13;

// variables
byte buttonState[qty];
byte buttonRead[qty];
unsigned long microsStart;

void setup() {
  pinMode(led, OUTPUT);
  for (int i = 0; i < qty; i++)
  {
    pinMode(buttons[i], INPUT_PULLUP);
    buttonState[i] = 0xFF;
    buttonRead[i] = 1;
  }
}

void loop() {
  buttonFilter();
  // your code starts here
  digitalWrite(led, buttonRead[0]); // debounced and filtered status of pin 2
}

void buttonFilter(void)
{
  if (micros() - microsStart >= 2000) // minimum interval between bounces = 2 ms
  {
    for (int i = 0; i < qty; i++)
    {
      buttonState[i] = (buttonState[i] << 1) | digitalRead(buttons[i]); // shift and read
      if ((buttonState[i] & B11111) == B01111) // if rising and high for 3 stable reads
      {
        buttonRead[i] = 1;
      }
      if ((buttonState[i] & B11111) == B10000) // if falling and low for 3 stable reads
      {
        buttonRead[i] = 0;
      }
    }
  }
  microsStart = micros();
}
Example of pattern recognition within 1 byte (bits 5,6,7 are ignored):

[b]Button State  Output  Comments[/b]
00000000      LOW     stable
000000[color=red][b]01[/b][/color]      LOW     noise   <-- typical debouncer detects rising edge
000000[color=red][b]10[/b][/color]      LOW     noise   <-- typical debouncer detects falling edge
000001[color=red][b]01[/b][/color]      LOW     noise   <-- typical debouncer detects rising edge
00001011      LOW     noise
00010111      LOW     noise
001[color=teal][b]01111[/b][/color]      HIGH    [color=teal][b]rising[/b][/color]
01011111      HIGH    stable
10111111      HIGH    stable
01111111      HIGH    stable
11111111      HIGH    stable
111111[color=red][b]10[/b][/color]      HIGH    noise   <-- typical debouncer detects falling edge
111111[color=red][b]01[/b][/color]      HIGH    noise   <-- typical debouncer detects rising edge
111110[color=red][b]10[/b][/color]      HIGH    noise   <-- typical debouncer detects falling edge
11110100      HIGH    noise 
11101000      HIGH    noise
110[color=teal][b]10000[/b][/color]      LOW     [color=teal][b]falling[/b][/color]
10100000      LOW     stable
01000000      LOW     stable
10000000      LOW     stable

Is there something to break ? Please explain, what is your question ?

Hello Caltoa,
This is my attempt at writing a universal input de-bouncer/filter function that is hopefully easy to use. I'm still quite green at programming, however I've tested this with random patterns by simulating noise (signal generator) and touching bare wires. It seems to work as intended. I'm asking if anyone is interested in testing and finds any issues, to please comment.
Regards,
dlloyd

//Inputs debounce and create array of current value 
// Author Tony Grimer Tenerife © 2013 
// Replacement Control System for table stepper control
//
//
//
// Version 1.0


#define RAISE 22   // first input pin number actual name  LIMIT1
#define STOP7 35   // LAST INPUT
#define DebounceCount 2 // number times we get same value on read before confirm its value into current
// these data arrays are declared in main sketch 
extern unsigned char  CurrentInputValue[];
extern int  InputCount[];
extern unsigned char SystemInputValue[];
extern unsigned char UpdatedInputValue[];
extern int Pin ;
void ConfigureIO()
{
  int i;
  Pin = RAISE;
  // Input First...
  for (i = RAISE; i <=STOP7; i++)
  {
    pinMode(i,INPUT_PULLUP);
  }

}

void ReadInput(int thispin) /// one pin at a time every 5 mS
{      
  CurrentInputValue[thispin- RAISE +1] = !digitalRead(thispin);  // actual read
  if (CurrentInputValue[thispin-RAISE+1] != UpdatedInputValue[thispin-RAISE+1])   // same as last time ?
  {  // different so start process of checking
    UpdatedInputValue[thispin-RAISE+1] = CurrentInputValue[thispin-RAISE+1] ;
    InputCount[thispin-RAISE+1] =0;
  }
  else
  { // same this time as last 
    if (InputCount[thispin-RAISE+1] != DebounceCount)
    {
      InputCount[thispin-RAISE+1]++;
    }
    else
    {
      SystemInputValue[thispin-RAISE+1] = UpdatedInputValue[thispin-RAISE+1];  // update array loop looks at for current state..
    }
  }
}

This is part of my generic read / debounce usually call it from a timer interrupt to get even sampling…

tgsuperspec, I like your generic read / debounce code because it looks for 2 equal reads before confirmation. So it would work both when the signal goes high or when it goes low. Also, you can change the number of equal reads in your define DebounceCount.

I see it mentions "one pin at a time every 5 mS" for reading our inputs. It looks like this time is set elsewhere to establish an interval between reads, so I guess the overall debounce time is 10 mS. So, for example, if there were 10 pins to debounce, would this increase the latency by 90 mS?

The code I posted works on an array of inputs (the example shows 10, but it can be anything from 1 input up to all available inputs). All inputs are read at once. I've set the interval between complete array reads to 2 mS.

Rather than each input requiring 1 byte for each reading, I use the byte as a shift register to store the history of the previous 8 readings. From this moving snapshot in time, the required pattern for rising and falling status are determined and used to change the output state.

An advantage is the rolling filter action where spurious noise is continuously rejected and never falsely triggers the debouncer. Using this method, it's possible (for example) to successfully debounce an input having 30 mS bounce time where the debounced signal is clean and responds in only 40 mS.

I was inspired to create this debouncer after reading this great article, especially referring to the last 2 plots of the microswitch where it says "I found it usually generated a pulse train guaranteed to play havoc with simple filter code. There's no high speed hash, just hard-to-eliminate solid ones and zeroes."

dlloyd:
The code I posted works on an array of inputs (the example shows 10, but it can be anything from 1 input up to all available inputs). All inputs are read at once. I’ve set the interval between complete array reads to 2 mS.

Rather than each input requiring 1 byte for each reading, I use the byte as a shift register to store the history of the previous 8 readings. From this moving snapshot in time, the required pattern for rising and falling status are determined and used to change the output state.

An advantage is the rolling filter action where spurious noise is continuously rejected and never falsely triggers the debouncer. Using this method, it’s possible (for example) to successfully debounce an input having 30 mS bounce time where the debounced signal is clean and responds in only 40 mS.

I was inspired to create this debouncer after reading this great article, especially referring to the last 2 plots of the microswitch where it says “I found it usually generated a pulse train guaranteed to play havoc with simple filter code. There’s no high speed hash, just hard-to-eliminate solid ones and zeroes.”

I thought your technique looked familiar. I had seen the same report as a pdf file that I referenced on another thread. I do have a couple notes/questions about your function that does the main work. Here it is again for reference (I slightly modified the bracing style to my preferred method to help keep track of brace pairs, otherwise this is the same as you posted at the head of this thread):

void buttonFilter(void)
{
  if (millis() - millisStart >= 2)
  {
    for (int i = 0; i < qty; i++)
    {
      buttonState[i] = (buttonState[i] << 1) | digitalRead(buttons[i]); // shift and read
      if ((buttonState[i] & B11111) == B01111)  // if rising and high for 3 stable reads
      {
        buttonRead[i] = 1;
      }
      if ((buttonState[i] & B11111) == B10000)  // if falling and low for 3 stable reads
      {
        buttonRead[i] = 0;
      }
    }
    millisStart = millis();
  }
}

By taking the snapshot from millis() where you are, you are actually increasing the minimum sampling period from the configured 2ms to (2 + (time to cycle through inputs))ms. I would move the millisStart = millis(); to be the first (instead of last) statement within the if (millis() - millisStart >= 2) statement block. Maybe even change from counting millis() to micros(), not for any higher timing precision but because AFAIK the micros() function takes less clock cycles than the millis() function. Leaving more clock cycles for things other than basic housekeeping.

I also just double-checked your timing variable, millisStart, size. It’s too small, and should really be unsigned long.

With further thought, I’m wondering if it wouldn’t be better to fire the function regularly with a timer interrupt. My thought here is if the actual function of what a sketch is designed to do makes single or multiple iterations of loop() last longer than 2ms (lots or poorly managed I/O with Wire, SPI, and/ or Serial, a lot of floating point math, lots of analogRead()s, etc) then the input latency increases. Unfortunately, I don’t have enough experience playing with timing interrupts (actually none…) to know without significant research how to implement one w/o breaking any of the core Arduino routines that also use interrupts behind the scenes.

Just food for thought.

Thanks Sembazuru for your comments and tips. I'm not sure if I have any technique to speak of yet, but I guess you could call it "rough around the edges".

By taking the snapshot from millis() where you are, you are actually increasing the minimum sampling period from the configured 2ms to (2 + (time to cycle through inputs))ms.

Yes I agree, however the time to cycle through inputs is very fast (see below).

Note that the 2 ms interval is the "period between reads". By using the shift register for pattern recognition (B01111 for rising edge and B10000 for falling edge) the over-all latency would be 8+ ms (in the example) but only if the input signal is clean. If the input signal is has considerable bounce, say 50 ms, then the rising edge would not be detected until B01111 occurs (58-60 ms). That way the function can safely reject noise while providing a responsive output.

Note that if the user's sketch takes longer than the 2 ms reading interval, this only adds to the latency which would not create a problem. If the user's sketch was so slow that it's iteration speed was only 10 Hz, then it would take about 1/2 second for the debouncer to recognize the correct pattern and change the output ... but it would still work.

This is because there is no time-out involved that tells the debouncer to start looking for another input event. It only looks at the 8-bit pattern. This pattern only shifts left after each read, which is determined by the main loop's execution speed.

I've timed the function on the Due. For 1,000 iterations of 10 inputs (10,000 reads) it took 790 us or 0.79 us per read. Outside of the function, I've timed just one digitalRead(pin) for 1,000 iterations and got 1170 uS or 1.17 us per read. For some reason the function is faster??

In this case, 10 inputs can be debounced with signals at 1 kHz with still lots clocks left over.

Thanks again for the suggestions... I'll change the millisStart to unsigned long (and its location in the function) and use the micros() function for its improved execution speed.