ESPHome 2026.6.0-dev
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_.empty())
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 uint8_t key = this->keys_[row * this->cols_ + col];
68 if (key == this->last_key_)
69 return;
70 this->last_key_ = key;
71 ESP_LOGV(TAG, "row %d, col %d, key '%c'", row, col, key);
72 for (auto &trigger : this->key_triggers_)
73 trigger->trigger(key);
74 this->send_key_(key);
75 }
76}
77
79 // Always read all pins when any input pin is accessed
80 return this->read_byte_16(REG_DATA_B, &this->input_mask_);
81}
82
84 // Return cached value for input pins, false for output pins
85 if (this->ddr_mask_ & (1 << pin)) {
86 return (this->input_mask_ & (1 << pin)) != 0;
87 }
88 return false;
89}
90
91void SX1509Component::digital_write_hw(uint8_t pin, bool bit_value) {
92 if ((~this->ddr_mask_) & (1 << pin)) {
93 // If the pin is an output, write high/low
94 uint16_t temp_reg_data = 0;
95 this->read_byte_16(REG_DATA_B, &temp_reg_data);
96 if (bit_value) {
97 output_state_ |= (1 << pin); // set bit in shadow register
98 } else {
99 output_state_ &= ~(1 << pin); // reset bit shadow register
100 }
101 for (uint16_t b = 0x8000; b; b >>= 1) {
102 if ((~ddr_mask_) & b) { // transfer bits of outputs, but don't mess with inputs
103 if (output_state_ & b) {
104 temp_reg_data |= b;
105 } else {
106 temp_reg_data &= ~b;
107 }
108 }
109 }
110 this->write_byte_16(REG_DATA_B, temp_reg_data);
111 }
112}
113
115 ESP_LOGI(TAG, "Configuring pin %u with flags %x", pin, flags);
116
117 uint16_t temp_word = 0;
118
119 this->read_byte_16(REG_DIR_B, &this->ddr_mask_);
120 if (flags & gpio::FLAG_OUTPUT) {
121 // Always disable input buffer
122 this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
123 temp_word |= (1 << pin);
124 this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
125
127 // Pullup must be disabled for open drain mode
128 this->read_byte_16(REG_PULL_UP_B, &temp_word);
129 temp_word &= ~(1 << pin);
130 this->write_byte_16(REG_PULL_UP_B, temp_word);
131 this->read_byte_16(REG_OPEN_DRAIN_B, &temp_word);
132 temp_word |= (1 << pin);
133 this->write_byte_16(REG_OPEN_DRAIN_B, temp_word);
134 ESP_LOGD(TAG, "Open drain output mode set for %u", pin);
135 } else {
136 ESP_LOGD(TAG, "Output Mode for %u", pin);
137 }
138
139 // Set direction to output
140 this->ddr_mask_ &= ~(1 << pin);
141 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
142 } else {
143 ESP_LOGD(TAG, "Input Mode for %u", pin);
144
145 // Always enable input buffer
146 this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
147 temp_word &= ~(1 << pin);
148 this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
149
150 // Pullup
151 this->read_byte_16(REG_PULL_UP_B, &temp_word);
152 if (flags & gpio::FLAG_PULLUP) {
153 temp_word |= (1 << pin);
154 } else {
155 temp_word &= ~(1 << pin);
156 }
157 this->write_byte_16(REG_PULL_UP_B, temp_word);
158
159 // Pulldown
160 this->read_byte_16(REG_PULL_DOWN_B, &temp_word);
162 temp_word |= (1 << pin);
163 } else {
164 temp_word &= ~(1 << pin);
165 }
166 this->write_byte_16(REG_PULL_DOWN_B, temp_word);
167
168 // Set direction to input
169 this->ddr_mask_ |= (1 << pin);
170 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
171 }
172}
173
175 uint16_t temp_word = 0;
176 uint8_t temp_byte = 0;
177
178 this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
179 temp_word |= (1 << pin);
180 this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
181
182 this->ddr_mask_ &= ~(1 << pin); // 0=output
183 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
184
185 this->read_byte(REG_CLOCK, &temp_byte);
186 temp_byte |= (1 << 6); // Internal 2MHz oscillator part 1 (set bit 6)
187 temp_byte &= ~(1 << 5); // Internal 2MHz oscillator part 2 (clear bit 5)
188 this->write_byte(REG_CLOCK, temp_byte);
189
190 this->read_byte(REG_MISC, &temp_byte);
191 temp_byte &= ~(1 << 7); // set linear mode bank B
192 temp_byte &= ~(1 << 3); // set linear mode bank A
193 temp_byte |= 0x70; // Frequency of the LED Driver clock ClkX of all IOs:
194 this->write_byte(REG_MISC, temp_byte);
195
196 this->read_byte_16(REG_LED_DRIVER_ENABLE_B, &temp_word);
197 temp_word |= (1 << pin);
198 this->write_byte_16(REG_LED_DRIVER_ENABLE_B, temp_word);
199
200 this->read_byte_16(REG_DATA_B, &temp_word);
201 temp_word &= ~(1 << pin);
202 output_state_ &= ~(1 << pin);
203 this->write_byte_16(REG_DATA_B, temp_word);
204}
205
206void SX1509Component::clock_(uint8_t osc_source, uint8_t osc_pin_function, uint8_t osc_freq_out, uint8_t osc_divider) {
207 osc_source = (osc_source & 0b11) << 5; // 2-bit value, bits 6:5
208 osc_pin_function = (osc_pin_function & 1) << 4; // 1-bit value bit 4
209 osc_freq_out = (osc_freq_out & 0b1111); // 4-bit value, bits 3:0
210 uint8_t reg_clock = osc_source | osc_pin_function | osc_freq_out;
211 this->write_byte(REG_CLOCK, reg_clock);
212
213 osc_divider = clamp<uint8_t>(osc_divider, 1, 7u);
214 this->clk_x_ = 2000000;
215 osc_divider = (osc_divider & 0b111) << 4; // 3-bit value, bits 6:4
216
217 uint8_t reg_misc = 0;
218 this->read_byte(REG_MISC, &reg_misc);
219 reg_misc &= ~(0b111 << 4);
220 reg_misc |= osc_divider;
221 this->write_byte(REG_MISC, reg_misc);
222}
223
225 uint8_t temp_byte = 0;
226
227 // setup row/col pins for INPUT OUTPUT
228 this->read_byte_16(REG_DIR_B, &this->ddr_mask_);
229 for (int i = 0; i < this->rows_; i++)
230 this->ddr_mask_ &= ~(1 << i);
231 for (int i = 8; i < (8 + this->cols_); i++)
232 this->ddr_mask_ |= (1 << i);
233 this->write_byte_16(REG_DIR_B, this->ddr_mask_);
234
235 this->read_byte(REG_OPEN_DRAIN_A, &temp_byte);
236 for (int i = 0; i < this->rows_; i++)
237 temp_byte |= (1 << i);
238 this->write_byte(REG_OPEN_DRAIN_A, temp_byte);
239
240 this->read_byte(REG_PULL_UP_B, &temp_byte);
241 for (int i = 0; i < this->cols_; i++)
242 temp_byte |= (1 << i);
243 this->write_byte(REG_PULL_UP_B, temp_byte);
244
245 if (debounce_time_ >= scan_time_) {
246 debounce_time_ = scan_time_ >> 1; // Force debounce_time to be less than scan_time
247 }
249 uint8_t scan_time_bits = 0;
250 for (uint8_t i = 7; i > 0; i--) {
251 if (scan_time_ & (1 << i)) {
252 scan_time_bits = i;
253 break;
254 }
255 }
256 scan_time_bits &= 0b111; // Scan time is bits 2:0
257 temp_byte = sleep_time_ | scan_time_bits;
258 this->write_byte(REG_KEY_CONFIG_1, temp_byte);
259 temp_byte = ((this->rows_ - 1) & 0b111) << 3; // 0 = off, 0b001 = 2 rows, 0b111 = 8 rows, etc.
260 temp_byte |= (this->cols_ - 1) & 0b111; // 0b000 = 1 column, ob111 = 8 columns, etc.
261 this->write_byte(REG_KEY_CONFIG_2, temp_byte);
262}
263
265 uint16_t key_data = 0;
266 this->read_byte_16(REG_KEY_DATA_1, &key_data);
267 return (0xFFFF ^ key_data);
268}
269
270void SX1509Component::set_debounce_config_(uint8_t config_value) {
271 // First make sure clock is configured
272 uint8_t temp_byte = 0;
273 this->read_byte(REG_MISC, &temp_byte);
274 temp_byte |= (1 << 4); // Just default to no divider if not set
275 this->write_byte(REG_MISC, temp_byte);
276 this->read_byte(REG_CLOCK, &temp_byte);
277 temp_byte |= (1 << 6); // default to internal osc.
278 this->write_byte(REG_CLOCK, temp_byte);
279
280 config_value &= 0b111; // 3-bit value
281 this->write_byte(REG_DEBOUNCE_CONFIG, config_value);
282}
283
285 uint8_t config_value = 0;
286
287 for (int i = 7; i >= 0; i--) {
288 if (time & (1 << i)) {
289 config_value = i + 1;
290 break;
291 }
292 }
293 config_value = clamp<uint8_t>(config_value, 0, 7);
294
295 set_debounce_config_(config_value);
296}
297
299 uint16_t debounce_enable = 0;
300 this->read_byte_16(REG_DEBOUNCE_ENABLE_B, &debounce_enable);
301 debounce_enable |= (1 << pin);
302 this->write_byte_16(REG_DEBOUNCE_ENABLE_B, debounce_enable);
303}
304
306
307void SX1509Component::set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols) {
308 set_debounce_time_(time);
309 for (uint16_t i = 0; i < num_rows; i++)
311 for (uint16_t i = 0; i < num_cols; i++)
312 set_debounce_pin_(i + 8);
313}
314
315} // 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:83
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:206
void set_debounce_pin_(uint8_t pin)
Definition sx1509.cpp:305
bool digital_read_hw(uint8_t pin) override
Definition sx1509.cpp:78
void set_debounce_config_(uint8_t config_value)
Definition sx1509.cpp:270
std::vector< SX1509KeyTrigger * > key_triggers_
Definition sx1509.h:85
void set_debounce_time_(uint8_t time)
Definition sx1509.cpp:284
void set_debounce_keypad_(uint8_t time, uint8_t num_rows, uint8_t num_cols)
Definition sx1509.cpp:307
void pin_mode(uint8_t pin, gpio::Flags flags)
Definition sx1509.cpp:114
std::vector< SX1509Processor * > keypad_binary_sensors_
Definition sx1509.h:84
void digital_write_hw(uint8_t pin, bool value) override
Definition sx1509.cpp:91
void setup_led_driver(uint8_t pin)
Definition sx1509.cpp:174
void set_debounce_enable_(uint8_t pin)
Definition sx1509.cpp:298
const uint32_t min_loop_period_
Definition sx1509.h:88
uint16_t flags
@ FLAG_OUTPUT
Definition gpio.h:28
@ FLAG_OPEN_DRAIN
Definition gpio.h:29
@ FLAG_PULLUP
Definition gpio.h:30
@ FLAG_PULLDOWN
Definition gpio.h:31
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:28