0
0
Embedded Cprogramming~7 mins

Debouncing as a state machine in Embedded C

Choose your learning style9 modes available
Introduction

Debouncing helps to ignore quick, unwanted changes in a button press. Using a state machine makes it easy to track button states clearly and avoid mistakes.

When reading a button press on a microcontroller to avoid false triggers.
When you want to detect a clean press and release of a switch.
When you need reliable input from mechanical buttons in an embedded system.
Syntax
Embedded C
typedef enum {
    BUTTON_RELEASED,
    BUTTON_DEBOUNCE_PRESS,
    BUTTON_PRESSED,
    BUTTON_DEBOUNCE_RELEASE
} ButtonState;

ButtonState state = BUTTON_RELEASED;

void updateButtonState(int buttonInput) {
    switch(state) {
        case BUTTON_RELEASED:
            if(buttonInput == 1) {
                state = BUTTON_DEBOUNCE_PRESS;
                // start debounce timer
            }
            break;
        case BUTTON_DEBOUNCE_PRESS:
            if(debounce_time_passed()) {
                if(buttonInput == 1) {
                    state = BUTTON_PRESSED;
                    // button confirmed pressed
                } else {
                    state = BUTTON_RELEASED;
                }
            }
            break;
        case BUTTON_PRESSED:
            if(buttonInput == 0) {
                state = BUTTON_DEBOUNCE_RELEASE;
                // start debounce timer
            }
            break;
        case BUTTON_DEBOUNCE_RELEASE:
            if(debounce_time_passed()) {
                if(buttonInput == 0) {
                    state = BUTTON_RELEASED;
                    // button confirmed released
                } else {
                    state = BUTTON_PRESSED;
                }
            }
            break;
    }
}

The state machine uses four states to track button press and release.

Debounce timers help confirm the button state after a short delay.

Examples
A simple state machine without debounce timers, just toggling states.
Embedded C
typedef enum { BUTTON_RELEASED, BUTTON_PRESSED } ButtonState;

ButtonState state = BUTTON_RELEASED;

void updateButtonState(int buttonInput) {
    if(state == BUTTON_RELEASED && buttonInput == 1) {
        state = BUTTON_PRESSED;
    } else if(state == BUTTON_PRESSED && buttonInput == 0) {
        state = BUTTON_RELEASED;
    }
}
State machine with debounce only on press, release is immediate.
Embedded C
typedef enum {
    BUTTON_RELEASED,
    BUTTON_DEBOUNCE_PRESS,
    BUTTON_PRESSED
} ButtonState;

ButtonState state = BUTTON_RELEASED;

void updateButtonState(int buttonInput) {
    switch(state) {
        case BUTTON_RELEASED:
            if(buttonInput == 1) {
                state = BUTTON_DEBOUNCE_PRESS;
                // start debounce timer
            }
            break;
        case BUTTON_DEBOUNCE_PRESS:
            if(debounce_time_passed()) {
                if(buttonInput == 1) {
                    state = BUTTON_PRESSED;
                } else {
                    state = BUTTON_RELEASED;
                }
            }
            break;
        case BUTTON_PRESSED:
            if(buttonInput == 0) {
                state = BUTTON_RELEASED;
            }
            break;
    }
}
Sample Program

This program simulates a button being pressed and released with debounce using a state machine. It prints messages when debounce starts and when button states are confirmed.

Embedded C
#include <stdio.h>
#include <stdbool.h>

typedef enum {
    BUTTON_RELEASED,
    BUTTON_DEBOUNCE_PRESS,
    BUTTON_PRESSED,
    BUTTON_DEBOUNCE_RELEASE
} ButtonState;

ButtonState state = BUTTON_RELEASED;

// Simulated debounce timer counter
int debounce_counter = 0;
const int debounce_threshold = 3; // number of cycles to confirm

// Simulated function to check if debounce time passed
bool debounce_time_passed() {
    return debounce_counter >= debounce_threshold;
}

void updateButtonState(int buttonInput) {
    switch(state) {
        case BUTTON_RELEASED:
            if(buttonInput == 1) {
                state = BUTTON_DEBOUNCE_PRESS;
                debounce_counter = 0;
                printf("Start debounce press\n");
            }
            break;
        case BUTTON_DEBOUNCE_PRESS:
            debounce_counter++;
            if(debounce_time_passed()) {
                if(buttonInput == 1) {
                    state = BUTTON_PRESSED;
                    printf("Button pressed confirmed\n");
                } else {
                    state = BUTTON_RELEASED;
                    printf("False press, back to released\n");
                }
            }
            break;
        case BUTTON_PRESSED:
            if(buttonInput == 0) {
                state = BUTTON_DEBOUNCE_RELEASE;
                debounce_counter = 0;
                printf("Start debounce release\n");
            }
            break;
        case BUTTON_DEBOUNCE_RELEASE:
            debounce_counter++;
            if(debounce_time_passed()) {
                if(buttonInput == 0) {
                    state = BUTTON_RELEASED;
                    printf("Button released confirmed\n");
                } else {
                    state = BUTTON_PRESSED;
                    printf("False release, back to pressed\n");
                }
            }
            break;
    }
}

int main() {
    // Simulated button input sequence: 0 means not pressed, 1 means pressed
    int inputs[] = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0};
    int len = sizeof(inputs) / sizeof(inputs[0]);

    for(int i = 0; i < len; i++) {
        printf("Input: %d, State: %d\n", inputs[i], state);
        updateButtonState(inputs[i]);
    }

    return 0;
}
OutputSuccess
Important Notes

Debounce timers prevent quick, noisy changes from triggering false button presses.

State machines make the logic clear and easy to follow.

Adjust debounce time based on your hardware's noise level.

Summary

Debouncing avoids false button triggers caused by mechanical noise.

Using a state machine helps track button states step-by-step.

Timers confirm the button state before changing it.