8
\$\begingroup\$

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.

\$\endgroup\$
  • 2
    \$\begingroup\$ Read this: ganssle.com/debouncing.pdf \$\endgroup\$ – Smith Nov 17 '16 at 15:48
  • \$\begingroup\$ There is nothing wrong with sampling in main loop, if you react in main loop. Interrupts are more appropriate if you want to react asynchronously. Sometimes you do, and sometimes you don't. \$\endgroup\$ – Eugene Ryabtsev Nov 18 '16 at 8:37
  • \$\begingroup\$ The best way to debounce is a simple low-pass filter. \$\endgroup\$ – lucas92 Nov 18 '16 at 20:53
8
\$\begingroup\$

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:

schematic

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.

\$\endgroup\$
  • \$\begingroup\$ bounce times vary from 0 for mercury switches to "few" ms typ for micro tactile switches to 30ms for clunky toggle switches , so 8ms is a good number considering bounce time increase with aging. \$\endgroup\$ – Sunnyskyguy EE75 Nov 18 '16 at 20:44
  • \$\begingroup\$ @TonyStewart.EEsince'75 I chose to do extensive testing with users using equipment with a variety of different kinds of switches and the 8 ms figure comes from a distillation of all that work. (I didn't worry so much about "theory" since the practice of building and making switches, and their sheer variety, made the collection and analysis of that data seem daunting.) I always use 8 ms, when possible, since it seems to be the sweet spot given long experience writing software that just works and where after-sale complaints go to an exact zero (on that point, anyway.) \$\endgroup\$ – jonk Nov 18 '16 at 20:49
  • \$\begingroup\$ @TonyStewart.EEsince'75 By the way, this testing INCLUDES the use of mercury wetted reed relays as part of key switches used in keyboards (which, I think, don't seem to be made anymore.) In these cases, though, I go to 1-2 ms sampling (depends on the unit.) \$\endgroup\$ – jonk Nov 18 '16 at 20:51
  • \$\begingroup\$ That laser garden light I mentioned one time ago.. has tactile membrane remote control switches with low bounce time but the programmer made them toggle at a 10Hz rate so one has to release them in <100ms otherwise power goes onoffonoff. ?.on another note.. Yamaha piano keyboard are extremely fast and support 10 key rollover while only the original IBM PC keyboard supported true leading edge rollover. Since then all keyboards are 1st strokes are leading edge and then trailing edge rollover which is a PITA for poor typing skills as mine \$\endgroup\$ – Sunnyskyguy EE75 Nov 18 '16 at 20:55
  • \$\begingroup\$ @TonyStewart.EEsince'75 This area of switch sampling is a sore point. The advent of cheap micros with zero external debouncing and who-knows-what switch applied, added to embedded programmer ignorance, has meant that I actually find problems with almost EVERY SINGLE embedded instrument with a keypad or push button. They ALL work terribly, in my option. And I think it is mostly because the programmers have little to no experience, just 'google it up and apply it' without thought. Sometimes, salting their code with random polling points, even. It's garbage. Upsetting. It's easy to get right. \$\endgroup\$ – jonk Nov 18 '16 at 21:00
5
\$\begingroup\$

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
\$\endgroup\$
4
\$\begingroup\$

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;
}
\$\endgroup\$
2
\$\begingroup\$

Interrupts are definitely great for hardware switches as well. By using interrupts, you are avoiding a big waste of resources and possibly energy, especially if you are dealing with battery-powered devices.

Also, as your code gets bigger and bigger, you'll see that it's even easier to implement the interrupts for buttons than polling them in your main loop.

As for your debouncing, it is probably a coding problem. I generally use a ~10ms timer for debouncing, while checking for the release of the button. Be sure to also temporarily disable the button interrupt while you debounce it, so the interrupt routine is not executed several times.

If you are still having problems, post the code here, so we can help.

\$\endgroup\$
1
\$\begingroup\$

This is pretty similar to Tony Stewart's Answer, but I think it could be expanded some.

The top schematic is for either a interrupt on low or on falling edge. The bottom schematic is for an interrupt on high or a rising edge.

schematic

simulate this circuit – Schematic created using CircuitLab

personally, given the cost of a capacitor, it is worth it to me to simply use it, rather than worrying if my software debounce is buggy.

Note that as Tony Stewart said, the time constant in this circuit is 10ms \$(R * C\$ or \$10k\Omega * 1 \mu F) \$. It is going to take somewhere from three to five time constants (depending on the sensitivity of your microcontroller for the button to reset itself, so if your microcontroller has issues with repeating the interrupt function, that may be the cause, and you may need to adjust the cap/resistor to make the interrupt not happen multiple times (That is, only if your interrupt is set to work on a high or low signal, and not the rising or falling edge.

Related to Hardware debouncing

\$\endgroup\$
  • 1
    \$\begingroup\$ Either version works for both +ve or -ve edge, especially if the interrupt pin has a schmitt style input characteristic (many do). Both SW1 & SW2 experience a current surge when closing. Some carbon-button pushbuttons may give different results than metal-dome pushbuttons. \$\endgroup\$ – glen_geek Nov 17 '16 at 16:36
1
\$\begingroup\$

Humans are slow, we don't need the immediate attention of a micro which is in the microseconds range.

This is not, of course, the only nor the right way to always do it but i find generally more sensible to setup a timer (many micros have sys ticks) to fire an interrupt at fixed intervals and shift the state of the pin into a variable for the code to examine later. You end up with a var which is full of ash during bouncing

10010110 ash

but at certain points in time you'll get this 4 values:
01111111 rising edge just debounced
11111111 button in steady state up
10000000 falling edge just debounced
00000000 button in steady state down

Most of the time, though, I just use a counter that resets during bouncing. It's fast, tested and easy to do.
If it fails then I try something smarter from the Ganssle document others suggested!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.