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