Using a Vertical Stack Counter to Debounce Switches

3 bit counter

At left is a simple EMSL AtmegaXX8 Target Board (same board I have written about before) 3 bit up/down counter circuit.

Push the left button? Counts down. Push the right button? Counts up.

Simple.

Sort of.

The switches are fully debounced using an extremely elegant algorithm that I ran across while trying to figure out how to efficiently debounce switches with an AVR controller.

Specifically, the software uses a vertical stack count to efficiently debounce up to 8 switches in very few instructions and only 3 bytes of memory.

Read on for schematic and a description of the software…

3LED2Switch.png

The software for this circuit can be found in my SVN repository. I added a third LED, but didn’t change the name of the source file. Oh well.

The circuit is terribly simple; two switches connected to ground and three LEDs each connected through a 1K resistor to VCC.

That is it; 8 discrete components.

Aside: I have never used EAGLE, a schematic and board layout package, before. It is awesomely powerful but woefully sucky at the same time. The user experience is just abysmal. Not terribly surprising as it is X11. There is both a commercial and a freeware version available.

But, at least, there is a downloadable binary. The alternative standard appears to be gEDA, for which there are no prebuilt binaries available. There is a Fink package available, but it takes a good 5 hours to build or so on a MacBook Pro.

But I digress.

Diving into the software that goes with this particular schematic, I’m going to discuss it in order of initialization, main loop, and event handling.


The initialization — the prelude of main() — looks like this:

