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