Merge remote-tracking branch 'upstream/pcf8574-pca9554-interrupt-pin' into integration

This commit is contained in:
J. Nick Koston
2026-04-04 09:08:11 -10:00
11 changed files with 44 additions and 15 deletions
+12 -11
View File
@@ -29,10 +29,9 @@ template<typename T, uint16_t N, typename P = typename std::conditional<(N > 256
class CachedGpioExpander {
public:
/// @brief Read the state of the given pin.
/// In polling mode (default), each read invalidates the pin's cache entry so
/// the next read of the same pin triggers a fresh hardware read.
/// In interrupt mode, the cache stays valid until explicitly invalidated by
/// reset_pin_cache_() (called from loop() when an interrupt fires).
/// By default, each read invalidates the pin's cache entry so the next read
/// of the same pin triggers a fresh hardware read. When invalidate_on_read
/// is disabled, the cache stays valid until explicitly cleared via reset_pin_cache_().
/// @param pin Pin number to read
/// @return Pin state
bool digital_read(P pin) {
@@ -40,8 +39,8 @@ class CachedGpioExpander {
const T pin_mask = (1 << (pin % BANK_SIZE));
// Check if specific pin cache is valid
if (this->read_cache_valid_[bank] & pin_mask) {
if (!this->interrupt_driven_) {
// Polling mode: invalidate pin so next read triggers hardware read
if (this->invalidate_on_read_) {
// Invalidate pin so next read triggers hardware read
this->read_cache_valid_[bank] &= ~pin_mask;
}
} else {
@@ -49,8 +48,8 @@ class CachedGpioExpander {
if (!this->digital_read_hw(pin))
return false;
// Mark bank cache as valid except the pin that is being returned now
// (in interrupt mode, mark all pins including this one as valid)
this->read_cache_valid_[bank] = std::numeric_limits<T>::max() & ~(this->interrupt_driven_ ? 0 : pin_mask);
// (when not invalidating on read, mark all pins including this one as valid)
this->read_cache_valid_[bank] = std::numeric_limits<T>::max() & ~(this->invalidate_on_read_ ? pin_mask : 0);
}
return this->digital_read_cache(pin);
}
@@ -78,8 +77,10 @@ class CachedGpioExpander {
/// @brief Invalidate cache. This function should be called in component loop().
void reset_pin_cache_() { memset(this->read_cache_valid_, 0x00, CACHE_SIZE_BYTES); }
/// @brief Enable interrupt-driven mode. Cache stays valid until reset_pin_cache_() is called.
void set_interrupt_driven_(bool interrupt_driven) { this->interrupt_driven_ = interrupt_driven; }
/// @brief Control whether digital_read() invalidates the pin's cache entry after reading.
/// When enabled (default), each read self-invalidates so the next read triggers a hardware read.
/// When disabled, cache stays valid until reset_pin_cache_() is explicitly called.
void set_invalidate_on_read_(bool invalidate) { this->invalidate_on_read_ = invalidate; }
static constexpr uint16_t BITS_PER_BYTE = 8;
static constexpr uint16_t BANK_SIZE = sizeof(T) * BITS_PER_BYTE;
@@ -87,7 +88,7 @@ class CachedGpioExpander {
static constexpr size_t CACHE_SIZE_BYTES = BANKS * sizeof(T);
T read_cache_valid_[BANKS]{0};
bool interrupt_driven_{false};
bool invalidate_on_read_{true};
};
} // namespace esphome::gpio_expander
+2 -2
View File
@@ -38,8 +38,8 @@ void PCA9554Component::setup() {
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PCA9554Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
// Cache stays valid until interrupt fires and loop() invalidates it
this->set_interrupt_driven_(true);
// Don't invalidate cache on read — only invalidate when interrupt fires
this->set_invalidate_on_read_(false);
// With interrupt pin, only run loop when interrupt fires
this->disable_loop();
}
+2 -2
View File
@@ -19,8 +19,8 @@ void PCF8574Component::setup() {
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
this->interrupt_pin_->attach_interrupt(&PCF8574Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE);
// Cache stays valid until interrupt fires and loop() invalidates it
this->set_interrupt_driven_(true);
// Don't invalidate cache on read — only invalidate when interrupt fires
this->set_invalidate_on_read_(false);
// With interrupt pin, only run loop when interrupt fires
this->disable_loop();
}
+5
View File
@@ -3,6 +3,11 @@ pca9554:
i2c_id: i2c_bus
pin_count: 8
address: 0x3F
- id: pca9554_hub_int
i2c_id: i2c_bus
pin_count: 8
address: 0x3E
interrupt_pin: ${interrupt_pin}
binary_sensor:
- platform: gpio
@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO15
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO15
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO2
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
+5
View File
@@ -3,6 +3,11 @@ pcf8574:
i2c_id: i2c_bus
address: 0x21
pcf8575: false
- id: pcf8574_hub_int
i2c_id: i2c_bus
address: 0x22
pcf8575: false
interrupt_pin: ${interrupt_pin}
binary_sensor:
- platform: gpio
@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO15
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO15
packages:
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
@@ -1,3 +1,6 @@
substitutions:
interrupt_pin: GPIO2
packages:
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml