diff --git a/components/tca6408/tca6408.cpp b/components/tca6408/tca6408.cpp index fc27923..e25e34d 100644 --- a/components/tca6408/tca6408.cpp +++ b/components/tca6408/tca6408.cpp @@ -6,74 +6,68 @@ namespace tca6408 { static const char *const TAG = "tca6408"; -// 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) +// Registers +static const uint8_t REG_INPUT = 0x00; +static const uint8_t REG_OUTPUT = 0x01; +static const uint8_t REG_POLARITY = 0x02; +static const uint8_t REG_CONFIG = 0x03; void TCA6408Component::setup() { - ESP_LOGCONFIG(TAG, "Setting up TCA6408 at address 0x%02X...", this->address_); + ESP_LOGCONFIG(TAG, "Setting up TCA6408 @ 0x%02X", this->address_); - // 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"); + // Default safe state: all inputs + this->mode_mask_ = 0xFF; + this->output_mask_ = 0x00; + this->input_mask_ = 0x00; + this->polarity_mask_ = 0x00; + + if (this->write_register(REG_POLARITY, 0x00) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to init 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"); + if (this->write_register(REG_CONFIG, this->mode_mask_) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to init config register"); this->mark_failed(); return; } - if (!this->read_gpio_outputs_()) { - ESP_LOGE(TAG, "Failed to read initial output state"); - this->mark_failed(); - return; - } + this->read_inputs_(); - // 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); // 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)"); + 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->enable_loop(); + ESP_LOGCONFIG(TAG, "Interrupt mode enabled"); } else { - this->enable_loop(); // Fallback to polling - ESP_LOGCONFIG(TAG, " Polling mode enabled"); + this->enable_loop(); + ESP_LOGCONFIG(TAG, "Polling mode enabled"); } - ESP_LOGCONFIG(TAG, " TCA6408 setup complete"); + 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(); // Schedule loop() to run soon + arg->enable_loop_soon_any_context(); } -/** - * 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; - // 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 + this->read_inputs_(); + this->reset_pin_cache_(); - // Check if INT line has returned high (no more pending interrupts) if (this->interrupt_pin_ && this->interrupt_pin_->digital_read()) { - this->disable_loop(); // Stop looping until next interrupt + // INT released -> stop looping until next interrupt + this->disable_loop(); } } } @@ -82,29 +76,13 @@ void TCA6408Component::dump_config() { ESP_LOGCONFIG(TAG, "TCA6408:"); 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_); + LOG_PIN(" INT Pin: ", this->interrupt_pin_); } -bool TCA6408Component::read_gpio_outputs_() { - if (this->is_failed()) return false; +bool TCA6408Component::read_inputs_() { uint8_t data = 0; - if (this->read_register(TCA6408_OUTPUT_PORT, &data) != i2c::ERROR_OK) { - this->status_set_warning(); - return false; - } - this->output_mask_ = data; - this->status_clear_warning(); - 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) { + if (this->read_register(REG_INPUT, &data) != i2c::ERROR_OK) { this->status_set_warning(); return false; } @@ -114,71 +92,65 @@ bool TCA6408Component::digital_read_hw(uint8_t pin) { return true; } -/** Write to a specific output pin */ -void TCA6408Component::digital_write_hw(uint8_t pin, bool value) { - if (this->is_failed()) return; +bool TCA6408Component::digital_read_hw(uint8_t pin) { + // full refresh for accuracy + return this->read_inputs_(); +} - if (value) { - this->output_mask_ |= (1U << pin); - } else { - this->output_mask_ &= ~(1U << pin); +bool TCA6408Component::digital_read_cache(uint8_t pin) { + bool value = (this->input_mask_ >> pin) & 0x01; + + if (this->polarity_mask_ & (1 << pin)) { + value = !value; } - if (this->write_register(TCA6408_OUTPUT_PORT, this->output_mask_) != i2c::ERROR_OK) { + return value; +} + +void TCA6408Component::digital_write_hw(uint8_t pin, bool value) { + if (value) + this->output_mask_ |= (1 << pin); + else + this->output_mask_ &= ~(1 << pin); + + if (this->write_register(REG_OUTPUT, this->output_mask_) != i2c::ERROR_OK) { this->status_set_warning(); } else { this->status_clear_warning(); } } -/** 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); // 0 = output + this->mode_mask_ &= ~(1 << pin); } else { - this->mode_mask_ |= (1U << pin); // 1 = input - if (this->interrupt_pin_ == nullptr) { - this->enable_loop(); // Enable polling if no interrupt pin - } + this->mode_mask_ |= (1 << pin); } - this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_); + + this->write_register(REG_CONFIG, 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); - } else { - this->polarity_mask_ &= ~(1U << pin); - } - this->write_register(TCA6408_POLARITY_PORT, this->polarity_mask_); + if (inverted) + this->polarity_mask_ |= (1 << pin); + else + this->polarity_mask_ &= ~(1 << pin); + + this->write_register(REG_POLARITY, 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; +// ================= GPIO PIN WRAPPER ================= - // Apply hardware polarity inversion if enabled for this pin - if (this->polarity_mask_ & (1U << pin)) { - value = !value; - } - return value; +void TCA6408GPIOPin::set_parent(TCA6408Component *parent) { + this->parent_ = parent; } -// ====================== TCA6408GPIOPin Wrapper ====================== +void TCA6408GPIOPin::set_pin(uint8_t pin) { + this->pin_ = pin; +} void TCA6408GPIOPin::setup() { this->pin_mode(this->flags_); - 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); - } } void TCA6408GPIOPin::pin_mode(gpio::Flags flags) { @@ -187,16 +159,20 @@ 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_; + return this->parent_->digital_read_cache(this->pin_) != this->inverted_; } void TCA6408GPIOPin::digital_write(bool value) { - this->parent_->digital_write(this->pin_, value != this->inverted_); + this->parent_->digital_write_hw(this->pin_, value != this->inverted_); +} + +void TCA6408GPIOPin::set_inverted(bool inverted) { + this->inverted_ = inverted; + this->parent_->set_polarity_inversion(this->pin_, inverted); } size_t TCA6408GPIOPin::dump_summary(char *buffer, size_t len) const { - return snprintf(buffer, len, "%u via TCA6408", this->pin_); + return snprintf(buffer, len, "TCA6408 pin %u", this->pin_); } } // namespace tca6408