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