Adds i2c timeout config (#4614)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
tracestep 2024-04-08 22:11:46 -03:00 committed by GitHub
parent 16d154e2e5
commit 5441213b27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 90 additions and 9 deletions

View file

@ -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,

View file

@ -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");

View file

@ -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;
};

View file

@ -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

View file

@ -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;
};