diff --git a/src/helpers/ui/MomentaryButton.cpp b/src/helpers/ui/MomentaryButton.cpp index 9d01e5b01..5700b9e6d 100644 --- a/src/helpers/ui/MomentaryButton.cpp +++ b/src/helpers/ui/MomentaryButton.cpp @@ -2,7 +2,7 @@ #define MULTI_CLICK_WINDOW_MS 280 -MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup, bool multiclick) { +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup, bool multiclick, int debounce_ms) { _pin = pin; _reverse = reverse; _pull = pulldownup; @@ -15,9 +15,12 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse _last_click_time = 0; _multi_click_window = multiclick ? MULTI_CLICK_WINDOW_MS : 0; _pending_click = false; + _debounce_ms = debounce_ms; + _last_debounce_time = 0; + _last_read = prev; } -MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, int analog_threshold) { +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, int analog_threshold, int debounce_ms) { _pin = pin; _reverse = false; _pull = false; @@ -30,6 +33,9 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, int analog_t _last_click_time = 0; _multi_click_window = MULTI_CLICK_WINDOW_MS; _pending_click = false; + _debounce_ms = debounce_ms; + _last_debounce_time = 0; + _last_read = prev; } void MomentaryButton::begin() { @@ -62,36 +68,57 @@ bool MomentaryButton::isPressed(int level) const { } } +void MomentaryButton::setDebounceMs(int ms) { + if (ms < 0) ms = 0; + _debounce_ms = ms; +} + +int MomentaryButton::getDebounceMs() const { + return _debounce_ms; +} + int MomentaryButton::check(bool repeat_click) { if (_pin < 0) return BUTTON_EVENT_NONE; int event = BUTTON_EVENT_NONE; - int btn = _threshold > 0 ? (analogRead(_pin) < _threshold) : digitalRead(_pin); - if (btn != prev) { - if (isPressed(btn)) { - down_at = millis(); - } else { - // button UP - if (_long_millis > 0) { - if (down_at > 0 && (unsigned long)(millis() - down_at) < _long_millis) { // only a CLICK if still within the long_press millis + int raw_btn = _threshold > 0 ? (analogRead(_pin) < _threshold) : digitalRead(_pin); + + // debounce: detect changes and wait for stability + if (raw_btn != _last_read) { + _last_debounce_time = millis(); + _last_read = raw_btn; + } + + int btn = raw_btn; // use the latest raw reading for logic below + + // Only accept the state change if it has been stable for _debounce_ms + if ((unsigned long)(millis() - _last_debounce_time) >= (unsigned long)_debounce_ms) { + if (btn != prev) { + if (isPressed(btn)) { + down_at = millis(); + } else { + // button UP + if (_long_millis > 0) { + if (down_at > 0 && (unsigned long)(millis() - down_at) < _long_millis) { // only a CLICK if still within the long_press millis + _click_count++; + _last_click_time = millis(); + _pending_click = true; + } + } else { _click_count++; _last_click_time = millis(); _pending_click = true; } - } else { - _click_count++; - _last_click_time = millis(); - _pending_click = true; - } - if (event == BUTTON_EVENT_CLICK && cancel) { - event = BUTTON_EVENT_NONE; - _click_count = 0; - _last_click_time = 0; - _pending_click = false; + if (event == BUTTON_EVENT_CLICK && cancel) { + event = BUTTON_EVENT_NONE; + _click_count = 0; + _last_click_time = 0; + _pending_click = false; + } + down_at = 0; } - down_at = 0; + prev = btn; } - prev = btn; } if (!isPressed(btn) && cancel) { // always clear the pending 'cancel' once button is back in UP state cancel = 0; @@ -133,6 +160,7 @@ int MomentaryButton::check(bool repeat_click) { break; default: // For 4+ clicks, treat as triple click? + event = BUTTON_EVENT_TRIPLE_CLICK; break; } diff --git a/src/helpers/ui/MomentaryButton.h b/src/helpers/ui/MomentaryButton.h index 358a343b0..91dbc9d91 100644 --- a/src/helpers/ui/MomentaryButton.h +++ b/src/helpers/ui/MomentaryButton.h @@ -20,14 +20,24 @@ class MomentaryButton { int _multi_click_window; bool _pending_click; + // Debounce support + unsigned long _last_debounce_time; + int _last_read; + int _debounce_ms; // debounce interval in milliseconds + bool isPressed(int level) const; public: - MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true); - MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold); + // debounce_ms: milliseconds of stable state required to accept changes (default 30ms) + MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true, int debounce_ms=30); + MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold, int debounce_ms=30); void begin(); int check(bool repeat_click=false); // returns one of BUTTON_EVENT_* void cancelClick(); // suppress next BUTTON_EVENT_CLICK (if already in DOWN state) uint8_t getPin() { return _pin; } bool isPressed() const; + + // Debounce control + void setDebounceMs(int ms); + int getDebounceMs() const; };