ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
pca9554.cpp
Go to the documentation of this file.
1#include "pca9554.h"
2#include "esphome/core/log.h"
3
4namespace esphome::pca9554 {
5
6// for 16 bit expanders, these addresses will be doubled.
7const uint8_t INPUT_REG = 0;
8const uint8_t OUTPUT_REG = 1;
9const uint8_t INVERT_REG = 2;
10const uint8_t CONFIG_REG = 3;
11
12static const char *const TAG = "pca9554";
13
15 this->reg_width_ = (this->pin_count_ + 7) / 8;
16 // Test to see if device exists
17 if (!this->read_inputs_()) {
18 ESP_LOGE(TAG, "PCA95xx not detected at 0x%02X", this->address_);
19 this->mark_failed();
20 return;
21 }
22
23 // No polarity inversion
24 this->write_register_(INVERT_REG, 0);
25 // All inputs at initialization
26 this->config_mask_ = 0;
27 // Invert mask as the part sees a 1 as an input
28 this->write_register_(CONFIG_REG, ~this->config_mask_);
29 // All outputs low
30 this->output_mask_ = 0;
31 this->write_register_(OUTPUT_REG, this->output_mask_);
32 // Read the inputs
33 this->read_inputs_();
34 ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
35 this->status_has_error());
36
37 if (this->interrupt_pin_ != nullptr) {
38 this->interrupt_pin_->setup();
40 // Don't invalidate cache on read — only invalidate when interrupt fires
41 this->set_invalidate_on_read_(false);
42 }
43 // Disable loop until an input pin is configured via pin_mode()
44 // For interrupt-driven mode, loop is re-enabled by the ISR
45 // For polling mode, loop is re-enabled when pin_mode() registers an input pin
46 this->disable_loop();
47}
50 // Invalidate the cache so the next digital_read() triggers a fresh I2C read
51 this->reset_pin_cache_();
52 // Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
53 // I2C read leave INT asserted without re-firing a falling edge, which would strand us with
54 // stale state forever; keep looping until the line is released so we self-heal.
55 if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
56 this->disable_loop();
57 }
58}
59
61 ESP_LOGCONFIG(TAG,
62 "PCA9554:\n"
63 " I/O Pins: %d",
64 this->pin_count_);
65 LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
66 LOG_I2C_DEVICE(this)
67 if (this->is_failed()) {
68 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
69 }
70}
71
73 // Read all pins from hardware into input_mask_
74 return this->read_inputs_(); // Return true if I2C read succeeded, false on error
75}
76
78 // Return the cached pin state from input_mask_
79 return this->input_mask_ & (1 << pin);
80}
81
82void PCA9554Component::digital_write_hw(uint8_t pin, bool value) {
83 if (value) {
84 this->output_mask_ |= (1 << pin);
85 } else {
86 this->output_mask_ &= ~(1 << pin);
87 }
88 this->write_register_(OUTPUT_REG, this->output_mask_);
89}
90
92 if (flags == gpio::FLAG_INPUT) {
93 // Clear mode mask bit
94 this->config_mask_ &= ~(1 << pin);
95 // Enable polling loop for input pins (not needed for interrupt-driven mode
96 // where the ISR handles re-enabling loop)
97 if (this->interrupt_pin_ == nullptr) {
98 this->enable_loop();
99 }
100 } else if (flags == gpio::FLAG_OUTPUT) {
101 // Set mode mask bit
102 this->config_mask_ |= 1 << pin;
103 }
104 this->write_register_(CONFIG_REG, ~this->config_mask_);
105}
106
108 uint8_t inputs[2];
109
110 if (this->is_failed()) {
111 ESP_LOGD(TAG, "Device marked failed");
112 return false;
113 }
114
115 this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_);
116 if (this->last_error_ != i2c::ERROR_OK) {
117 this->status_set_warning();
118 ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
119 return false;
120 }
121 this->status_clear_warning();
122 this->input_mask_ = inputs[0];
123 if (this->reg_width_ == 2) {
124 this->input_mask_ |= inputs[1] << 8;
125 }
126 return true;
127}
128
129bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) {
130 uint8_t outputs[2];
131 outputs[0] = (uint8_t) value;
132 outputs[1] = (uint8_t) (value >> 8);
133 this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_);
134 if (this->last_error_ != i2c::ERROR_OK) {
135 this->status_set_warning();
136 ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
137 return false;
138 }
139
140 this->status_clear_warning();
141 return true;
142}
143
145
148bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
149void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
150size_t PCA9554GPIOPin::dump_summary(char *buffer, size_t len) const {
151 return buf_append_printf(buffer, len, 0, "%u via PCA9554", this->pin_);
152}
153
154} // namespace esphome::pca9554
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void enable_loop()
Enable this component's loop.
Definition component.h:246
bool status_has_warning() const
Definition component.h:278
bool status_has_error() const
Definition component.h:280
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:289
virtual void setup()=0
virtual bool digital_read()=0
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:107
bool digital_read(P pin)
Read the state of the given pin.
Definition cached_gpio.h:37
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) const
writes an array of bytes to a specific register in the I²C device
Definition i2c.cpp:34
uint8_t address_
store the address of the device on the bus
Definition i2c.h:270
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len)
reads an array of bytes from a specific register in the I²C device
Definition i2c.cpp:25
float get_setup_priority() const override
Definition pca9554.cpp:144
size_t pin_count_
number of bits the expander has
Definition pca9554.h:41
bool digital_read_cache(uint8_t pin) override
Definition pca9554.cpp:77
InternalGPIOPin * interrupt_pin_
Definition pca9554.h:52
bool write_register_(uint8_t reg, uint16_t value)
Definition pca9554.cpp:129
static void IRAM_ATTR gpio_intr(PCA9554Component *arg)
Definition pca9554.cpp:48
uint16_t output_mask_
The mask to write as output state - 1 means HIGH, 0 means LOW.
Definition pca9554.h:47
size_t reg_width_
width of registers
Definition pca9554.h:43
void digital_write_hw(uint8_t pin, bool value) override
Definition pca9554.cpp:82
bool digital_read_hw(uint8_t pin) override
Definition pca9554.cpp:72
uint16_t input_mask_
The state of the actual input pin states - 1 means HIGH, 0 means LOW.
Definition pca9554.h:49
void pin_mode(uint8_t pin, gpio::Flags flags)
Helper function to set the pin mode of a pin.
Definition pca9554.cpp:91
uint16_t config_mask_
Mask for the pin config - 1 means OUTPUT, 0 means INPUT.
Definition pca9554.h:45
void setup() override
Check i2c availability and setup masks.
Definition pca9554.cpp:14
esphome::i2c::ErrorCode last_error_
Storage for last I2C error seen.
Definition pca9554.h:51
void pin_mode(gpio::Flags flags) override
Definition pca9554.cpp:147
PCA9554Component * parent_
Definition pca9554.h:72
void digital_write(bool value) override
Definition pca9554.cpp:149
size_t dump_summary(char *buffer, size_t len) const override
Definition pca9554.cpp:150
uint16_t flags
@ INTERRUPT_FALLING_EDGE
Definition gpio.h:51
@ FLAG_OUTPUT
Definition gpio.h:28
@ FLAG_INPUT
Definition gpio.h:27
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:14
const uint8_t INPUT_REG
Definition pca9554.cpp:7
const uint8_t INVERT_REG
Definition pca9554.cpp:9
const uint8_t CONFIG_REG
Definition pca9554.cpp:10
const uint8_t OUTPUT_REG
Definition pca9554.cpp:8
constexpr float IO
For components that represent GPIO pins like PCF8573.
Definition component.h:39
const void size_t len
Definition hal.h:64