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

Updated Makefile.master

If you have been using my Makefile for Arduino sketches I've been updating it regularly with bug fixes and improvements, the latest being automated header file dependency checking. You can find the current version of the Makefile.master here.

Categories : Tech, AVR

PROGMEM and GCC bug 34734

The Atmel AVR CPUs that are used in the Arduino boards aren't the familiar Von Neumann architecture machines we are all familiar with, where there is a "flat" address space that's shared between both code and data. Instead they are Harvard Architecture, where there's separate address spaces for code and data, i.e. there are two "address 0"s. In fact on the AVR there are three address 0's, one for code (Flash), one for data (SRAM) and one for non-volatile memory (EEPROM). There's also a very restricted amount of SRAM - usually less than 2K. That means that if you want to store lots of data (in my case, LED strip patterns) you need to store it in program memory (Flash) and access it with special instructions. Data is put into program memory by tagging the variable definition with the PROGMEM macro, e.g.

static const char message[] PROGMEM = "Hello world";

However, there's a long-standing gcc bug that results in a warning from GCC when warnings are enabled: error: only initialized variables can be placed into program memory area. I don't particularly want to compile with all the warnings disabled, and it turns out there's a relatively simple workaround:

// Workaround for http://gcc.gnu.org/bugzilla/show_bug.cgi?id=34734
#undef PROGMEM
#define PROGMEM __attribute__((section(".progmem.data")))

The standard PROGMEM macro expands to __attribute__((__progmem__)), and from looking at the GCC source, the __progmen__ attribute puts the variable that it has been used to tag in the .progmem.data section so the above should be directly equivalent, and it certainly works fine for me.

Categories : Tech, AVR