How does a timer interrupt solve the problem of pushbutton debouncing?

A program uses an ISR to increment a counter variable whenever a pushbutton is hit. The pushbutton isn't debounced so it's possible that multiple external interrupts are triggered per pushbutton hit.

The solution proposed in mbed cookbook "pushbutton" (See section "Using PinDetect and a Callback Function") suggests using a timer interrupt to check for a change in pin state (in this case, we are checking the state of a pushbutton).

I don't fully understand the solution. What I understand so far is that instead of letting a change in a signal on an input pin trigger an interrupt, we effectively give a high-priority (hardware) poll on an input pin to see if it has changed state.

If the hardware polling frequency is fast enough to catch a change in signal before the signal bounces and registers that as a single hit. But then how is this different from regular polling?

Suppose a button is accepted when during 50ms the average is above or below 50%. That 50ms is the response time and it is a fixed time that determines how responsive the device is.

To get that 50ms, a hardware timer of 100Hz could be used with a filter over 5 samples. That 100Hz has a fixed influence on the system. There are very dumb and very complex mathematical calculations to do debouncing with samples at a fixed rate. Those calculations do indeed need a fixed sample rate.

When a keypad matrix is used, an interrupt is often not possible. The timer will just read the matrix and extract the pushed buttons.

A design like that has no weird consequences for longer or shorter delays. That is a good design for an embedded system. That is how embedded devices were designed for decades.

If the button would be connected to an interrupt, the interrupt could not be triggered (no button pressed) or the interrupt could be triggered at an extremely high rate (dirty and bouncing contacts). Maybe even a few kHz burst. It can no longer be predicted what the impact of the interrupt is for the device. Therefor it is a very bad design. That means that if the device would get older and the buttons get worse, the device could stop working. I'm not joking, that would really be a terrible bad design.

A software timer can be too soft. When the code uses delays, the software timer might be delayed and the pressing the button is not always the same anymore.

In the Arduino, it is often possible to avoid the use of timers and interrupts. If no delays are used, the millis() function is often enough for a software timer with good responsive buttons.

Long story short : a timer interrupt to read buttons has a fixed impact on the software, and the fixed sample rate makes it possible to use debounce calculations. For the Arduino, a software timer with millis() is often enough.

Koepel: Suppose a button is accepted when during 50ms the average is above or below 50%. That 50ms is the response time and it is a fixed time that determines how responsive the device is.

To get that 50ms, a hardware timer of 100Hz could be used with a filter over 5 samples. That 100Hz has a fixed influence on the system. There are very dumb and very complex mathematical calculations to do debouncing with samples at a fixed rate. Those calculations do indeed need a fixed sample rate.

When a keypad matrix is used, an interrupt is often not possible. The timer will just read the matrix and extract the pushed buttons.

A design like that has no weird consequences for longer or shorter delays. That is a good design for an embedded system. That is how embedded devices were designed for decades.

If the button would be connected to an interrupt, the interrupt could not be triggered (no button pressed) or the interrupt could be triggered at an extremely high rate (dirty and bouncing contacts). Maybe even a few kHz burst. It can no longer be predicted what the impact of the interrupt is for the device. Therefor it is a very bad design. That means that if the device would get older and the buttons get worse, the device could stop working. I'm not joking, that would really be a terrible bad design.

A software timer can be too soft. When the code uses delays, the software timer might be delayed and the pressing the button is not always the same anymore.

In the Arduino, it is often possible to avoid the use of timers and interrupts. If no delays are used, the millis() function is often enough for a software timer with good responsive buttons.

Long story short : a timer interrupt to read buttons has a fixed impact on the software, and the fixed sample rate makes it possible to use debounce calculations. For the Arduino, a software timer with millis() is often enough.

I am not sure if you actually discussed the OP question.

Ever since I started coding for Arduino I have objected to the "looping forever" approach, especially when the task is to "react on input".

The often used argument that "the processor is fast " so polling for external events will eventually succeed is just plain wrong.

Some "gurus" will actually say not to use interrupts on Arduino. Ever.

(I do not wish to get trapped in that pointless argument ever again.)

My question / point - when interrupt is used to DETECT external event , such as "button pushed" , why not let the ISR do the debouncing?

I prefer to let the ISR FINISH the task initiated by the interrupt WHEN there is NOTHING else to do anyway. ( Another often touted and wrong argument - the ISR MUST be as short as possible - going back to "forever loop" concept. )

Koepel explained it well. The difference is, that you don’t have weird side effects if you loop() interval’s execution time differs over time.

