I've written a small sketch targeted at the Arduino Uno (ATmega328P) to debounce a mechanical pushbutton using the summing/integration technique:
#include <IntegratingDebounce.h>
#define PIN_BTN 4
IntegratingDebounce *btn;
ISR (TIMER2_OVF_vect) {
ideb_poll(btn);
} // end timer2 overflow ISR
void set_debounce_timer() {
TCNT2 = 0; // Clear timer
TCCR2A &= ~( bit( WGM21 ) | bit( WGM20 ) ); // Set waveform generation mode to normal
TCCR2B &= ~bit( WGM22 );
TIMSK2 |= bit( TOIE2 ); // Enable interrupt on overflow
TCCR2B |= 7; // Start clocking the timer
} // set_debounce_timer()
void setup() {
Serial.begin(9600);
pinMode(PIN_BTN, INPUT_PULLUP);
ideb_init(&btn, PIN_BTN, 4);
set_debounce_timer();
} // setup()
void loop() {
while (ideb_read(btn) == HIGH); // wait until the button pin goes low
Serial.println("Pressed!");
delay(100);
} // loop()
An IntegratingDebounce structure stores the data needed to perform the debounce. I keep a pointer to the structure, btn, in the global memory space.
The timer2 overflow ISR periodically polls the state of the button pin, updates the internal integrator, and sets the (also internal) debounced output accordingly. The ideb_read() function reads the debounced output out of the structure.
I'm having trouble determining where the volatile keyword should be placed in the declaration for the btn structure pointer. First I declared it as:
volatile IntegratingDebounce *btn;
I thought this would make all of the fields in the structure pointed to by btn volatile, but button presses are not detected in loop(). Declaring btn as a volatile pointer rather than a pointer to a volatile structure generates the expected output:
IntegratingDebounce *volatile btn;
This seems backwards. Why does this work?
IntegratingDebounce.h
#ifndef __INTEGRATING_DEBOUNCE_H__
#define __INTEGRATING_DEBOUNCE_H__
#include <Arduino.h>
#include <stdint.h>
extern "C" {
typedef struct IntegratingDebounce_S IntegratingDebounce;
typedef uint8_t ideb_size_t;
// Create a debounce structure and initialize.
void ideb_init(
IntegratingDebounce **ideb,
uint8_t pin, // I/O pin to debounce
ideb_size_t clamp // debounce clamp; the maximum value the
// internal sum is able to take on
);
// Poll the state of the pin, integrate, and update the debounced output.
void ideb_poll(IntegratingDebounce *ideb);
// Read the debounced state of the pin.
uint8_t ideb_read(IntegratingDebounce *ideb);
// Reset the internal integrator to either the LOW or HIGH state.
void ideb_reset(IntegratingDebounce *ideb, uint8_t state);
} // extern C
#endif
IntegratingDebounce.c
#include "IntegratingDebounce.h"
struct IntegratingDebounce_S {
uint8_t pin; // pin to debounce
uint8_t output; // debounced output
ideb_size_t clamp; // debounce clamp
ideb_size_t integrator; // clamped running sum
};
void ideb_init(IntegratingDebounce **ideb, uint8_t pin, uint8_t clamp) {
*ideb = (IntegratingDebounce *)malloc(sizeof(IntegratingDebounce));
(*ideb)->pin = pin;
(*ideb)->clamp = clamp;
(*ideb)->output = 0;
(*ideb)->integrator = 0;
} // ideb_init()
void ideb_poll(IntegratingDebounce *ideb) {
// update the integrator based on the present state of the pin:
// - if LOW, decrement the integerator; if HIGH, increment the integrator
// - constrain the integrator to values between 0 and 'clamp'
if (digitalRead(ideb->pin) == LOW) {
if (ideb->integrator > 0) ideb->integrator--;
} // if
else {
if (ideb->integrator < ideb->clamp) ideb->integrator++;
} // else
// update the output based on the value of the integrator; the output
// will not change until the integrator reaches a limiting value
if (ideb->integrator == 0 && ideb->output == HIGH) {
ideb->output = LOW;
} // if
else if (ideb->integrator == ideb->clamp && ideb->output == LOW) {
ideb->output = HIGH;
} // else if
} // ideb_poll()
uint8_t ideb_read(IntegratingDebounce *ideb) {
return ideb->output;
} // ideb_read()
void ideb_reset(IntegratingDebounce *ideb, uint8_t state) {
if (state == LOW) {
ideb->integrator = 0;
ideb->output = LOW;
} // if
else {
ideb->integrator = ideb->clamp;
ideb->output = HIGH;
} // else
} // ideb_reset()