ESPHome 2026.5.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 {
5namespace pca9554 {
6
7// for 16 bit expanders, these addresses will be doubled.
8const uint8_t INPUT_REG = 0;
9const uint8_t OUTPUT_REG = 1;
10const uint8_t INVERT_REG = 2;
11const uint8_t CONFIG_REG = 3;
12
13static const char *const TAG = "pca9554";
14
16 this->reg_width_ = (this->pin_count_ + 7) / 8;
17 // Test to see if device exists
18 if (!this->read_inputs_()) {
19 ESP_LOGE(TAG, "PCA95xx not detected at 0x%02X", this->address_);
20 this->mark_failed();
21 return;
22 }
23
24 // No polarity inversion
25 this->write_register_(INVERT_REG, 0);
26 // All inputs at initialization
27 this->config_mask_ = 0;
28 // Invert mask as the part sees a 1 as an input
29 this->write_register_(CONFIG_REG, ~this->config_mask_);
30 // All outputs low
31 this->output_mask_ = 0;
32 this->write_register_(OUTPUT_REG, this->output_mask_);
33 // Read the inputs
34 this->read_inputs_();
35 ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
36 this->status_has_error());
37
38 if (this->interrupt_pin_ != nullptr) {
39 this->interrupt_pin_->setup();
41 // Don't invalidate cache on read — only invalidate when interrupt fires
42 this->set_invalidate_on_read_(false);
43 }
44 // Disable loop until an input pin is configured via pin_mode()
45 // For interrupt-driven mode, loop is re-enabled by the ISR
46 // For polling mode, loop is re-enabled when pin_mode() registers an input pin
47 this->disable_loop();
48}
51 // Invalidate the cache so the next digital_read() triggers a fresh I2C read
52 this->reset_pin_cache_();
53 if (this->interrupt_pin_ != nullptr) {
54 // Interrupt-driven: disable loop until next interrupt fires
55 this->disable_loop();
56 }
57}
58
60 ESP_LOGCONFIG(TAG,
61 "PCA9554:\n"
62 " I/O Pins: %d",
63 this->pin_count_);
64 LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
65 LOG_I2C_DEVICE(this)
66 if (this->is_failed()) {
67 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
68 }
69}
70
72 // Read all pins from hardware into input_mask_
73 return this->read_inputs_(); // Return true if I2C read succeeded, false on error
74}
75
77 // Return the cached pin state from input_mask_
78 return this->input_mask_ & (1 << pin);
79}
80
81void PCA9554Component::digital_write_hw(uint8_t pin, bool value) {
82 if (value) {
83 this->output_mask_ |= (1 << pin);
84 } else {
85 this->output_mask_ &= ~(1 << pin);
86 }
87 this->write_register_(OUTPUT_REG, this->output_mask_);
88}
89
91 if (flags == gpio::FLAG_INPUT) {
92 // Clear mode mask bit
93 this->config_mask_ &= ~(1 << pin);
94 // Enable polling loop for input pins (not needed for interrupt-driven mode
95 // where the ISR handles re-enabling loop)
96 if (this->interrupt_pin_ == nullptr) {
97 this->enable_loop();
98 }
99 } else if (flags == gpio::FLAG_OUTPUT) {
100 // Set mode mask bit
101 this->config_mask_ |= 1 << pin;
102 }
103 this->write_register_(CONFIG_REG, ~this->config_mask_);
104}
105
107 uint8_t inputs[2];
108
109 if (this->is_failed()) {
110 ESP_LOGD(TAG, "Device marked failed");
111 return false;
112 }
113
114 this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_);
115 if (this->last_error_ != i2c::ERROR_OK) {
116 this->status_set_warning();
117 ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
118 return false;
119 }
120 this->status_clear_warning();
121 this->input_mask_ = inputs[0];
122 if (this->reg_width_ == 2) {
123 this->input_mask_ |= inputs[1] << 8;
124 }
125 return true;
126}
127
128bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) {
129 uint8_t outputs[2];
130 outputs[0] = (uint8_t) value;
131 outputs[1] = (uint8_t) (value >> 8);
132 this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_);
133 if (this->last_error_ != i2c::ERROR_OK) {
134 this->status_set_warning();
135 ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
136 return false;
137 }
138
139 this->status_clear_warning();
140 return true;
141}
142
144
147bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
148void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
149size_t PCA9554GPIOPin::dump_summary(char *buffer, size_t len) const {
150 return buf_append_printf(buffer, len, 0, "%u via PCA9554", this->pin_);
151}
152
153} // namespace pca9554
154} // namespace esphome
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:284
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:258
bool status_has_warning() const
Definition component.h:290
bool status_has_error() const
Definition component.h:292
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:306
virtual void setup()=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:143
size_t pin_count_
number of bits the expander has
Definition pca9554.h:42
bool digital_read_cache(uint8_t pin) override
Definition pca9554.cpp:76
InternalGPIOPin * interrupt_pin_
Definition pca9554.h:53
bool write_register_(uint8_t reg, uint16_t value)
Definition pca9554.cpp:128
static void IRAM_ATTR gpio_intr(PCA9554Component *arg)
Definition pca9554.cpp:49
uint16_t output_mask_
The mask to write as output state - 1 means HIGH, 0 means LOW.
Definition pca9554.h:48
size_t reg_width_
width of registers
Definition pca9554.h:44
void digital_write_hw(uint8_t pin, bool value) override
Definition pca9554.cpp:81
bool digital_read_hw(uint8_t pin) override
Definition pca9554.cpp:71
uint16_t input_mask_
The state of the actual input pin states - 1 means HIGH, 0 means LOW.
Definition pca9554.h:50
void pin_mode(uint8_t pin, gpio::Flags flags)
Helper function to set the pin mode of a pin.
Definition pca9554.cpp:90
uint16_t config_mask_
Mask for the pin config - 1 means OUTPUT, 0 means INPUT.
Definition pca9554.h:46
void setup() override
Check i2c availability and setup masks.
Definition pca9554.cpp:15
esphome::i2c::ErrorCode last_error_
Storage for last I2C error seen.
Definition pca9554.h:52
void pin_mode(gpio::Flags flags) override
Definition pca9554.cpp:146
PCA9554Component * parent_
Definition pca9554.h:73
void digital_write(bool value) override
Definition pca9554.cpp:148
size_t dump_summary(char *buffer, size_t len) const override
Definition pca9554.cpp:149
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:8
const uint8_t INVERT_REG
Definition pca9554.cpp:10
const uint8_t CONFIG_REG
Definition pca9554.cpp:11
const uint8_t OUTPUT_REG
Definition pca9554.cpp:9
constexpr float IO
For components that represent GPIO pins like PCF8573.
Definition component.h:38
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:1045