mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Adds i2c timeout config (#4614)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -4,6 +4,7 @@ import esphome.final_validate as fv | ||||
| from esphome import pins | ||||
| from esphome.const import ( | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_TIMEOUT, | ||||
|     CONF_ID, | ||||
|     CONF_INPUT, | ||||
|     CONF_OUTPUT, | ||||
| @@ -59,6 +60,7 @@ CONFIG_SCHEMA = cv.All( | ||||
|             cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( | ||||
|                 cv.frequency, cv.Range(min=0, min_included=False) | ||||
|             ), | ||||
|             cv.Optional(CONF_TIMEOUT): cv.positive_time_period, | ||||
|             cv.Optional(CONF_SCAN, default=True): cv.boolean, | ||||
|         } | ||||
|     ).extend(cv.COMPONENT_SCHEMA), | ||||
| @@ -81,6 +83,8 @@ async def to_code(config): | ||||
|  | ||||
|     cg.add(var.set_frequency(int(config[CONF_FREQUENCY]))) | ||||
|     cg.add(var.set_scan(config[CONF_SCAN])) | ||||
|     if CONF_TIMEOUT in config: | ||||
|         cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds))) | ||||
|     if CORE.using_arduino: | ||||
|         cg.add_library("Wire", None) | ||||
|  | ||||
| @@ -119,23 +123,56 @@ async def register_i2c_device(var, config): | ||||
|  | ||||
|  | ||||
| def final_validate_device_schema( | ||||
|     name: str, *, min_frequency: cv.frequency = None, max_frequency: cv.frequency = None | ||||
|     name: str, | ||||
|     *, | ||||
|     min_frequency: cv.frequency = None, | ||||
|     max_frequency: cv.frequency = None, | ||||
|     min_timeout: cv.time_period = None, | ||||
|     max_timeout: cv.time_period = None, | ||||
| ): | ||||
|     hub_schema = {} | ||||
|     if min_frequency is not None: | ||||
|     if (min_frequency is not None) and (max_frequency is not None): | ||||
|         hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( | ||||
|             min=cv.frequency(min_frequency), | ||||
|             min_included=True, | ||||
|             max=cv.frequency(max_frequency), | ||||
|             max_included=True, | ||||
|             msg=f"Component {name} requires a frequency between {min_frequency} and {max_frequency} for the I2C bus", | ||||
|         ) | ||||
|     elif min_frequency is not None: | ||||
|         hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( | ||||
|             min=cv.frequency(min_frequency), | ||||
|             min_included=True, | ||||
|             msg=f"Component {name} requires a minimum frequency of {min_frequency} for the I2C bus", | ||||
|         ) | ||||
|  | ||||
|     if max_frequency is not None: | ||||
|     elif max_frequency is not None: | ||||
|         hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( | ||||
|             max=cv.frequency(max_frequency), | ||||
|             max_included=True, | ||||
|             msg=f"Component {name} cannot be used with a frequency of over {max_frequency} for the I2C bus", | ||||
|         ) | ||||
|  | ||||
|     if (min_timeout is not None) and (max_timeout is not None): | ||||
|         hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range( | ||||
|             min=cv.time_period(min_timeout), | ||||
|             min_included=True, | ||||
|             max=cv.time_period(max_timeout), | ||||
|             max_included=True, | ||||
|             msg=f"Component {name} requires a timeout between {min_timeout} and {max_timeout} for the I2C bus", | ||||
|         ) | ||||
|     elif min_timeout is not None: | ||||
|         hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range( | ||||
|             min=cv.time_period(min_timeout), | ||||
|             min_included=True, | ||||
|             msg=f"Component {name} requires a minimum timeout of {min_timeout} for the I2C bus", | ||||
|         ) | ||||
|     elif max_timeout is not None: | ||||
|         hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range( | ||||
|             max=cv.time_period(max_timeout), | ||||
|             max_included=True, | ||||
|             msg=f"Component {name} cannot be used with a timeout of over {max_timeout} for the I2C bus", | ||||
|         ) | ||||
|  | ||||
|     return cv.Schema( | ||||
|         {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, | ||||
|         extra=cv.ALLOW_EXTRA, | ||||
|   | ||||
| @@ -52,6 +52,18 @@ void ArduinoI2CBus::set_pins_and_clock_() { | ||||
| #else | ||||
|   wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_)); | ||||
| #endif | ||||
|   if (timeout_ > 0) {  // if timeout specified in yaml | ||||
| #if defined(USE_ESP32) | ||||
|     // https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.cpp | ||||
|     wire_->setTimeOut(timeout_ / 1000);  // unit: ms | ||||
| #elif defined(USE_ESP8266) | ||||
|     // https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h | ||||
|     wire_->setClockStretchLimit(timeout_);  // unit: us | ||||
| #elif defined(USE_RP2040) | ||||
|     // https://github.com/earlephilhower/ArduinoCore-API/blob/e37df85425e0ac020bfad226d927f9b00d2e0fb7/api/Stream.h | ||||
|     wire_->setTimeout(timeout_ / 1000);  // unit: ms | ||||
| #endif | ||||
|   } | ||||
|   wire_->setClock(frequency_); | ||||
| } | ||||
|  | ||||
| @@ -60,6 +72,15 @@ void ArduinoI2CBus::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "  SDA Pin: GPIO%u", this->sda_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  SCL Pin: GPIO%u", this->scl_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Frequency: %u Hz", this->frequency_); | ||||
|   if (timeout_ > 0) { | ||||
| #if defined(USE_ESP32) | ||||
|     ESP_LOGCONFIG(TAG, "  Timeout: %u ms", this->timeout_ / 1000); | ||||
| #elif defined(USE_ESP8266) | ||||
|     ESP_LOGCONFIG(TAG, "  Timeout: %u us", this->timeout_); | ||||
| #elif defined(USE_RP2040) | ||||
|     ESP_LOGCONFIG(TAG, "  Timeout: %u ms", this->timeout_ / 1000); | ||||
| #endif | ||||
|   } | ||||
|   switch (this->recovery_result_) { | ||||
|     case RECOVERY_COMPLETED: | ||||
|       ESP_LOGCONFIG(TAG, "  Recovery: bus successfully recovered"); | ||||
|   | ||||
| @@ -27,6 +27,7 @@ class ArduinoI2CBus : public I2CBus, public Component { | ||||
|   void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } | ||||
|   void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } | ||||
|   void set_frequency(uint32_t frequency) { frequency_ = frequency; } | ||||
|   void set_timeout(uint32_t timeout) { timeout_ = timeout; } | ||||
|  | ||||
|  private: | ||||
|   void recover_(); | ||||
| @@ -38,6 +39,7 @@ class ArduinoI2CBus : public I2CBus, public Component { | ||||
|   uint8_t sda_pin_; | ||||
|   uint8_t scl_pin_; | ||||
|   uint32_t frequency_; | ||||
|   uint32_t timeout_ = 0; | ||||
|   bool initialized_ = false; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| #ifdef USE_ESP_IDF | ||||
|  | ||||
| #include "i2c_bus_esp_idf.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/application.h" | ||||
| #include <cstring> | ||||
| #include <cinttypes> | ||||
| #include <cstring> | ||||
| #include "esphome/core/application.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace i2c { | ||||
| @@ -45,6 +45,20 @@ void IDFI2CBus::setup() { | ||||
|     this->mark_failed(); | ||||
|     return; | ||||
|   } | ||||
|   if (timeout_ > 0) {  // if timeout specified in yaml: | ||||
|     if (timeout_ > 13000) { | ||||
|       ESP_LOGW(TAG, "i2c timeout of %" PRIu32 "us greater than max of 13ms on esp-idf, setting to max", timeout_); | ||||
|       timeout_ = 13000; | ||||
|     } | ||||
|     err = i2c_set_timeout(port_, timeout_ * 80);  // unit: APB 80MHz clock cycle | ||||
|     if (err != ESP_OK) { | ||||
|       ESP_LOGW(TAG, "i2c_set_timeout failed: %s", esp_err_to_name(err)); | ||||
|       this->mark_failed(); | ||||
|       return; | ||||
|     } else { | ||||
|       ESP_LOGV(TAG, "i2c_timeout set to %d ticks (%d us)", timeout_ * 80, timeout_); | ||||
|     } | ||||
|   } | ||||
|   err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM); | ||||
|   if (err != ESP_OK) { | ||||
|     ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err)); | ||||
| @@ -62,6 +76,9 @@ void IDFI2CBus::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "  SDA Pin: GPIO%u", this->sda_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  SCL Pin: GPIO%u", this->scl_pin_); | ||||
|   ESP_LOGCONFIG(TAG, "  Frequency: %" PRIu32 " Hz", this->frequency_); | ||||
|   if (timeout_ > 0) { | ||||
|     ESP_LOGCONFIG(TAG, "  Timeout: %" PRIu32 "us", this->timeout_); | ||||
|   } | ||||
|   switch (this->recovery_result_) { | ||||
|     case RECOVERY_COMPLETED: | ||||
|       ESP_LOGCONFIG(TAG, "  Recovery: bus successfully recovered"); | ||||
| @@ -127,6 +144,8 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) { | ||||
|     return ERROR_UNKNOWN; | ||||
|   } | ||||
|   err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); | ||||
|   // i2c_master_cmd_begin() will block for a whole second if no ack: | ||||
|   // https://github.com/espressif/esp-idf/issues/4999 | ||||
|   i2c_cmd_link_delete(cmd); | ||||
|   if (err == ESP_FAIL) { | ||||
|     // transfer not acked | ||||
|   | ||||
| @@ -29,6 +29,7 @@ class IDFI2CBus : public I2CBus, public Component { | ||||
|   void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } | ||||
|   void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } | ||||
|   void set_frequency(uint32_t frequency) { frequency_ = frequency; } | ||||
|   void set_timeout(uint32_t timeout) { timeout_ = timeout; } | ||||
|  | ||||
|  private: | ||||
|   void recover_(); | ||||
| @@ -41,6 +42,7 @@ class IDFI2CBus : public I2CBus, public Component { | ||||
|   uint8_t scl_pin_; | ||||
|   bool scl_pullup_enabled_; | ||||
|   uint32_t frequency_; | ||||
|   uint32_t timeout_ = 0; | ||||
|   bool initialized_ = false; | ||||
| }; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user