#pragma once #include #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; };