ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
i2c_bus_arduino.cpp
Go to the documentation of this file.
1#if defined(USE_ARDUINO) && !defined(USE_ESP32)
2
3#include "i2c_bus_arduino.h"
4#include <Arduino.h>
5#include <cstring>
8#include "esphome/core/log.h"
9
10namespace esphome::i2c {
11
12static const char *const TAG = "i2c.arduino";
13
14// Maximum bytes to log in hex format (truncates larger transfers)
15static constexpr size_t I2C_MAX_LOG_BYTES = 32;
16
18 recover_();
19
20#if defined(USE_ESP8266)
21 wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
22#elif defined(USE_RP2040)
23 static bool first = true;
24 if (first) {
25 wire_ = &Wire;
26 first = false;
27 } else {
28 wire_ = &Wire1; // NOLINT(cppcoreguidelines-owning-memory)
29 }
30#endif
31
32 this->set_pins_and_clock_();
33
34 this->initialized_ = true;
35 if (this->scan_) {
36 ESP_LOGV(TAG, "Scanning bus for active devices");
37 this->i2c_scan_();
38 }
39}
40
41void ArduinoI2CBus::set_pins_and_clock_() {
42#ifdef USE_RP2040
43 wire_->setSDA(this->sda_pin_);
44 wire_->setSCL(this->scl_pin_);
45 wire_->begin();
46#else
47 wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
48#endif
49 if (timeout_ > 0) { // if timeout specified in yaml
50#if defined(USE_ESP8266)
51 // https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h
52 wire_->setClockStretchLimit(timeout_); // unit: us
53#elif defined(USE_RP2040)
54 // https://github.com/earlephilhower/ArduinoCore-API/blob/e37df85425e0ac020bfad226d927f9b00d2e0fb7/api/Stream.h
55 wire_->setTimeout(timeout_ / 1000); // unit: ms
56#endif
57 }
58 wire_->setClock(frequency_);
59}
60
62 ESP_LOGCONFIG(TAG, "I2C Bus:");
63 ESP_LOGCONFIG(TAG,
64 " SDA Pin: GPIO%u\n"
65 " SCL Pin: GPIO%u\n"
66 " Frequency: %u Hz",
67 this->sda_pin_, this->scl_pin_, this->frequency_);
68 if (timeout_ > 0) {
69#if defined(USE_ESP8266)
70 ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_);
71#elif defined(USE_RP2040)
72 ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
73#endif
74 }
75 switch (this->recovery_result_) {
77 ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
78 break;
80 ESP_LOGCONFIG(TAG, " Recovery: failed, SCL is held low on the bus");
81 break;
83 ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus");
84 break;
85 }
86 if (this->scan_) {
87 ESP_LOGI(TAG, "Results from bus scan:");
88 if (scan_results_.empty()) {
89 ESP_LOGI(TAG, "Found no devices");
90 } else {
91 for (const auto &s : scan_results_) {
92 if (s.second) {
93 ESP_LOGI(TAG, "Found device at address 0x%02X", s.first);
94 } else {
95 ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
96 }
97 }
98 }
99 }
100}
101
102ErrorCode ArduinoI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
103 uint8_t *read_buffer, size_t read_count) {
104#if defined(USE_ESP8266)
105 this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances
106#endif
107 if (!initialized_) {
108 ESP_LOGD(TAG, "i2c bus not initialized!");
110 }
111
112#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
113 char hex_buf[format_hex_pretty_size(I2C_MAX_LOG_BYTES)];
114 ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty_to(hex_buf, write_buffer, write_count));
115#endif
116
117 uint8_t status = 0;
118 if (write_count != 0 || read_count == 0) {
119 wire_->beginTransmission(address);
120 size_t ret = wire_->write(write_buffer, write_count);
121 if (ret != write_count) {
122 ESP_LOGV(TAG, "TX failed");
123 return ERROR_UNKNOWN;
124 }
125 status = wire_->endTransmission(read_count == 0);
126 }
127 if (status == 0 && read_count != 0) {
128 size_t ret2 = wire_->requestFrom(address, read_count, true);
129 if (ret2 != read_count) {
130 ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", read_count, address, ret2);
131 return ERROR_TIMEOUT;
132 }
133 for (size_t j = 0; j != read_count; j++)
134 read_buffer[j] = wire_->read();
135 }
136 // Avoid switch to prevent compiler-generated lookup table in RAM on ESP8266
137 if (status == 0)
138 return ERROR_OK;
139 if (status == 1) {
140 ESP_LOGVV(TAG, "TX failed: buffer not large enough");
141 return ERROR_UNKNOWN;
142 }
143 if (status == 2 || status == 3) {
144 ESP_LOGVV(TAG, "TX failed: not acknowledged: %u", status);
146 }
147 if (status == 5) {
148 ESP_LOGVV(TAG, "TX failed: timeout");
149 return ERROR_UNKNOWN;
150 }
151 ESP_LOGVV(TAG, "TX failed: unknown error %u", status);
152 return ERROR_UNKNOWN;
153}
154
158void ArduinoI2CBus::recover_() {
159 ESP_LOGI(TAG, "Performing bus recovery");
160
161 // For the upcoming operations, target for a 100kHz toggle frequency.
162 // This is the maximum frequency for I2C running in standard-mode.
163 // The actual frequency will be lower, because of the additional
164 // function calls that are done, but that is no problem.
165 const auto half_period_usec = 1000000 / 100000 / 2;
166
167 // Activate input and pull up resistor for the SCL pin.
168 pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
169
170 // This should make the signal on the line HIGH. If SCL is pulled low
171 // on the I2C bus however, then some device is interfering with the SCL
172 // line. In that case, the I2C bus cannot be recovered.
173 delayMicroseconds(half_period_usec);
174 if (digitalRead(scl_pin_) == LOW) { // NOLINT
175 ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the bus");
176 recovery_result_ = RECOVERY_FAILED_SCL_LOW;
177 return;
178 }
179
180 // From the specification:
181 // "If the data line (SDA) is stuck LOW, send nine clock pulses. The
182 // device that held the bus LOW should release it sometime within
183 // those nine clocks."
184 // We don't really have to detect if SDA is stuck low. We'll simply send
185 // nine clock pulses here, just in case SDA is stuck. Actual checks on
186 // the SDA line status will be done after the clock pulses.
187
188 // Make sure that switching to output mode will make SCL low, just in
189 // case other code has setup the pin for a HIGH signal.
190 digitalWrite(scl_pin_, LOW); // NOLINT
191
192 delayMicroseconds(half_period_usec);
193 for (auto i = 0; i < 9; i++) {
194 // Release pull up resistor and switch to output to make the signal LOW.
195 pinMode(scl_pin_, INPUT); // NOLINT
196 pinMode(scl_pin_, OUTPUT); // NOLINT
197 delayMicroseconds(half_period_usec);
198
199 // Release output and activate pull up resistor to make the signal HIGH.
200 pinMode(scl_pin_, INPUT); // NOLINT
201 pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
202 delayMicroseconds(half_period_usec);
203
204 // When SCL is kept LOW at this point, we might be looking at a device
205 // that applies clock stretching. Wait for the release of the SCL line,
206 // but not forever. There is no specification for the maximum allowed
207 // time. We yield and reset the WDT, so as to avoid triggering reset.
208 // No point in trying to recover the bus by forcing a uC reset. Bus
209 // should recover in a few ms or less else not likely to recovery at
210 // all.
211 auto wait = 250;
212 while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT
213 App.feed_wdt();
214 delayMicroseconds(half_period_usec * 2);
215 }
216 if (digitalRead(scl_pin_) == LOW) { // NOLINT
217 ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");
218 recovery_result_ = RECOVERY_FAILED_SCL_LOW;
219 return;
220 }
221 }
222
223 // Activate input and pull resistor for the SDA pin, so we can verify
224 // that SDA is pulled HIGH in the following step.
225 pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
226 digitalWrite(sda_pin_, LOW); // NOLINT
227
228 // By now, any stuck device ought to have sent all remaining bits of its
229 // transaction, meaning that it should have freed up the SDA line, resulting
230 // in SDA being pulled up.
231 if (digitalRead(sda_pin_) == LOW) { // NOLINT
232 ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle");
233 recovery_result_ = RECOVERY_FAILED_SDA_LOW;
234 return;
235 }
236
237 // From the specification:
238 // "I2C-bus compatible devices must reset their bus logic on receipt of
239 // a START or repeated START condition such that they all anticipate
240 // the sending of a target address, even if these START conditions are
241 // not positioned according to the proper format."
242 // While the 9 clock pulses from above might have drained all bits of a
243 // single byte within a transaction, a device might have more bytes to
244 // transmit. So here we'll generate a START condition to snap the device
245 // out of this state.
246 // SCL and SDA are already high at this point, so we can generate a START
247 // condition by making the SDA signal LOW.
248 delayMicroseconds(half_period_usec);
249 pinMode(sda_pin_, INPUT); // NOLINT
250 pinMode(sda_pin_, OUTPUT); // NOLINT
251
252 // From the specification:
253 // "A START condition immediately followed by a STOP condition (void
254 // message) is an illegal format. Many devices however are designed to
255 // operate properly under this condition."
256 // Finally, we'll bring the I2C bus into a starting state by generating
257 // a STOP condition.
258 delayMicroseconds(half_period_usec);
259 pinMode(sda_pin_, INPUT); // NOLINT
260 pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
261
262 recovery_result_ = RECOVERY_COMPLETED;
263}
264} // namespace esphome::i2c
265
266#endif // defined(USE_ARDUINO) && !defined(USE_ESP32)
uint8_t address
Definition bl0906.h:4
uint8_t status
Definition bl0942.h:8
void feed_wdt(uint32_t time=0)
ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer, size_t read_count) override
bool scan_
Should we scan ? Can be set in the yaml.
Definition i2c_bus.h:61
std::vector< std::pair< uint8_t, bool > > scan_results_
array containing scan results
Definition i2c_bus.h:60
void i2c_scan_()
Scans the I2C bus for devices.
Definition i2c.cpp:12
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition i2c_bus.h:12
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:14
@ ERROR_TIMEOUT
timeout while waiting to receive bytes
Definition i2c_bus.h:17
@ ERROR_NOT_ACKNOWLEDGED
I2C bus acknowledgment not received.
Definition i2c_bus.h:16
@ ERROR_NOT_INITIALIZED
call method to a not initialized bus
Definition i2c_bus.h:18
@ ERROR_UNKNOWN
miscellaneous I2C error during execution
Definition i2c_bus.h:20
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:29
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:353
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1103
Application App
Global storage of Application pointer - only one Application can exist.