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