From 85036d64d99cfd1963e98b2c88ac174e1e80311c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 4 Apr 2026 10:03:40 -1000 Subject: [PATCH] Fix MCP23x17 MIRROR bit, add dump_config logging, and clear PI4IOE5V6408 latched interrupts - MCP23017/MCP23S17: Enable MIRROR bit in IOCON when interrupt_pin is configured so INTA and INTB are ORed together, allowing a single interrupt wire to catch changes on all 16 pins - All MCP23xxx variants: Log interrupt_pin in dump_config for debugging - PI4IOE5V6408: Read interrupt status register during setup to clear any latched state that could hold INT line low - MCP23XXXBase: Move setup_interrupt_pin_() to protected visibility --- esphome/components/mcp23008/mcp23008.cpp | 7 ++++-- esphome/components/mcp23017/mcp23017.cpp | 23 ++++++++++++++----- esphome/components/mcp23s08/mcp23s08.cpp | 3 ++- esphome/components/mcp23s17/mcp23s17.cpp | 23 +++++++++++++------ .../components/mcp23xxx_base/mcp23xxx_base.h | 17 +++++++------- .../components/pi4ioe5v6408/pi4ioe5v6408.cpp | 3 +++ 6 files changed, 51 insertions(+), 25 deletions(-) diff --git a/esphome/components/mcp23008/mcp23008.cpp b/esphome/components/mcp23008/mcp23008.cpp index 1023c06eb55..5f73e03f6f6 100644 --- a/esphome/components/mcp23008/mcp23008.cpp +++ b/esphome/components/mcp23008/mcp23008.cpp @@ -23,10 +23,13 @@ void MCP23008::setup() { this->write_reg(mcp23x08_base::MCP23X08_IOCON, iocon | IOCON_ODR); } - this->setup_interrupt_pin(); + this->setup_interrupt_pin_(); } -void MCP23008::dump_config() { ESP_LOGCONFIG(TAG, "MCP23008:"); } +void MCP23008::dump_config() { + ESP_LOGCONFIG(TAG, "MCP23008:"); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); +} bool MCP23008::read_reg(uint8_t reg, uint8_t *value) { if (this->is_failed()) diff --git a/esphome/components/mcp23017/mcp23017.cpp b/esphome/components/mcp23017/mcp23017.cpp index ae1c2d216d6..212c15ccf27 100644 --- a/esphome/components/mcp23017/mcp23017.cpp +++ b/esphome/components/mcp23017/mcp23017.cpp @@ -6,7 +6,8 @@ namespace mcp23017 { static const char *const TAG = "mcp23017"; -static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin +static constexpr uint8_t IOCON_MIRROR = 0x40; // Mirror INTA/INTB pins +static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin void MCP23017::setup() { uint8_t iocon; @@ -19,16 +20,26 @@ void MCP23017::setup() { this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); + uint8_t iocon_flags = 0; if (this->open_drain_ints_) { - // enable open-drain interrupt pins, 3.3V-safe - this->write_reg(mcp23x17_base::MCP23X17_IOCONA, iocon | IOCON_ODR); - this->write_reg(mcp23x17_base::MCP23X17_IOCONB, iocon | IOCON_ODR); + iocon_flags |= IOCON_ODR; + } + if (this->interrupt_pin_ != nullptr) { + // Mirror INTA/INTB so either pin fires for changes on any port + iocon_flags |= IOCON_MIRROR; + } + if (iocon_flags != 0) { + this->write_reg(mcp23x17_base::MCP23X17_IOCONA, iocon | iocon_flags); + this->write_reg(mcp23x17_base::MCP23X17_IOCONB, iocon | iocon_flags); } - this->setup_interrupt_pin(); + this->setup_interrupt_pin_(); } -void MCP23017::dump_config() { ESP_LOGCONFIG(TAG, "MCP23017:"); } +void MCP23017::dump_config() { + ESP_LOGCONFIG(TAG, "MCP23017:"); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); +} bool MCP23017::read_reg(uint8_t reg, uint8_t *value) { if (this->is_failed()) diff --git a/esphome/components/mcp23s08/mcp23s08.cpp b/esphome/components/mcp23s08/mcp23s08.cpp index d48b9156a10..983c1aa6008 100644 --- a/esphome/components/mcp23s08/mcp23s08.cpp +++ b/esphome/components/mcp23s08/mcp23s08.cpp @@ -35,12 +35,13 @@ void MCP23S08::setup() { this->write_reg(mcp23x08_base::MCP23X08_IOCON, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR); } - this->setup_interrupt_pin(); + this->setup_interrupt_pin_(); } void MCP23S08::dump_config() { ESP_LOGCONFIG(TAG, "MCP23S08:"); LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); } bool MCP23S08::read_reg(uint8_t reg, uint8_t *value) { diff --git a/esphome/components/mcp23s17/mcp23s17.cpp b/esphome/components/mcp23s17/mcp23s17.cpp index 24753dbf2a9..db9a34e2302 100644 --- a/esphome/components/mcp23s17/mcp23s17.cpp +++ b/esphome/components/mcp23s17/mcp23s17.cpp @@ -7,9 +7,10 @@ namespace mcp23s17 { static const char *const TAG = "mcp23s17"; // IOCON register bits -static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode -static constexpr uint8_t IOCON_HAEN = 0x08; // Hardware address enable -static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin +static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode +static constexpr uint8_t IOCON_MIRROR = 0x40; // Mirror INTA/INTB pins +static constexpr uint8_t IOCON_HAEN = 0x08; // Hardware address enable +static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin void MCP23S17::set_device_address(uint8_t device_addr) { if (device_addr != 0) { @@ -37,18 +38,26 @@ void MCP23S17::setup() { this->read_reg(mcp23x17_base::MCP23X17_OLATA, &this->olat_a_); this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_); + uint8_t iocon_flags = IOCON_SEQOP | IOCON_HAEN; if (this->open_drain_ints_) { - // enable open-drain interrupt pins, 3.3V-safe (addressed, only this chip) - this->write_reg(mcp23x17_base::MCP23X17_IOCONA, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR); - this->write_reg(mcp23x17_base::MCP23X17_IOCONB, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR); + iocon_flags |= IOCON_ODR; + } + if (this->interrupt_pin_ != nullptr) { + // Mirror INTA/INTB so either pin fires for changes on any port + iocon_flags |= IOCON_MIRROR; + } + if (this->open_drain_ints_ || this->interrupt_pin_ != nullptr) { + this->write_reg(mcp23x17_base::MCP23X17_IOCONA, iocon_flags); + this->write_reg(mcp23x17_base::MCP23X17_IOCONB, iocon_flags); } - this->setup_interrupt_pin(); + this->setup_interrupt_pin_(); } void MCP23S17::dump_config() { ESP_LOGCONFIG(TAG, "MCP23S17:"); LOG_PIN(" CS Pin: ", this->cs_); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); } bool MCP23S17::read_reg(uint8_t reg, uint8_t *value) { diff --git a/esphome/components/mcp23xxx_base/mcp23xxx_base.h b/esphome/components/mcp23xxx_base/mcp23xxx_base.h index 7ddfd267681..7838e301276 100644 --- a/esphome/components/mcp23xxx_base/mcp23xxx_base.h +++ b/esphome/components/mcp23xxx_base/mcp23xxx_base.h @@ -18,15 +18,6 @@ template class MCP23XXXBase : public Component, public gpio_expander: void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } float get_setup_priority() const override { return setup_priority::IO; } - void setup_interrupt_pin() { - if (this->interrupt_pin_ != nullptr) { - this->interrupt_pin_->setup(); - this->interrupt_pin_->attach_interrupt(&MCP23XXXBase::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); - this->set_invalidate_on_read_(false); - this->disable_loop(); - } - } - void loop() override { this->reset_pin_cache_(); if (this->interrupt_pin_ != nullptr) { @@ -35,6 +26,14 @@ template class MCP23XXXBase : public Component, public gpio_expander: } protected: + void setup_interrupt_pin_() { + if (this->interrupt_pin_ != nullptr) { + this->interrupt_pin_->setup(); + this->interrupt_pin_->attach_interrupt(&MCP23XXXBase::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); + this->set_invalidate_on_read_(false); + this->disable_loop(); + } + } static void IRAM_ATTR gpio_intr(MCP23XXXBase *arg) { arg->enable_loop_soon_any_context(); } // read a given register diff --git a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp index b09567ba1e1..69de3997290 100644 --- a/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp +++ b/esphome/components/pi4ioe5v6408/pi4ioe5v6408.cpp @@ -35,6 +35,9 @@ void PI4IOE5V6408Component::setup() { } if (this->interrupt_pin_ != nullptr) { + // Clear any latched interrupt status from before boot + uint8_t status; + this->read_byte(PI4IOE5V6408_REGISTER_INTERRUPT_STATUS, &status); this->interrupt_pin_->setup(); this->interrupt_pin_->attach_interrupt(&PI4IOE5V6408Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); this->set_invalidate_on_read_(false);