mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-16 16:16:37 +00:00
A single button press could surface as two CLICKs on the e-ink build, most visibly as start+stop on the stopwatch. Two contact-bounce paths fed the IRQ edge-capture machinery a phantom press/release pair: - a bounce edge accepted just after a clean release was replayed as a real press — ISR_DEBOUNCE_MS (5 ms) was too short for the joystick switch; raised to 25 ms so the settling burst is swallowed. - the live-pin self-heal reconciled prev against a single raw digitalRead, which can sample a bouncing contact mid-flap and synthesise a transition. It now acts only once the divergence has been stable for ISR_DEBOUNCE_MS, so a momentary read can't inject a click; a genuinely lost edge still heals (~25 ms later) so the button can't stick. Both thresholds stay far below any human tap cadence (>100 ms), so rapid multi-tap navigation still registers every tap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
75 lines
2.6 KiB
C++
75 lines
2.6 KiB
C++
#pragma once
|
|
|
|
#include <Arduino.h>
|
|
|
|
#define BUTTON_EVENT_NONE 0
|
|
#define BUTTON_EVENT_CLICK 1
|
|
#define BUTTON_EVENT_LONG_PRESS 2
|
|
#define BUTTON_EVENT_DOUBLE_CLICK 3
|
|
#define BUTTON_EVENT_TRIPLE_CLICK 4
|
|
|
|
class MomentaryButton {
|
|
int8_t _pin;
|
|
int8_t prev, cancel;
|
|
bool _reverse, _pull;
|
|
int _long_millis;
|
|
int _threshold; // analog mode
|
|
unsigned long down_at;
|
|
uint8_t _click_count;
|
|
unsigned long _last_click_time;
|
|
int _multi_click_window;
|
|
bool _pending_click;
|
|
|
|
bool isPressed(int level) const;
|
|
void applyTransition(int btn, unsigned long at);
|
|
|
|
#ifdef BUTTON_USE_INTERRUPTS
|
|
// GPIO-IRQ edge capture: boards whose display blocks the main loop for a
|
|
// long time per refresh (e-ink) can miss an entire press+release cycle if
|
|
// check() only ever samples the live pin. The ISR latches edges with their
|
|
// own timestamp into a small ring buffer; check() replays them so no tap
|
|
// gets lost while the loop is stuck waiting on the panel.
|
|
int8_t _isr_slot = -1;
|
|
static const uint8_t EDGE_BUF_SIZE = 16;
|
|
volatile uint8_t _edge_level[EDGE_BUF_SIZE];
|
|
volatile uint32_t _edge_time[EDGE_BUF_SIZE];
|
|
volatile uint8_t _edge_head = 0, _edge_tail = 0;
|
|
volatile uint32_t _last_isr_edge = 0;
|
|
// Self-heal debounce: only reconcile against the live pin once a divergence
|
|
// from `prev` has persisted, so a single raw read of a bouncing contact can't
|
|
// inject a phantom press/release pair (a double-click).
|
|
bool _healing = false;
|
|
uint8_t _heal_level = 0;
|
|
uint32_t _heal_since = 0;
|
|
|
|
void pushEdge(uint8_t level, uint32_t at);
|
|
bool popEdge(uint8_t &level, uint32_t &at);
|
|
void isrHandler();
|
|
static MomentaryButton* _isr_table[8];
|
|
|
|
public:
|
|
// MAX_ISR_BUTTONS/isrTrampolineN must be public: attachInterrupt() needs a
|
|
// plain free-function pointer, taken from a file-scope table outside the
|
|
// class (see MomentaryButton.cpp), which needs both to size and fill itself.
|
|
static const uint8_t MAX_ISR_BUTTONS = 8;
|
|
static void isrTrampoline0();
|
|
static void isrTrampoline1();
|
|
static void isrTrampoline2();
|
|
static void isrTrampoline3();
|
|
static void isrTrampoline4();
|
|
static void isrTrampoline5();
|
|
static void isrTrampoline6();
|
|
static void isrTrampoline7();
|
|
private:
|
|
#endif
|
|
|
|
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);
|
|
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;
|
|
};
|