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