Switch debouncing, the hard and the soft way

I'm using a quadrature encoder for my LED lights project, to allow the controller to select the pattern that is to be displayed. The encoder consists of two parts, a momentary push button switch activated by pushing the shaft down, and a rotary encoder, activated by rotating the shaft left or right. As it's only a relatively cheap part, the quadrature encoder is mechanical rather than the more expensive optical type. The quadrature encoder is the relative type, with an A and a B output. The switch outputs relative left/right motion rather than an absolute position. Both A and B output a square wave, with the waveforms being 90 degrees out of phase with each other. The practical consequence of that is that when rotating one direction, when the A output is low the B output will be high, and when rotating in the opposite direction, when the A output is low the B output will also be low. The exact relationship between the high and low levels and the rotation direction depends on the particular encoder, but the principle is always the same - by sampling the B output when the A output goes low we can determine the rotation direction of the encoder. So far, so good.

Because the encoder is mechanical, it has one of the features of nearly all mechanical switches - contact bounce. In a nutshell, when a mechanical switch is opened or closed there's an initial period where the switch contacts oscillate against each other producing a flurry of short on/off transitions before finally setting to the new switch state. There are two main techniques for coping with switch bounce in digital systems, hardware and software. There's a plethora of material on the internet on how to implement these two approaches, the vast bulk of them are complicated, either requiring software state machines or multiple hardware components.

I'm a big fan of "the simplest thing that works", so the first thing was to characterise the behaviour of the switch. It turns out that the 'click' switch has quite a long bounce period of 10msec or more, whereas the 'turn' encoder has a much shorter bounce period of somewhere between 250usec to 500usec.

The approach for the 'click' switch is simple. I hooked up one side of the switch to an AVR IO pin and configured the IO pin so that it pulls up to 5V. The other side of the switch is tied to ground, so closing the switch pulls the IO pin to ground. I've used an IO pin that's capable of generating an interrupt when the pin is pulled low. When triggered, the ISR sets a flag to ignore any future interrupts and schedules a task 25msec into the future, using the Task framework I've described in earlier posts. When the task runs, it simply re-reads the IO pin, and if it's still low, the switch is considered to have been pressed. There's no need to build a complicated state machine that samples the pin multiple times and averages the state and so on, it is sufficient to simply re-check the state of the pin once after a suitable delay to allow the switch to settle.

The encoder is more problematic. The tick frequency used by the Task system is 1msec which is longer than the period it takes the switch to move between each detent. Another approach would be to poll the switch, but I need every CPU cycle available for driving the LED strips, I can't afford to waste hundreds of cycles waiting for the encoder to settle. I therefore used the simplest possible hardware solution. I connected the A and B pins of the encoder to two AVR IO pins, again both configured to pull their output high. The other side of the encoder is tied to ground, so as the switch is rotated it alternately pulls A and B to ground. The IO port connected to A is configured to generate an interrupt when it is pulled low, and the ISR then reads the state of the port connected to B. If B is low the encoder is rotating in one direction, if it is high it is going in the other direction. OK, but what about solving the bounce? To do that I simply connected a capacitor between A and ground, and another between B and ground. That means that when the A or B output goes low, it has to remain low for long enough to discharge the capacitor below the voltage that the AVR interprets as 'O' - approximately 2.6V. The short low pulses caused by the encoder bouncing aren't enough to do that, so the ISR doesn't get triggered until after the encoder has settled. Finding the right capacitor value required a little experimentation - too low and it doesn't smooth out the bounces, too high and it smooths out the high/low transitions so much we miss encoder pulses. A value that seems to work well for my particular switch is 22nF.

I hope I've persuaded you that switch de-bouncing doesn't need to be complicated, despite what the internet says :-) The key thing is to characterise the switch you are using, and use the appropriate approach, be it hardware or software.

Categories : Tech, AVR


Re: Switch debouncing, the hard and the soft way

I found (on the web, not myself: I'm not that smart :) ) a nice way that to deal with rotary encoders without having to bother about debouncing (although it's not interrupt-based, so it requires polling): www.circuitsathome.com/mcu/reading-rotary-encoder-on-arduino. It works for me, but it gave me quite a headache before I noticed that for each click of the knob, my encoder advances twice...

Re: Switch debouncing, the hard and the soft way

Yes, that code is another way of doing the same thing, just not a very good one. The downside is it requires polling at a fairly high rate for it to work - you need to be sampling at least 4 times faster than the time it takes to transition between detents, possibly much faster. The <code>enc_states</code> array is effectively acting as a filter to discard not just invalid transitions caused by switch bounces, it's also being used to discard the cases where nothing has changed. For a 4-bit tuple of (oldA, oldB, newA, newB) those 'nothing has changed' cases are the values (0000) = 0, (0101) = 5, (1010) = 10 and (1111) = 15. If you look at the table, those entries are all 0 (invalid). The other 0 entries are true 'illegal' values plus there are 4 x -1 entries and 4 x 1 entries, which is why you need to divide by 4 to get the true count - I'm not sure how your encoder is working if you say you are dividing by 2.

If you read my Don't delay() post you'll see why this approach is so toxic. Because of the need to poll at least 4 times faster than the transition time, the code is very, very inefficient - if you look at the example sketch you linked to it doesn't do anything at all other than poll the encoder and print the value out - not exactly useful. Plus it's extremely fragile - something a simple as dropping the serial data rate makes the entire thing fall apart - he notes that fact towards the end of the post. And simply adding an ISR as he suggests doesn't help all that much as you end up generating an interrupt for each switch bounce and therefore getting multiple counts per click.

It's also not really very helpful to think of an incremental rotary encoder as having '4 increments per click' - that's not really how such encoders work. It's far more helpful to think of them as having 4 edges per click (see Wikipedia):

        _____       _____ 
A _____|     |_____|     |_____
     _____       _____       __
B __|     |_____|     |_____|

A  0  0  1  1  0  0  1  1
B  0  1  1  0  0  1  1  0
  |<  click  >|<  click  >|

If you look carefully a the diagram you'll see that both A and B have 2 edges per increment, each pin has 1 rising edge and and 1 falling edge. The way I've handled this is to set up the ISR so that it's triggered on the falling edge of pin A. In the ISR I double-check that pin A is still low, and then check the value of pin B - both pins are both connected to the same IO port so I can sample them simultaneously with a single IO read. If pin B is 0 the encoder is going in one direction, if it's 1 it is going in the opposite direction - the direction will depend on exactly how things are wired up. By doing it that way you only get a single ISR call per click, as each pin only has one falling edge per click.

All the capacitors are doing is 'smoothing' the rising and falling edges so that you get a more gradual transition, so that any higher-frequency switch bounce is masked out. The capacitor in conjunction with the pull up resistors and the hysteresis of the ATmega IO ports means that (with carefully selected capacitor values) you can debounce the encoder entirely in hardware and not have to burn most of your precious processor cycles doing so.

Of course a properly engineered solution would either use an optical encoder that doesn't have switch bounce, or use a hardware circuit to do so - there are a number of different approaches to doing this. However this is Arduino-land so a simple hack with a capacitor works just fine :-)

Re: Switch debouncing, the hard and the soft way

 Your capacitor-debounce approach has the added benefit of some degree of ESD dissipation/handling that otherwise wouldn't be there :)