From 8d33c6de364e3f231cdb2ae9b1bedb588381eb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Obrembski?= Date: Sat, 3 May 2025 00:54:27 +0200 Subject: [PATCH] Added Banking support to tca9555, fixed input bug (#8003) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../components/gpio_expander/cached_gpio.h | 29 ++++++++++++++----- esphome/components/tca9555/tca9555.cpp | 17 +++++++---- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/esphome/components/gpio_expander/cached_gpio.h b/esphome/components/gpio_expander/cached_gpio.h index 784c5f0f4a..78c675cdb2 100644 --- a/esphome/components/gpio_expander/cached_gpio.h +++ b/esphome/components/gpio_expander/cached_gpio.h @@ -8,30 +8,45 @@ namespace esphome { namespace gpio_expander { /// @brief A class to cache the read state of a GPIO expander. +/// This class caches reads between GPIO Pins which are on the same bank. +/// This means that for reading whole Port (ex. 8 pins) component needs only one +/// I2C/SPI read per main loop call. It assumes, that one bit in byte identifies one GPIO pin +/// Template parameters: +/// T - Type which represents internal register. Could be uint8_t or uint16_t. Adjust to +/// match size of your internal GPIO bank register. +/// N - Number of pins template class CachedGpioExpander { public: bool digital_read(T pin) { - if (!this->read_cache_invalidated_[pin]) { - this->read_cache_invalidated_[pin] = true; - return this->digital_read_cache(pin); + uint8_t bank = pin / (sizeof(T) * BITS_PER_BYTE); + if (this->read_cache_invalidated_[bank]) { + this->read_cache_invalidated_[bank] = false; + if (!this->digital_read_hw(pin)) + return false; } - return this->digital_read_hw(pin); + return this->digital_read_cache(pin); } void digital_write(T pin, bool value) { this->digital_write_hw(pin, value); } protected: + /// @brief Call component low level function to read GPIO state from device virtual bool digital_read_hw(T pin) = 0; + /// @brief Call component read function from internal cache. virtual bool digital_read_cache(T pin) = 0; + /// @brief Call component low level function to write GPIO state to device virtual void digital_write_hw(T pin, bool value) = 0; + const uint8_t cache_byte_size_ = N / (sizeof(T) * BITS_PER_BYTE); + /// @brief Invalidate cache. This function should be called in component loop(). void reset_pin_cache_() { - for (T i = 0; i < N; i++) { - this->read_cache_invalidated_[i] = false; + for (T i = 0; i < this->cache_byte_size_; i++) { + this->read_cache_invalidated_[i] = true; } } - std::array read_cache_invalidated_{}; + static const uint8_t BITS_PER_BYTE = 8; + std::array read_cache_invalidated_{}; }; } // namespace gpio_expander diff --git a/esphome/components/tca9555/tca9555.cpp b/esphome/components/tca9555/tca9555.cpp index cf0894427f..e065398c46 100644 --- a/esphome/components/tca9555/tca9555.cpp +++ b/esphome/components/tca9555/tca9555.cpp @@ -76,15 +76,20 @@ bool TCA9555Component::read_gpio_modes_() { bool TCA9555Component::digital_read_hw(uint8_t pin) { if (this->is_failed()) return false; - bool success; - uint8_t data[2]; - success = this->read_bytes(TCA9555_INPUT_PORT_REGISTER_0, data, 2); - this->input_mask_ = (uint16_t(data[1]) << 8) | (uint16_t(data[0]) << 0); - - if (!success) { + uint8_t data; + uint8_t bank_number = pin < 8 ? 0 : 1; + uint8_t register_to_read = bank_number ? TCA9555_INPUT_PORT_REGISTER_1 : TCA9555_INPUT_PORT_REGISTER_0; + if (!this->read_bytes(register_to_read, &data, 1)) { this->status_set_warning("Failed to read input register"); return false; } + uint8_t second_half = this->input_mask_ >> 8; + uint8_t first_half = this->input_mask_; + if (bank_number) { + this->input_mask_ = (data << 8) | (uint16_t(first_half) << 0); + } else { + this->input_mask_ = (uint16_t(second_half) << 8) | (data << 0); + } this->status_clear_warning(); return true;