From 9fc18e8d8becb421c01c173d4e1edcb1d1306579 Mon Sep 17 00:00:00 2001 From: Dave Williams | DitroniX | G8PUO Date: Sat, 13 Jun 2026 09:18:32 +0100 Subject: [PATCH] Update tca6408.cpp --- components/TCA6408/tca6408.cpp | 99 +++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/components/TCA6408/tca6408.cpp b/components/TCA6408/tca6408.cpp index b348061..fc27923 100644 --- a/components/TCA6408/tca6408.cpp +++ b/components/TCA6408/tca6408.cpp @@ -6,46 +6,74 @@ namespace tca6408 { static const char *const TAG = "tca6408"; -static const uint8_t TCA6408_INPUT_PORT = 0x00; -static const uint8_t TCA6408_OUTPUT_PORT = 0x01; -static const uint8_t TCA6408_POLARITY_PORT = 0x02; -static const uint8_t TCA6408_CONFIG_PORT = 0x03; +// TCA6408 Register Addresses (from datasheet) +static const uint8_t TCA6408_INPUT_PORT = 0x00; // Input Port Register +static const uint8_t TCA6408_OUTPUT_PORT = 0x01; // Output Port Register +static const uint8_t TCA6408_POLARITY_PORT = 0x02; // Polarity Inversion Register +static const uint8_t TCA6408_CONFIG_PORT = 0x03; // Configuration Register (1 = input, 0 = output) void TCA6408Component::setup() { ESP_LOGCONFIG(TAG, "Setting up TCA6408 at address 0x%02X...", this->address_); - if (this->write_register(TCA6408_POLARITY_PORT, 0x00) != i2c::ERROR_OK || - this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_) != i2c::ERROR_OK || - !this->read_gpio_outputs_()) { + // Initialize chip to a known safe state + if (this->write_register(TCA6408_POLARITY_PORT, 0x00) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to write polarity register"); this->mark_failed(); return; } + if (this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to write config register"); + this->mark_failed(); + return; + } + + if (!this->read_gpio_outputs_()) { + ESP_LOGE(TAG, "Failed to read initial output state"); + this->mark_failed(); + return; + } + + // Configure interrupt mode if requested if (this->interrupt_pin_ != nullptr) { this->interrupt_pin_->setup(); - this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - this->interrupt_pin_->attach_interrupt(&TCA6408Component::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); - this->set_invalidate_on_read_(false); - ESP_LOGCONFIG(TAG, " Interrupt mode enabled (active-low)"); + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); // Required for open-drain INT + this->interrupt_pin_->attach_interrupt(&TCA6408Component::gpio_intr, this, + gpio::INTERRUPT_FALLING_EDGE); + this->set_invalidate_on_read_(false); // We manage cache manually for efficiency + ESP_LOGCONFIG(TAG, " Interrupt mode enabled (active-low INT pin)"); } else { - this->enable_loop(); + this->enable_loop(); // Fallback to polling ESP_LOGCONFIG(TAG, " Polling mode enabled"); } + + ESP_LOGCONFIG(TAG, " TCA6408 setup complete"); } +/** + * Interrupt Service Routine (ISR) + * Runs in interrupt context - must be very lightweight + */ void IRAM_ATTR TCA6408Component::gpio_intr(TCA6408Component *arg) { arg->interrupt_triggered_ = true; - arg->enable_loop_soon_any_context(); + arg->enable_loop_soon_any_context(); // Schedule loop() to run soon } +/** + * Main loop - handles interrupt processing + * Only runs when an interrupt occurs (in interrupt mode) + */ void TCA6408Component::loop() { if (this->interrupt_triggered_) { this->interrupt_triggered_ = false; - this->digital_read_hw(0); - this->reset_pin_cache_(); + // Reading Input Port automatically clears the INT pin on TCA6408 + this->digital_read_hw(0); // pin argument is ignored - reads all 8 bits + this->reset_pin_cache_(); // Notify ESPHome that inputs may have changed + + // Check if INT line has returned high (no more pending interrupts) if (this->interrupt_pin_ && this->interrupt_pin_->digital_read()) { - this->disable_loop(); + this->disable_loop(); // Stop looping until next interrupt } } } @@ -55,6 +83,8 @@ void TCA6408Component::dump_config() { LOG_I2C_DEVICE(this); ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + ESP_LOGCONFIG(TAG, " Mode Mask: 0x%02X (1 = input)", this->mode_mask_); + ESP_LOGCONFIG(TAG, " Polarity Mask: 0x%02X", this->polarity_mask_); } bool TCA6408Component::read_gpio_outputs_() { @@ -69,25 +99,31 @@ bool TCA6408Component::read_gpio_outputs_() { return true; } +/** Read current state of all input pins from hardware */ bool TCA6408Component::digital_read_hw(uint8_t pin) { if (this->is_failed()) return false; + uint8_t data = 0; if (this->read_register(TCA6408_INPUT_PORT, &data) != i2c::ERROR_OK) { this->status_set_warning(); return false; } + this->input_mask_ = data; this->status_clear_warning(); return true; } +/** Write to a specific output pin */ void TCA6408Component::digital_write_hw(uint8_t pin, bool value) { if (this->is_failed()) return; + if (value) { this->output_mask_ |= (1U << pin); } else { this->output_mask_ &= ~(1U << pin); } + if (this->write_register(TCA6408_OUTPUT_PORT, this->output_mask_) != i2c::ERROR_OK) { this->status_set_warning(); } else { @@ -95,16 +131,20 @@ void TCA6408Component::digital_write_hw(uint8_t pin, bool value) { } } +/** Configure pin direction (input/output) */ void TCA6408Component::pin_mode(uint8_t pin, gpio::Flags flags) { if (flags & gpio::FLAG_OUTPUT) { - this->mode_mask_ &= ~(1U << pin); + this->mode_mask_ &= ~(1U << pin); // 0 = output } else { - this->mode_mask_ |= (1U << pin); - if (this->interrupt_pin_ == nullptr) this->enable_loop(); + this->mode_mask_ |= (1U << pin); // 1 = input + if (this->interrupt_pin_ == nullptr) { + this->enable_loop(); // Enable polling if no interrupt pin + } } this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_); } +/** Set hardware polarity inversion for inputs */ void TCA6408Component::set_polarity_inversion(uint8_t pin, bool inverted) { if (inverted) { this->polarity_mask_ |= (1U << pin); @@ -114,21 +154,31 @@ void TCA6408Component::set_polarity_inversion(uint8_t pin, bool inverted) { this->write_register(TCA6408_POLARITY_PORT, this->polarity_mask_); } +/** Read cached value with polarity inversion applied */ bool TCA6408Component::digital_read_cache(uint8_t pin) { bool value = (this->input_mask_ & (1U << pin)) != 0; - if (this->polarity_mask_ & (1U << pin)) value = !value; + + // Apply hardware polarity inversion if enabled for this pin + if (this->polarity_mask_ & (1U << pin)) { + value = !value; + } return value; } -// TCA6408GPIOPin +// ====================== TCA6408GPIOPin Wrapper ====================== + void TCA6408GPIOPin::setup() { this->pin_mode(this->flags_); - if (this->inverted_) this->parent_->set_polarity_inversion(this->pin_, true); + if (this->inverted_) { + this->parent_->set_polarity_inversion(this->pin_, true); + } } void TCA6408GPIOPin::set_inverted(bool inverted) { this->inverted_ = inverted; - if (this->parent_) this->parent_->set_polarity_inversion(this->pin_, inverted); + if (this->parent_) { + this->parent_->set_polarity_inversion(this->pin_, inverted); + } } void TCA6408GPIOPin::pin_mode(gpio::Flags flags) { @@ -137,6 +187,7 @@ void TCA6408GPIOPin::pin_mode(gpio::Flags flags) { } bool TCA6408GPIOPin::digital_read() { + // Note: inversion handled in digital_read_cache() for inputs return this->parent_->digital_read(this->pin_) != this->inverted_; } @@ -149,4 +200,4 @@ size_t TCA6408GPIOPin::dump_summary(char *buffer, size_t len) const { } } // namespace tca6408 -} // namespace esphome \ No newline at end of file +} // namespace esphome