[i2c] Fix RP2040 I2C bus selection based on pin assignment instead of definition order

The RP2040 I2C bus was previously assigned Wire/Wire1 based on the order
I2C buses were defined in YAML, not based on which GPIO pins were used.
This caused I2C1 to not work when only a single bus was configured with
I2C1 pins (e.g., GPIO6/GPIO7).

Now selects the correct Wire instance using the RP2040/RP2350 GPIO pin
mapping formula: (pin / 2) % 2. Also adds config validation to catch
SDA/SCL pin mismatches and duplicate controller assignments.

Closes https://github.com/esphome/esphome/issues/14742
This commit is contained in:
J. Nick Koston
2026-03-12 13:19:20 -10:00
parent 05d285ba86
commit 0400c2d3a3
2 changed files with 35 additions and 4 deletions
+30
View File
@@ -93,11 +93,25 @@ def _bus_declare_type(value):
raise NotImplementedError
def _rp2040_i2c_controller(pin):
"""Return the I2C controller number (0 or 1) for a given RP2040/RP2350 GPIO pin."""
return (pin // 2) % 2
def validate_config(config):
if CORE.is_esp32:
return cv.require_framework_version(
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
)(config)
if CORE.is_rp2040:
sda_controller = _rp2040_i2c_controller(config[CONF_SDA])
scl_controller = _rp2040_i2c_controller(config[CONF_SCL])
if sda_controller != scl_controller:
raise cv.Invalid(
f"SDA pin GPIO{config[CONF_SDA]} is on I2C{sda_controller} but "
f"SCL pin GPIO{config[CONF_SCL]} is on I2C{scl_controller}. "
f"Both pins must be on the same I2C controller."
)
return config
@@ -146,6 +160,22 @@ 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.is_rp2040:
if len(full_config) > 2:
raise cv.Invalid(
"The maximum number of I2C interfaces for RP2040/RP2350 is 2"
)
if len(full_config) > 1:
controllers = [
_rp2040_i2c_controller(conf[CONF_SDA]) for conf in full_config
]
if len(set(controllers)) != len(controllers):
raise cv.Invalid(
"Multiple I2C buses are configured to use the same I2C controller. "
"Each bus must use pins on a different controller "
"(I2C0: SDA on GPIO 0,4,8,12,16,20,24,28; "
"I2C1: SDA on GPIO 2,6,10,14,18,22,26)."
)
if CORE.is_esp32 and get_esp32_variant() in ESP32_I2C_CAPABILITIES:
variant = get_esp32_variant()
max_num = ESP32_I2C_CAPABILITIES[variant]["NUM"]
+5 -4
View File
@@ -20,12 +20,13 @@ void ArduinoI2CBus::setup() {
#if defined(USE_ESP8266)
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
#elif defined(USE_RP2040)
static bool first = true;
if (first) {
// Select Wire instance based on pin assignment, not definition order.
// RP2040 I2C controller is determined by GPIO: (pin / 2) % 2
// I2C0 SDA: GPIO 0,4,8,12,16,20,24,28 I2C1 SDA: GPIO 2,6,10,14,18,22,26
if ((this->sda_pin_ / 2) % 2 == 0) {
wire_ = &Wire;
first = false;
} else {
wire_ = &Wire1; // NOLINT(cppcoreguidelines-owning-memory)
wire_ = &Wire1;
}
#endif