Skip to content

Instantly share code, notes, and snippets.

@sr105
Last active May 6, 2025 15:52
Show Gist options
  • Save sr105/b23dea5815026db70d74a3ccd3d52311 to your computer and use it in GitHub Desktop.
Save sr105/b23dea5815026db70d74a3ccd3d52311 to your computer and use it in GitHub Desktop.
// TL;DR Jump to the end and look at delay_debounce2()
// The first part of an excellent pair of
// [articles on button debouncing](https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-i/)
// included code (below) using a delay approach. The code looked awful to me and had a couple bugs. I thought, if we're
// going to say something is not the correct approach, we should at least give it the best fighting
// chance that we can. So, this file takes the original code and refactors it 3 times to a much
// nicer solution. The first two refactors are in comments and in psuedo-Python format for readability.
// The keys to the simplification are 1) choosing the values of our ButtonStates enum values to overlap with
// the output of read_button(), and 2) re-writing it more like a state machine and seeing how multiple states
// collapse into common code.
// The original code including missing state transitions.
enum ButtonStates { UP, DOWN, PRESS, RELEASE };
enum ButtonStates delay_debounce(enum ButtonStates button_state) {
if (read_button()){ /* if pressed */
if (button_state == PRESS){
button_state = DOWN;
}
if (button_state == UP){
_delay_ms(5);
if (read_button() == 1){
button_state = PRESS;
}
}
} else { /* if not pressed */
if (button_state == RELEASE){
button_state = UP;
}
if (button_state == DOWN){
if (read_button() == 0){
_delay_ms(5);
if (read_button() == 0){
button_state = RELEASE;
}
}
}
}
return button_state;
}
/*
Desired state machine:
DOWN <-pressed-|-released-> RELEASE -released-> UP <-released-|-pressed-> PRESS -pressed-> DOWN
// 1st Attempt
//
// - Flip the logic to be state based instead of based on the button result
if DOWN and released: // DOWN can only change to RELEASE
delay
if released:
state = RELEASE
else if UP and pressed: // UP can only change to PRESS
delay
if pressed:
state = PRESS
else if RELEASE: // RELEASE transition state: must return to a stable state
if released:
state = UP
else:
state = DOWN
else if PRESS: // PRESS transition state: must return to a stable state
if pressed:
state = DOWN
else:
state = UP
return state
// Can we do better than this using specific numbers? Yes!
UP = released = 0
DOWN = pressed = 1
RELEASE = released(0) + 2 = 2 // The reasoning for this will become clear later
PRESS = pressed(1) + 2 = 3 // The reasoning for this will become clear later
// Ugly naming...
// Button States: UP(0), DOWN(1), RELEASE(2), PRESS(3)
// Button pressed states: released(0), pressed(1)
Handle cases from easiest to hardest:
- No change since last time
- Transition state
- Possible change from stable state
// 2nd Attempt
//
button_pressed = read_button()
if state == button_pressed: // DOWN(1) == pressed(1) or UP(0) == released(0)
return state // Hint: we could have also returned button_pressed!
if RELEASE or PRESS: // RELEASE or PRESS: transition states: must return to a stable state
return button_pressed // DOWN(1) = pressed(1) or UP(0) = released(0)
// We must be in DOWN & released or UP & pressed states, i.e. a stable state with a potential change
delay
button_pressed = read_button()
if DOWN(1) and released(0):
return RELEASE
if UP(0) and pressed(1):
return PRESS
// returned back to stable. must have been noise
return state
// 3rd Attempt (collapse code)
//
// - The first two conditionals from above can be combined!
// - The second block can be simplified if we cheat and assign these values to states RELEASE and PRESS:
// - RELEASE = released + 2
// - PRESS = pressed + 2
button_pressed = read_button()
if state == button_pressed or state > UP(1): // Either state is stable and unchanged or is a transition state
return button_pressed
// We must be in DOWN & released or UP & pressed states, i.e. a stable state with a potential change
delay
button_pressed = read_button()
if state != button_pressed:
return button_pressed + 2 // return a transition state
// state returned back to stable after delay, i.e. no change
return state
*/
typedef int bool;
// Note: these specific values matter
enum ButtonStates { DOWN = 0, UP = 1, RELEASE = 2, PRESS = 3};
// Returns true(1) if pressed, false(0) if released
bool read_button();
enum ButtonStates delay_debounce2(enum ButtonStates button_state) {
bool button_pressed = read_button();
// If current state is...
if (
// - stable and unchanged or
button_state == button_pressed ||
// - any state other than stable: transition or invalid
(button_state != DOWN && button_state != UP)
)
return (enum ButtonStates)button_pressed;
// We must be in DOWN & released or UP & pressed states, i.e. a stable state with a potential change
_delay_ms(5);
button_pressed = read_button();
// state is a transition state
if (button_state != button_pressed)
// TODO The `+ 2` is a bit magic-number ugly. Perhaps, it should have just been this for readability:
// return button_pressed ? PRESS : RELEASE;
return (enum ButtonStates)(button_pressed + 2);
// state returned back to stable after delay, i.e. no change
return button_state;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment