From 0400c2d3a39315686290c6d4d0f6f4e8ca458c74 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Mar 2026 13:19:20 -1000 Subject: [PATCH] [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 --- esphome/components/i2c/__init__.py | 30 ++++++++++++++++++++++ esphome/components/i2c/i2c_bus_arduino.cpp | 9 ++++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/esphome/components/i2c/__init__.py b/esphome/components/i2c/__init__.py index de3f2be6740..3884204ae5c 100644 --- a/esphome/components/i2c/__init__.py +++ b/esphome/components/i2c/__init__.py @@ -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"] diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index 5120eb4c007..e339fe59fc8 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -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