Differences between Interrupts and sampling for hardware button?
Debouncing is a FAQ. You should be able to find... nearly unlimited numbers of web pages on the topic. Smith commented about Jack Ganssle's widely read PDF on the topic, as well. And with all these answers you've got both hardware and software methods.
I'll add to this "literature" just a little bit by mainly talking about ideas that aren't already covered well. But before I do, a point or two:
- Debouncing in analog hardware can achieve results that you cannot achieve by a switch "observed" only digitally on a periodic basis by polling or even by hardware pin-change events. But you can do "well enough" for all intents and purposes, digitally. Almost no one these days uses external analog debouncing solutions. But I have used everything from pulse stretching using one-shots (74121) to techniques mentioned by Jack Ganssle here.
- For those doing embedded programming only and not at all interested in learning about electronics, debouncing switches is probably one of the two basic skill sets needed. Operating LEDs is probably the other one. And by this, I don't mean having only one skill at these. I mean being able to do it in a number of ways. So you really do need to fully apprehend what Jack Ganssle writes about, and still more, regarding switches.
Since I've mentioned pulse stretching using a 74121 and since Jack Ganssle does not mention it, and neither does anyone here as yet, I may as well provide this additional link as additional suggested reading on using the 74121 or 555 as a one-shot timer for debouncing switches.
Now, on to doing this through observation with a microcontroller.
I usually use a state machine to handle debouncing. This is almost always driven by a regular "heartbeat" timer I set to about \$8\:\textrm{ms}\$, where possible. (I generally do NOT use edge-triggered interrupt events for several reasons.)
The state machine looks like this:
simulate this circuit – Schematic created using CircuitLab
The value of DEBOUNCED for the switch could take on "inactive", "active", and "unknown" values. In this way, you could make sure that your software waits until the switch value settles down after initialization. But usually, I don't bother with that. I replace the "unknown" value with some default value and just use a binary value system, instead.
The state machine is entered by first setting the debounced value to its default and then entering the "CHANGING" state of the state machine. At each time interval (typically \$8\:\textrm{ms}\$ if I can get away with it), I will read the current switch value and perform an update of the current state and possibly, the debounced value. Then I just exit. The high level code then only accesses the debounced state.
If it matters to me, I may also keep a prior debounced state. In these cases, when updating the debounced state itself, I will first copy that state to a 'prior debounced state'. I can then use the pair of values to determine if there has been a debounced transition. Sometimes, I don't care about transitions. Sometimes, I do. So it depends. But in all cases, I only want to know about transitions that have been debounced. I never care about runt transitions. So high level code never uses any of the internal state that the state machine uses for its own work.
One of the nice things about this method is that I can debounce an entire port of switches, at once. And I can do it without a single branch in the interrupt code, too. This means very fast and short debouncing code for up to the port-width of the microcontroller (usually 8 bits wide.) An example from the Atmel AT90 shows how this is achieved using a Timer0 interrupt event:
.equ SWPORTPINS = PINB
.def SwRawCurr = r4
.def SwRawPrev = r5
.def SwState = r6
.def SwDebCurr = r7
.def SwDebPrev = r8
; Debounce the input switches.
mov SwRawPrev, SwRawCurr
in SwRawCurr, SWPORTPINS
mov Timer0Tmp1, SwRawCurr
eor Timer0Tmp1, SwRawPrev
mov Timer0Tmp0, Timer0Tmp1
or Timer0Tmp1, SwState
mov SwState, Timer0Tmp0
mov Timer0Tmp0, Timer0Tmp1
com Timer0Tmp0
and Timer0Tmp1, SwDebCurr
and Timer0Tmp0, SwRawCurr
or Timer0Tmp1, Timer0Tmp0
mov SwDebPrev, SwDebCurr
mov SwDebCurr, Timer0Tmp1
Now, this example shows the full deal, including the previous and current debounced switch values. And it performs all of the necessary state transitions, as well. I don't show the initialization of this code. But the above gets the point across about how easy the state machine is to operate and how little code is required to do so. It's quite fast and simple and does not require branching (which sometimes involves additional cycles as well as additional code space.)
I prefer using \$8\:\textrm{ms}\$ timing because long, long testing with a variety of different people using equipment I've worked on in the past has led me there. I've tried longer periods and when I do so, I start to get people telling me that the "responsiveness" isn't "brisk" enough. (These days, with children growing up working real-time "shoot 'em up" gaming, I might even shorten it up further. They will complain bitterly about even slight delays caused by modern digital TVs in setting up and displaying a frame.)
Some people will have very clear feelings about how crisp and responsive a system should be. Crisp and responsive means sample more often, not less. But personally, I find \$20\:\textrm{ms}\$ observation periods acceptable. (I do not find longer times good enough even for me, though.)
Please note that the state machine I've mentioned must first enter the SETTLED state and then stay there for one more sample time before the value for DEBOUNCED is updated. So pushing a button and holding it, even in the best circumstance, will require these transistions:
- change from SETTLED to CHANGING
- change from CHANGING to SETTLED
- stay in SETTLED, updating DEBOUNCED
So a new debounced state requires a minimum of 3 sample time periods to achieve.
A push button will require at least 6 sample times to go from inactive, to active, and then back to inactive.
I mentioned the above details so that it is absolutely clear that a sample time of \$8\:\textrm{ms}\$ means that it somewhere between \$16\:\textrm{ms} \lt t \le 24\:\textrm{ms}\$ to go from inactive to a recognized active debounced result. And it will take another \$24\:\textrm{ms}\$ before the state can return to inactive. That's a minimum of \$40\:\textrm{ms} \lt t \le 48\:\textrm{ms}\$ to go through an entire push button cycle.
Using longer sample times will have correspondingly longer periods. Using the \$20\:\textrm{ms}\$ I mentioned as "acceptable" to me already then means somewhere around \$100\:\textrm{ms} \lt t \le 120\:\textrm{ms}\$ for an entire push button cycle. And that is getting squarely up into the area where people do tend to notice. I certainly don't like the "feel" if it gets any longer than that.
If you go this route, don't be cavalier about using longer sample times. If you must, then I think you also must do a lot of testing with users/consumers.
And if you are developing code for a typing keyboard, then use shorter times. The record for a typist was set decades ago at 217 wpm. This results in about one key every \$45\:\textrm{ms}\$. Typists like that are hitting multiple keys in a controlled ordering. To get good performance for very fast typists using a mercury-wetted reed relay switching system, I found that \$2\:\textrm{ms}\$ worked well.
Debounce can be done in software by masking IRQ's for the bounce time or in hardware by adding a holding capacitor with your RC =T > bounce time ranging from 1 to 15ms depending on size of switch.
- e.g. 100k pullup and 0.1μF across switch = 10ms @63% or ~8ms at 50%Vdd or if using Schmitt Trigger gate @1.33V=Vil from 5V or ~73% V+ ~12ms
To make a SW de-bounce, record the timestamp of current event and check for delay from last valid event:
#define DELAY_DEBOUNCE 150
uint32_t __ts_lastpress = 0;
ISR(some_vector)
{
uint32_t now = millis(); // some timer tick counter
if ( now - __ts_lastpress < DELAY_DEBOUNCE )
return; // ignore it
__ts_lastpress = now;
// do the job here
}
UPD: with little modification you can register double-clicks:
#define DELAY_DEBOUNCE 150
#define DELAY_DOUBLE_CLICK 600
uint32_t __ts_lastpress = 0;
ISR(some_vector)
{
uint32_t now = millis(); // some timer tick counter
if ( now - __ts_lastpress < DELAY_DEBOUNCE )
return; // ignore it
// do the job here
if ( now - __ts_lastpress < DELAY_DOUBLE_CLICK )
{
// it is double click
}
else
{
// it is single click
}
__ts_lastpress = now;
}