The Raspberry Pi Pico 2, built on the RP2350 chip, has firmly replaced the original Pico as the default bench microcontroller for embedded projects in 2026. With its dual-core Arm Cortex-M33 (and optional RISC-V) architecture, enhanced Programmable I/O (PIO), and a completely redesigned I2C peripheral, it offers massive headroom for sensor polling and data logging. However, that redesigned I2C block also introduces new debugging quirks that catch makers migrating from the RP2040 off guard.

This guide walks you through building a high-speed I2C environmental sensor hub using the RPI Pico 2 and a BME280 sensor. We will cover the exact hardware decision path, provide a complete, error-handled MicroPython script, and break down the most common I2C timeout errors specific to the RP2350 silicon.

Which RPI Pico 2 Variant Should You Buy?

Raspberry Pi now ships several SKUs under the Pico 2 umbrella. Choosing the wrong one means a second trip to the shop or waiting weeks for shipping. Use this decision tree to lock in your hardware pick.

Project Requirement Board Variant Part Number Approx. Price
Offline data logging, motor control, pure sensor hubs Pico 2 (Base) SC0226 $5.00
MQTT telemetry, WiFi/BLE, cloud dashboard integration Pico 2 W SC0228 $7.00
Immediate breadboard prototyping without soldering Pico 2 with Pre-soldered Headers SC0227 $6.00
The Concrete Pick: If you are building this specific I2C sensor hub on a breadboard today and do not need WiFi, buy the Raspberry Pi Pico 2 with pre-soldered headers (Part # SC0227). It saves you 20 minutes of header soldering and ensures reliable breadboard contact for high-speed I2C lines, which are highly sensitive to loose jumper wires.

Parts List and Pin Mapping

Before wiring, gather these exact components. The RP2350's I2C peripheral is strictly compliant with SMBus timing, meaning component tolerances matter more than they did on the older RP2040.

Bill of Materials (BOM)

  • Microcontroller: Raspberry Pi Pico 2 (RP2350, Base, Pre-soldered headers)
  • Sensor: BME280 Breakout Board (Adafruit #2652 or generic 3.3V variant with onboard voltage regulator)
  • Resistors: 2x 4.7kΩ through-hole resistors (for I2C pull-ups)
  • Prototyping: Half-size breadboard, solid-core jumper wires
  • Firmware: MicroPython v1.24.0+ (specifically the RP2350 build)

Pin Mapping Table

This build targets the base RPI Pico 2 variant. We are using I2C0 on the default GPIO4/GPIO5 pins to keep the routing clean.

Pico 2 Pin RP2350 GPIO BME280 Breakout Pin Function / Notes
Pin 6 GP4 SDI / SDA I2C0 Data (Requires 4.7kΩ pull-up to 3.3V)
Pin 7 GP5 SCK / SCL I2C0 Clock (Requires 4.7kΩ pull-up to 3.3V)
Pin 36 3V3(OUT) VIN / VCC 3.3V Power Output from Pico onboard regulator
Pin 38 GND GND Common Ground
Hardware Warning: Never wire the BME280 VCC pin to the Pico 2's VBUS (Pin 40, 5V) unless your specific breakout board explicitly states it has an onboard 3.3V LDO regulator. Feeding 5V directly into a raw BME280 sensor chip will instantly destroy the silicon.

Step-by-Step Build and MicroPython Code

This code targets the base Raspberry Pi Pico 2 (RP2350) running MicroPython. It initializes the I2C bus at 400kHz (Fast Mode), scans for the sensor, and reads the Chip ID register to verify communication before attempting full telemetry reads.

Wiring Steps

  1. Insert the Pico 2 and BME280 breakout into the breadboard, ensuring they span the center trench.
  2. Connect Pico Pin 36 (3V3) to the BME280 VCC, and Pico Pin 38 (GND) to BME280 GND.
  3. Connect Pico GP4 to BME280 SDA, and GP5 to BME280 SCL.
  4. Critical: Insert a 4.7kΩ resistor between the 3V3 rail and the SDA line. Insert a second 4.7kΩ resistor between the 3V3 rail and the SCL line. Do not skip this step.
  5. Connect the Pico 2 to your PC via USB-C and flash the latest RP2350 MicroPython UF2 file.

Compilable MicroPython Script


from machine import Pin, I2C
import time

# --- PIN & CONFIGURATION DEFINITIONS ---
SDA_PIN = 4
SCL_PIN = 5
I2C_FREQ = 400000  # 400kHz Fast Mode
BME_ADDR = 0x76    # Default address (0x77 if SDO pin is tied high)
CHIP_ID_REG = 0xD0
EXPECTED_ID = 0x60 # BME280 Chip ID

# Initialize I2C0
i2c = I2C(0, sda=Pin(SDA_PIN), scl=Pin(SCL_PIN), freq=I2C_FREQ)

def scan_and_verify():
    print('Scanning I2C bus...')
    devices = i2c.scan()
    if not devices:
        raise RuntimeError('No I2C devices found. Check wiring and pull-ups.')
    
    print(f'Found devices at: {[hex(d) for d in devices]}')
    
    if BME_ADDR not in devices:
        raise RuntimeError(f'BME280 not found at {hex(BME_ADDR)}. Check SDO pin state.')

    # Read Chip ID to verify actual communication
    try:
        chip_id = i2c.readfrom_mem(BME_ADDR, CHIP_ID_REG, 1)[0]
        if chip_id == EXPECTED_ID:
            print(f'Success: BME280 verified (Chip ID: {hex(chip_id)})')
        else:
            print(f'Warning: Unexpected Chip ID {hex(chip_id)}. Is this a BME280?')
    except OSError as e:
        print(f'Failed to read Chip ID: {e}')
        raise

def main():
    try:
        scan_and_verify()
        print('Sensor hub initialized successfully. Ready for telemetry loop.')
        # Add your continuous read loop here
    except Exception as e:
        print(f'Fatal initialization error: {e}')
        # Blink onboard LED to indicate hardware fault
        led = Pin(25, Pin.OUT)
        for _ in range(10):
            led.toggle()
            time.sleep(0.2)

if __name__ == '__main__':
    main()

Debugging: Fixing I2C Timeouts on the RP2350

When migrating from the RP2040 to the RP2350, the most common failure mode is encountering a hard timeout during I2C transactions. The RP2350 features a completely new I2C peripheral implementation that enforces stricter SMBus-style timeouts and does not silently recover from clock stretching glitches the way the old DesignWare block did.

The Exact Error String

If your code hangs and then crashes, you will see this exact traceback in the Thonny or REPL console:

OSError: [Errno 110] ETIMEDOUT

First Three Things to Check (Ranked by Likelihood)

  1. Missing or Incorrect Pull-Up Resistors (90% of cases): The RP2350 GPIO pads have different schmitt trigger hysteresis characteristics than the RP2040. Without physical 4.7kΩ pull-up resistors to 3.3V, the SDA/SCL lines will float, and the RP2350's stricter timeout logic will immediately throw ETIMEDOUT. Fix: Add 4.7kΩ physical resistors. Do not rely on internal pull-ups for 400kHz I2C.
  2. I2C Address Mismatch (0x76 vs 0x77): Many generic BME280 breakouts from online marketplaces have the SDO pin pulled high by default, shifting the address to 0x77. Fix: Run a basic i2c.scan() script to verify the actual hex address, and update the BME_ADDR variable in the code.
  3. Capacitive Loading on Long Wires: If you are using jumper wires longer than 15cm, the bus capacitance exceeds the 400pF limit for 400kHz Fast Mode, causing the rising edges to slope too slowly for the RP2350 to register. Fix: Drop the I2C_FREQ in the code to 100000 (100kHz Standard Mode) or shorten the wires.

Extending and Simplifying the Build

Depending on your end goal, you will likely want to modify this baseline hub. Here is exactly how to scale it up or strip it down.

How to Simplify (The 'Quick Test' Configuration)

If you are out of 4.7kΩ resistors and just need to verify a sensor on your desk for 5 minutes, you can force the RP2350 to use internal pull-ups and drop the bus speed. Change the I2C initialization line to:


i2c = I2C(0, sda=Pin(SDA_PIN, pull=Pin.PULL_UP), scl=Pin(SCL_PIN, pull=Pin.PULL_UP), freq=50000)

Note: The internal pull-ups are roughly 50kΩ-60kΩ, which is too weak for 400kHz. Dropping the frequency to 50kHz gives the weak pull-ups enough time to charge the bus capacitance. This is strictly for bench testing, not production.

How to Extend (Dual-Core Data Logging)

The RP2350's dual Cortex-M33 cores are perfect for separating I/O blocking from data processing. To extend this into a robust logger:

  • Add an SPI SD Card Module: Wire an SD card breakout to the Pico 2's SPI0 pins (GP16-GP19). Use the sdcard.py and os modules to mount the filesystem.
  • Split the Cores: Use MicroPython's _thread module. Run the I2C sensor polling on Core 0 in a tight loop, pushing readings into a thread-safe queue. Run the SD card write operations on Core 1. This prevents the I2C bus from timing out while the SD card controller is busy writing FAT32 clusters.
  • Upgrade to Pico 2 W: If you want to push the data to an MQTT broker instead of an SD card, swap the base board for the Pico 2 W. The pinout for I2C0 and SPI0 remains identical, meaning your physical wiring and pin mapping tables require zero changes.

For deeper architectural details on the new peripherals, refer to the official RP2350 Datasheet and the MicroPython machine.I2C documentation. The RPI Pico 2 is a massive leap forward, provided you respect the stricter timing requirements of its new silicon.