Wednesday, May 20, 2009

The Best Switch Debounce Routine Ever

Switch debouncing is the process of filtering out the mechanical chatter that comes from switches (and relays) when their contacts touch or release. The duration of the chatter depends on the physical properties of the contacts but can last for as long as several tens of milliseconds. A comprehensive backgrounder to switch debouncing can be found here so I won’t repeat that content here. But I want to share with you an effective software debounce routine that is elegant in its simplicity, efficient and perfectly scalable.

Consider the following ‘bouncy’ signal.

Up until t=3, the input signal is in a low state. At t=3, some EMI induces some spurious noise on the wire that we want to reject. At t=5, the switch is in a bouncy state as a result of being activated. And at t=6 and beyond, the switch is in a stable high state.

The goal of the debounce routine is to reject spurious signals such as those found at t=3 while still reporting a valid transition within a timely fashion. This can be done by taking periodic samples of the signal and reporting an update once several samples agree with one another. The exact number of samples and periodicity will depend on the environment that your application will be used in and the immediacy that you need to report an update.

So let:
A = Current Switch Sample (T=0)
B = Previous Switch Sample (T=-1)
C = Sample taken prior to B (T=-2)
Z = Last reported debounce value
Z’ = New debounce value

If (A = B = C) then
Z’ = A
Else
Z’ = Z
EndIf

We can create a karnaugh map for Z’ using the inputs A, B, C and Z.

There are a number of benefits to this routine over other routines I've seen:

  1. It works for debouncing an entire input port as well as individual bits.
  2. It doesn’t consume any timer resources other than those required to periodically call the routine.
  3. It can be readily implemented in programmable hardware, and
  4. It is perfectly scalable. So you can expand the number of samples without modifying the basic algorithm. For instance, the equation for debouncing across 4 samples is:
    Z’ = Z(A+B+C+D)+A.B.C.D
    And the equation for sampling across 5 samples would be:
    Z’ = Z(A+B+C+D+E)+A.B.C.D.E

A sample C routine is listed below (please excuse the formatting):
int debounce(int SampleA)
{
static int SampleB = 0;
static int SampleC = 0;
static int LastDebounceResult = 0;

LastDebounceResult = LastDebounceResult & (SampleA | SampleB | SampleC)
| (SampleA & SampleB & SampleC);
SampleC = SampleB;
SampleB = SampleA;
return LastDebounceResult;
}

13 comments:

  1. Good idea. Bonus points for the Karnaugh map...I haven't seen one of those in ages. Unfortunately, your implementation will require a lot of obnoxious copying and pasting to handle longer debounce periods.

    However, the better option would be written in VHDL (or Verilog) with a finite state machine that uses a counter to make sure that the signal doesn't change for a specified count period (which would be a generic) before allowing the output to change. You could also use another generic with a generate statement to debounce a whole bus. Finally, you should have a clock-enable input that you use to divide the clock frequency, so you can debounce for very long periods without having to use a ridiculously large counter.

    ReplyDelete
  2. Hi AJ.
    I purposely steered away from using a counter for every signal because when I first dreamed up this algorithm, I was using a piddly little 8-bit micro and I wanted to keep the code really lean.
    But having said that, if you want to go for a hardware implementation, the algorithm above will slip into hardware pretty easily. SampleA will feed into a FF and SampleB and SampleC will just chain of SampleA's FF shift register style. You then pull the samples all together through the AND/OR logic to get your output. Use a single clock running at the sample period to clock the whole shift register chain and your done.
    But in answer to the problem of lots of copying and pasting for longer sequences, you can still deal with that in software using a ring buffer. I mocked up this code as an example. Maybe others can optimize it even further.
    #define NUM_SAMPLES 10

    int debounce (int Sample)
    {
    static Samples[NUM_SAMPLES];
    static index = 0;
    static LastDebounceResult = 0;
    int andTerm = Sample;
    int orTerm = Sample;

    Samples[index++] = Sample;
    if (index >= NUM_SAMPLES)
    index = 0;

    for (int j = 1; j < NUM_SAMPLES; j++)
    {
    andTerm &= Samples[j];
    orTerm |= Samples[j];
    }

    return (LastDebounceResult = LastDebounceResult & orTerm | andTerm);
    }

    ReplyDelete
    Replies
    1. Thank you Dr Marty for the elegant algorithm! I will use your debounce routine with ring buffer.

      There is a typo on the for loop, it should start with j=0:

      for (int j = 0; j < NUM_SAMPLES; j++)

      Delete
  3. Hey

    seam like a nice algorithm but I just dont understand how to implement it. I mean, Im reading my port where theres a switch, then I call the debounce routine only once?

    ReplyDelete
  4. You need to set up a timer and call this routine every 10-20ms.

    ReplyDelete
  5. Thank you this is great. I tried to do something similar myself but got it a little backwards. You can also debounce switches using an RC filter or an SR latch made with nand gates. I have used both types in the past. In fact micro-controller reset switches are best debounced using the RC filter I think.

    ReplyDelete
  6. I am impressed, an elegant software solution!

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. I wrote a lovely Embedded C++ templated version of this algorithm for use with mbed; though it would work very good with Arduino too with slight modifications. The class stores a pointer to a memory address so that you can store debounced switch states from shift registers sequentially with debounced General Purpose Input Ports. This allows one to store the button states as packed bits, and index with the with a single byte. Sorry, the example code won't copy and paste right so you'll have to check out the repo.

    https://developer.mbed.org/users/BlueStorm/code/FastDebouncer

    ReplyDelete
  11. Just some more musings from the "peanut gallery". I appreciate the simplicity of your approach for reading a bank of inputs.

    It's good to see others such as yourself and Gannsle recognizing the need for good debounce algorithms. This strategy is familiar to me as it's the "de-facto" way to do this where I work.

    As a note for doing this in hardware (VHDL) I think the resources are the same to use a shift register and compare against a constant of all ones vs a constant of all zeros (example 0xFF, 0x00). If it's anything but one of those values it's in a transitional state.

    Looking at Gannsle's data I don't see that more than 4 reads at about 3ms intervals is needed for valid consensus. Increasing sampling rate and increasing number of reads reduces the statistical odds of synchronously sampling some sort of EMI or fast transient disturbance.

    When resources are not as limited, a count up/count down register acting like an integrator I have found is the best for fast response time and robustness. This structure behaves like an integrator followed by a Schmitt trigger.

    If doing this in a loop then you can also evaluate this as an inequality. May be splitting hairs comparing "!=" operation to logical "&&" followed by logical "||"

    gpioDebounceBuf[gpioDBBfIndex] = gpioRead();
    tmp = 0;
    for(uint8_t i=0; i= N_SAMPLES)
    gpioDBBfIndex = 0;

    ReplyDelete
    Replies
    1. Just wanted to clarify about the VHDL implementation:
      You shift each input (bit) into its own shift register and compare that against a constant.

      When you look at the number of bits used whether it's a ring buffer 8-bits wide by 8 elements long, or whether it's 8 shift registers of 8 bits it's the same.

      I don't think there is a clear win or lose which way you do the logic.

      As for a software implementation I can clearly see the advantage.

      Delete