ESPHome  2022.12.8
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"
6 #include <Arduino.h>
7 #include <cstring>
8 
9 namespace esphome {
10 namespace i2c {
11 
12 static const char *const TAG = "i2c.arduino";
13 
15  recover_();
16 
17 #if defined(USE_ESP32)
18  static uint8_t next_bus_num = 0;
19  if (next_bus_num == 0) {
20  wire_ = &Wire;
21  } else {
22  wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory)
23  }
24  next_bus_num++;
25 #elif defined(USE_ESP8266)
26  wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
27 #elif defined(USE_RP2040)
28  static bool first = true;
29  if (first) {
30  wire_ = &Wire;
31  first = false;
32  } else {
33  wire_ = &Wire1; // NOLINT(cppcoreguidelines-owning-memory)
34  }
35 #endif
36 
37 #ifdef USE_RP2040
38  wire_->setSDA(this->sda_pin_);
39  wire_->setSCL(this->scl_pin_);
40  wire_->begin();
41 #else
42  wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
43 #endif
44  wire_->setClock(frequency_);
45  initialized_ = true;
46  if (this->scan_) {
47  ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
48  this->i2c_scan_();
49  }
50 }
52  ESP_LOGCONFIG(TAG, "I2C Bus:");
53  ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
54  ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
55  ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_);
56  switch (this->recovery_result_) {
57  case RECOVERY_COMPLETED:
58  ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
59  break;
61  ESP_LOGCONFIG(TAG, " Recovery: failed, SCL is held low on the bus");
62  break;
64  ESP_LOGCONFIG(TAG, " Recovery: failed, SDA is held low on the bus");
65  break;
66  }
67  if (this->scan_) {
68  ESP_LOGI(TAG, "Results from i2c bus scan:");
69  if (scan_results_.empty()) {
70  ESP_LOGI(TAG, "Found no i2c devices!");
71  } else {
72  for (const auto &s : scan_results_) {
73  if (s.second) {
74  ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first);
75  } else {
76  ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
77  }
78  }
79  }
80  }
81 }
82 
83 ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
84  // logging is only enabled with vv level, if warnings are shown the caller
85  // should log them
86  if (!initialized_) {
87  ESP_LOGVV(TAG, "i2c bus not initialized!");
88  return ERROR_NOT_INITIALIZED;
89  }
90  size_t to_request = 0;
91  for (size_t i = 0; i < cnt; i++)
92  to_request += buffers[i].len;
93  size_t ret = wire_->requestFrom((int) address, (int) to_request, 1);
94  if (ret != to_request) {
95  ESP_LOGVV(TAG, "RX %u from %02X failed with error %u", to_request, address, ret);
96  return ERROR_TIMEOUT;
97  }
98 
99  for (size_t i = 0; i < cnt; i++) {
100  const auto &buf = buffers[i];
101  for (size_t j = 0; j < buf.len; j++)
102  buf.data[j] = wire_->read();
103  }
104 
105 #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
106  char debug_buf[4];
107  std::string debug_hex;
108 
109  for (size_t i = 0; i < cnt; i++) {
110  const auto &buf = buffers[i];
111  for (size_t j = 0; j < buf.len; j++) {
112  snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
113  debug_hex += debug_buf;
114  }
115  }
116  ESP_LOGVV(TAG, "0x%02X RX %s", address, debug_hex.c_str());
117 #endif
118 
119  return ERROR_OK;
120 }
121 ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
122  // logging is only enabled with vv level, if warnings are shown the caller
123  // should log them
124  if (!initialized_) {
125  ESP_LOGVV(TAG, "i2c bus not initialized!");
126  return ERROR_NOT_INITIALIZED;
127  }
128 
129 #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
130  char debug_buf[4];
131  std::string debug_hex;
132 
133  for (size_t i = 0; i < cnt; i++) {
134  const auto &buf = buffers[i];
135  for (size_t j = 0; j < buf.len; j++) {
136  snprintf(debug_buf, sizeof(debug_buf), "%02X", buf.data[j]);
137  debug_hex += debug_buf;
138  }
139  }
140  ESP_LOGVV(TAG, "0x%02X TX %s", address, debug_hex.c_str());
141 #endif
142 
143  wire_->beginTransmission(address);
144  size_t written = 0;
145  for (size_t i = 0; i < cnt; i++) {
146  const auto &buf = buffers[i];
147  if (buf.len == 0)
148  continue;
149  size_t ret = wire_->write(buf.data, buf.len);
150  written += ret;
151  if (ret != buf.len) {
152  ESP_LOGVV(TAG, "TX failed at %u", written);
153  return ERROR_UNKNOWN;
154  }
155  }
156  uint8_t status = wire_->endTransmission(stop);
157  if (status == 0) {
158  return ERROR_OK;
159  } else if (status == 1) {
160  // transmit buffer not large enough
161  ESP_LOGVV(TAG, "TX failed: buffer not large enough");
162  return ERROR_UNKNOWN;
163  } else if (status == 2 || status == 3) {
164  ESP_LOGVV(TAG, "TX failed: not acknowledged");
165  return ERROR_NOT_ACKNOWLEDGED;
166  }
167  ESP_LOGVV(TAG, "TX failed: unknown error %u", status);
168  return ERROR_UNKNOWN;
169 }
170 
174 void ArduinoI2CBus::recover_() {
175  ESP_LOGI(TAG, "Performing I2C bus recovery");
176 
177  // For the upcoming operations, target for a 100kHz toggle frequency.
178  // This is the maximum frequency for I2C running in standard-mode.
179  // The actual frequency will be lower, because of the additional
180  // function calls that are done, but that is no problem.
181  const auto half_period_usec = 1000000 / 100000 / 2;
182 
183  // Activate input and pull up resistor for the SCL pin.
184  pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
185 
186  // This should make the signal on the line HIGH. If SCL is pulled low
187  // on the I2C bus however, then some device is interfering with the SCL
188  // line. In that case, the I2C bus cannot be recovered.
189  delayMicroseconds(half_period_usec);
190  if (digitalRead(scl_pin_) == LOW) { // NOLINT
191  ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus");
192  recovery_result_ = RECOVERY_FAILED_SCL_LOW;
193  return;
194  }
195 
196  // From the specification:
197  // "If the data line (SDA) is stuck LOW, send nine clock pulses. The
198  // device that held the bus LOW should release it sometime within
199  // those nine clocks."
200  // We don't really have to detect if SDA is stuck low. We'll simply send
201  // nine clock pulses here, just in case SDA is stuck. Actual checks on
202  // the SDA line status will be done after the clock pulses.
203 
204  // Make sure that switching to output mode will make SCL low, just in
205  // case other code has setup the pin for a HIGH signal.
206  digitalWrite(scl_pin_, LOW); // NOLINT
207 
208  delayMicroseconds(half_period_usec);
209  for (auto i = 0; i < 9; i++) {
210  // Release pull up resistor and switch to output to make the signal LOW.
211  pinMode(scl_pin_, INPUT); // NOLINT
212  pinMode(scl_pin_, OUTPUT); // NOLINT
213  delayMicroseconds(half_period_usec);
214 
215  // Release output and activate pull up resistor to make the signal HIGH.
216  pinMode(scl_pin_, INPUT); // NOLINT
217  pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
218  delayMicroseconds(half_period_usec);
219 
220  // When SCL is kept LOW at this point, we might be looking at a device
221  // that applies clock stretching. Wait for the release of the SCL line,
222  // but not forever. There is no specification for the maximum allowed
223  // time. We'll stick to 500ms here.
224  auto wait = 20;
225  while (wait-- && digitalRead(scl_pin_) == LOW) { // NOLINT
226  delay(25);
227  }
228  if (digitalRead(scl_pin_) == LOW) { // NOLINT
229  ESP_LOGE(TAG, "Recovery failed: SCL is held LOW during clock pulse cycle");
230  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(sda_pin_, INPUT_PULLUP); // NOLINT
238  digitalWrite(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(sda_pin_) == LOW) { // NOLINT
244  ESP_LOGE(TAG, "Recovery failed: SDA is held LOW after clock pulse cycle");
245  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(sda_pin_, INPUT); // NOLINT
262  pinMode(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(sda_pin_, INPUT); // NOLINT
272  pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
273 
274  recovery_result_ = RECOVERY_COMPLETED;
275 }
276 } // namespace i2c
277 } // namespace esphome
278 
279 #endif // USE_ESP_IDF
std::vector< std::pair< uint8_t, bool > > scan_results_
Definition: i2c_bus.h:64
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:281
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override
Definition: a4988.cpp:4
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition: core.cpp:29
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:27