1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-05 04:42:21 +01:00

Fix I2C recovery ESP32 esp-idf (#2438)

Co-authored-by: Maurice Makaay <mmakaay1@xs4all.net>
This commit is contained in:
Maurice Makaay
2021-10-04 12:33:25 +02:00
committed by GitHub
parent 49f46a7cdd
commit 5c06cd8eb3
3 changed files with 137 additions and 44 deletions

View File

@@ -12,6 +12,7 @@ static const char *const TAG = "i2c.arduino";
void ArduinoI2CBus::setup() {
recover_();
#ifdef USE_ESP32
static uint8_t next_bus_num = 0;
if (next_bus_num == 0)
@@ -109,11 +110,19 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn
void ArduinoI2CBus::recover_() {
ESP_LOGI(TAG, "Performing I2C bus recovery");
// Activate the pull up resistor on the SCL pin. This should make the
// signal on the line HIGH. If SCL is pulled low on the I2C bus however,
// then some device is interfering with the SCL line. In that case,
// the I2C bus cannot be recovered.
pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
// For the upcoming operations, target for a 100kHz toggle frequency.
// This is the maximum frequency for I2C running in standard-mode.
// The actual frequency will be lower, because of the additional
// function calls that are done, but that is no problem.
const auto half_period_usec = 1000000 / 100000 / 2;
// Activate input and pull up resistor for the SCL pin.
pinMode(scl_pin_, INPUT_PULLUP); // NOLINT
// This should make the signal on the line HIGH. If SCL is pulled low
// on the I2C bus however, then some device is interfering with the SCL
// line. In that case, the I2C bus cannot be recovered.
delayMicroseconds(half_period_usec);
if (digitalRead(scl_pin_) == LOW) { // NOLINT
ESP_LOGE(TAG, "Recovery failed: SCL is held LOW on the I2C bus");
recovery_result_ = RECOVERY_FAILED_SCL_LOW;
@@ -125,25 +134,13 @@ void ArduinoI2CBus::recover_() {
// device that held the bus LOW should release it sometime within
// those nine clocks."
// We don't really have to detect if SDA is stuck low. We'll simply send
// nine clock pulses here, just in case SDA is stuck.
// nine clock pulses here, just in case SDA is stuck. Actual checks on
// the SDA line status will be done after the clock pulses.
// Use a 100kHz toggle frequency (i.e. the maximum frequency for I2C
// running in standard-mode). The resulting frequency will be lower,
// because of the additional function calls that are done, but that
// is no problem.
const auto half_period_usec = 1000000 / 100000 / 2;
// Make sure that switching to mode OUTPUT will make SCL low, just in
// case other code has setup the pin to output a HIGH signal.
// Make sure that switching to output mode will make SCL low, just in
// case other code has setup the pin for a HIGH signal.
digitalWrite(scl_pin_, LOW); // NOLINT
// Activate the pull up resistor for SDA, so after the clock pulse cycle
// we can verify if SDA is pulled high. Also make sure that switching to
// mode OUTPUT will make SDA low.
pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
digitalWrite(sda_pin_, LOW); // NOLINT
ESP_LOGI(TAG, "Sending 9 clock pulses to drain any stuck device output");
delayMicroseconds(half_period_usec);
for (auto i = 0; i < 9; i++) {
// Release pull up resistor and switch to output to make the signal LOW.
@@ -171,6 +168,11 @@ void ArduinoI2CBus::recover_() {
}
}
// Activate input and pull resistor for the SDA pin, so we can verify
// that SDA is pulled HIGH in the following step.
pinMode(sda_pin_, INPUT_PULLUP); // NOLINT
digitalWrite(sda_pin_, LOW); // NOLINT
// By now, any stuck device ought to have sent all remaining bits of its
// transation, meaning that it should have freed up the SDA line, resulting
// in SDA being pulled up.
@@ -191,10 +193,9 @@ void ArduinoI2CBus::recover_() {
// out of this state.
// SCL and SDA are already high at this point, so we can generate a START
// condition by making the SDA signal LOW.
ESP_LOGI(TAG, "Generate START condition to reset bus logic of I2C devices");
delayMicroseconds(half_period_usec);
pinMode(sda_pin_, INPUT); // NOLINT
pinMode(sda_pin_, OUTPUT); // NOLINT
delayMicroseconds(half_period_usec);
// From the specification:
// "A START condition immediately followed by a STOP condition (void
@@ -202,7 +203,7 @@ void ArduinoI2CBus::recover_() {
// operate properly under this condition."
// Finally, we'll bring the I2C bus into a starting state by generating
// a STOP condition.
ESP_LOGI(TAG, "Generate STOP condition to finalize recovery");
delayMicroseconds(half_period_usec);
pinMode(sda_pin_, INPUT); // NOLINT
pinMode(sda_pin_, INPUT_PULLUP); // NOLINT