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