ESPHome  2024.4.1
pca9554.cpp
Go to the documentation of this file.
1 #include "pca9554.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace pca9554 {
6 
7 // for 16 bit expanders, these addresses will be doubled.
8 const uint8_t INPUT_REG = 0;
9 const uint8_t OUTPUT_REG = 1;
10 const uint8_t INVERT_REG = 2;
11 const uint8_t CONFIG_REG = 3;
12 
13 static const char *const TAG = "pca9554";
14 
16  ESP_LOGCONFIG(TAG, "Setting up PCA9554/PCA9554A...");
17  this->reg_width_ = (this->pin_count_ + 7) / 8;
18  // Test to see if device exists
19  if (!this->read_inputs_()) {
20  ESP_LOGE(TAG, "PCA95xx not detected at 0x%02X", this->address_);
21  this->mark_failed();
22  return;
23  }
24 
25  // No polarity inversion
26  this->write_register_(INVERT_REG, 0);
27  // All inputs at initialization
28  this->config_mask_ = 0;
29  // Invert mask as the part sees a 1 as an input
30  this->write_register_(CONFIG_REG, ~this->config_mask_);
31  // All outputs low
32  this->output_mask_ = 0;
33  this->write_register_(OUTPUT_REG, this->output_mask_);
34  // Read the inputs
35  this->read_inputs_();
36  ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
37  this->status_has_error());
38 }
39 
41  // The read_inputs_() method will cache the input values from the chip.
42  this->read_inputs_();
43  // Clear all the previously read flags.
44  this->was_previously_read_ = 0x00;
45 }
46 
48  ESP_LOGCONFIG(TAG, "PCA9554:");
49  ESP_LOGCONFIG(TAG, " I/O Pins: %d", this->pin_count_);
50  LOG_I2C_DEVICE(this)
51  if (this->is_failed()) {
52  ESP_LOGE(TAG, "Communication with PCA9554 failed!");
53  }
54 }
55 
56 bool PCA9554Component::digital_read(uint8_t pin) {
57  // Note: We want to try and avoid doing any I2C bus read transactions here
58  // to conserve I2C bus bandwidth. So what we do is check to see if we
59  // have seen a read during the time esphome is running this loop. If we have,
60  // we do an I2C bus transaction to get the latest value. If we haven't
61  // we return a cached value which was read at the time loop() was called.
62  if (this->was_previously_read_ & (1 << pin))
63  this->read_inputs_(); // Force a read of a new value
64  // Indicate we saw a read request for this pin in case a
65  // read happens later in the same loop.
66  this->was_previously_read_ |= (1 << pin);
67  return this->input_mask_ & (1 << pin);
68 }
69 
70 void PCA9554Component::digital_write(uint8_t pin, bool value) {
71  if (value) {
72  this->output_mask_ |= (1 << pin);
73  } else {
74  this->output_mask_ &= ~(1 << pin);
75  }
76  this->write_register_(OUTPUT_REG, this->output_mask_);
77 }
78 
80  if (flags == gpio::FLAG_INPUT) {
81  // Clear mode mask bit
82  this->config_mask_ &= ~(1 << pin);
83  } else if (flags == gpio::FLAG_OUTPUT) {
84  // Set mode mask bit
85  this->config_mask_ |= 1 << pin;
86  }
87  this->write_register_(CONFIG_REG, ~this->config_mask_);
88 }
89 
91  uint8_t inputs[2];
92 
93  if (this->is_failed()) {
94  ESP_LOGD(TAG, "Device marked failed");
95  return false;
96  }
97 
98  if ((this->last_error_ = this->read_register(INPUT_REG * this->reg_width_, inputs, this->reg_width_, true)) !=
100  this->status_set_warning();
101  ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
102  return false;
103  }
104  this->status_clear_warning();
105  this->input_mask_ = inputs[0];
106  if (this->reg_width_ == 2) {
107  this->input_mask_ |= inputs[1] << 8;
108  }
109  return true;
110 }
111 
112 bool PCA9554Component::write_register_(uint8_t reg, uint16_t value) {
113  uint8_t outputs[2];
114  outputs[0] = (uint8_t) value;
115  outputs[1] = (uint8_t) (value >> 8);
116  if ((this->last_error_ = this->write_register(reg * this->reg_width_, outputs, this->reg_width_, true)) !=
118  this->status_set_warning();
119  ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
120  return false;
121  }
122 
123  this->status_clear_warning();
124  return true;
125 }
126 
128 
129 // Run our loop() method very early in the loop, so that we cache read values before
130 // before other components call our digital_read() method.
131 float PCA9554Component::get_loop_priority() const { return 9.0f; } // Just after WIFI
132 
133 void PCA9554GPIOPin::setup() { pin_mode(flags_); }
134 void PCA9554GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
135 bool PCA9554GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
136 void PCA9554GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
137 std::string PCA9554GPIOPin::dump_summary() const {
138  char buffer[32];
139  snprintf(buffer, sizeof(buffer), "%u via PCA9554", pin_);
140  return buffer;
141 }
142 
143 } // namespace pca9554
144 } // namespace esphome
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop=true)
reads an array of bytes from a specific register in the I²C device
Definition: i2c.cpp:10
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
bool status_has_warning() const
Definition: component.cpp:149
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition: i2c.h:149
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:51
const uint8_t OUTPUT_REG
Definition: pca9554.cpp:9
std::string dump_summary() const override
Definition: pca9554.cpp:137
void pin_mode(gpio::Flags flags) override
Definition: pca9554.cpp:134
float get_setup_priority() const override
Definition: pca9554.cpp:127
void digital_write(bool value) override
Definition: pca9554.cpp:136
uint16_t was_previously_read_
Flags to check if read previously during this loop.
Definition: pca9554.h:49
bool status_has_error() const
Definition: component.cpp:150
void digital_write(uint8_t pin, bool value)
Helper function to write the value of a pin.
Definition: pca9554.cpp:70
No error found during execution of method.
Definition: i2c_bus.h:13
size_t pin_count_
number of bits the expander has
Definition: pca9554.h:39
void status_clear_warning()
Definition: component.cpp:166
bool digital_read(uint8_t pin)
Helper function to read the value of a pin.
Definition: pca9554.cpp:56
uint16_t input_mask_
The state of the actual input pin states - 1 means HIGH, 0 means LOW.
Definition: pca9554.h:47
void loop() override
Poll for input changes periodically.
Definition: pca9554.cpp:40
const uint32_t flags
Definition: stm32flash.h:85
uint16_t config_mask_
Mask for the pin config - 1 means OUTPUT, 0 means INPUT.
Definition: pca9554.h:43
uint16_t output_mask_
The mask to write as output state - 1 means HIGH, 0 means LOW.
Definition: pca9554.h:45
uint8_t address_
store the address of the device on the bus
Definition: i2c.h:269
size_t reg_width_
width of registers
Definition: pca9554.h:41
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
const float IO
For components that represent GPIO pins like PCF8573.
Definition: component.cpp:17
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
const uint8_t CONFIG_REG
Definition: pca9554.cpp:11
void pin_mode(uint8_t pin, gpio::Flags flags)
Helper function to set the pin mode of a pin.
Definition: pca9554.cpp:79
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop=true)
writes an array of bytes to a specific register in the I²C device
Definition: i2c.cpp:25
const uint8_t INPUT_REG
Definition: pca9554.cpp:8
float get_loop_priority() const override
Definition: pca9554.cpp:131
bool write_register_(uint8_t reg, uint16_t value)
Definition: pca9554.cpp:112
const uint8_t INVERT_REG
Definition: pca9554.cpp:10