int main(void) {
    DDRB = 0xFF; // Make all PB* -- PORT B -- pins output.
    PORTB = 0xFF; // turn all PB* -- PORT B -- pins off.

This turns all 8 bits of PORTB, pins PB0 through PB7, into output pins. Only PB0, PB1, and PB2 are used, but there is no harm in setting them all.

It also sets all pins to be outputting a logic 1; to be pulled high. Since the LEDs are connected to VCC through resistors, this effectively turns all of the LEDs off. A bit misleading, but — as a comment on a previous article indicates — the Atmel chips can handle more current through an output pin when acting as ground (sink) then when acting as VCC (source).

    bit_clear_8(DDRC, BIT(PC0) | BIT(PC1)); // PC0, PC1 is an input
    bit_set_8(PORTC, BIT(PC0) | BIT(PC1)); // Turn on pull-up resistor on PC0, PC1

This configures the pins PC0 and PC1 to be input pins. It also turns on the internal pull-up resistor on said pins. Without that second line of code, you would need to add pull-up resistors externally to the chip.

If you forget that line of code (I forget to turn on the pull-up for PC1 when I added a second switch) and don’t have an external pull-up resistor, it is very likely that the input pin will quite happily float between on and off quite rapidly as you wave your hand over the circuit. Makes for a good random number generator, but isn’t very useful for any kind of a push-a-button,make-something-happen circuit.

    bit_set_8(TCCR0B, BIT(CS01));
    bit_set_8(TIMSK0, BIT(TOIE0));

This block of code configures one of the chip’s timer’s into a mode where it counts from 0 (default) to 255 and, upon rolling over to 0, sends an overflow interrupt signal.

Setting bit TOIE0 of TIMSK0 turns on the overflow interrupt whenever Counter0 overflows. Counter0 simply counts up 1 unit per clock cycle, unless configured otherwise.

Setting bit CS01 of TCCR0B configures the timer to only increment once per every 8 clock cycles.

Since the clock is running at 1mhz, every 8 clock cycles times 256 counts between interrupts yields one interrupt every 2 milliseconds or so (unless my math is whack).

I use this once-every-2-milliseconds signal to sample the inputs and debounce using the vertical stack counter.

    
    sei(); // turn on interrupts

Finally, the sei() call simply enables interrupts. That is, it effectively causes the configured set of interrupts (and other stuff) to become active and start doing their thing.

This instruction is equivalent to setting the I bit of the AVR status register (SREG).


The “main loop” is rather straightforward.

volatile unsigned char ledState = 0;
....
int main(void) {
	// ... initialization code (see above) ....
    while (1) {
        PORTB = ~ledState;
    }
}

It simply updates the output pins on PORTB to be the currently desired LED state.

Or, since the LEDs turn ON when their corresponding pin is OFF, it simply writes the inverse of the current state of the LEDs to PORTB. This allows the internal program logic to be that bit much more straightforward.

Obviously, the real program functionality is squirreled away in the logic that maintains the ledState.


Now that everything is configured to rely upon an overflow interrupt to do the actual work of tracking user input and translating that to state changes, exactly how is that done?

ISR(TIMER0_OVF_vect) {

First, declare a handler for the TIMER0 overflow signal.

    unsigned char sample = PINC;
    unsigned char changes = debounceB(sample);

Then, read the current state of the PINC inputs. In this case, I’m reading all 8 bits even though only 2 pins are configured as inputs. The other 6 will be effectively ignored.

The debounceB function contains the vertical stack counter based debouncing logic. We’ll get to that next.

changes will contain the bits of PINC that have remained stable over the last 4 samples. Thus, the bits that are set in changes are the bits that have either been set or cleared for all 4 of the last passes through this function; they are debounced.

    
    if (bit_read_8(changes, BIT(PC0)) &&
        (!bit_read_8(sample, BIT(PC0)))) {
        ledState ++;
        ledState = ledState % 8;
    }
    if (bit_read_8(changes, BIT(PC1)) &&
        (!bit_read_8(sample, BIT(PC1)))) {
        ledState --;
        ledState = ledState % 8;
    }

These two bits of logic simply test to see if either the switch on pin PC0 or PC1 are pushed (and debounced).

Since the pins are configured to use pull-up resistors and the switches are connected to GND, pushing the switch yields a logical zero on the corresponding pin.

The test in changes determines if the bit is stable and the test in sample determines the state of the switch; on or off.

}

And that is it.


Almost. The real work is done in the routine that does the debouncing itself.

A vertical stack counter works by effectively “stacking” the bits in multiple bytes. In this case, there are two bytes. One byte contains all of the least-significant-bits and the other byte contains all of the most-significant-bits.

End result? 2 bytes represent 8 2 bit counters.

In this case, I’m only using 2 of the 2 bit counters. This leaves me considerable room to expand and the bitwise math is quite efficient.

static unsigned char debounceB0,debounceB1,debouncedBState;

The debouncing routine requires three bytes of persistent state; the vertical counters live in debounceB0 and debounceB1. The previous state is held in debouncedBState.

unsigned debounceB(unsigned char sample)
{
    unsigned char delta, changes;
    
    // Set delta to changes from last sample
    delta = sample ^ debouncedBState;

This sets delta to be the difference between the previous state (debouncedBState) and the latest sample. Any bits that have changed will be set in delta.

    // Increment counters
    debounceB1 = debounceB1 ^ debounceB0;
    debounceB0  = ~debounceB0;

This tricksy bit of bitwise math simply increments all of the vertical counters. If they overflow, they wrap back to 0.

    
    // reset any unchanged bits
    debounceB0 &= delta;
    debounceB1 &= delta;

Any counters associated with bits that didn’t change between the last sample and this sample are reset.

    // update state & calculate returned change set
    changes = ~(~delta | debounceB0 | debounceB1);
    debouncedBState ^= changes;
    return changes;
}

This sets changes to be the set of bits for which no change was detected and for which the counter has rolled back around to zero. If either the bit has changed in this pass (thus causing the counter to reset to 0) or the counter has yet to overflow, the state will still be considered to be volatile and, thus, the corresponding bit in changes will be zero.

It is a terribly elegant little routine. I found allusions to it in a couple of different AVR forums and finally found a description of the vertical counter portion of the algorithm here. From there, it was a matter of figuring out how to use changing bits in the sample to appropriately reset the counters.

Here is another explanation and a bit of pseudo code. I found it helpful in vetting my implementation.



3 Responses to “Using a Vertical Stack Counter to Debounce Switches”

  1. Jeff McCune says:

    Thank you for publishing such great information from a “getting started” perspective. I recently started playing with AVR’s as a hobby, and your code and articles are a wonderful resource.

    Keep up the great work.

    Cheers,
    -Jeff McCune

  2. John C. Randolph says:

    Found an interesting paper on hardware approaches to debouncing:

    http://www.ganssle.com/debouncing.pdf

    -jcr

  3. BrainMedley » Blog Archive » There And Back Again, A Servo Project says:

    […] convenience macros I’ve accumulated into bit_helpers.h.  The switches are debounced with a vertical stack counter (thanks, Bill!)  Both the ADC and timer are flexible but complex peripherals, requiring a fair […]

Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>