Update tca6408.cpp

This commit is contained in:
Dave Williams | DitroniX | G8PUO
2026-06-13 09:18:32 +01:00
committed by GitHub
parent 1d57fba502
commit 9fc18e8d8b
+75 -24
View File
@@ -6,46 +6,74 @@ namespace tca6408 {
static const char *const TAG = "tca6408"; static const char *const TAG = "tca6408";
static const uint8_t TCA6408_INPUT_PORT = 0x00; // TCA6408 Register Addresses (from datasheet)
static const uint8_t TCA6408_OUTPUT_PORT = 0x01; static const uint8_t TCA6408_INPUT_PORT = 0x00; // Input Port Register
static const uint8_t TCA6408_POLARITY_PORT = 0x02; static const uint8_t TCA6408_OUTPUT_PORT = 0x01; // Output Port Register
static const uint8_t TCA6408_CONFIG_PORT = 0x03; 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() { void TCA6408Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up TCA6408 at address 0x%02X...", this->address_); ESP_LOGCONFIG(TAG, "Setting up TCA6408 at address 0x%02X...", this->address_);
if (this->write_register(TCA6408_POLARITY_PORT, 0x00) != i2c::ERROR_OK || // Initialize chip to a known safe state
this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_) != i2c::ERROR_OK || if (this->write_register(TCA6408_POLARITY_PORT, 0x00) != i2c::ERROR_OK) {
!this->read_gpio_outputs_()) { ESP_LOGE(TAG, "Failed to write polarity register");
this->mark_failed(); this->mark_failed();
return; 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) { if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup(); this->interrupt_pin_->setup();
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); 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->interrupt_pin_->attach_interrupt(&TCA6408Component::gpio_intr, this,
this->set_invalidate_on_read_(false); gpio::INTERRUPT_FALLING_EDGE);
ESP_LOGCONFIG(TAG, " Interrupt mode enabled (active-low)"); this->set_invalidate_on_read_(false); // We manage cache manually for efficiency
ESP_LOGCONFIG(TAG, " Interrupt mode enabled (active-low INT pin)");
} else { } else {
this->enable_loop(); this->enable_loop(); // Fallback to polling
ESP_LOGCONFIG(TAG, " Polling mode enabled"); 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) { void IRAM_ATTR TCA6408Component::gpio_intr(TCA6408Component *arg) {
arg->interrupt_triggered_ = true; 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() { void TCA6408Component::loop() {
if (this->interrupt_triggered_) { if (this->interrupt_triggered_) {
this->interrupt_triggered_ = false; 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()) { 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); LOG_I2C_DEVICE(this);
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); 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_() { bool TCA6408Component::read_gpio_outputs_() {
@@ -69,25 +99,31 @@ bool TCA6408Component::read_gpio_outputs_() {
return true; return true;
} }
/** Read current state of all input pins from hardware */
bool TCA6408Component::digital_read_hw(uint8_t pin) { bool TCA6408Component::digital_read_hw(uint8_t pin) {
if (this->is_failed()) return false; if (this->is_failed()) return false;
uint8_t data = 0; uint8_t data = 0;
if (this->read_register(TCA6408_INPUT_PORT, &data) != i2c::ERROR_OK) { if (this->read_register(TCA6408_INPUT_PORT, &data) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
return false; return false;
} }
this->input_mask_ = data; this->input_mask_ = data;
this->status_clear_warning(); this->status_clear_warning();
return true; return true;
} }
/** Write to a specific output pin */
void TCA6408Component::digital_write_hw(uint8_t pin, bool value) { void TCA6408Component::digital_write_hw(uint8_t pin, bool value) {
if (this->is_failed()) return; if (this->is_failed()) return;
if (value) { if (value) {
this->output_mask_ |= (1U << pin); this->output_mask_ |= (1U << pin);
} else { } else {
this->output_mask_ &= ~(1U << pin); this->output_mask_ &= ~(1U << pin);
} }
if (this->write_register(TCA6408_OUTPUT_PORT, this->output_mask_) != i2c::ERROR_OK) { if (this->write_register(TCA6408_OUTPUT_PORT, this->output_mask_) != i2c::ERROR_OK) {
this->status_set_warning(); this->status_set_warning();
} else { } 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) { void TCA6408Component::pin_mode(uint8_t pin, gpio::Flags flags) {
if (flags & gpio::FLAG_OUTPUT) { if (flags & gpio::FLAG_OUTPUT) {
this->mode_mask_ &= ~(1U << pin); this->mode_mask_ &= ~(1U << pin); // 0 = output
} else { } else {
this->mode_mask_ |= (1U << pin); this->mode_mask_ |= (1U << pin); // 1 = input
if (this->interrupt_pin_ == nullptr) this->enable_loop(); if (this->interrupt_pin_ == nullptr) {
this->enable_loop(); // Enable polling if no interrupt pin
}
} }
this->write_register(TCA6408_CONFIG_PORT, this->mode_mask_); 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) { void TCA6408Component::set_polarity_inversion(uint8_t pin, bool inverted) {
if (inverted) { if (inverted) {
this->polarity_mask_ |= (1U << pin); 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_); 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 TCA6408Component::digital_read_cache(uint8_t pin) {
bool value = (this->input_mask_ & (1U << pin)) != 0; 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; return value;
} }
// TCA6408GPIOPin // ====================== TCA6408GPIOPin Wrapper ======================
void TCA6408GPIOPin::setup() { void TCA6408GPIOPin::setup() {
this->pin_mode(this->flags_); 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) { void TCA6408GPIOPin::set_inverted(bool inverted) {
this->inverted_ = 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) { void TCA6408GPIOPin::pin_mode(gpio::Flags flags) {
@@ -137,6 +187,7 @@ void TCA6408GPIOPin::pin_mode(gpio::Flags flags) {
} }
bool TCA6408GPIOPin::digital_read() { 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(this->pin_) != this->inverted_;
} }
@@ -149,4 +200,4 @@ size_t TCA6408GPIOPin::dump_summary(char *buffer, size_t len) const {
} }
} // namespace tca6408 } // namespace tca6408
} // namespace esphome } // namespace esphome