I am working on a project that requires me to listen to a series of very brief, very quick button presses and to decode these button press events depending on the timing between them. It has nothing to do with Morse code, but the encoding is similar: there are three groups of button presses, presses within each group are separated by 100-300 ms, groups are separated by 300-1000 ms, and a pause of >1000 ms signifies the end of the current encoding batch.
As I suspected, the tactile micro-buttons I am using are rather bouncy / noisy. I am extremely space-limited on this project and wished to avoid implementing a hardware debounce solution. So I began experimenting with software debounce algorithms. I tried various polling algorithms and interrupt algorithms, but ultimately none of these worked because it turns out that while the vast majority of my button presses generate bounce lasting less than 90 ms, sometimes the bounce lasts rather substantially more than 100 ms, and handling any bounce longer than 100 ms with a timing algorithm (whether polling- or interrupt-based) would conflict with my requirement for 100 ms resolution on the individual button presses.
Just as I was about to revisit my enclosure / packaging plan to see how I could possibly fit an RC circuit and a Schmitt trigger in there, I ran into a couple of websites describing a debouncing algorithm that emulates an RC circuit and a Schmitt trigger using only a digital pin for input.
The algorithm is a hybrid count- and time-based debouncer, and includes hysteresis -- just like a real Schmitt trigger. Here are the links to the two articles I found:
http://www.edn.com/file/13370-70705di.pdf
http://web.engr.oregonstate.edu/~traylor/ece473/lectures/debounce.pdf ( see page 8 )
Here is a snippet of my code implementing the algorithm:
//This is within my 'void loop()' function//
//Variables for RC-Schmitt Trigger emulator debounce algorithm
uint8_t y_old=0, temp=0;
boolean buttonPressed = false;
//Button polling loop
while(true){
//Grab pin state
boolean pinState = PIND & ( 1 << 2 ); //Reading pin state directly off register (pin PD2 / digital 2)
pinState = !pinState; // The button grounds the pin, which is otherwise pulled high, so we flip it
// to achieve TRUE = buttonpress
//This is the "RC circuit" part of the emulator, a recursive low-pass filter of recent pin states
//Equation: y_old = (pinState * 0.25) + (y_old * 0.75)
//First we get (y_old * 0.75)
temp = (y_old >> 2); //Bitwise operation for (y_old / 4)
y_old -= temp; // y_old - (y_old/4) is equivalent to (y_old * 0.75)
//Second we add (pinState * 0.25) to y_old, but only if pinState is 1 (since otherwise the product is 0)
//Note that we are actually adding 63 rather than (1*0.25). This is because we are using a byte
//to represent the pin state in the equation for the purpose of having sufficient resolution to
//later apply the hysteresis of the virtual Schmitt trigger. 63 is the integer result of multiplying
//255 (maximum 8-bit number) by 0.25.
if(pinState) y_old += 0x3f; //0x3f is hex for 63
//This is the Schmitt trigger part of the algorithm. Note the hysteresis -- different thresholds for
//detecting button-down and button-up
//Thresholds are 240 (for a valid press) and 15 (for a valid release) [out of 255]
//(These may be tuned to the particular switch/button being debounced)
if( (y_old > 0xf0) && (!buttonPressed) ){ //This means a real press-down has occurred
buttonPressed = true;
keypresses++; //For debugging, I tracked the number of detected keypresses to compare against what
//my finger was telling me
PORTD |= ( 1 << 7); //This lights an LED connected to PD7, or Arduino digital port 7
}
else if( (y_old < 0x0f) && (buttonPressed) ){ //This means a real release has occurred
buttonPressed = false;
PORTD &= ~(1 << 7); //This turns off the LED connected to PD7 (Arduino digital port 7)
}
delay(4); //The algorithm specifies that the above routine be called by a timer interrupt every
//4-5 ms. With the default Schmitt thresholds of 0x0f (15) and 0xf0 (240), a delay of 4 ms
//here results in a debouncing period of approximately 50 ms (it is not exact because
//this is not a pure timing algorithm, so depending on actual bounce, the period may vary
//somewhat
}
(For the above code to work, pin 7 (PD7) must be set to output, pin 2 (PD2) to input, and pin 2 (PD2) must be pulled up via an external or internal resistor.)
Now, I don't know how closely this code actually emulates an analog RC/Schmitt debouncing circuit, but it actually works. It works extremely well. For my scenario of multiple quick button presses, it works where all other algorithms I've tried have failed.
Hope this helps someone out!