Button Debounce on AVR Microcontrollers

AVR Microcontrollers

Debounce is the software technique used to interface physical buttons with microcontrollers.

Microcontrollers rarely live in a world of their own, but generally interface with the external environment using additional electronic and hardware. The most common input devices are buttons. However, buttons aren’t perfect, and tend to send dirty signals while they settle to a new state: they “bounce”. This is due to the construction of the mechanical contacts in a button.

This problem is usually handled in software using various techniques.

As a reference, I will use the attiny13a, but the same technique can be applied with any microcontroller.

The technique I like to use is based on a timer ticking at regular intervals (usually, 1msec) and a variable used like a shift register.

The first thing I need is a timer raising an interrupt every 1 msec. To achieve that, I set Timer0 in CTC mode (Compare and Match): with a system clock at 1.2Mhz, and a prescaler of 8 (essentially a clock divider), we obtain a timer frequency of 150khz. In a millisecond, we have 1/1000 of that, which is 150. Hence we set the CTC register to 149:

void timer0_init()
{
  TIMSK0 |= _BV(OCIE0A);
  OCR0A = 149;
  TCCR0A |= _BV(WGM01); // CTC mode
  TCCR0B |= _BV(CS01);  // prescaler DIV8
}

The second thing is the interrupt handler: I personally like to set a variable to 1 and leave the handling of the interrupt into the main loop. I do this because with one timer I can:

  • trigger a longer running piece of code without staying inside the interrupt handler (i.e. interrupt handler should be as short as possible)
  • count arbitrary multipliers of the 1 msec base time
volatile uint8_t tick = 0;

ISR(TIM0_COMPA_vect)
{
  tick = 1;
}

In the main loop, I essentially poll on the tick variable until it becomes 1, then I update the button status and check if it is pressed or depressed.

typedef struct
{
  uint8_t acc;
  // additional fields for more complex features
} button_t;

int main(void)
{
  button_t button = {0};

  // setting port direction and pull up resistors
  PORTB = _BV(PB3);

  timer0_init();
  sei();

  while (1)
  {
    while (!tick)
      ;
    tick = 0;

    button->acc = (button->acc << 1) | (PINB & _BV(PB3)) ? 0 : 1);
    switch (button.acc)
    {
    case 0x00:
      // do something
      break;
    case 0xFF:
      // do something
      break;
    default:
      // status is changing/unstable - do nothing
      break;
    }
  }
}

The whole debounce happens in the acc variable of the button_t struct: in order to consider the button either pressed or depressed, the variable need to have collected 8 consecutive samples (i.e. button status) of the same level. This also means there is a minimum lag of 8 milliseconds (8 * 1msec of timer0) before the microcontroller takes actions, but this is a very short time for a human interface.

The entire source code for this article is published on github, where I light up an LED when the button is pressed.

Read next