Electronic – Differences between Interrupts and sampling for hardware button


I have a hardware button I connected to an interrupt, but my problem is it bounces a bit, making the button press unreliable. I think a good deal of these issues would be solved by sampling in the main loop, but that just feels technically wrong.

Are interrupts more appropriate for intra-circuit communication or are interrupts also appropriate for hardware switches? If so what debounce techniques can I use?

I've tried keeping a timer variable and checking it against current time, delays, and other techniques. It seems as if the bounces are just so fast it doesn't matter.

Best Answer

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:

  1. 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.
  2. 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:

  1. change from SETTLED to CHANGING
  2. change from CHANGING to SETTLED
  3. 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.