diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 40d8049617..b983fb7636 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -2,6 +2,7 @@ #include "i2c_bus_arduino.h" #include "esphome/core/log.h" +#include #include namespace esphome { @@ -10,6 +11,7 @@ namespace i2c { 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) @@ -90,6 +92,32 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn return ERROR_UNKNOWN; } +void ArduinoI2CBus::recover() { + // Perform I2C bus recovery, see + // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf + // or see the linux kernel implementation, e.g. + // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + + // try to get about 100kHz toggle frequency + const auto half_period_usec = 1000000 / 100000 / 2; + const auto recover_scl_periods = 9; + + // configure scl as output + pinMode(scl_pin_, OUTPUT); // NOLINT + + // set scl high + digitalWrite(scl_pin_, 1); // NOLINT + + // in total generate 9 falling-rising edges + for (auto i = 0; i < recover_scl_periods; i++) { + delayMicroseconds(half_period_usec); + digitalWrite(scl_pin_, 0); // NOLINT + delayMicroseconds(half_period_usec); + digitalWrite(scl_pin_, 1); // NOLINT + } + + delayMicroseconds(half_period_usec); +} } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_arduino.h b/esphome/components/i2c/i2c_bus_arduino.h index 220027b3d4..49be0c358c 100644 --- a/esphome/components/i2c/i2c_bus_arduino.h +++ b/esphome/components/i2c/i2c_bus_arduino.h @@ -22,6 +22,9 @@ class ArduinoI2CBus : public I2CBus, public Component { void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + private: + void recover(); + protected: TwoWire *wire_; bool scan_; diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 8bf97b63ec..5ce5d40c00 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -1,6 +1,7 @@ #ifdef USE_ESP_IDF #include "i2c_bus_esp_idf.h" +#include "esphome/core/hal.h" #include "esphome/core/log.h" #include @@ -13,6 +14,8 @@ void IDFI2CBus::setup() { static i2c_port_t next_port = 0; port_ = next_port++; + recover(); + i2c_config_t conf{}; memset(&conf, 0, sizeof(conf)); conf.mode = I2C_MODE_MASTER; @@ -141,6 +144,41 @@ ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) { return ERROR_OK; } +void IDFI2CBus::recover() { + // Perform I2C bus recovery, see + // https://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf + // or see the linux kernel implementation, e.g. + // https://elixir.bootlin.com/linux/v5.14.6/source/drivers/i2c/i2c-core-base.c#L200 + + // try to get about 100kHz toggle frequency + const auto half_period_usec = 1000000 / 100000 / 2; + const auto recover_scl_periods = 9; + const gpio_num_t scl_pin = static_cast(scl_pin_); + + // configure scl as output + gpio_config_t conf{}; + conf.pin_bit_mask = 1ULL << static_cast(scl_pin_); + conf.mode = GPIO_MODE_OUTPUT; + conf.pull_up_en = GPIO_PULLUP_DISABLE; + conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + conf.intr_type = GPIO_INTR_DISABLE; + + gpio_config(&conf); + + // set scl high + gpio_set_level(scl_pin, 1); + + // in total generate 9 falling-rising edges + for (auto i = 0; i < recover_scl_periods; i++) { + delayMicroseconds(half_period_usec); + gpio_set_level(scl_pin, 0); + delayMicroseconds(half_period_usec); + gpio_set_level(scl_pin, 1); + } + + delayMicroseconds(half_period_usec); +} + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c_bus_esp_idf.h b/esphome/components/i2c/i2c_bus_esp_idf.h index c7e67145a3..9985e618f8 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.h +++ b/esphome/components/i2c/i2c_bus_esp_idf.h @@ -24,6 +24,9 @@ class IDFI2CBus : public I2CBus, public Component { void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } void set_frequency(uint32_t frequency) { frequency_ = frequency; } + private: + void recover(); + protected: i2c_port_t port_; bool scan_;