Files
esphome/esphome/components/pi4ioe5v6408/pi4ioe5v6408.h
Claude bcb3ab5dd8 [gpio_expander] Add interrupt pin support for efficient event-driven operation
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.
2025-11-17 21:45:47 +00:00

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