Imagine you poll the buttons in your loop() and at some time you update a TFT via I2C. That takes 200ms and if the user presses a key within that time, you won’t notice. => BAD.

Why does it work?

Look at the chart. Lets assume the arrows are the interrupt poll times (15ms). The rectangles are the button’s output signal. Most buttons bounce up to 10ms. If you poll every 15ms and you “raise” your events if your polling value changes from 0 to 1 you’ll never be affected by bounces.

@Vaclav:

Some “gurus” will actually say not to use interrupts on Arduino. Ever.

Bullshit.

wrong argument - the ISR MUST be as short as possible

For beginners it’s a very helpful rule if they learn that they keep their ISRs as short as possible.
If you do SPI/I2C or any other “slow” stuff within an ISR you really have to know what you’re doing and what side effects you have to expect.

thomai:
Koepel explained it well. The difference is, that you don’t have weird side effects if you loop() interval’s execution time differs over time.

Imagine you poll the buttons in your loop() and at some time you update a TFT via I2C. That takes 200ms and if the user presses a key within that time, you won’t notice. => BAD.

Why does it work?

Look at the chart. Lets assume the arrows are the interrupt poll times (15ms). The rectangles are the button’s output signal. Most buttons bounce up to 10ms. If you poll every 15ms and you “raise” your events if your polling value changes from 0 to 1 you’ll never be affected by bounces.

I think I understand yours (and Koepel’s) point: execution time over an iteration of a main program’s body may vary, making polling in the program body unreliable if we want a regular “polling” routine (i.e., one that guarantees that a change in input value will always be detected) since program execution may “stall” at sections of code, preventing regular polling.

Timer interrupts, being a hardware interrupt, prioritizes regular input checking.

The debouncing technique, nevertheless, works by “waiting out” the bouncy portion of the signal change right? Except in the case of the use of a timer interrupt, the duration between each regular hardware interrupt is long enough for any bouncing to have occurred.

100% Correct.

As Koepel mentioned, the debounce techniques vary from "very dumb" to "very complex", so there surely are debouncing techniques which include timer intervals way shorter than the button bounce interval, but the ones described here work exactly that way.

You don't need sampling or fancy timeouts. If you poll switches every 50ms in a timer ISR, you automatically miss/ignore any bouncing that lasts less than 50ms. Assuming that the button press lasts more than 50ms, you could get your first "on" during the bouncing, or as much as 50ms after the final closure, but you shouldn't ever see consecutive bounce and final "on" values...

For a hardware level-changing ISR, this works quite well:

const byte SWITCH_PIN = 2;
const unsigned long DEBOUNCE_TIME = 30;   // ms

unsigned long clicks;
volatile bool pressed;
volatile unsigned long whenPressed;

void pressedISR ()
  {
    
  if (pressed || ((millis () - whenPressed) < DEBOUNCE_TIME))
    return;
    
  whenPressed = millis ();
  pressed = true;
  } // end of pressedISR

void setup ()
  {
  Serial.begin (115200);
  Serial.println (F("Starting"));
  pinMode (SWITCH_PIN, INPUT_PULLUP);
  attachInterrupt (0, pressedISR, FALLING);
  }  // end of setup

void loop ()
  {

  if (pressed)
    {
    clicks++;
    Serial.print (F("Pressed at "));
    Serial.print (whenPressed);
    Serial.print (F(", count = "));
    Serial.println (clicks);
    
    pressed = false;
    } // end of switch pressed

  // do other stuff here
  
  }  // end of loop

No extra timers needed (apart from the one used by millis() which you have anyway). This does not have any delays in it.

However during testing I did get some spurious counts, I can’t quite work out why. However a 1 µF capacitor between the pin and ground seems to remove most of them.

For more discussion: Gammon Forum : Electronics : Microprocessors : Switches tutorial

@Nick Gammon: I think there’s a typo (0 should be SWITCH_PIN).

attachInterrupt (0, pressedISR, FALLING);

And since you waste a lot of CPU because of excessive interrupt usage while bouncing, you could save the millis() result within the ISR in a variable to save at least a few cycles (it’d be the same result as I think that the result of millis() won’t change during an ISR) ;D

Interrupt 0 is digital pin 2. That’s the way it works. And interrupt 1 is digital pin 3.

I could save a bit of time by reading millis() once, but that was really just a demo.

As to the excessive interrupts, whilst they would happen, once the variable “pressed” is set, the ISR immediately exits, so it doesn’t read millis() again.

You could try relying on the hardware debounce and merely have the ISR set the flag.