Last active
May 6, 2025 15:52
-
-
Save sr105/b23dea5815026db70d74a3ccd3d52311 to your computer and use it in GitHub Desktop.
Refactored debounce code using delays from https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-i/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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