ESPHome 2026.10.2
Loading...
Searching...
No Matches
sx1509.cpp
Go to the documentation of this file.
1#include "sx1509.h"
3#include "esphome/core/log.h"
4
5namespace esphome::sx1509 {
6
7static const char *const TAG = "sx1509";
8
10 ESP_LOGV(TAG, " Resetting devices");
11 if (!this->write_byte(REG_RESET, 0x12)) {
12 this->mark_failed();
13 return;
14 }
15 this->write_byte(REG_RESET, 0x34);
16
17 uint16_t data;
18 if (!this->read_byte_16(REG_INTERRUPT_MASK_A, &data)) {
19 this->mark_failed();
20 return;
21 }
22 if (data != 0xFF00) {
23 this->mark_failed();
24 return;
25 }
28 if (this->has_keypad_)
29 this->setup_keypad_();
30}
31
33 ESP_LOGCONFIG(TAG, "SX1509:");
34 if (this->is_failed()) {
35 ESP_LOGE(TAG, "Setting up SX1509 failed!");
36 }
37 LOG_I2C_DEVICE(this);
38}
39
41 // Reset cache at the start of each loop
42 this->reset_pin_cache_();
43
44 if (this->has_keypad_) {
46 return;
48 uint16_t key_data = this->read_key_data();
49 for (auto *binary_sensor : this->keypad_binary_sensors_)
50 binary_sensor->process(key_data);
51 if (this->keys_ == nullptr)
52 return;
53 if (key_data == 0) {
54 this->last_key_ = 0;
55 return;
56 }
57 int row, col;
58 for (row = 0; row < 8; row++) {
59 if (key_data & (1 << row))
60 break;
61 }
62 for (col = 8; col < 16; col++) {
63 if (key_data & (1 << col))
64 break;
65 }
66 col -= 8;
67 if (row >= this->rows_ || col >= this->cols_) // a partial read can leave only a row or a column bit
68 return;
69 uint8_t key = progmem_read_byte(&this->keys_[row * this->cols_ + col]);
70 if (key == this->last_key_)
71 return;
72 this->last_key_ = key;
73 ESP_LOGV(TAG, "row %d, col %d, key '%c'", row, col, key);
74 for (auto &trigger : this->key_triggers_)
75 trigger->trigger(key);
76 this->send_key_(key);
77 }
78}
79
81 // Always read all pins when any input pin is accessed
82 return this->read_byte_16(REG_DATA_B, &this->input_mask_);
83}
84
86 // Return cached value for input pins, false for output pins
87 if (this->ddr_mask_ & (1 << pin)) {
88 return (this->input_mask_ & (1 << pin)) != 0;
89 }
90 return false;
91}
92
93void SX1509Component::digital_write_hw(uint8_t pin, bool bit_value) {
94 if ((~this->ddr_mask_) & (1 << pin)) {
95 // If the pin is an output, write high/low
96 uint16_t temp_reg_data = 0;
97 this->read_byte_16(REG_DATA_B, &temp_reg_data);
98 if (bit_value) {
99 output_state_ |= (1 << pin); // set bit in shadow register
100 } else {
101 output_state_ &= ~(1 << pin); // reset bit shadow register
102 }
103 for (uint16_t b = 0x8000; b; b >>= 1) {
104 if ((~ddr_mask_) & b) { // transfer bits of outputs, but don't mess with inputs
105 if (output_state_ & b) {
106 temp_reg_data |= b;
107 } else {
108 temp_reg_data &= ~b;
109 }
110 }
111 }
112 this->write_byte_16(REG_DATA_B, temp_reg_data);
113 }
114}
115
117 ESP_LOGI(TAG, "Configuring pin %u with flags %x", pin, flags);
118
119 uint16_t temp_word = 0;
120
121 this->read_byte_16(REG_DIR_B, &this->ddr_mask_);
122 if (flags & gpio::FLAG_OUTPUT) {
123 // Always disable input buffer
124 this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
125 temp_word |= (1 << pin);
126 this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
127
129 // Pullup must be disabled for open drain mode
130 this->read_byte_16(REG_PULL_UP_B, &temp_word);
131 temp_word &= ~(1 << pin);
132 this->write_byte_16(REG_PULL_UP_B, temp_word);
133 this->read_byte_16(REG_OPEN_DRAIN_B, &temp_word);
134 temp_word |= (1 << pin);
135 this->write_byte_16(REG_OPEN_DRAIN_B, temp_word);
136 ESP_LOGD(TAG, "Open drain output mode set for %u", pin);
137 } else {
138 ESP_LOGD(TAG, "Output Mode for %u", pin);
139 }
140
141 // Set direction to output
142 this->ddr_mask_ &= ~(1 << pin);
143 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
144 } else {
145 ESP_LOGD(TAG, "Input Mode for %u", pin);
146
147 // Always enable input buffer
148 this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
149 temp_word &= ~(1 << pin);
150 this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
151
152 // Pullup
153 this->read_byte_16(REG_PULL_UP_B, &temp_word);
154 if (flags & gpio::FLAG_PULLUP) {
155 temp_word |= (1 << pin);
156 } else {
157 temp_word &= ~(1 << pin);
158 }
159 this->write_byte_16(REG_PULL_UP_B, temp_word);
160
161 // Pulldown
162 this->read_byte_16(REG_PULL_DOWN_B, &temp_word);
164 temp_word |= (1 << pin);
165 } else {
166 temp_word &= ~(1 << pin);
167 }
168 this->write_byte_16(REG_PULL_DOWN_B, temp_word);
169
170 // Set direction to input
171 this->ddr_mask_ |= (1 << pin);
172 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
173 }
174}
175
177 uint16_t temp_word = 0;
178 uint8_t temp_byte = 0;
179
180 this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
181 temp_word |= (1 << pin);
182 this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
183
184 this->ddr_mask_ &= ~(1 << pin); // 0=output
185 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
186
187 this->read_byte(REG_CLOCK, &temp_byte);
188 temp_byte |= (1 << 6); // Internal 2MHz oscillator part 1 (set bit 6)
189 temp_byte &= ~(1 << 5); // Internal 2MHz oscillator part 2 (clear bit 5)
190 this->write_byte(REG_CLOCK, temp_byte);
191
192 this->read_byte(REG_MISC, &temp_byte);
193 temp_byte &= ~(1 << 7); // set linear mode bank B
194 temp_byte &= ~(1 << 3); // set linear mode bank A
195 temp_byte |= 0x70; // Frequency of the LED Driver clock ClkX of all IOs:
196 this->write_byte(REG_MISC, temp_byte);
197
198 this->read_byte_16(REG_LED_DRIVER_ENABLE_B, &temp_word);
199 temp_word |= (1 << pin);
200 this->write_byte_16(REG_LED_DRIVER_ENABLE_B, temp_word);
201
202 this->read_byte_16(REG_DATA_B, &temp_word);
203 temp_word &= ~(1 << pin);
204 output_state_ &= ~(1 << pin);
205 this->write_byte_16(REG_DATA_B, temp_word);
206}
207
208void SX1509Component::clock_(uint8_t osc_source, uint8_t osc_pin_function, uint8_t osc_freq_out, uint8_t osc_divider) {
209 osc_source = (osc_source & 0b11) << 5; // 2-bit value, bits 6:5
210 osc_pin_function = (osc_pin_function & 1) << 4; // 1-bit value bit 4
211 osc_freq_out = (osc_freq_out & 0b1111); // 4-bit value, bits 3:0
212 uint8_t reg_clock = osc_source | osc_pin_function | osc_freq_out;
213 this->write_byte(REG_CLOCK, reg_clock);
214
215 osc_divider = clamp<uint8_t>(osc_divider, 1, 7u);
216 this->clk_x_ = 2000000;
217 osc_divider = (osc_divider & 0b111) << 4; // 3-bit value, bits 6:4
218
219 uint8_t reg_misc = 0;
220 this->read_byte(REG_MISC, &reg_misc);
221 reg_misc &= ~(0b111 << 4);
222 reg_misc |= osc_divider;
223 this->write_byte(REG_MISC, reg_misc);
224}
225
227 uint8_t temp_byte = 0;
228
229 // setup row/col pins for INPUT OUTPUT
230 this->read_byte_16(REG_DIR_B, &this->ddr_mask_);
231 for (int i = 0; i < this->rows_; i++)
232 this->ddr_mask_ &= ~(1 << i);
233 for (int i = 8; i < (8 + this->cols_); i++)
234 this->ddr_mask_ |= (1 << i);
235 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
236
237 this->read_byte(REG_OPEN_DRAIN_A, &temp_byte);
238 for (int i = 0; i < this->rows_; i++)
239 temp_byte |= (1 << i);
240 this->write_byte(REG_OPEN_DRAIN_A, temp_byte);
241
242 this->read_byte(REG_PULL_UP_B, &temp_byte);
243 for (int i = 0; i < this->cols_; i++)
244 temp_byte |= (1 << i);
245 this->write_byte(REG_PULL_UP_B, temp_byte);
246
247 if (debounce_time_ >= scan_time_) {
248 debounce_time_ = scan_time_ >> 1; // Force debounce_time to be less than scan_time
249 }
251 uint8_t scan_time_bits = 0;
252 for (uint8_t i = 7; i > 0; i--) {
253 if (scan_time_ & (1 << i)) {
254 scan_time_bits = i;
255 break;
256 }
257 }
258 scan_time_bits &= 0b111; // Scan time is bits 2:0
259 temp_byte = sleep_time_ | scan_time_bits;
260 this->write_byte(REG_KEY_CONFIG_1, temp_byte);
261 temp_byte = ((this->rows_ - 1) & 0b111) << 3; // 0 = off, 0b001 = 2 rows, 0b111 = 8 rows, etc.
262 temp_byte |= (this->cols_ - 1) & 0b111; // 0b000 = 1 column, ob111 = 8 columns, etc.
263 this->write_byte(REG_KEY_CONFIG_2, temp_byte);
264}
265
267 uint16_t key_data = 0;
268 this->read_byte_16(REG_KEY_DATA_1, &key_data);
269 return (0xFFFF ^ key_data);
270}
271
272void SX1509Component::set_debounce_config_(uint8_t config_value) {
273 // First make sure clock is configured
274 uint8_t temp_byte = 0;
275 this->read_byte(REG_MISC, &temp_byte);
276 temp_byte |= (1 << 4); // Just default to no divider if not set
277 this->write_byte(REG_MISC, temp_byte);
278 this->read_byte(REG_CLOCK, &temp_byte);
279 temp_byte |= (1 << 6); // default to internal osc.
280 this->write_byte(REG_CLOCK, temp_byte);
281
282 config_value &= 0b111; // 3-bit value
283 this->write_byte(REG_DEBOUNCE_CONFIG, config_value);
284}
285
287 uint8_t config_value = 0;
288
289 for (int i = 7; i >= 0; i--) {
290 if (time & (1 << i)) {
291 config_value = i + 1;
292 break;
293 }
294 }
295 config_value = clamp<uint8_t>(config_value, 0, 7);
296
297 set_debounce_config_(config_value);
298}
299
301 uint16_t debounce_enable = 0;
302 this->read_byte_16(REG_DEBOUNCE_ENABLE_B, &debounce_enable);
303 debounce_enable |= (1 << pin);
304 this->write_byte_16(REG_DEBOUNCE_ENABLE_B, debounce_enable);
305}
306
308
309void SX1509Component::set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols) {
310 set_debounce_time_(time);
311 for (uint16_t i = 0; i < num_rows; i++)
313 for (uint16_t i = 0; i < num_cols; i++)
314 set_debounce_pin_(i + 8);
315}
316
317} // namespace esphome::sx1509
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
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
bool read_byte_16(uint8_t a_register, uint16_t *data)
Definition i2c.h:249
bool write_byte_16(uint8_t a_register, uint16_t data) const
Definition i2c.h:267
bool digital_read_cache(uint8_t pin) override
Definition sx1509.cpp:85
void clock_(uint8_t osc_source=2, uint8_t osc_pin_function=1, uint8_t osc_freq_out=0, uint8_t osc_divider=0)
Definition sx1509.cpp:208
void set_debounce_pin_(uint8_t pin)
Definition sx1509.cpp:307
bool digital_read_hw(uint8_t pin) override
Definition sx1509.cpp:80
void set_debounce_config_(uint8_t config_value)
Definition sx1509.cpp:272
std::vector< SX1509KeyTrigger * > key_triggers_
Definition sx1509.h:86
void set_debounce_time_(uint8_t time)
Definition sx1509.cpp:286
void set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols)
Definition sx1509.cpp:309
void pin_mode(uint8_t pin, gpio::Flags flags)
Definition sx1509.cpp:116
std::vector< SX1509Processor * > keypad_binary_sensors_
Definition sx1509.h:85
void digital_write_hw(uint8_t pin, bool value) override
Definition sx1509.cpp:93
void setup_led_driver(uint8_t pin)
Definition sx1509.cpp:176
void set_debounce_enable_(uint8_t pin)
Definition sx1509.cpp:300
const uint32_t min_loop_period_
Definition sx1509.h:89
uint16_t flags
@ FLAG_OUTPUT
Definition gpio.h:26
@ FLAG_OPEN_DRAIN
Definition gpio.h:27
@ FLAG_PULLUP
Definition gpio.h:28
@ FLAG_PULLDOWN
Definition gpio.h:29
const char *const TAG
Definition spi.cpp:7
Here you'll find the Arduino code used to interface with the SX1509 I2C 16 I/O expander.
const uint8_t REG_DEBOUNCE_CONFIG
const uint8_t REG_MISC
const uint8_t REG_CLOCK
const uint8_t REG_KEY_CONFIG_2
const uint8_t REG_DATA_B
const uint8_t REG_LED_DRIVER_ENABLE_B
const uint8_t REG_KEY_CONFIG_1
const uint8_t REG_OPEN_DRAIN_B
const uint8_t REG_PULL_UP_B
const uint8_t REG_INPUT_DISABLE_B
const uint8_t REG_RESET
const uint8_t REG_DEBOUNCE_ENABLE_B
const uint8_t REG_INTERRUPT_MASK_A
const uint8_t REG_PULL_DOWN_B
const uint8_t REG_DIR_B
const uint8_t INTERNAL_CLOCK_2MHZ
Definition sx1509.h:16
const uint8_t REG_OPEN_DRAIN_A
const uint8_t REG_KEY_DATA_1
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition hal.cpp:48
uint32_t IRAM_ATTR HOT millis()
Definition hal.cpp:25
uint8_t progmem_read_byte(const uint8_t *addr)
Definition hal.h:43