ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
pca9505.cpp
Go to the documentation of this file.
1#include "pca9505.h"
2#include "esphome/core/log.h"
3
4namespace esphome::pca9505 {
5
6// Each register block holds one byte per port. Add the port number to reach a single
7// port, or set the auto-increment bit to transfer the whole block in one go.
8static constexpr uint8_t INPUT_REG = 0x00;
9static constexpr uint8_t OUTPUT_REG = 0x08;
10static constexpr uint8_t INVERT_REG = 0x10;
11static constexpr uint8_t CONFIG_REG = 0x18;
12static constexpr uint8_t MASK_REG = 0x20;
13static constexpr uint8_t AUTO_INCREMENT = 0x80;
14
15ESPHOME_LOG_TAG(TAG, "pca9505");
16
18 // Test to see if device exists
19 if (!this->read_bank_(0)) {
20 ESP_LOGE(TAG, "PCA9505 not detected at 0x%02X", this->address_);
21 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
22 return;
23 }
24
25 // No polarity inversion
26 this->write_all_banks_(INVERT_REG, 0x00);
27 // All outputs low, before anything is switched to being an output
28 this->write_all_banks_(OUTPUT_REG, 0x00);
29 // All inputs at initialization. The part sees a 1 as an input, so this is the
30 // inverse of config_, which is all zeroes here.
31 this->write_all_banks_(CONFIG_REG, 0xFF);
32
33 // Mask every interrupt. This must be written, not assumed: the chip keeps its
34 // registers through an MCU reboot, so a previous run may have left pins unmasked.
35 for (uint8_t &bank : this->mask_) {
36 bank = 0xFF;
37 }
38 this->write_all_banks_(MASK_REG, 0xFF);
39
40 if (this->interrupt_pin_ != nullptr) {
41 this->interrupt_pin_->setup();
43 // Don't invalidate cache on read - only invalidate when interrupt fires
44 this->set_invalidate_on_read_(false);
45 }
46
47 // Nothing to poll until pin_mode() registers an input pin.
48 this->disable_loop();
49}
50
52
54 // Invalidate the cache so the next digital_read() triggers a fresh I2C read
55 this->reset_pin_cache_();
56 // Only disable the loop once INT has actually gone HIGH. Input transitions that straddle the
57 // I2C read leave INT asserted without re-firing a falling edge, which would strand us with
58 // stale state forever; keep looping until the line is released so we self-heal.
59 if (this->interrupt_pin_ != nullptr && this->interrupt_pin_->digital_read()) {
60 this->disable_loop();
61 }
62}
63
65 ESP_LOGCONFIG(TAG, "PCA9505:");
66 LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
67 LOG_I2C_DEVICE(this)
68}
69
70bool PCA9505Component::digital_read_hw(uint8_t pin) { return this->read_bank_(pin / 8); }
71
72bool PCA9505Component::digital_read_cache(uint8_t pin) { return this->input_[pin / 8] & (1 << (pin % 8)); }
73
74void PCA9505Component::digital_write_hw(uint8_t pin, bool value) {
75 const uint8_t bank = pin / 8;
76 const uint8_t mask = 1 << (pin % 8);
77 if (value) {
78 this->output_[bank] |= mask;
79 } else {
80 this->output_[bank] &= ~mask;
81 }
82 this->write_bank_(OUTPUT_REG, bank, this->output_[bank]);
83}
84
86 const uint8_t bank = pin / 8;
87 const uint8_t mask = 1 << (pin % 8);
88 if (flags & gpio::FLAG_INPUT) {
89 this->config_[bank] &= ~mask;
90 // An input may raise INT; the polling loop is only needed when there is no interrupt pin.
91 this->mask_[bank] &= ~mask;
92 if (this->interrupt_pin_ == nullptr) {
93 this->enable_loop();
94 }
95 } else if (flags & gpio::FLAG_OUTPUT) {
96 this->config_[bank] |= mask;
97 // An output must never raise INT.
98 this->mask_[bank] |= mask;
99 }
100 // Invert as the part sees a 1 as an input
101 this->write_bank_(CONFIG_REG, bank, ~this->config_[bank]);
102 // Without an interrupt pin INT is unconnected, so the masks written at setup can stay
103 if (this->interrupt_pin_ != nullptr) {
104 this->write_bank_(MASK_REG, bank, this->mask_[bank]);
105 }
106}
107
109 if (this->is_failed())
110 return false;
111
112 if (this->read_register(INPUT_REG + bank, &this->input_[bank], 1) != i2c::ERROR_OK) {
113 this->status_set_warning(LOG_STR("Failed to read input register"));
114 return false;
115 }
116 this->status_clear_warning();
117 return true;
118}
119
120bool PCA9505Component::write_bank_(uint8_t reg, uint8_t bank, uint8_t value) {
121 if (this->is_failed())
122 return false;
123
124 if (this->write_register(reg + bank, &value, 1) != i2c::ERROR_OK) {
125 this->status_set_warning(LOG_STR("Failed to write register"));
126 return false;
127 }
128 this->status_clear_warning();
129 return true;
130}
131
132bool PCA9505Component::write_all_banks_(uint8_t reg, uint8_t value) {
133 if (this->is_failed())
134 return false;
135
136 uint8_t values[BANK_COUNT];
137 for (uint8_t &bank : values) {
138 bank = value;
139 }
140 if (this->write_register(reg | AUTO_INCREMENT, values, sizeof(values)) != i2c::ERROR_OK) {
141 this->status_set_warning(LOG_STR("Failed to write register"));
142 return false;
143 }
144 this->status_clear_warning();
145 return true;
146}
147
149
150void PCA9505GPIOPin::setup() { this->pin_mode(this->flags_); }
151
152void PCA9505GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
153
154bool PCA9505GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
155
156void PCA9505GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
157
158size_t PCA9505GPIOPin::dump_summary(char *buffer, size_t len) const {
159 return buf_append_printf(buffer, len, 0, "%u via PCA9505", this->pin_);
160}
161
162} // namespace esphome::pca9505
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:274
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:248
void disable_loop()
Disable this component's loop.
void status_clear_warning()
Definition component.h:291
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
Definition gpio.h:29
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
bool write_all_banks_(uint8_t reg, uint8_t value)
Definition pca9505.cpp:132
uint8_t output_[BANK_COUNT]
The output state per bank - 1 means HIGH, 0 means LOW.
Definition pca9505.h:44
static void IRAM_ATTR gpio_intr(PCA9505Component *arg)
Definition pca9505.cpp:51
InternalGPIOPin * interrupt_pin_
Definition pca9505.h:49
uint8_t config_[BANK_COUNT]
The pin config per bank - 1 means OUTPUT, 0 means INPUT.
Definition pca9505.h:42
void pin_mode(uint8_t pin, gpio::Flags flags)
Definition pca9505.cpp:85
uint8_t mask_[BANK_COUNT]
Which pins may pull INT low - 1 means masked, matching the chip.
Definition pca9505.h:48
float get_setup_priority() const override
Definition pca9505.cpp:148
bool digital_read_cache(uint8_t pin) override
Definition pca9505.cpp:72
uint8_t input_[BANK_COUNT]
The input pin states per bank - 1 means HIGH, 0 means LOW.
Definition pca9505.h:46
bool write_bank_(uint8_t reg, uint8_t bank, uint8_t value)
Definition pca9505.cpp:120
void digital_write_hw(uint8_t pin, bool value) override
Definition pca9505.cpp:74
bool digital_read_hw(uint8_t pin) override
Definition pca9505.cpp:70
void digital_write(bool value) override
Definition pca9505.cpp:156
void pin_mode(gpio::Flags flags) override
Definition pca9505.cpp:152
size_t dump_summary(char *buffer, size_t len) const override
Definition pca9505.cpp:158
uint16_t flags
uint32_t buffer
Definition gif.cpp:50
@ INTERRUPT_FALLING_EDGE
Definition gpio_pin.h:53
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
ESPHOME_LOG_TAG(TAG, "pca9505")
constexpr float IO
For components that represent GPIO pins like PCF8573.
Definition component.h:41
const void size_t len
Definition hal.h:68