ESPHome 2025.8.2
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 <Arduino.h>
5#include <cstring>
8#include "esphome/core/log.h"
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 this->port_ = next_bus_num;
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::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
115 uint8_t *read_buffer, size_t read_count) {
116#if defined(USE_ESP8266)
117 this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances
118#endif
119 if (!initialized_) {
120 ESP_LOGD(TAG, "i2c bus not initialized!");
122 }
123
124 ESP_LOGV(TAG, "0x%02X TX %s", address, format_hex_pretty(write_buffer, write_count).c_str());
125
126 uint8_t status = 0;
127 if (write_count != 0 || read_count == 0) {
128 wire_->beginTransmission(address);
129 size_t ret = wire_->write(write_buffer, write_count);
130 if (ret != write_count) {
131 ESP_LOGV(TAG, "TX failed");
132 return ERROR_UNKNOWN;
133 }
134 status = wire_->endTransmission(read_count == 0);
135 }
136 if (status == 0 && read_count != 0) {
137 size_t ret2 = wire_->requestFrom(address, read_count, true);
138 if (ret2 != read_count) {
139 ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", read_count, address, ret2);
140 return ERROR_TIMEOUT;
141 }
142 for (size_t j = 0; j != read_count; j++)
143 read_buffer[j] = wire_->read();
144 }
145 switch (status) {
146 case 0:
147 return ERROR_OK;
148 case 1:
149 // transmit buffer not large enough
150 ESP_LOGVV(TAG, "TX failed: buffer not large enough");
151 return ERROR_UNKNOWN;
152 case 2:
153 case 3:
154 ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status);
156 case 5:
157 ESP_LOGVV(TAG, "TX failed: timeout");
158 return ERROR_UNKNOWN;
159 case 4:
160 default:
161 ESP_LOGVV(TAG, "TX failed: unknown error %u", status);
162 return ERROR_UNKNOWN;
163 }
164}
165
169void ArduinoI2CBus::recover_() {
170 ESP_LOGI(TAG, "Performing bus recovery");
171
172 // For the upcoming operations, target for a 100kHz toggle frequency.
173 // This is the maximum frequency for I2C running in standard-mode.
174 // The actual frequency will be lower, because of the additional
175 // function calls that are done, but that is no problem.
176 const auto half_period_usec = 1000000 / 100000 / 2;
177
178 // Activate input and pull up resistor for the SCL pin.
179 pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
180
181 // This should make the signal on the line HIGH. If SCL is pulled low
182 // on the I2C bus however, then some device is interfering with the SCL
183 // line. In that case, the I2C bus cannot be recovered.
184 delayMicroseconds(half_period_usec);
185 if (digitalRead(scl_pin_) == LOW) { // NOLINT
186 ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the bus");
187 recovery_result_ = RECOVERY_FAILED_SCL_LOW;
188 return;
189 }
190
191 // From the specification:
192 // "If the data line (SDA) is stuck LOW, send nine clock pulses. The
193 // device that held the bus LOW should release it sometime within
194 // those nine clocks."
195 // We don't really have to detect if SDA is stuck low. We'll simply send
196 // nine clock pulses here, just in case SDA is stuck. Actual checks on
197 // the SDA line status will be done after the clock pulses.
198
199 // Make sure that switching to output mode will make SCL low, just in
200 // case other code has setup the pin for a HIGH signal.
201 digitalWrite(scl_pin_, LOW); // NOLINT
202
203 delayMicroseconds(half_period_usec);
204 for (auto i = 0; i < 9; i++) {
205 // Release pull up resistor and switch to output to make the signal LOW.
206 pinMode(scl_pin_, INPUT); // NOLINT
207 pinMode(scl_pin_, OUTPUT); // NOLINT
208 delayMicroseconds(half_period_usec);
209
210 // Release output and activate pull up resistor to make the signal HIGH.
211 pinMode(scl_pin_, INPUT); // NOLINT
212 pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
213 delayMicroseconds(half_period_usec);
214
215 // When SCL is kept LOW at this point, we might be looking at a device
216 // that applies clock stretching. Wait for the release of the SCL line,
217 // but not forever. There is no specification for the maximum allowed
218 // time. We yield and reset the WDT, so as to avoid triggering reset.
219 // No point in trying to recover the bus by forcing a uC reset. Bus
220 // should recover in a few ms or less else not likely to recovery at
221 // all.
222 auto wait = 250;
223 while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT
224 App.feed_wdt();
225 delayMicroseconds(half_period_usec * 2);
226 }
227 if (digitalRead(scl_pin_) == LOW) { // NOLINT
228 ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");
229 recovery_result_ = RECOVERY_FAILED_SCL_LOW;
230 return;
231 }
232 }
233
234 // Activate input and pull resistor for the SDA pin, so we can verify
235 // that SDA is pulled HIGH in the following step.
236 pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
237 digitalWrite(sda_pin_, LOW); // NOLINT
238
239 // By now, any stuck device ought to have sent all remaining bits of its
240 // transaction, meaning that it should have freed up the SDA line, resulting
241 // in SDA being pulled up.
242 if (digitalRead(sda_pin_) == LOW) { // NOLINT
243 ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle");
244 recovery_result_ = RECOVERY_FAILED_SDA_LOW;
245 return;
246 }
247
248 // From the specification:
249 // "I2C-bus compatible devices must reset their bus logic on receipt of
250 // a START or repeated START condition such that they all anticipate
251 // the sending of a target address, even if these START conditions are
252 // not positioned according to the proper format."
253 // While the 9 clock pulses from above might have drained all bits of a
254 // single byte within a transaction, a device might have more bytes to
255 // transmit. So here we'll generate a START condition to snap the device
256 // out of this state.
257 // SCL and SDA are already high at this point, so we can generate a START
258 // condition by making the SDA signal LOW.
259 delayMicroseconds(half_period_usec);
260 pinMode(sda_pin_, INPUT); // NOLINT
261 pinMode(sda_pin_, OUTPUT); // NOLINT
262
263 // From the specification:
264 // "A START condition immediately followed by a STOP condition (void
265 // message) is an illegal format. Many devices however are designed to
266 // operate properly under this condition."
267 // Finally, we'll bring the I2C bus into a starting state by generating
268 // a STOP condition.
269 delayMicroseconds(half_period_usec);
270 pinMode(sda_pin_, INPUT); // NOLINT
271 pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
272
273 recovery_result_ = RECOVERY_COMPLETED;
274}
275} // namespace i2c
276} // namespace esphome
277
278#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 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:106
std::vector< std::pair< uint8_t, bool > > scan_results_
array containing scan results
Definition i2c_bus.h:105
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
Definition i2c_bus.h:14
@ ERROR_TIMEOUT
timeout while waiting to receive bytes
Definition i2c_bus.h:19
@ ERROR_NOT_ACKNOWLEDGED
I2C bus acknowledgment not received.
Definition i2c_bus.h:18
@ ERROR_NOT_INITIALIZED
call method to a not initialized bus
Definition i2c_bus.h:20
@ ERROR_UNKNOWN
miscellaneous I2C error during execution
Definition i2c_bus.h:22
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition core.cpp:31
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:280
Application App
Global storage of Application pointer - only one Application can exist.