11eb2584ef
Arduino Uno + WS2812B LED strip controller with a text-based lightshow system. Shows are defined as .txt files (hex color + fade duration per step), converted to PROGMEM headers by convert_all.py, and navigated at runtime via a debounced button (tap/double-tap/hold). BSD 2-Clause license. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
3.3 KiB
C++
104 lines
3.3 KiB
C++
/*
|
|
* button.cpp — Debounced button with single-tap, double-tap, and hold detection.
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "button.h"
|
|
#include <Arduino.h>
|
|
|
|
// ---- 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;
|
|
}
|