[sx1509] Add interrupt pin support

Add optional interrupt_pin configuration to eliminate I2C polling
for GPIO reads. When configured, the component disables its loop
and only reads the I2C bus when the interrupt fires, matching
the pattern used by PCF8574, PCA9554, and other GPIO expanders.

When keypad mode is active, the loop continues running for key
scanning regardless of interrupt configuration.
This commit is contained in:
J. Nick Koston
2026-04-09 13:28:24 -10:00
parent ab71f5276f
commit 59065c71a8
7 changed files with 37 additions and 0 deletions

View File

@@ -5,6 +5,7 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_INPUT,
CONF_INTERRUPT_PIN,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
@@ -75,6 +76,7 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(SX1509Component),
cv.Optional(CONF_KEYPAD): cv.Schema(KEYPAD_SCHEMA),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
}
)
.extend(cv.COMPONENT_SCHEMA)
@@ -86,6 +88,8 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if conf := config.get(CONF_KEYPAD):
cg.add(var.set_rows_cols(conf[CONF_KEY_ROWS], conf[CONF_KEY_COLUMNS]))
if (

View File

@@ -28,10 +28,23 @@ void SX1509Component::setup() {
delayMicroseconds(500);
if (this->has_keypad_)
this->setup_keypad_();
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&SX1509Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
this->set_invalidate_on_read_(false);
}
// Disable loop until an input pin is configured via pin_mode()
// or keypad is active. For interrupt-driven mode, loop is re-enabled by the ISR.
if (!this->has_keypad_) {
this->disable_loop();
}
}
void IRAM_ATTR SX1509Component::gpio_intr(SX1509Component *arg) { arg->enable_loop_soon_any_context(); }
void SX1509Component::dump_config() {
ESP_LOGCONFIG(TAG, "SX1509:");
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
if (this->is_failed()) {
ESP_LOGE(TAG, "Setting up SX1509 failed!");
}
@@ -41,6 +54,9 @@ void SX1509Component::dump_config() {
void SX1509Component::loop() {
// Reset cache at the start of each loop
this->reset_pin_cache_();
if (this->interrupt_pin_ != nullptr && !this->has_keypad_) {
this->disable_loop();
}
if (this->has_keypad_) {
if (millis() - this->last_loop_timestamp_ < min_loop_period_)
@@ -169,6 +185,9 @@ void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) {
// Set direction to input
this->ddr_mask_ |= (1 << pin);
this->write_byte_16(REG_DIR_B, this->ddr_mask_);
if (this->interrupt_pin_ == nullptr) {
this->enable_loop();
}
}
}

View File

@@ -46,6 +46,7 @@ class SX1509Component : public Component,
uint16_t read_key_data();
void set_pin_value(uint8_t pin, uint8_t i_on) { this->write_byte(REG_I_ON[pin], i_on); };
void pin_mode(uint8_t pin, gpio::Flags flags);
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
uint32_t get_clock() { return this->clk_x_; };
void set_rows_cols(uint8_t rows, uint8_t cols) {
this->rows_ = rows;
@@ -63,6 +64,8 @@ class SX1509Component : public Component,
void setup_led_driver(uint8_t pin);
protected:
static void IRAM_ATTR gpio_intr(SX1509Component *arg);
// Virtual methods from CachedGpioExpander
bool digital_read_hw(uint8_t pin) override;
bool digital_read_cache(uint8_t pin) override;
@@ -85,6 +88,7 @@ class SX1509Component : public Component,
std::vector<SX1509Processor *> keypad_binary_sensors_;
std::vector<SX1509KeyTrigger *> key_triggers_;
InternalGPIOPin *interrupt_pin_{nullptr};
uint32_t last_loop_timestamp_ = 0;
const uint32_t min_loop_period_ = 15; // ms

View File

@@ -2,6 +2,7 @@ sx1509:
- id: sx1509_hub
i2c_id: i2c_bus
address: 0x3E
interrupt_pin: ${interrupt_pin}
keypad:
key_rows: 2
key_columns: 2

View File

@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO15
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml

View File

@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO15
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml

View File

@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO2
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml