[i2c] Fix port logic with ESP-IDF (#12063)

Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
Sébastien Blanchet
2025-12-08 14:12:15 -05:00
committed by GitHub
parent 9f60aed9b0
commit 7a20c85eec
7 changed files with 146 additions and 19 deletions

View File

@@ -1,9 +1,12 @@
import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
from esphome.pins import check_strapping_pin
# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c5/include/hal/i2c_ll.h
_ESP32C5_I2C_LP_PINS = {"SDA": 2, "SCL": 3}
_ESP32C5_SPI_PSRAM_PINS = {
16: "SPICS0",
17: "SPIQ",
@@ -43,3 +46,13 @@ def esp32_c5_validate_supports(value):
check_strapping_pin(value, _ESP32C5_STRAPPING_PINS, _LOGGER)
return value
def esp32_c5_validate_lp_i2c(value):
lp_sda_pin = _ESP32C5_I2C_LP_PINS["SDA"]
lp_scl_pin = _ESP32C5_I2C_LP_PINS["SCL"]
if int(value[CONF_SDA]) != lp_sda_pin or int(value[CONF_SCL]) != lp_scl_pin:
raise cv.Invalid(
f"Low power i2c interface is only supported on GPIO{lp_sda_pin} SDA and GPIO{lp_scl_pin} SCL for ESP32-C5"
)
return value

View File

@@ -1,9 +1,12 @@
import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
from esphome.pins import check_strapping_pin
# https://github.com/espressif/esp-idf/blob/master/components/esp_hal_i2c/esp32c6/include/hal/i2c_ll.h
_ESP32C6_I2C_LP_PINS = {"SDA": 6, "SCL": 7}
_ESP32C6_SPI_PSRAM_PINS = {
24: "SPICS0",
25: "SPIQ",
@@ -43,3 +46,13 @@ def esp32_c6_validate_supports(value):
check_strapping_pin(value, _ESP32C6_STRAPPING_PINS, _LOGGER)
return value
def esp32_c6_validate_lp_i2c(value):
lp_sda_pin = _ESP32C6_I2C_LP_PINS["SDA"]
lp_scl_pin = _ESP32C6_I2C_LP_PINS["SCL"]
if int(value[CONF_SDA]) != lp_sda_pin or int(value[CONF_SCL]) != lp_scl_pin:
raise cv.Invalid(
f"Low power i2c interface is only supported on GPIO{lp_sda_pin} SDA and GPIO{lp_scl_pin} SCL for ESP32-C6"
)
return value

View File

@@ -1,9 +1,12 @@
import logging
import esphome.config_validation as cv
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER, CONF_SCL, CONF_SDA
from esphome.pins import check_strapping_pin
# https://documentation.espressif.com/esp32-p4-chip-revision-v1.3_datasheet_en.pdf
_ESP32P4_LP_PINS = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}
_ESP32P4_USB_JTAG_PINS = {24, 25}
_ESP32P4_STRAPPING_PINS = {34, 35, 36, 37, 38}
@@ -36,3 +39,14 @@ def esp32_p4_validate_supports(value):
pass
check_strapping_pin(value, _ESP32P4_STRAPPING_PINS, _LOGGER)
return value
def esp32_p4_validate_lp_i2c(value):
if (
int(value[CONF_SDA]) not in _ESP32P4_LP_PINS
or int(value[CONF_SCL]) not in _ESP32P4_LP_PINS
):
raise cv.Invalid(
f"Low power i2c interface for ESP32-P4 is only supported on low power interface GPIO{min(_ESP32P4_LP_PINS)} - GPIO{max(_ESP32P4_LP_PINS)}"
)
return value

View File

@@ -2,6 +2,23 @@ import logging
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32
from esphome.components.esp32 import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C5,
VARIANT_ESP32C6,
VARIANT_ESP32C61,
VARIANT_ESP32H2,
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
get_esp32_variant,
)
from esphome.components.esp32.gpio_esp32_c5 import esp32_c5_validate_lp_i2c
from esphome.components.esp32.gpio_esp32_c6 import esp32_c6_validate_lp_i2c
from esphome.components.esp32.gpio_esp32_p4 import esp32_p4_validate_lp_i2c
from esphome.components.zephyr import (
zephyr_add_overlay,
zephyr_add_prj_conf,
@@ -16,6 +33,7 @@ from esphome.const import (
CONF_I2C,
CONF_I2C_ID,
CONF_ID,
CONF_LOW_POWER_MODE,
CONF_SCAN,
CONF_SCL,
CONF_SDA,
@@ -40,6 +58,25 @@ IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component)
ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component)
I2CDevice = i2c_ns.class_("I2CDevice")
ESP32_I2C_CAPABILITIES = {
# https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/soc_caps.h
VARIANT_ESP32: {"NUM": 2, "HP": 2},
VARIANT_ESP32C2: {"NUM": 1, "HP": 1},
VARIANT_ESP32C3: {"NUM": 1, "HP": 1},
VARIANT_ESP32C5: {"NUM": 2, "HP": 1, "LP": 1},
VARIANT_ESP32C6: {"NUM": 2, "HP": 1, "LP": 1},
VARIANT_ESP32C61: {"NUM": 1, "HP": 1},
VARIANT_ESP32H2: {"NUM": 2, "HP": 2},
VARIANT_ESP32P4: {"NUM": 3, "HP": 2, "LP": 1},
VARIANT_ESP32S2: {"NUM": 2, "HP": 2},
VARIANT_ESP32S3: {"NUM": 2, "HP": 2},
}
VALIDATE_LP_I2C = {
VARIANT_ESP32C5: esp32_c5_validate_lp_i2c,
VARIANT_ESP32C6: esp32_c6_validate_lp_i2c,
VARIANT_ESP32P4: esp32_p4_validate_lp_i2c,
}
LP_I2C_VARIANT = list(VALIDATE_LP_I2C.keys())
CONF_SDA_PULLUP_ENABLED = "sda_pullup_enabled"
CONF_SCL_PULLUP_ENABLED = "scl_pullup_enabled"
@@ -91,6 +128,13 @@ CONFIG_SCHEMA = cv.All(
cv.positive_time_period,
),
cv.Optional(CONF_SCAN, default=True): cv.boolean,
cv.Optional(CONF_LOW_POWER_MODE): cv.All(
cv.only_on_esp32,
esp32.only_on_variant(
supported=LP_I2C_VARIANT, msg_prefix="Low power i2c"
),
cv.boolean,
),
}
).extend(cv.COMPONENT_SCHEMA),
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040, PLATFORM_NRF52]),
@@ -102,6 +146,31 @@ def _final_validate(config):
full_config = fv.full_config.get()[CONF_I2C]
if CORE.using_zephyr and len(full_config) > 1:
raise cv.Invalid("Second i2c is not implemented on Zephyr yet")
if CORE.using_esp_idf and get_esp32_variant() in ESP32_I2C_CAPABILITIES:
variant = get_esp32_variant()
max_num = ESP32_I2C_CAPABILITIES[variant]["NUM"]
if len(full_config) > max_num:
raise cv.Invalid(
f"The maximum number of i2c interfaces for {variant} is {max_num}"
)
if variant in LP_I2C_VARIANT:
max_lp_num = ESP32_I2C_CAPABILITIES[variant]["LP"]
max_hp_num = ESP32_I2C_CAPABILITIES[variant]["HP"]
lp_num = sum(
CONF_LOW_POWER_MODE in conf and conf[CONF_LOW_POWER_MODE]
for conf in full_config
)
hp_num = len(full_config) - lp_num
if CONF_LOW_POWER_MODE in config and config[CONF_LOW_POWER_MODE]:
VALIDATE_LP_I2C[variant](config)
if lp_num > max_lp_num:
raise cv.Invalid(
f"The maximum number of low power i2c interfaces for {variant} is {max_lp_num}"
)
if hp_num > max_hp_num:
raise cv.Invalid(
f"The maximum number of high power i2c interfaces for {variant} is {max_hp_num}"
)
FINAL_VALIDATE_SCHEMA = _final_validate
@@ -155,6 +224,8 @@ async def to_code(config):
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
if CORE.using_arduino and not CORE.is_esp32:
cg.add_library("Wire", None)
if CONF_LOW_POWER_MODE in config:
cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE])))
def i2c_device_schema(default_address):

