mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
fix(eink): debounce edge capture + self-heal so one press isn't a double-tap
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>
This commit is contained in:
@@ -3,7 +3,13 @@
|
|||||||
#define MULTI_CLICK_WINDOW_MS 280
|
#define MULTI_CLICK_WINDOW_MS 280
|
||||||
|
|
||||||
#ifdef BUTTON_USE_INTERRUPTS
|
#ifdef BUTTON_USE_INTERRUPTS
|
||||||
#define ISR_DEBOUNCE_MS 5
|
// Contact-bounce guard for the edge-capture ISR. Must be long enough to swallow
|
||||||
|
// a switch's full settling burst, otherwise a bounce edge accepted just after a
|
||||||
|
// clean release is replayed as a phantom press and — once the live-pin self-heal
|
||||||
|
// pulls the level back — surfaces as a second CLICK (seen as a double-tap, e.g.
|
||||||
|
// start+stop on the stopwatch). 5 ms was too tight for the joystick switch.
|
||||||
|
// Still far below any human tap cadence (>100 ms), so real fast bursts are kept.
|
||||||
|
#define ISR_DEBOUNCE_MS 25
|
||||||
|
|
||||||
MomentaryButton* MomentaryButton::_isr_table[MomentaryButton::MAX_ISR_BUTTONS] = { nullptr };
|
MomentaryButton* MomentaryButton::_isr_table[MomentaryButton::MAX_ISR_BUTTONS] = { nullptr };
|
||||||
|
|
||||||
@@ -168,10 +174,25 @@ int MomentaryButton::check(bool repeat_click) {
|
|||||||
// Self-heal: if an edge was ever lost (buffer overflow, a debounce-dropped
|
// Self-heal: if an edge was ever lost (buffer overflow, a debounce-dropped
|
||||||
// settling edge, or a missed GPIOTE event), prev would otherwise stay
|
// settling edge, or a missed GPIOTE event), prev would otherwise stay
|
||||||
// diverged from the hardware until the next captured edge — a stuck button.
|
// diverged from the hardware until the next captured edge — a stuck button.
|
||||||
// Reconcile against the live pin; a no-op whenever the edge stream already
|
// Reconcile against the live pin, but only once the divergence has been
|
||||||
// matches it, which is the normal case.
|
// STABLE for ISR_DEBOUNCE_MS: a single raw read can catch a contact
|
||||||
|
// mid-bounce and would otherwise synthesise a phantom press/release pair
|
||||||
|
// (a double-click — e.g. start+stop on the stopwatch). A no-op whenever the
|
||||||
|
// edge stream already matches the pin, which is the normal case.
|
||||||
int live = digitalRead(_pin);
|
int live = digitalRead(_pin);
|
||||||
if (live != prev) applyTransition(live, millis());
|
uint32_t now = millis();
|
||||||
|
if (live != prev) {
|
||||||
|
if (!_healing || live != _heal_level) {
|
||||||
|
_healing = true;
|
||||||
|
_heal_level = (uint8_t)live;
|
||||||
|
_heal_since = now;
|
||||||
|
} else if ((uint32_t)(now - _heal_since) >= ISR_DEBOUNCE_MS) {
|
||||||
|
applyTransition(live, now);
|
||||||
|
_healing = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_healing = false;
|
||||||
|
}
|
||||||
btn = prev;
|
btn = prev;
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ class MomentaryButton {
|
|||||||
volatile uint32_t _edge_time[EDGE_BUF_SIZE];
|
volatile uint32_t _edge_time[EDGE_BUF_SIZE];
|
||||||
volatile uint8_t _edge_head = 0, _edge_tail = 0;
|
volatile uint8_t _edge_head = 0, _edge_tail = 0;
|
||||||
volatile uint32_t _last_isr_edge = 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);
|
void pushEdge(uint8_t level, uint32_t at);
|
||||||
bool popEdge(uint8_t &level, uint32_t &at);
|
bool popEdge(uint8_t &level, uint32_t &at);
|
||||||
|
|||||||
Reference in New Issue
Block a user