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.
Debouncing as a state machine in 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.
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;
}
}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;
}
}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.
#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; }
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.
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.
Practice
What is the main purpose of using debouncing in embedded C button handling?
Solution
Step 1: Understand mechanical noise in buttons
Mechanical buttons can create multiple rapid on/off signals when pressed or released, called noise.Step 2: Purpose of debouncing
Debouncing filters out these rapid changes to detect a single clean press or release.Final Answer:
To avoid false triggers caused by mechanical noise -> Option AQuick Check:
Debouncing = Avoid false triggers [OK]
- Thinking debouncing speeds up detection
- Confusing debouncing with power saving
- Assuming it increases sensitivity
Which of the following is the correct way to declare a state variable for a debounce state machine in embedded C?
?Solution
Step 1: Identify proper state variable type
Using an enum clearly names states and improves code readability.Step 2: Check syntax correctness
enum ButtonState {IDLE, DEBOUNCE, PRESSED}; ButtonState state = IDLE; correctly declares an enum and initializes the state variable.Final Answer:
enum ButtonState {IDLE, DEBOUNCE, PRESSED}; ButtonState state = IDLE; -> Option CQuick Check:
Enum for states = enum ButtonState {IDLE, DEBOUNCE, PRESSED}; ButtonState state = IDLE; [OK]
- Assigning string to int variable
- Using bool for multiple states
- Using char without clear meaning
Given this snippet of a debounce state machine, what will be the value of state after 3 calls if the button is pressed and held?
enum State {IDLE, DEBOUNCE, PRESSED};
State state = IDLE;
int timer = 0;
void update(bool buttonPressed) {
switch(state) {
case IDLE:
if(buttonPressed) {
state = DEBOUNCE;
timer = 2;
}
break;
case DEBOUNCE:
if(timer > 0) timer--;
else state = PRESSED;
break;
case PRESSED:
if(!buttonPressed) state = IDLE;
break;
}
}
// Calls:
update(true);
update(true);
update(true);
Solution
Step 1: Trace state changes through calls
First call: state IDLE -> DEBOUNCE, timer=2. Second call: DEBOUNCE, timer=2>0, timer-- to 1, still DEBOUNCE. Third call: DEBOUNCE, timer=1>0, timer-- to 0, still DEBOUNCE (else not executed).Step 2: Confirm final state after 3 calls
Button is still pressed, state=DEBOUNCE (timer=0 now, changes to PRESSED on next call).Final Answer:
DEBOUNCE -> Option DQuick Check:
Timer counts down, state changes only if timer==0 at start of DEBOUNCE [OK]
- Thinking PRESSED after exactly 3 calls
- Not decrementing timer
- Resetting state incorrectly
Identify the bug in this debounce state machine code snippet:
enum State {IDLE, DEBOUNCE, PRESSED};
State state = IDLE;
int timer = 0;
void update(bool buttonPressed) {
switch(state) {
case IDLE:
if(buttonPressed) {
state = DEBOUNCE;
timer = 3;
}
break;
case DEBOUNCE:
if(timer == 0) state = PRESSED;
else timer--;
break;
case PRESSED:
if(!buttonPressed) state = IDLE;
break;
}
}
What is the problem?
Solution
Step 1: Analyze timer decrement logic
The code checks if timer == 0 before decrementing, so timer reaches 0 but state changes only next call.Step 2: Identify effect on debounce timing
This causes the debounce to last one extra cycle longer than intended.Final Answer:
Timer decrements after checking zero, causing one extra cycle -> Option BQuick Check:
Check timer before decrement causes delay [OK]
- Thinking state never changes
- Ignoring timer initialization
- Assuming button ignored in PRESSED
You want to implement a debounce state machine that only confirms a button press if the button is held for at least 5 cycles. Which approach below correctly implements this behavior?
enum State {IDLE, DEBOUNCE, PRESSED};
State state = IDLE;
int timer = 0;
void update(bool buttonPressed) {
switch(state) {
case IDLE:
if(buttonPressed) {
state = DEBOUNCE;
timer = 5;
}
break;
case DEBOUNCE:
if(buttonPressed) {
if(timer > 0) timer--;
else state = PRESSED;
} else {
state = IDLE;
}
break;
case PRESSED:
if(!buttonPressed) state = IDLE;
break;
}
}
What happens if the button is pressed for 3 cycles then released?
Solution
Step 1: Trace timer decrement with button held 3 cycles
Timer starts at 5, decrements each cycle while buttonPressed is true, so after 3 cycles timer = 2.Step 2: Button released before timer reaches zero
On release, state immediately returns to IDLE without reaching PRESSED.Final Answer:
State returns to IDLE without reaching PRESSED -> Option AQuick Check:
Hold less than 5 cycles resets state [OK]
- Assuming PRESSED reached early
- Thinking DEBOUNCE lasts indefinitely
- Believing immediate PRESSED on press
Ready to prove it?
Pass the test and this pattern moves unknown
