Debounce

By | 2012-09-11

It’s been a while since I’ve had to use this knowledge, but I happened upon this request on Hackaday. It’s from a few years ago, and so I won’t be submitting to them; but am inspired enough to write an article about debouncing.

So first, what is “debouncing”? More importantly, what is “bouncing” in such a way that it needs debouncing? The answer is buttons (amongst other things). In an embedded project you often have a couple of buttons that the user can press to activate various functions. In the would of full computers, the hardware and operating system take care of all the nasty stuff and so you just get key pressed and key released messages. When you’re dealing with switches wired straight into a pin on the microcontroller chip, the nasty stuff is your problem.

The nasty stuff in question is that when the button is pressed, the signal doesn’t simply go from “off” to “on”, it goes off, on, off, on, off, on, off, on. Imagine this was a button that represented “next channel” on a TV remote; if you don’t process that signal a bit first, you have received four “next channel” presses, instead of the one that the user wanted.

Plot of bouncing signal

Plot of bouncing signal

The above plot shows exactly what’s going on; in this case “high” means “off” and “low” means “on”. The two traces are showing, effectively, the same signal.

There are a number of ways that designers of these embedded systems typically deal with this:

  • Only consider a change valid if it persists for two samples.
  • Wait for a change; start a timer; after x milliseconds, look again. Only if the two samples are the same is the change valid.
  • Low pass filters on the inputs.

Taking the last of these first. Yuck. You’re basically smoothing out the sharp edges, the signal changes slowly from A to B as the capacitor in the filter charges or discharges. Throwing hardware at the problem is sloppy. It’s wasteful of components and board space and is unreliable because the exact values for the filter aren’t easy to determine in advance – you need a sample of the actual switch, and some typical button press styles (heavy handed people generate different bounce profiles to light fingered ones). In fact, there is so much variation possible, that it’s not really viable to pick a filter that leaves the system responsive. You don’t want the user pressing a button and then having a noticeable delay while the action is noticed. Further, if you slow the response too much, the system will completely miss the button push.

Option one. Easy to implement, but very unreliable. It’s easy to see why looking at the plot. The bounces don’t come in nice uniform chunks, and they certainly don’t arrange themselves to the sample period of your program. So, while this method will get you over some high frequency bounce, it won’t do a good job in all cases.

Option two. This is the one most commonly used. It works pretty well. You can see why from the trace, after a millisecond, the bouncing has finished and the flat trace means the signal is reliable. There is a drawback: you have to pick a long enough time that all possible bouncing must be well and truly over. If we picked one millisecond on the basis of the above plot, what will happen on the day that the bounce continues for 1.5 milliseconds? So, you have to go a bit mad and pick an excessively long timeout.

My solution (although I’m not claiming I was the first to do it, I’m just saying that this is the method I picked and have used for years) is a combination of these two techniques. As far as I can tell it is the most reliable and lowest-latency method for debouncing that there is.

Here’s how it goes:

  • Wait for a change
  • Start a timer
  • If another change happens while the timer is running, reset it
  • When the timer reaches its end, sample again. That is your debounced signal.

The timer in this case can be set very short, it needs only be longer than the longest time between bounces, rather than longer than the total period of bouncing expected. It works because it solves the problem that we never know which is the last bounce, there could always be one more. What we’re really doing is requiring that the signal maintain a particular state for X milliseconds, before we consider it valid.

This method adapts to light fingers, heavy fingers, cheap hardware, expensive hardware. As long as we pick our X such that it is longer than the slowest possible bounce, we’ll be fine. Going by the above trace, I think I’d pick one millisecond.

How would we implement this though? Well, it can be tricky if you need to handle multiple buttons simultaneously, since we have no way of knowing which one would be pressed first, or which one will finish bouncing first. Therefore we need a timer per button.

bit button_signal_now[NUMBER_OF_BUTTONS];
bit button_signal_last[NUMBER_OF_BUTTONS];
uint8_t button_timer[NUMBER_OF_BUTTONS];

void interrupt_TIMER0() interrupt TIMER0
{
    TimerTicks++;
}

void main()
{
    unsigned int i;

    while( 1 ) {

        // ...

        for( i = 0; i < NUMBER_OF_BUTTONS; i++ ) {
            // Change of button state resets the per-button timer
            if( button_signal_now[i] != button_signal_last[i] )
                button_timer[i] = BUTTON_TIMEOUT;
            // A timer tick since our last look decrements the
            // active per-button timers
            if( TimerTick > 0 && button_timer[i] > 0 )
                button_timer[i]--;
            // Store the current state for next time
            button_signal_last[i] = button_signal_now[i];
            // If a timer has reached zero, then the button state is
            // considered valid
            if( button_timer[i] == 0 )
                button_state[i] = button_signal_now[i];
        }
        // Acknowledge the tick
        if( TimerTick > 0 )
            TimerTick--;

        // ...

    }
}

Ta da. Our timer interrupt handler is small and fast (which they always should be); our handler can cope if a few ticks are missed because something more pressing comes up; and we can handle as many buttons as are needed. I’ve taken a few liberties with syntax and race conditions for the sake of clarity, but the idea should be clear.

Leave a Reply