-
-
Save mikee47/1b056a8462facdd000a0a83e77cba173 to your computer and use it in GitHub Desktop.
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
| #pragma once | |
| #include "oled_gui.h" | |
| template <typename T> | |
| T clamped_sub(T lhs, T rhs) | |
| { | |
| return lhs > rhs ? lhs - rhs : 0; | |
| } | |
| template <const unsigned long VALID_MS, | |
| const unsigned long HOLD_MS, | |
| const unsigned long REPEAT_MS> | |
| struct DebouncedKey | |
| { | |
| static constexpr uint32_t validTicks = OneShotFastMs::checkTime<VALID_MS>(); | |
| static constexpr uint32_t holdTicks = OneShotFastMs::checkTime<HOLD_MS>(); | |
| static constexpr uint32_t repeatTicks = OneShotFastMs::checkTime<REPEAT_MS>(); | |
| // comparing DURATIONS with < is allowed | |
| // comparing TIMESTAMPS is not. use - and overflow math | |
| // assume `now` is always measured at or after `last_change` | |
| // returns true on debounced change | |
| bool add_sample(bool sample) | |
| { | |
| switch(state) { | |
| case State::Idle: | |
| if(sample) { | |
| timer.reset<VALID_MS>(); | |
| state = State::Debounce1; | |
| } | |
| break; | |
| case State::Debounce1: | |
| if (timer.expired()) { | |
| if(sample) { | |
| // Changing Idle -> Active | |
| state = State::Active; | |
| return true; | |
| } else { | |
| state = State::Idle; | |
| } | |
| } else if(!sample) { | |
| timer.reset<VALID_MS>(); | |
| } | |
| break; | |
| case State::Active: | |
| if(!sample) { | |
| timer.reset<VALID_MS>(); | |
| state = State::Debounce2; | |
| } | |
| break; | |
| case State::Debounce2: | |
| if (timer.expired()) { | |
| if(!sample) { | |
| // Changing from Active -> Idle | |
| state = State::Idle; | |
| return true; | |
| } else { | |
| state = State::Active; | |
| } | |
| } else if(sample) { | |
| timer.reset(); | |
| } | |
| break; | |
| } | |
| // No change | |
| return false; | |
| } | |
| bool has_changed() | |
| { | |
| switch(state) { | |
| case State::Debounce1: | |
| return add_sample(true); | |
| case State::Debounce2: | |
| return add_sample(false); | |
| default: | |
| return false; | |
| } | |
| } | |
| bool is_set() | |
| { | |
| has_changed(); | |
| return state == State::Active; | |
| } | |
| bool is_held() | |
| { | |
| return is_set() && (timer.elapsedTicks() >= validTicks + holdTicks); | |
| } | |
| unsigned long count_repeats() | |
| { | |
| if(is_set()) { | |
| return clamped_sub(timer.elapsedTicks(), validTicks + holdTicks) / repeatTicks; | |
| } | |
| return 0; | |
| } | |
| unsigned long consume_repeats() | |
| { | |
| unsigned long counts = count_repeats(now); | |
| unsigned long consumed = clamped_sub(counts, repeats); | |
| repeats = counts; | |
| return consumed; | |
| } | |
| enum class State { | |
| Idle, | |
| Debounce1, // Debouncing Idle->Active | |
| Active, | |
| Debounce2, // Debouncing Active->Idle | |
| }; | |
| OneShotFastMs timer; | |
| State state{State::Idle}; | |
| unsigned long repeats = 0; | |
| }; | |
| struct Key | |
| { | |
| const size_t pin; | |
| char const* const name; | |
| void (*action)(void); | |
| DebouncedKey<DEBOUNCE_KEY_VALID_MS, | |
| DEBOUNCE_KEY_HOLD_MS, | |
| DEBOUNCE_KEY_REPEAT_MS> | |
| debounced{}; | |
| }; | |
| Key keys[] = {{PIN_KEY_UP, "KEY_UP", upAction}, | |
| {PIN_KEY_DOWN, "KEY_DOWN", downAction}, | |
| {PIN_KEY_ENTER, "KEY_ENTER", enterAction}, | |
| {PIN_KEY_BACK, "KEY_BACK", backAction}}; | |
| void init_keys() | |
| { | |
| for (auto& key : keys) | |
| { | |
| attachInterrupt( | |
| key.pin, InterruptDelegate([&key] { | |
| if (key.debounced.add_sample(digitalRead(key.pin) == LOW)) | |
| { | |
| if (key.debounced.is_set()) | |
| { | |
| key.action(); | |
| } | |
| } | |
| }), | |
| CHANGE); | |
| pinMode(key.pin, INPUT_PULLUP); | |
| } | |
| } | |
| void update_input() | |
| { | |
| for (auto& key : keys) | |
| { | |
| if (key.debounced.has_changed()) | |
| { | |
| if (key.debounced.is_set()) | |
| { | |
| key.action(); | |
| } | |
| } | |
| auto consumed = key.debounced.consume_repeats(); | |
| for (int i = 0; i < consumed; ++i) | |
| { | |
| key.action(); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mikee47 - It would be great if we have a sample demonstrating how to press a button for a desired time and handle all debouncing logic as suggested here. This way new users can start directly testing and using the code in their projects.