View File

@@ -16,13 +16,10 @@ namespace i2c {
static const char *const TAG = "i2c.idf";
void IDFI2CBus::setup() {
static i2c_port_t next_port = I2C_NUM_0;
this->port_ = next_port;
if (this->port_ == I2C_NUM_MAX) {
ESP_LOGE(TAG, "No more than %u buses supported", I2C_NUM_MAX);
this->mark_failed();
return;
}
static i2c_port_t next_hp_port = I2C_NUM_0;
#if SOC_LP_I2C_SUPPORTED
static i2c_port_t next_lp_port = LP_I2C_NUM_0;
#endif
if (this->timeout_ > 13000) {
ESP_LOGW(TAG, "Using max allowed timeout: 13 ms");
@@ -31,23 +28,35 @@ void IDFI2CBus::setup() {
this->recover_();
next_port = (i2c_port_t) (next_port + 1);
i2c_master_bus_config_t bus_conf{};
memset(&bus_conf, 0, sizeof(bus_conf));
bus_conf.sda_io_num = gpio_num_t(sda_pin_);
bus_conf.scl_io_num = gpio_num_t(scl_pin_);
bus_conf.i2c_port = this->port_;
bus_conf.glitch_ignore_cnt = 7;
#if SOC_LP_I2C_SUPPORTED
if (this->port_ < SOC_HP_I2C_NUM) {
bus_conf.clk_source = I2C_CLK_SRC_DEFAULT;
} else {
if (this->lp_mode_) {
if ((next_lp_port - LP_I2C_NUM_0) == SOC_LP_I2C_NUM) {
ESP_LOGE(TAG, "No more than %u LP buses supported", SOC_LP_I2C_NUM);
this->mark_failed();
return;
}
this->port_ = next_lp_port;
next_lp_port = (i2c_port_t) (next_lp_port + 1);
bus_conf.lp_source_clk = LP_I2C_SCLK_DEFAULT;
}
#else
bus_conf.clk_source = I2C_CLK_SRC_DEFAULT;
} else {
#endif
if (next_hp_port == SOC_HP_I2C_NUM) {
ESP_LOGE(TAG, "No more than %u HP buses supported", SOC_HP_I2C_NUM);
this->mark_failed();
return;
}
this->port_ = next_hp_port;
next_hp_port = (i2c_port_t) (next_hp_port + 1);
bus_conf.clk_source = I2C_CLK_SRC_DEFAULT;
#if SOC_LP_I2C_SUPPORTED
}
#endif
bus_conf.i2c_port = this->port_;
bus_conf.flags.enable_internal_pullup = sda_pullup_enabled_ || scl_pullup_enabled_;
esp_err_t err = i2c_new_master_bus(&bus_conf, &this->bus_);
if (err != ESP_OK) {

View File

@@ -30,6 +30,9 @@ class IDFI2CBus : public InternalI2CBus, public Component {
void set_scl_pullup_enabled(bool scl_pullup_enabled) { this->scl_pullup_enabled_ = scl_pullup_enabled; }
void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
#if SOC_LP_I2C_SUPPORTED
void set_lp_mode(bool lp_mode) { this->lp_mode_ = lp_mode; }
#endif
int get_port() const override { return this->port_; }
@@ -48,6 +51,9 @@ class IDFI2CBus : public InternalI2CBus, public Component {
uint32_t frequency_{};
uint32_t timeout_ = 0;
bool initialized_ = false;
#if SOC_LP_I2C_SUPPORTED
bool lp_mode_ = false;
#endif
};
} // namespace i2c

View File

@@ -559,6 +559,7 @@ CONF_LOGS = "logs"
CONF_LONGITUDE = "longitude"
CONF_LOOP_TIME = "loop_time"
CONF_LOW = "low"
CONF_LOW_POWER_MODE = "low_power_mode"
CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference"
CONF_MAC_ADDRESS = "mac_address"
CONF_MAGNITUDE = "magnitude"