ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
tca8418.cpp
Go to the documentation of this file.
1#include "tca8418.h"
3#include "esphome/core/log.h"
4
5namespace esphome::tca8418 {
6
7ESPHOME_LOG_TAG(TAG, "tca8418");
8
9// Minimum gap between event queue reads; the ten entry queue holds anything arriving sooner.
10static constexpr uint32_t POLL_INTERVAL_MS = 50;
11
13
15 // The configuration register reads back, so use it to check the device is there.
16 uint8_t config;
17 if (!this->read_byte(TCA8418_REG_CFG, &config)) {
18 ESP_LOGE(TAG, "Failed to read from device - check wiring / address");
19 this->mark_failed();
20 return;
21 }
22
23 if (!this->configure_pins_()) {
24 ESP_LOGE(TAG, "Failed to configure pins");
25 this->mark_failed();
26 return;
27 }
28
29 // Report key events, and keep the interrupt asserted while events remain queued.
30 if (!this->write_byte(TCA8418_REG_CFG, TCA8418_CFG_KEY_INT_EN | TCA8418_CFG_INT_CFG)) {
31 ESP_LOGE(TAG, "Failed to write the configuration register");
32 this->mark_failed();
33 return;
34 }
35
36 if (this->interrupt_pin_ != nullptr) {
37 this->interrupt_pin_->setup();
38 // The interrupt output is active low, so a falling edge means an event was
39 // queued. The handler only wakes the loop; the queue is read from there.
41 }
42
43 // Discard anything the device queued before it was configured, and clear
44 // every interrupt flag: leaving one set holds the interrupt output asserted.
45 if (!this->drain_events_(false)) {
46 ESP_LOGE(TAG, "Failed to clear the event queue");
47 this->mark_failed();
48 return;
49 }
50
51 // With an interrupt pin there is nothing to do until it fires. Stay running
52 // if the device still has something queued, since that produces no new edge.
53 if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read())
54 this->disable_loop();
55}
56
58 // Each of the three registers covers a group of pins: ROW0-7, COL0-7, COL8-9.
59 uint8_t matrix[3] = {0, 0, 0};
60 if (this->rows_ > 0) {
61 matrix[0] = static_cast<uint8_t>((1U << this->rows_) - 1U);
62 const uint8_t low_columns = this->columns_ > 8 ? 8 : this->columns_;
63 matrix[1] = static_cast<uint8_t>((1U << low_columns) - 1U);
64 if (this->columns_ > 8)
65 matrix[2] = static_cast<uint8_t>((1U << (this->columns_ - 8U)) - 1U);
66 }
67
68 // Pins outside the matrix report individually when they are in event mode.
69 // The device ignores these bits for pins that belong to the matrix.
70 const uint8_t gpi[3] = {
71 static_cast<uint8_t>(this->gpi_events_ ? ~matrix[0] : 0),
72 static_cast<uint8_t>(this->gpi_events_ ? ~matrix[1] : 0),
73 static_cast<uint8_t>(this->gpi_events_ ? (0x03 & ~matrix[2]) : 0),
74 };
75
76 for (uint8_t i = 0; i < 3; i++) {
77 // Enable the internal pull-ups (a 0 bit enables the pull-up) so that
78 // buttons can simply switch their pin to ground.
79 if (!this->write_byte(TCA8418_REG_GPIO_PULL1 + i, 0x00) || !this->write_byte(TCA8418_REG_KP_GPIO1 + i, matrix[i]) ||
80 !this->write_byte(TCA8418_REG_GPI_EM1 + i, gpi[i]) || !this->write_byte(TCA8418_REG_GPIO_INT_EN1 + i, gpi[i])) {
81 return false;
82 }
83 }
84 return true;
85}
86
89 if (now - this->last_poll_ < POLL_INTERVAL_MS)
90 return;
91 this->last_poll_ = now;
92
93 if (!this->drain_events_(true)) {
94 this->status_set_warning();
95 return;
96 }
98
99 // Events that arrive during a read keep INT asserted without a new edge, so only stop once it is clear
100 if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read())
101 this->disable_loop();
102}
103
105 uint8_t count;
106 if (!this->read_byte(TCA8418_REG_KEY_LCK_EC, &count))
107 return false;
108 count &= TCA8418_EVENT_COUNT_MASK;
109 // An idle poll has no flags to clear, unless INT is still asserted from a failed clear
110 if (count == 0 && dispatch && (this->interrupt_pin_ == nullptr || this->interrupt_pin_->digital_read()))
111 return true;
112 // The overflow flag stays clear with a full queue, so check the count
113 if (dispatch && count >= TCA8418_FIFO_DEPTH) {
114 ESP_LOGW(TAG, "Event queue is full - some key presses may have been lost");
115 }
116
117 uint8_t event;
118 for (uint8_t i = 0; i < count; i++) {
119 if (!this->read_byte(TCA8418_REG_KEY_EVENT_A, &event))
120 return false;
121 if (dispatch)
122 this->dispatch_(event & TCA8418_KEY_CODE_MASK, (event & TCA8418_KEY_PRESSED) != 0);
123 }
124 return this->write_byte(TCA8418_REG_INT_STAT, TCA8418_INT_STAT_ALL);
125}
126
127uint8_t TCA8418Component::key_char_(uint8_t key) const {
128 if (key == 0 || key > this->keys_size_)
129 return 0;
130 return progmem_read_byte(&this->keys_[key - 1]);
131}
132
133void TCA8418Component::dispatch_(uint8_t key, bool pressed) {
134 ESP_LOGV(TAG, "Key %u %s", key, pressed ? LOG_STR_LITERAL("pressed") : LOG_STR_LITERAL("released"));
135
136#ifdef TCA8418_LISTENER_COUNT
137 for (auto *listener : this->listeners_) {
138 if (pressed) {
139 listener->key_pressed(key);
140 } else {
141 listener->key_released(key);
142 }
143 }
144#endif
145
146 if (!pressed)
147 return;
148
149 // Key collectors and automations receive the mapped character when a key map
150 // is configured, and the key number otherwise.
151 const uint8_t key_char = this->key_char_(key);
152 this->send_key_(key_char != 0 ? key_char : key);
153}
154
156 ESP_LOGCONFIG(TAG,
157 "TCA8418:\n"
158 " Individual inputs: %s",
159 YESNO(this->gpi_events_));
160 if (this->rows_ > 0) {
161 ESP_LOGCONFIG(TAG, " Key matrix: %u rows x %u columns", this->rows_, this->columns_);
162 }
163 LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
164 LOG_I2C_DEVICE(this);
165 if (this->is_failed()) {
166 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
167 }
168}
169
170} // namespace esphome::tca8418
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:291
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:29
bool write_byte(uint8_t a_register, uint8_t data) const
Definition i2c.h:265
bool read_byte(uint8_t a_register, uint8_t *data)
Definition i2c.h:240
static void IRAM_ATTR interrupt_handler(TCA8418Component *arg)
Wakes the loop so queued events are read. Runs in interrupt context.
Definition tca8418.cpp:12
bool configure_pins_()
Write the matrix / individual-input pin configuration. Returns false on I2C failure.
Definition tca8418.cpp:57
InternalGPIOPin * interrupt_pin_
Definition tca8418.h:74
StaticVector< TCA8418Listener *, TCA8418_LISTENER_COUNT > listeners_
Definition tca8418.h:77
bool drain_events_(bool dispatch)
Read every queued key event, dispatching them if asked, then clear the interrupt flags.
Definition tca8418.cpp:104
uint8_t key_char_(uint8_t key) const
The character mapped to a matrix key, or 0 when it has none.
Definition tca8418.cpp:127
void dispatch_(uint8_t key, bool pressed)
Definition tca8418.cpp:133
unsigned count
Definition gif.cpp:51
@ INTERRUPT_FALLING_EDGE
Definition gpio_pin.h:53
ESPHOME_LOG_TAG(TAG, "tca8418")
Application App
Global storage of Application pointer - only one Application can exist.
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:51
static void uint32_t