CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
TCA9535Keypad.cpp
Go to the documentation of this file.
1
5
6#include "cdc_hal/IKeypad.h"
7#include "cdc_hal/II2cBus.h"
8#include "cdc_hal/hw_config.h"
10#include "cdc_log.h"
11#include "esp_attr.h"
12#include "esp_heap_caps.h"
13#include "driver/gpio.h"
14#include "freertos/FreeRTOS.h"
15#include "freertos/idf_additions.h"
16#include "freertos/task.h"
17#include "freertos/semphr.h"
18
19static const char* TAG = "Keypad";
20
21namespace cdc::hal {
22
24static constexpr uint8_t REG_INPUT_0 = 0x00;
25static constexpr uint8_t REG_INPUT_1 = 0x01;
26static constexpr uint8_t REG_OUTPUT_0 = 0x02;
27static constexpr uint8_t REG_OUTPUT_1 = 0x03;
28static constexpr uint8_t REG_POLARITY_0 = 0x04;
29static constexpr uint8_t REG_POLARITY_1 = 0x05;
30static constexpr uint8_t REG_CONFIG_0 = 0x06;
31static constexpr uint8_t REG_CONFIG_1 = 0x07;
32
34static constexpr uint32_t TASK_STACK_SIZE = 6144;
35static constexpr UBaseType_t TASK_PRIORITY = 5;
36static constexpr uint32_t POLL_TIMEOUT_MS = 50;
37static constexpr uint32_t DEBOUNCE_MS = 10;
38
41static constexpr uint32_t LONG_PRESS_THRESHOLD_MS = 800;
42
44static constexpr size_t KEY_BUFFER_SIZE = 16;
45
48static constexpr uint8_t KEY_BIT_0 = 0;
49static constexpr uint8_t KEY_BIT_1 = 1;
50static constexpr uint8_t KEY_BIT_2 = 2;
51static constexpr uint8_t KEY_BIT_3 = 3;
52static constexpr uint8_t KEY_BIT_4 = 4;
53static constexpr uint8_t KEY_BIT_5 = 5;
54static constexpr uint8_t KEY_BIT_6 = 6;
55static constexpr uint8_t KEY_BIT_7 = 7;
56static constexpr uint8_t KEY_BIT_8 = 8;
57static constexpr uint8_t KEY_BIT_9 = 9;
58static constexpr uint8_t KEY_BIT_NO = 11;
59static constexpr uint8_t KEY_BIT_YES = 10;
60
62static constexpr uint16_t KEY_MASK_ALL = 0x0FFF;
63
65static constexpr uint16_t KEY_STATE_IDLE = KEY_MASK_ALL;
66
68static constexpr uint16_t KEY_STATE_INVALID = 0xFFFF;
69
71static constexpr uint16_t PANIC_CHORD_BITS = (1u << KEY_BIT_NO) | (1u << KEY_BIT_YES);
72
78static constexpr uint16_t maskForBit(uint8_t bit) {
79 return static_cast<uint16_t>(KEY_MASK_ALL ^ (1u << bit));
80}
81
87static Key rawToKey(uint16_t raw) {
88 switch (raw & KEY_MASK_ALL) {
89 case maskForBit(KEY_BIT_0): return Key::KEY_0;
90 case maskForBit(KEY_BIT_1): return Key::KEY_1;
91 case maskForBit(KEY_BIT_2): return Key::KEY_2;
92 case maskForBit(KEY_BIT_3): return Key::KEY_3;
93 case maskForBit(KEY_BIT_4): return Key::KEY_4;
94 case maskForBit(KEY_BIT_5): return Key::KEY_5;
95 case maskForBit(KEY_BIT_6): return Key::KEY_6;
96 case maskForBit(KEY_BIT_7): return Key::KEY_7;
97 case maskForBit(KEY_BIT_8): return Key::KEY_8;
98 case maskForBit(KEY_BIT_9): return Key::KEY_9;
99 case maskForBit(KEY_BIT_NO): return Key::KEY_NO; // Cancel/N
100 case maskForBit(KEY_BIT_YES): return Key::KEY_YES; // OK/Y
101 default: return Key::KEY_NONE;
102 }
103}
104
110static uint16_t keyToMask(Key key) {
111 switch (key) {
112 case Key::KEY_0: return maskForBit(KEY_BIT_0);
113 case Key::KEY_1: return maskForBit(KEY_BIT_1);
114 case Key::KEY_2: return maskForBit(KEY_BIT_2);
115 case Key::KEY_3: return maskForBit(KEY_BIT_3);
116 case Key::KEY_4: return maskForBit(KEY_BIT_4);
117 case Key::KEY_5: return maskForBit(KEY_BIT_5);
118 case Key::KEY_6: return maskForBit(KEY_BIT_6);
119 case Key::KEY_7: return maskForBit(KEY_BIT_7);
120 case Key::KEY_8: return maskForBit(KEY_BIT_8);
121 case Key::KEY_9: return maskForBit(KEY_BIT_9);
122 case Key::KEY_NO: return maskForBit(KEY_BIT_NO);
123 case Key::KEY_YES: return maskForBit(KEY_BIT_YES);
124 default: return KEY_STATE_INVALID;
125 }
126}
127
131class TCA9535Keypad : public IKeypad {
132public:
133 TCA9535Keypad() = default;
134
135 // IService implementation
136 bool init() override;
137 bool start() override;
138 void stop() override;
139 core::ServiceState getState() const override { return state_; }
140 const char* getName() const override { return "keypad"; }
141
142 // IKeypad implementation
143 void poll() override {} // Handled by task
144 bool isKeyPressed(Key key) const override;
145 Key getNextKey() override;
146 bool hasKey() const override;
147 bool anyKeyDown() const override;
148 void setCallback(KeyCallback callback) override { callback_ = callback; }
149 void setLongPressEnabled(bool enabled, uint32_t thresholdMs) override;
150 void setLongPressCallback(LongPressCallback callback) override { longPressCallback_ = callback; }
151 void setPanicChordCallback(PanicChordCallback callback) override { panicChordCallback_ = callback; }
152 void setDeferShortPress(uint32_t source, bool enabled) override {
153 if (enabled) deferSources_ |= source;
154 else deferSources_ &= ~source;
155 }
156 void setKeyRepeat(uint16_t initial_ms, uint16_t period_ms) override {
157 keyRepeatInitialMs_ = initial_ms;
158 keyRepeatPeriodMs_ = period_ms;
159 }
160 void prepareForSleep() override;
161 void recoverFromSleep() override;
162 void clearBuffer() override;
163
164private:
165 uint16_t readInputs() const;
166 void bufferAddKey(Key key);
167 Key bufferGetKey();
168 static void taskFunc(void* arg);
169 static void IRAM_ATTR isrHandler(void* arg);
170
172
173 // I2C device
174 II2cBus* bus_ = nullptr;
175 I2cDeviceHandle device_ = nullptr;
176
177 // Key buffer (circular) - protected by bufferMux_
178 Key keyBuffer_[KEY_BUFFER_SIZE] = {};
179 uint8_t bufferHead_ = 0;
180 uint8_t bufferTail_ = 0;
181 mutable portMUX_TYPE bufferMux_ = portMUX_INITIALIZER_UNLOCKED;
182
183 // Task handles
184 SemaphoreHandle_t semaphore_ = nullptr;
185 TaskHandle_t taskHandle_ = nullptr;
186
187 // State
188 uint16_t lastRawState_ = KEY_STATE_INVALID;
189 volatile bool inSleepMode_ = false;
190
191 // Callbacks
192 KeyCallback callback_ = nullptr;
193 LongPressCallback longPressCallback_ = nullptr;
194 PanicChordCallback panicChordCallback_ = nullptr;
195 bool panicChordFired_ = false;
196 uint32_t deferSources_ = 0;
197 bool longPressEnabled_ = false;
198 uint32_t longPressThresholdMs_ = LONG_PRESS_THRESHOLD_MS;
199
200 // Long-press tracking
201 Key pressedKey_ = Key::KEY_NONE;
202 uint32_t pressStartTime_ = 0;
203 bool longPressFired_ = false;
204
205 // Key-repeat (re-emit held key; mutually exclusive with long-press)
206 uint16_t keyRepeatInitialMs_ = 0;
207 uint16_t keyRepeatPeriodMs_ = 0;
208 uint32_t lastRepeatMs_ = 0;
209};
210
216 if (state_ != core::ServiceState::UNINITIALIZED) {
217 return state_ == core::ServiceState::INITIALIZED ||
219 }
220
221 // Get I2C bus
222 bus_ = getI2cBus0();
223 if (!bus_ || bus_->getState() != core::ServiceState::INITIALIZED) {
224 LOG_E(TAG, "I2C bus not initialized");
226 return false;
227 }
228
229 // Add TCA9535 device
230 if (bus_->addDevice(EXPANDER_ADDR, &device_) != ESP_OK) {
231 LOG_E(TAG, "Failed to add TCA9535 device");
233 return false;
234 }
235
236 // Configure TCA9535
237 uint8_t allHigh = 0xFF;
238 uint8_t noInvert = 0x00;
239 uint8_t allInputs = 0xFF;
240
241 // Set output registers high (for proper pull-up reading)
242 if (bus_->writeReg(device_, REG_OUTPUT_0, &allHigh, 1) != ESP_OK ||
243 bus_->writeReg(device_, REG_OUTPUT_1, &allHigh, 1) != ESP_OK ||
244 bus_->writeReg(device_, REG_POLARITY_0, &noInvert, 1) != ESP_OK ||
245 bus_->writeReg(device_, REG_POLARITY_1, &noInvert, 1) != ESP_OK ||
246 bus_->writeReg(device_, REG_CONFIG_0, &allInputs, 1) != ESP_OK ||
247 bus_->writeReg(device_, REG_CONFIG_1, &allInputs, 1) != ESP_OK) {
248 LOG_E(TAG, "Failed to configure TCA9535");
250 return false;
251 }
252
253 // Read initial state to clear any pending interrupt
254 lastRawState_ = readInputs();
255 LOG_I(TAG, "Initial state: 0x%04X", lastRawState_);
256
257 // Create semaphore
258 semaphore_ = xSemaphoreCreateBinary();
259 if (!semaphore_) {
260 LOG_E(TAG, "Failed to create semaphore");
262 return false;
263 }
264
265 // Stack must live in internal RAM, never PSRAM: this task runs the
266 // ViewStack dispatch, which performs SPI-flash IO (FAT plugin partition,
267 // NVS). The flash driver disables the cache during such operations, which
268 // makes a PSRAM-resident stack unreachable (esp_task_stack_is_sane_cache_disabled).
269 BaseType_t ret = xTaskCreateWithCaps(taskFunc, "keypad", TASK_STACK_SIZE,
270 this, TASK_PRIORITY, &taskHandle_,
271 MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
272 if (ret != pdPASS) {
273 LOG_E(TAG, "Failed to create task");
274 vSemaphoreDelete(semaphore_);
275 semaphore_ = nullptr;
277 return false;
278 }
279
280 // Configure interrupt pin
281 gpio_config_t io_conf = {};
282 io_conf.pin_bit_mask = (1ULL << EXP_IRQ_PIN);
283 io_conf.mode = GPIO_MODE_INPUT;
284 io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
285 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
286 io_conf.intr_type = GPIO_INTR_NEGEDGE;
287 gpio_config(&io_conf);
288
289 // Install ISR service (may already be installed by another driver)
290 esp_err_t err = gpio_install_isr_service(0);
291 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
292 LOG_E(TAG, "Failed to install ISR service: %s", esp_err_to_name(err));
293 vSemaphoreDelete(semaphore_);
294 semaphore_ = nullptr;
296 return false;
297 }
298 gpio_isr_handler_add(EXP_IRQ_PIN, isrHandler, this);
299
301 LOG_I(TAG, "TCA9535 keypad initialized (IRQ=GPIO%d)", EXP_IRQ_PIN);
302 return true;
303}
304
310 if (state_ == core::ServiceState::INITIALIZED ||
311 state_ == core::ServiceState::STOPPED) {
313 return true;
314 }
315 return state_ == core::ServiceState::STARTED;
316}
317
322 if (state_ == core::ServiceState::STARTED) {
324 }
325}
326
331uint16_t TCA9535Keypad::readInputs() const {
332 if (!device_) return KEY_STATE_INVALID;
333
334 uint8_t lo = 0xFF, hi = 0xFF;
335 if (bus_->readReg(device_, REG_INPUT_0, &lo, 1) != ESP_OK) return KEY_STATE_INVALID;
336 if (bus_->readReg(device_, REG_INPUT_1, &hi, 1) != ESP_OK) return KEY_STATE_INVALID;
337
338 return (uint16_t)((hi << 8) | lo);
339}
340
347 uint16_t mask = keyToMask(key);
348 if (mask == KEY_STATE_INVALID) return false;
349
350 uint16_t current = readInputs();
351 return (current & KEY_MASK_ALL) == mask;
352}
353
359 return bufferGetKey();
360}
361
367 portENTER_CRITICAL(&bufferMux_);
368 bool hasKey = bufferHead_ != bufferTail_;
369 portEXIT_CRITICAL(&bufferMux_);
370 return hasKey;
371}
372
378 uint16_t current = readInputs();
379 // KEY_STATE_IDLE = all 12 keys released (bits 0-11 high, bits 12-15 don't care)
380 return (current & KEY_MASK_ALL) != KEY_STATE_IDLE;
381}
382
388void TCA9535Keypad::setLongPressEnabled(bool enabled, uint32_t thresholdMs) {
389 longPressEnabled_ = enabled;
390 longPressThresholdMs_ = thresholdMs;
391}
392
397void TCA9535Keypad::bufferAddKey(Key key) {
398 if (key == Key::KEY_NONE) return;
399
400 portENTER_CRITICAL(&bufferMux_);
401 uint8_t nextHead = (bufferHead_ + 1) % KEY_BUFFER_SIZE;
402 if (nextHead != bufferTail_) {
403 keyBuffer_[bufferHead_] = key;
404 bufferHead_ = nextHead;
405 }
406 portEXIT_CRITICAL(&bufferMux_);
407}
408
413Key TCA9535Keypad::bufferGetKey() {
414 portENTER_CRITICAL(&bufferMux_);
415 if (bufferHead_ == bufferTail_) {
416 portEXIT_CRITICAL(&bufferMux_);
417 return Key::KEY_NONE;
418 }
419
420 Key key = keyBuffer_[bufferTail_];
421 bufferTail_ = (bufferTail_ + 1) % KEY_BUFFER_SIZE;
422 portEXIT_CRITICAL(&bufferMux_);
423 return key;
424}
425
430void IRAM_ATTR TCA9535Keypad::isrHandler(void* arg) {
431 auto* self = static_cast<TCA9535Keypad*>(arg);
432 if (self->inSleepMode_) return;
433
434 BaseType_t xHigherPriorityTaskWoken = pdFALSE;
435 xSemaphoreGiveFromISR(self->semaphore_, &xHigherPriorityTaskWoken);
436 portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
437}
438
443void TCA9535Keypad::taskFunc(void* arg) {
444 auto* self = static_cast<TCA9535Keypad*>(arg);
445 LOG_I(TAG, "Keypad task started");
446
447 while (true) {
448 // Wait for IRQ or timeout (poll fallback)
449 xSemaphoreTake(self->semaphore_, pdMS_TO_TICKS(POLL_TIMEOUT_MS));
450
451 // Small debounce delay
452 vTaskDelay(pdMS_TO_TICKS(DEBOUNCE_MS));
453
454 // Drop input events while the system is heading into lockdown:
455 // the lockdown window leaves enough time for the e-paper to render
456 // the final screen and the user must not be able to mutate any
457 // residual state in that window.
458 if (cdc::core::SystemLock::instance().isLocked()) {
459 continue;
460 }
461
462 // Read current state
463 uint16_t raw = self->readInputs();
464
465 if (raw != self->lastRawState_) {
466 // Count pressed (active-low) bits within the keypad mask.
467 // Multiple simultaneous keys are ambiguous: skip both press and release
468 // events until the user resolves to a single-key or no-key state.
469 uint16_t pressedBits = static_cast<uint16_t>((~raw) & KEY_MASK_ALL);
470 uint8_t pressedCount = __builtin_popcount(pressedBits);
471
472 if (pressedCount > 1) {
473 // Ambiguous chord-press: emit no single-key events. The reserved
474 // N+Y rescue chord is the one recognized combination; fire once
475 // per hold.
476 if (pressedBits == PANIC_CHORD_BITS && !self->panicChordFired_) {
477 self->panicChordFired_ = true;
478 if (self->panicChordCallback_) self->panicChordCallback_();
479 }
480 } else {
481 self->panicChordFired_ = false;
482 Key key = rawToKey(raw);
483
484 // Key press detection
485 if (key != Key::KEY_NONE) {
486 // In deferred mode the short press is buffered on release
487 // (and only if no long-press fired), so a hold yields the
488 // long-press alone. Default mode buffers on key-down.
489 if (self->deferSources_ == 0) {
490 self->bufferAddKey(key);
491 }
492
493 if (self->callback_) {
494 self->callback_(key, true);
495 }
496
497 // Start long-press / key-repeat tracking
498 if (self->longPressEnabled_) {
499 self->pressedKey_ = key;
500 self->pressStartTime_ = xTaskGetTickCount() * portTICK_PERIOD_MS;
501 self->lastRepeatMs_ = self->pressStartTime_;
502 self->longPressFired_ = false;
503 }
504 } else {
505 // Key release
506 if (self->deferSources_ != 0 && !self->longPressFired_ &&
507 self->pressedKey_ != Key::KEY_NONE) {
508 self->bufferAddKey(self->pressedKey_);
509 }
510 if (self->callback_ && self->pressedKey_ != Key::KEY_NONE) {
511 self->callback_(self->pressedKey_, false);
512 }
513 self->pressedKey_ = Key::KEY_NONE;
514 }
515
516 self->lastRawState_ = raw;
517 }
518 }
519
520 // While a single key is held: either repeat it (key-repeat enabled) or
521 // fire the long-press once. The two are mutually exclusive.
522 if (self->pressedKey_ != Key::KEY_NONE) {
523 uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
524 if (self->keyRepeatPeriodMs_ > 0) {
525 if (now - self->pressStartTime_ >= self->keyRepeatInitialMs_ &&
526 now - self->lastRepeatMs_ >= self->keyRepeatPeriodMs_) {
527 self->lastRepeatMs_ = now;
528 self->bufferAddKey(self->pressedKey_);
529 }
530 } else if (self->longPressEnabled_ && !self->longPressFired_) {
531 if (now - self->pressStartTime_ >= self->longPressThresholdMs_) {
532 self->longPressFired_ = true;
533 if (self->longPressCallback_) {
534 self->longPressCallback_(self->pressedKey_);
535 }
536 }
537 }
538 }
539 }
540}
541
546 LOG_D(TAG, "Preparing keypad for sleep...");
547
548 // 1. Disable ISR processing flag (prevents ISR from doing anything)
549 inSleepMode_ = true;
550
551 // 2. Disable GPIO interrupt at hardware level
552 gpio_intr_disable(EXP_IRQ_PIN);
553}
554
559 LOG_D(TAG, "Recovering keypad after sleep...");
560
561 // 1. Disable GPIO interrupt (may already be disabled, but be safe)
562 gpio_intr_disable(EXP_IRQ_PIN);
563
564 // 2. Wait for all keys to be released (level-triggered wakeup keeps pin LOW)
565 int timeout = 200; // 2 seconds max (200 * 10ms)
566 while (anyKeyDown() && timeout > 0) {
567 vTaskDelay(pdMS_TO_TICKS(10));
568 timeout--;
569 }
570 vTaskDelay(pdMS_TO_TICKS(30)); // Extra debounce
571
572 // 3. Clear key buffer (keys from wakeup press)
573 clearBuffer();
574
575 // 4. Reset last raw state to current state
576 lastRawState_ = readInputs();
577
578 // 5. Re-enable ISR processing flag
579 inSleepMode_ = false;
580
581 // 6. Restore edge-triggered interrupt type (wakeup used level-triggered)
582 gpio_set_intr_type(EXP_IRQ_PIN, GPIO_INTR_NEGEDGE);
583
584 // 7. Re-enable GPIO interrupt
585 gpio_intr_enable(EXP_IRQ_PIN);
586
587 LOG_D(TAG, "Keypad recovered, state: 0x%04X", lastRawState_);
588}
589
594 portENTER_CRITICAL(&bufferMux_);
595 bufferHead_ = 0;
596 bufferTail_ = 0;
597 portEXIT_CRITICAL(&bufferMux_);
598}
599
602
608 return &g_keypad;
609}
610
611} // namespace cdc::hal
static const char * TAG
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
static SystemLock & instance()
Returns the process-wide lockdown latch singleton.
virtual esp_err_t readReg(I2cDeviceHandle dev, uint8_t reg, uint8_t *data, size_t len)=0
void(*)() PanicChordCallback
Definition IKeypad.h:86
void(*)(Key key) LongPressCallback
Definition IKeypad.h:79
void setPanicChordCallback(PanicChordCallback callback) override
bool hasKey() const override
Returns whether key buffer currently contains entries.
core::ServiceState getState() const override
void setDeferShortPress(uint32_t source, bool enabled) override
bool anyKeyDown() const override
Returns whether any keypad key is physically held down.
bool init() override
Initializes keypad hardware, ISR, and worker task.
Key getNextKey() override
Retrieves next buffered key press.
void stop() override
Stops keypad service state.
bool isKeyPressed(Key key) const override
Checks whether specified key is currently pressed.
const char * getName() const override
void clearBuffer() override
Clears pending key buffer.
bool start() override
Starts keypad service state.
void setKeyRepeat(uint16_t initial_ms, uint16_t period_ms) override
void recoverFromSleep() override
Restores keypad operation after wakeup.
void setLongPressEnabled(bool enabled, uint32_t thresholdMs) override
Enables/disables long-press detection and sets threshold.
void prepareForSleep() override
Prepares keypad interrupt handling for system sleep entry.
void setLongPressCallback(LongPressCallback callback) override
void setCallback(KeyCallback callback) override
#define EXPANDER_ADDR
Definition hw_config.h:18
#define EXP_IRQ_PIN
Definition hw_config.h:19
static TCA9535Keypad g_keypad
Singleton keypad instance.
static constexpr uint16_t KEY_STATE_INVALID
Sentinel value returned when an I2C read fails or no key is mapped.
static constexpr uint8_t REG_POLARITY_0
static constexpr uint8_t REG_INPUT_1
static constexpr uint32_t DEBOUNCE_MS
static constexpr uint8_t REG_OUTPUT_0
static constexpr uint8_t KEY_BIT_3
static constexpr uint32_t TASK_STACK_SIZE
Keypad task scheduling configuration.
void(*)(Key key, bool pressed) KeyCallback
Definition IKeypad.h:27
II2cBus * getI2cBus0()
Returns singleton instance of I2C bus 0.
Definition I2cBus.cpp:275
static Key rawToKey(uint16_t raw)
Converts raw 16-bit keypad state to a Key enum value.
static constexpr uint8_t KEY_BIT_8
static constexpr uint16_t KEY_STATE_IDLE
Idle state: every key released, all 12 bits high.
IKeypad * getKeypadInstance()
Returns the singleton keypad service instance.
void * I2cDeviceHandle
Definition II2cBus.h:10
static constexpr uint8_t REG_CONFIG_1
static constexpr uint8_t KEY_BIT_5
static constexpr uint8_t KEY_BIT_1
static constexpr uint8_t KEY_BIT_4
static constexpr uint8_t KEY_BIT_0
Bit positions of each physical key on the TCA9535 P0/P1 ports. Active-low: a pressed key drives its c...
static constexpr uint8_t REG_POLARITY_1
static constexpr uint8_t REG_INPUT_0
TCA9535 register-address constants.
static constexpr uint16_t PANIC_CHORD_BITS
Reserved rescue chord: N and Y held together (anti-block instant lock).
static constexpr uint8_t REG_CONFIG_0
static uint16_t keyToMask(Key key)
Converts a Key enum value to its expected raw 12-bit mask.
static constexpr UBaseType_t TASK_PRIORITY
static constexpr uint32_t LONG_PRESS_THRESHOLD_MS
Default long-press detection threshold in milliseconds. Long enough to avoid accidental triggers,...
static constexpr uint8_t KEY_BIT_7
static constexpr uint8_t KEY_BIT_6
static constexpr uint8_t KEY_BIT_9
static constexpr uint16_t maskForBit(uint8_t bit)
Builds the active-low raw state for a single pressed key.
static constexpr uint32_t POLL_TIMEOUT_MS
static constexpr uint8_t KEY_BIT_NO
static constexpr size_t KEY_BUFFER_SIZE
Ring-buffer configuration for queued key events.
static constexpr uint8_t REG_OUTPUT_1
static constexpr uint16_t KEY_MASK_ALL
Mask of all 12 keypad bits (P0.0..P1.3).
static constexpr uint8_t KEY_BIT_2
static constexpr uint8_t KEY_BIT_YES