Files
Amirine_Cosplay_Lights/arduino/cosplay_lights/button.cpp
T
bgrolleman 11eb2584ef Initial commit — Amirine Cosplay Lights
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>
2026-05-21 10:16:56 +02:00

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;
}