|
| 1 | +/* |
| 2 | + * ESPButton.h |
| 3 | + * Ticker-scan based Button Handler with debounce. |
| 4 | + * Default scan interval is 16ms (60FPS). |
| 5 | + * All added Buttons are scanned by a global Ticker (by os timer). |
| 6 | + * |
| 7 | + * Usage: |
| 8 | + * in setup(): |
| 9 | + * pinMode(pin, INPUT_PULLUP / INPUT /... ); |
| 10 | + * ESPButton.add(id, pin, pin_down_digital); |
| 11 | + * ESPButton.setCallback(...); // handle your ButtonEvent by id |
| 12 | + * ESPButton.begin(); // call begin to start scan. |
| 13 | + * in loop(): |
| 14 | + * ESPButton.loop(); // will notify the event in loop (not in interrupt timer) |
| 15 | + * |
| 16 | + * Created on: 2020-03-08 |
| 17 | + * Last update: 2020-04-14 |
| 18 | + * Author: Wang Bin |
| 19 | + */ |
| 20 | + |
| 21 | +#ifndef ESPBUTTON_H_ |
| 22 | +#define ESPBUTTON_H_ |
| 23 | + |
| 24 | +#include <Arduino.h> |
| 25 | +#include <functional> |
| 26 | +#include <Ticker.h> |
| 27 | + |
| 28 | +#define ESPBUTTON_DEBUG(message, ...) //printf_P(PSTR("[%7d] ESPButton: " message "\n"), millis(), ##__VA_ARGS__) |
| 29 | + |
| 30 | +typedef struct _ESPButtonEntry { |
| 31 | + uint8_t id = -1; |
| 32 | + uint8_t pin = -1; |
| 33 | + uint8_t pin_down_digital = LOW; //按下状态时的digital值 |
| 34 | + |
| 35 | + bool stable_down = false; //稳定状态的下的按键状态 (true: down, false: up) |
| 36 | + uint32_t stable_threshold = 40; //如果按键状态维持一段时间未变化就认为是stable |
| 37 | + bool is_stable = false; //当前是否是稳定状态,只有稳定状态下才会进一步处理 |
| 38 | + bool raw_down = false; //未消抖的原始按键状态 |
| 39 | + uint32_t raw_changed_time = 0; //未消抖的原始按键状态的改变的时间 |
| 40 | + |
| 41 | + //void (*ext_digitalRead)(uint8_t pin); |
| 42 | + //如果该pin不是ESP芯片的引脚,而是从其他的扩展芯片读取的,就需要传入ext_digitalRead |
| 43 | + std::function<uint8_t(uint8_t pin)> ext_digitalRead = nullptr; |
| 44 | + //====== |
| 45 | + bool longclicked = false; //用来保证一次按下只能触发一次长按 |
| 46 | + bool down_handled = false; //标志着是否已经处理了该次down按下事件(比如已经触发了长按) |
| 47 | + //down->up,在规定时间内再次down就是双击,否则超时就是单击 |
| 48 | + bool wait_doubleclick = false; //标志着是否等待着双击事件 |
| 49 | + uint32_t down_time = 0; //ms |
| 50 | + uint32_t up_time = 0; |
| 51 | + |
| 52 | + uint32_t longclick_threshold = 5000; |
| 53 | + uint32_t doubleclick_threshold = 150; //按下释放后在此时间间隔内又按下认为是双击 |
| 54 | + |
| 55 | + bool longclick_enable = true; |
| 56 | + bool doubleclick_enable = true; |
| 57 | + //====== |
| 58 | + struct _ESPButtonEntry *next; |
| 59 | +} ESPButtonEntry; |
| 60 | + |
| 61 | +enum ESPButtonEvent { |
| 62 | + ESPBUTTONEVENT_NONE = 0, |
| 63 | + ESPBUTTONEVENT_SINGLECLICK, |
| 64 | + ESPBUTTONEVENT_DOUBLECLICK, |
| 65 | + ESPBUTTONEVENT_LONGCLICK |
| 66 | +}; |
| 67 | +class ESPButtonClass; |
| 68 | + |
| 69 | +static void _esp32_ticker_cb(ESPButtonClass *esp_button); |
| 70 | + |
| 71 | +class ESPButtonClass { |
| 72 | + |
| 73 | +public: |
| 74 | + |
| 75 | + typedef std::function<void(uint8_t id, ESPButtonEvent event)> espbutton_callback; |
| 76 | + |
| 77 | + Ticker ticker; |
| 78 | + ESPButtonEntry *entries = nullptr; |
| 79 | + espbutton_callback callback; |
| 80 | + ESPButtonEvent notify_event = ESPBUTTONEVENT_NONE; |
| 81 | + uint8_t notify_id = 0; |
| 82 | + |
| 83 | + ESPButtonClass() { |
| 84 | + } |
| 85 | + ~ESPButtonClass() { |
| 86 | + } |
| 87 | + |
| 88 | + void begin() { |
| 89 | + ticker.detach(); |
| 90 | +#if defined(ESP8266) |
| 91 | + ticker.attach_ms(16, std::bind(&ESPButtonClass::tick, this)); |
| 92 | +#elif defined(ESP32) |
| 93 | + ticker.attach_ms(16, _esp32_ticker_cb, this); |
| 94 | +#endif |
| 95 | + } |
| 96 | + |
| 97 | + ESPButtonEntry* add(uint8_t _id, uint8_t _pin, uint8_t _pin_down_digital, |
| 98 | + bool _doubleclick_enable = false, bool _longclick_enable = true) { |
| 99 | + ESPButtonEntry *entry = new ESPButtonEntry(); |
| 100 | + entry->id = _id; |
| 101 | + entry->pin = _pin; |
| 102 | + entry->pin_down_digital = _pin_down_digital; |
| 103 | + entry->doubleclick_enable = _doubleclick_enable; |
| 104 | + entry->longclick_enable = _longclick_enable; |
| 105 | + |
| 106 | + //初始化entry的状态??暂时不需要,我们就是认为按键默认就是未按下的 |
| 107 | + //entry->laststate_is_down = digitalReadEntryIsDown(entry); |
| 108 | + //加入链表 |
| 109 | + entry->next = entries; |
| 110 | + entries = entry; |
| 111 | + return entry; |
| 112 | + } |
| 113 | + |
| 114 | + void setCallback(espbutton_callback _callback) { |
| 115 | + callback = _callback; |
| 116 | + } |
| 117 | + |
| 118 | + PGM_P getButtonEventDescription(ESPButtonEvent e) { |
| 119 | + switch (e) { |
| 120 | + case ESPBUTTONEVENT_SINGLECLICK: |
| 121 | + return PSTR("SingleClick"); |
| 122 | + case ESPBUTTONEVENT_DOUBLECLICK: |
| 123 | + return PSTR("DoubleClick"); |
| 124 | + case ESPBUTTONEVENT_LONGCLICK: |
| 125 | + return PSTR("LongClick"); |
| 126 | + default: |
| 127 | + return PSTR("<unknown event>"); |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + void tick() { |
| 132 | + ESPButtonEntry *entry = entries; |
| 133 | + while (entry) { |
| 134 | + tickEntry(entry); |
| 135 | + entry = entry->next; |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + void loop() { |
| 140 | + if (callback && (notify_event != ESPBUTTONEVENT_NONE)) { |
| 141 | + callback(notify_id, notify_event); |
| 142 | + notify_id = 0; |
| 143 | + notify_event = ESPBUTTONEVENT_NONE; |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | +private: |
| 148 | + |
| 149 | + bool digitalReadEntryIsDown(ESPButtonEntry *entry) { |
| 150 | + if (entry->ext_digitalRead) { |
| 151 | + return entry->ext_digitalRead(entry->pin) == entry->pin_down_digital; |
| 152 | + } |
| 153 | + return digitalRead(entry->pin) == entry->pin_down_digital; |
| 154 | + } |
| 155 | + |
| 156 | + void tickEntry(ESPButtonEntry *entry) { |
| 157 | + const uint32_t t = millis(); |
| 158 | + const bool down = digitalReadEntryIsDown(entry); |
| 159 | + if (down != entry->raw_down) { |
| 160 | + entry->raw_down = down; |
| 161 | + entry->is_stable = false; |
| 162 | + entry->raw_changed_time = t; |
| 163 | + ESPBUTTON_DEBUG("change (%s)", down ? PSTR("down") : PSTR("up")); |
| 164 | + } else { // down == raw_down |
| 165 | + // 在stable_threshold时间内一直没有变化,认为是stable |
| 166 | + if (!entry->is_stable) { |
| 167 | + if (t - entry->raw_changed_time > entry->stable_threshold) { |
| 168 | + ESPBUTTON_DEBUG("t: %d, raw: %d", t, entry->raw_changed_time);ESPBUTTON_DEBUG("stable (%s)", down ? PSTR("down") : PSTR("up")); |
| 169 | + entry->is_stable = true; |
| 170 | + } |
| 171 | + } |
| 172 | + } |
| 173 | + if (!entry->is_stable) { |
| 174 | + //ESPBUTTON_DEBUG("not stable"); |
| 175 | + return; |
| 176 | + } |
| 177 | + //以上代码能检测出超过一定时间的稳定了的状态,等稳定了之后再做处理 |
| 178 | + |
| 179 | + if (entry->stable_down == down) { |
| 180 | + handleEntryUnchanged(entry); |
| 181 | + return; |
| 182 | + } else { |
| 183 | + entry->stable_down = down; |
| 184 | + handleEntryChanged(entry); |
| 185 | + } |
| 186 | + |
| 187 | + } |
| 188 | + |
| 189 | + void handleEntryChanged(ESPButtonEntry *entry) { |
| 190 | + const bool down = entry->stable_down; |
| 191 | + //仅有单击事件就在down的时候直接回调? 暂时不这么做,类比实体开关,按下不松手的时候,就是一直开着的状态 |
| 192 | + //逻辑如下: |
| 193 | + //单击:按下->释放->且释放一段时间内没有第二次按下 |
| 194 | + //双击:按下->释放->且释放一段时间内执行第二次按下时触发 |
| 195 | + //长按:按下一段时间内未释放 |
| 196 | + if (down) { //down |
| 197 | + if (entry->wait_doubleclick && entry->doubleclick_enable) { |
| 198 | + //规定时间内第二次down了,认为是双击 |
| 199 | + //亲测,一般情况下我的双击up->第二次down的间隔是80~100左右 |
| 200 | + ESPBUTTON_DEBUG("doubleclick, wait %d", (millis() - entry->up_time)); |
| 201 | + entry->down_handled = true; |
| 202 | + notifyEvent(entry, ESPBUTTONEVENT_DOUBLECLICK); |
| 203 | + } else { |
| 204 | + //第一次按下 |
| 205 | + entry->down_handled = false; |
| 206 | + } |
| 207 | + entry->down_time = millis(); |
| 208 | + entry->longclicked = false; |
| 209 | + entry->wait_doubleclick = false; |
| 210 | + } else { //up |
| 211 | + if (!entry->down_handled) { |
| 212 | + if (entry->doubleclick_enable) { |
| 213 | + //在loop中延时等待第二次按下 |
| 214 | + entry->up_time = millis(); |
| 215 | + entry->wait_doubleclick = true; |
| 216 | + } else { |
| 217 | + entry->down_handled = true; |
| 218 | + notifyEvent(entry, ESPBUTTONEVENT_SINGLECLICK); |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + } |
| 224 | + |
| 225 | + void handleEntryUnchanged(ESPButtonEntry *entry) { |
| 226 | + bool down = entry->stable_down; |
| 227 | + if (down) { //down |
| 228 | + if (entry->longclick_enable) { |
| 229 | + if (!entry->longclicked && !entry->down_handled) { |
| 230 | + if (millis() - entry->down_time > entry->longclick_threshold) { |
| 231 | + entry->longclicked = true; |
| 232 | + entry->down_handled = true; |
| 233 | + notifyEvent(entry, ESPBUTTONEVENT_LONGCLICK); |
| 234 | + } |
| 235 | + } |
| 236 | + } |
| 237 | + } else { //up |
| 238 | + entry->longclicked = false; |
| 239 | + if (entry->wait_doubleclick && entry->doubleclick_enable) { |
| 240 | + if (millis() - entry->up_time > entry->doubleclick_threshold) { |
| 241 | + entry->wait_doubleclick = false; |
| 242 | + entry->down_handled = true; |
| 243 | + //key2DoClick(); |
| 244 | + notifyEvent(entry, ESPBUTTONEVENT_SINGLECLICK); |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + void notifyEvent(ESPButtonEntry *entry, ESPButtonEvent event) { |
| 252 | + ESPBUTTON_DEBUG("Button(%d): %s", entry->id, getButtonEventDescription(event)); |
| 253 | + // Save the Event and notify it in loop |
| 254 | + if (notify_event != ESPBUTTONEVENT_NONE) { |
| 255 | + ESPBUTTON_DEBUG("Warnning! Previous Button Event is not handled in loop!"); |
| 256 | + } |
| 257 | + notify_event = event; |
| 258 | + notify_id = entry->id; |
| 259 | +// if (callback) { |
| 260 | +// callback(entry->id, event); |
| 261 | +// } |
| 262 | + } |
| 263 | + |
| 264 | +}; |
| 265 | + |
| 266 | +ESPButtonClass ESPButton; |
| 267 | + |
| 268 | +static void _esp32_ticker_cb(ESPButtonClass *esp_button) { |
| 269 | + esp_button->tick(); |
| 270 | +} |
| 271 | + |
| 272 | +#endif /* ESPBUTTON_H_ */ |
0 commit comments