Saturday, January 16, 2021

Read and debounce multiple buttons using interrupt | AVR microcontrollers

This library provides an easy way for reading and debouncing one or many buttons connected to a microcontroller. It can also read a button combination, button long press and button double press.

Button debouncing library for AVR microcontrollers

What is button debouncing

When a button is pressed or released it takes a certain amount of time before the two contacts reach a stable state. During that time the contacts are known to be bouncing sending multiple on off signals to the microcontroller before they settle to either on or off.

The bouncing time period depends on the speed the button is pressed, the force, the button quality and the age of the button. With time the contacts will oxidize especially with low quality buttons.

For one or two buttons the debouncing can be made in hardware by connecting a capacitor in parallel with the button that will keep the voltage across the button stable during press or release. But when having many buttons it is more practical to make the debouncing in software where it is also easier to modify the debouncing time compared to a capacitor.

Button debouncing in software using the Binary Button Debounce (BBM) method

Button debouncing using Binary Button Debounce (BBD)

Every time the debouncing function runs, the states of all buttons are saved in a binary representation in one or multiple bytes. In a byte can fit up to 8 buttons and each bit represents a button - 0 the button is not pressed, 1 the button is pressed. I call this a snapshot. Each snapshot is saved in an array and at the end every bit must be 1 for a particular button to be considered pressed.

Connecting push buttons to a microcontroller
Connecting push buttons to a microcontroller

Using the library

It is not necessary to modify the settings inside the library header file but there are a few settings that can be modified if the default values are not desired.

BUTTONS_SIZE_LIMIT - can be BTNS_LIMIT_8 that can hold up to 8 buttons or BTNS_LIMIT_16 for up to 16 buttons.

MIN_PRESS_TIME - debouncing time in milliseconds. 10 or 20 m. Default value is 10ms.

TIMER_ISR_RESOLUTION - by default is 1ms because the millis interrupt triggers every 1ms.

Setup function


This function will set the pins for the buttons as inputs. Since the buttons can be on multiple ports and their number can vary this is the simplest solution I found for the user to pass the location of the buttons to the library.

BUTTONS_LOCATION: an array that indicates on what ports and pin numbers the buttons are located.

NR_OF_BUTTONS_AND_PORTS: the size of the array.


#define NR_OF_BUTTONS_AND_PORTS      9

uint8_t BUTTONS_LOCATION[NR_OF_BUTTONS_AND_PORTS] = {'B', 2, 0, 5, 4, 'C', 5, 'D', 7};

Here we have some buttons on port B on pins 2, 0, 5 and 4, one button on port C pin 5 and one on port D pin 7. The array size is 3 ports + 6 buttons = 9.

To be able to identify what each button does and also if a combination of buttons was pressed an enum fits perfect for this purpose.

enum Buttons{
	BTN_UP 			= 1,	// PB2
	BTN_DOWN		= 2,	// PB0
	BTN_LEFT		= 4,	// PB5
	BTN_RIGHT		= 8,	// PB4
	BTN_MENU_OK		= 16,	// PC5
	BTN_START_STOP	        = 32	// PD7

The name of the buttons can be anything and more buttons can be added or removed from the list. The important thing is the order that they are inside the enum. Notice that the order of the enum is similar to that of the array. Also important is the number of each button in the enum - they must start at 1 and continue as follows 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 and so on.

Reasons for this can be seen later in an example but the cool thing is that any combination of buttons can be checked like this BTN_UP | BTN_DOWN. This will be true in an if statement or a switch case if both buttons are pressed.

Reading the buttons

There are two functions for reading the buttons. One returns the currently pressed buttons and the other the buttons that were pressed and released.

For example in many projects like a clock or a flashlight there is no room for many buttons and 1 or 2 buttons must accomplish many functions. Entering setup mode can be made as a secondary function by a long press of 2 seconds while a short press can accomplish a main action like switching the lights. Or some times a single button press must do something only once regardless if the user holds the button down and other times it is desired to repeat that button's function - when changing a numerical value for example.


Pushed buttons: returns an 8 or 16 bit value where each bit represents the state of the button at that bit position. Thanks to the Buttons enum there is no need for binary shifting to check which button was pressed. See the below examples on how easy is to check the button state.

Even if the user holds the button pressed continuously the function will return the on states only once. For example holding the key "a" will only return "a" whereas the following function will repeat the action.


Pressed buttons: same as the function above except that here the function returns the on states of the buttons as long as they are pressed. For example holding the key "a" will return "aaaaaaaaaaaaa" if the user doesn't implement a delay in code to account for this.


Double clicked buttons: returns the button that was pressed twice very quickly. This is simillar to a mouse doubleclick.


Button combination: returns the button combination that was pressed during a certain time interval. Default: 200ms.

Debouncing functions


Must be placed in a timer interrupt (ideally 1ms resolution). After a certain time it will set the    NEXT_DEBOUNCE_READY flag that indicates that the debouncing function can run.


This is the debouncing function that is executed by the main loop when NEXT_DEBOUNCE_READY is 1. When debouncing is ready it will set the DEBOUNCE_FINISH flag then the main loop can read which buttons are pressed.

monitorPressTime: represents how many milliseconds a button must be pressed to be considered a long press. 0 means this option is not used. After this timeout the flag BUTTON_LONG_PRESS will be set and the main loop can check which button was long pressed.

void btnSetLongPressTime(uint16_t monitorPressTime);

The time in milliseconds after which a button is considered long pressed, can be set using the above function.


#include "buttonDebouncer.h"
#include "millis.h"

uint8_t BUTTONS_LOCATION[NR_OF_BUTTONS_AND_PORTS] = {'B', 2, 0, 5, 4, 'C', 5, 'D', 7}; 

enum Buttons{
	BTN_UP 		= 1,	// PB2
	BTN_DOWN	= 2,	// PB0
	BTN_LEFT	= 4,	// PB5
	BTN_RIGHT	= 8,	// PB4
	BTN_MENU_OK	= 16,	// PC5
int main(void){
	buttons_t pressed_buttons = 0;
	buttons_t pushed_buttons = 0;
	buttons_t button_combination = 0;
	// Setup millis
	// Setup button debouncer
	// Trigger button long press after 2000 milliseconds (2 seconds)
	// Enable global interrupts
		// Check if debouncing is complete
			pressed_buttons = debouncerPressedButtons();
			pushed_buttons = debouncerPushedButtons();
			button_combination = debouncerButtonCombination();
			// If a button was long pressed 
			        // Check which button
				pushed_buttons = pressed_buttons;             
			// Button combination
				case BTN_PLUS | BTN_ALARM:
					// Both buttons are pressed
					// Code...

			// Check if a button was double clicked
			if(BUTTON_DOUBLE_CLICK && debouncerDoubleClicked() == BTN_SETUP){
				// BTN_SETUP was double clicked
				// Code...
			// Check what button or combination of buttons were pressed
				case BTN_UP:
						// This button was long pressed
						// Code...
						// This button was pushed but not long pressed
						// Code...
				case BTN_DOWN:
					// Code...
				case BTN_LEFT:
					// Code...
				case BTN_RIGHT:
					// Code...
				case BTN_MENU_OK:
					// Code...
				case BTN_START_STOP:
					// Code...
 // Millis timer
	// Default: debouncing every 10ms, 1ms timer resolution

If you don't have a timer interrupt you can use the millis library from




No comments:

Post a Comment