mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:45:15 +00:00
This commit adds interrupt-based GPIO expander support to eliminate continuous
polling and significantly reduce I2C/SPI traffic and CPU usage.
## Key Changes
### Core Infrastructure (gpio_expander/cached_gpio.h)
- Extended CachedGpioExpander base class with interrupt pin support
- Added setup_interrupt_pin() method to attach native GPIO interrupt handlers
- Implemented interrupt-driven cache management:
- ISR sets flag and enables component loop when interrupt fires
- process_interrupt_() reads chip-specific interrupt status registers
- Cache remains valid between interrupts (no polling)
- Component loop automatically disabled until next interrupt
- Added virtual read_interrupt_status_() for chip-specific implementations
- Maintains backward compatibility: interrupt_pin is optional, defaults to polling
### MCP23xxx Family Support
- MCP23x17 (16-pin): Added INTF/INTCAP register reading
- MCP23x08 (8-pin): Added INTF/INTCAP register reading
- Configured IOCON register for interrupt mirroring (INTA=INTB)
- Combined with existing open_drain_ints support
- Chips: MCP23017, MCP23008, MCP23S17, MCP23S08
### PI4IOE5V6408 Support
- Implemented interrupt status register (0x13) reading
- Reads input state register (0x0F) to capture values and clear interrupt
- Single 8-pin bank design
### Python Configuration
- Added CONF_INTERRUPT_PIN to component schemas
- Uses pins.internal_gpio_input_pin_schema for validation
- Optional configuration maintains full backward compatibility
- Example:
```yaml
mcp23017:
id: my_expander
interrupt_pin: GPIO5 # Connect to INTA/INTB
binary_sensor:
- platform: gpio
pin:
mcp23xxx: my_expander
number: 0
interrupt: CHANGE # Existing config, now uses hardware interrupt
```
### Testing
- Updated component test configurations
- Added interrupt_pin test cases for both MCP23017 and PI4IOE5V6408
- Tests both polling mode (no interrupt_pin) and interrupt mode
## Benefits
| Aspect | Before (Polling) | After (Interrupts) |
|----------------|------------------|-------------------|
| I2C Reads | 60-120/sec | 2 per state change|
| CPU Usage | Continuous loop | ISR + event-driven|
| Latency | ~16ms (loop) | <1ms (ISR) |
| Power | Higher | Lower (sleep) |
## Implementation Details
**Interrupt Flow:**
1. Setup: Native GPIO interrupt attached to INTA/INTB pin (FALLING edge)
2. ISR: Sets pending flag, calls enable_loop_soon_any_context()
3. Loop: Reads interrupt status register to identify changed pins
4. Cache: Updates only changed pins, keeps cache valid
5. Optimization: Calls disable_loop() until next interrupt
**Safety:**
- Uses volatile bool for interrupt_pending_ flag
- IRAM_ATTR on ISR for fast execution
- Gracefully falls back to polling if interrupt_pin not configured
- No changes required to binary_sensor platform code
## Backward Compatibility
✅ Existing configurations work unchanged (polling mode)
✅ interrupt_pin is optional - add when ready
✅ No breaking changes to any APIs
✅ Binary sensors automatically benefit from interrupt efficiency
Addresses the need for efficient GPIO expander operation by eliminating
unnecessary continuous polling when hardware interrupts are available.
77 lines
2.4 KiB
C++
77 lines
2.4 KiB
C++
#pragma once
|
|
|
|
#include "esphome/components/gpio_expander/cached_gpio.h"
|
|
#include "esphome/components/i2c/i2c.h"
|
|
#include "esphome/core/component.h"
|
|
#include "esphome/core/hal.h"
|
|
|
|
namespace esphome {
|
|
namespace pi4ioe5v6408 {
|
|
class PI4IOE5V6408Component : public Component,
|
|
public i2c::I2CDevice,
|
|
public gpio_expander::CachedGpioExpander<uint8_t, 8> {
|
|
public:
|
|
PI4IOE5V6408Component() = default;
|
|
|
|
void setup() override;
|
|
void pin_mode(uint8_t pin, gpio::Flags flags);
|
|
|
|
float get_setup_priority() const override;
|
|
void dump_config() override;
|
|
void loop() override;
|
|
|
|
/// Indicate if the component should reset the state during setup
|
|
void set_reset(bool reset) { this->reset_ = reset; }
|
|
|
|
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_internal_ = pin; }
|
|
|
|
protected:
|
|
bool digital_read_hw(uint8_t pin) override;
|
|
bool digital_read_cache(uint8_t pin) override;
|
|
void digital_write_hw(uint8_t pin, bool value) override;
|
|
optional<uint8_t> read_interrupt_status_(uint8_t bank) override;
|
|
|
|
/// Mask for the pin mode - 1 means output, 0 means input
|
|
uint8_t mode_mask_{0x00};
|
|
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
|
uint8_t output_mask_{0x00};
|
|
/// The state read in digital_read_hw - 1 means HIGH, 0 means LOW
|
|
uint8_t input_mask_{0x00};
|
|
/// The mask to write as input buffer state - 1 means enabled, 0 means disabled
|
|
uint8_t pull_enable_mask_{0x00};
|
|
/// The mask to write as pullup state - 1 means pullup, 0 means pulldown
|
|
uint8_t pull_up_down_mask_{0x00};
|
|
|
|
bool reset_{true};
|
|
|
|
/// Internal interrupt pin reference (stored before setup)
|
|
InternalGPIOPin *interrupt_pin_internal_{nullptr};
|
|
|
|
bool read_gpio_modes_();
|
|
bool write_gpio_modes_();
|
|
bool read_gpio_outputs_();
|
|
};
|
|
|
|
class PI4IOE5V6408GPIOPin : public GPIOPin, public Parented<PI4IOE5V6408Component> {
|
|
public:
|
|
void setup() override;
|
|
void pin_mode(gpio::Flags flags) override;
|
|
bool digital_read() override;
|
|
void digital_write(bool value) override;
|
|
std::string dump_summary() const override;
|
|
|
|
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
|
void set_inverted(bool inverted) { this->inverted_ = inverted; }
|
|
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
|
|
|
|
gpio::Flags get_flags() const override { return this->flags_; }
|
|
|
|
protected:
|
|
uint8_t pin_;
|
|
bool inverted_;
|
|
gpio::Flags flags_;
|
|
};
|
|
|
|
} // namespace pi4ioe5v6408
|
|
} // namespace esphome
|