/* * button.cpp — Debounced button with single-tap, double-tap, and hold detection. * SPDX-License-Identifier: BSD-2-Clause */ #include "button.h" #include // ---- Timing constants -------------------------------------------------- static const uint16_t DEBOUNCE_MS = 50; // minimum stable time before accepting a state change static const uint16_t HOLD_MS = 800; // hold this long to emit BTN_HOLD static const uint16_t DOUBLE_TAP_MS = 400; // second tap must arrive within this window // ---- State ------------------------------------------------------------- static uint8_t s_pin; // Debounce state static bool s_raw_state; // last raw digitalRead result static bool s_debounced; // stable debounced state (true = pressed) static uint32_t s_debounce_time; // millis() when raw state last changed // Tap counting static uint8_t s_tap_count; // taps accumulated in the current sequence static uint32_t s_last_release_ms; // millis() of the most recent release // Hold detection static uint32_t s_press_start_ms; // millis() when current press began static bool s_hold_fired; // true after BTN_HOLD has been emitted for this press // ---- Helpers ----------------------------------------------------------- // Returns true when the button is physically pressed. // INPUT_PULLUP means LOW = pressed. static inline bool pin_is_pressed() { return digitalRead(s_pin) == LOW; } // ---- Public API -------------------------------------------------------- void button_begin(uint8_t pin) { s_pin = pin; s_raw_state = false; s_debounced = false; s_debounce_time = 0; s_tap_count = 0; s_last_release_ms = 0; s_press_start_ms = 0; s_hold_fired = false; pinMode(pin, INPUT_PULLUP); } ButtonEvent button_update() { uint32_t now = millis(); bool raw = pin_is_pressed(); // ---- Debounce ---- // Reset the debounce timer whenever the raw reading changes. if (raw != s_raw_state) { s_raw_state = raw; s_debounce_time = now; } // Only update the debounced state after the signal has been stable. bool prev = s_debounced; if ((now - s_debounce_time) >= DEBOUNCE_MS) { s_debounced = raw; } bool just_pressed = ( s_debounced && !prev); bool just_released = (!s_debounced && prev); // ---- Press start ---- if (just_pressed) { s_press_start_ms = now; s_hold_fired = false; } // ---- Hold detection (fires once while the button is held) ---- if (s_debounced && !s_hold_fired && (now - s_press_start_ms) >= HOLD_MS) { s_hold_fired = true; s_tap_count = 0; // discard any pending taps so hold doesn't also trigger a tap return BTN_HOLD; } // ---- Count taps on each release (only if hold didn't fire) ---- if (just_released && !s_hold_fired) { s_tap_count++; s_last_release_ms = now; } // ---- Resolve tap count after the double-tap window expires ---- // Wait until the button is up AND the window has passed before deciding. if (s_tap_count > 0 && !s_debounced && (now - s_last_release_ms) >= DOUBLE_TAP_MS) { uint8_t taps = s_tap_count; s_tap_count = 0; if (taps == 1) return BTN_TAP; if (taps >= 2) return BTN_DOUBLE_TAP; } return BTN_NONE; }