The Raspberry Pi Pico 2, powered by the RP2350 chip, is the current standard for high-performance, low-cost embedded projects in 2026. When you need precise environmental data without the drift issues of cheap analog sensors, pairing the Pico 2 with a Bosch BME280 via I2C is the most reliable bench and field setup. This guide provides the exact wiring, a zero-dependency MicroPython script with raw register compensation math, and the specific debugging steps for I2C bus failures.

Raspberry Pi Pico 2 vs. Original Pico (RP2040) Hardware Specs

Before wiring the board, it is critical to understand why the Pico 2 (RP2350) changes the math for embedded sensor logging compared to the original RP2040. The RP2350 introduces a dual-core architecture that allows you to run ARM Cortex-M33 and RISC-V Hazard3 cores simultaneously, alongside a dedicated security subsystem and doubled SRAM. This extra memory is crucial when buffering I2C sensor data before writing to an SD card or pushing to an MQTT broker.

Specification Original Pi Pico (RP2040) Raspberry Pi Pico 2 (RP2350)
Processor Cores Dual ARM Cortex-M0+ @ 133MHz Dual ARM Cortex-M33 / RISC-V @ 150MHz
SRAM 264 KB 520 KB
Flash Memory 2 MB (QSPI) 4 MB (QSPI)
I2C Peripherals 2x I2C Controllers 2x I2C Controllers (Improved FIFO)
Security Features None (Basic Bootloader) ARM TrustZone, Secure Boot, OTP
Typical Price (2026) $4.00 USD $7.00 USD

For I2C operations, the RP2350 features improved FIFO depth on its I2C controllers, reducing the chance of buffer overruns when reading the 26-byte calibration block from the BME280 at higher clock speeds. For the complete architectural breakdown, refer to the official Raspberry Pi Pico documentation.

Parts List and I2C Pin Mapping

Using the correct breakout board prevents logic-level mismatch issues. The BME280 is strictly a 3.3V device; feeding it 5V I2C lines will permanently destroy the sensor's internal CMOS.

Required Components

  • Microcontroller: Raspberry Pi Pico 2 (with pre-soldered headers) - Approx. $7.00
  • Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID 2652) - Approx. $10.00. This board includes onboard 10kΩ pull-up resistors and a 3.3V LDO regulator.
  • Wiring: 4x 24 AWG stranded silicone jumper wires (Male-to-Female).
  • Prototyping: Standard 400-point half-size solderless breadboard.

I2C Pin Mapping Table

The Pico 2 defaults to I2C0 on GPIO4 and GPIO5. While you can remap I2C to almost any pin via the PIO or alternate functions, sticking to the default I2C0 block minimizes MicroPython configuration overhead.

Pico 2 Pin GPIO Number BME280 Breakout Pin Wire Color Function
Pin 6 GPIO 4 SDI / SDA Orange I2C0 Data (Bidirectional)
Pin 7 GPIO 5 SCK / SCL Yellow I2C0 Clock (Master Output)
Pin 36 3V3(OUT) VIN / VCC Red 3.3V Power Supply
Pin 38 GND GND Black Common Ground Reference

Step-by-Step Wiring Procedure

  1. Seat the Pico 2: Press the Raspberry Pi Pico 2 into the center trench of the breadboard, ensuring pins 1-20 are on the left and 21-40 are on the right.
  2. Connect Power: Route the red jumper from Pin 36 (3V3 OUT) to the BME280 VIN pin. Warning: Do not use Pin 40 (VBUS/5V) to power the BME280 directly if you are using a raw sensor chip without an LDO. The Adafruit 2652 breakout handles 5V, but feeding 3.3V is safer for I2C logic alignment.
  3. Connect Ground: Route the black jumper from Pin 38 (GND) to the BME280 GND pin. A floating ground will cause erratic I2C ACK/NACK behavior.
  4. Wire I2C Data (SDA): Connect the orange wire from Pin 6 (GPIO4) to the BME280 SDA pin.
  5. Wire I2C Clock (SCL): Connect the yellow wire from Pin 7 (GPIO5) to the BME280 SCL pin.
  6. Verify Pull-ups: If using a raw BME280 module from a generic marketplace (not Adafruit/SparkFun), verify it has 4.7kΩ or 10kΩ pull-up resistors to 3.3V on the SDA and SCL lines. The Pico 2 internal pull-ups (accessible via machine.Pin.PULL_UP) are roughly 50kΩ, which is too weak for reliable I2C communication at 400kHz.

MicroPython Code for BME280 I2C Reading

This code targets the Raspberry Pi Pico 2 (RP2350) running MicroPython v1.23 or newer. Unlike generic tutorials that require you to download third-party bme280.py modules, this script implements the raw I2C register reads and the exact Bosch temperature compensation algorithm directly from the BME280 datasheet. It requires zero external dependencies.

from machine import Pin, I2C
import time
import struct

# Target Board: Raspberry Pi Pico 2 (RP2350)
# I2C0 Configuration: SDA=GPIO4, SCL=GPIO5, Freq=400kHz
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)

# BME280 I2C Addresses (0x76 if SDO is GND, 0x77 if SDO is VCC)
BME280_ADDR = 0x76 

# Register Map
REG_CHIP_ID = 0xD0
REG_RESET = 0xE0
REG_CTRL_HUM = 0xF2
REG_STATUS = 0xF3
REG_CTRL_MEAS = 0xF4
REG_CONFIG = 0xF5
REG_PRESS_MSB = 0xF7
REG_TEMP_MSB = 0xFA
REG_CALIB_00 = 0x88  # Temp/Press calib data start

def scan_bus():
    devices = i2c.scan()
    if not devices:
        raise RuntimeError('No I2C devices found. Check wiring.')
    print(f'Found I2C devices at: {[hex(d) for d in devices]}')
    return devices

def read_raw_temp():
    # Read 3 bytes of temperature data (20-bit)
    data = i2c.readfrom_mem(BME280_ADDR, REG_TEMP_MSB, 3)
    raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
    return raw

def read_calibration():
    # Read temperature calibration registers (0x88 to 0x8D)
    calib_data = i2c.readfrom_mem(BME280_ADDR, REG_CALIB_00, 6)
    # Unpack little-endian: unsigned short, signed short, signed short
    dig_T1, dig_T2, dig_T3 = struct.unpack('

Debugging I2C Failures and ENODEV Errors

The most common point of failure when bringing up I2C on the Pico 2 is encountering the following exact error string in the Thonny or REPL console:

OSError: [Errno 121] ENODEV

This error means the MicroPython I2C driver sent a clock pulse and address byte, but the BME280 failed to pull the SDA line low to acknowledge (NACK). The Pico 2's I2C peripheral correctly timed out and threw the ENODEV (No Device) exception.

The First Three Things to Check When It Fails

  1. Run an I2C Bus Scan: Before reading registers, execute i2c.scan(). If it returns an empty list [], the Pico cannot see the sensor at all. If it returns [0x77] instead of [0x76], your BME280 breakout has the SDO pin pulled high. Update BME280_ADDR = 0x77 in the code.
  2. Verify SDA and SCL are not swapped: I2C is not symmetric. GPIO4 must be SDA, and GPIO5 must be SCL. Swapping them will result in an immediate ENODEV error because the clock line will never toggle correctly.
  3. Check for 5V Logic Contamination: If you accidentally wired the BME280 SDA/SCL to a 5V source, or if you are using a 5V Arduino-level shifter that is stuck high, the Pico 2's 3.3V GPIO pins cannot pull the line low enough to register an ACK. Measure the SDA and SCL lines with a multimeter; they should idle at ~3.2V to 3.3V, not 5V.
Callout Tip: Wire Capacitance and Length
I2C is designed for on-board communication, not long cable runs. If your jumper wires exceed 30cm (12 inches), the parasitic capacitance of the wire will round off the I2C clock edges, causing the BME280 to miss bits. If you must run long wires, drop the I2C frequency in the code from 400000 to 100000 (100kHz Standard Mode).

Extending or Simplifying the Sensor Build

Once the baseline temperature reading is stable, you will likely need to adapt the circuit for a specific deployment environment.

How to Extend the Build

  • Add MQTT Telemetry: If you upgrade to the Raspberry Pi Pico 2 W (which includes the CYW43439 wireless chip), you can import the network and umqtt.simple libraries to push the temp_c variable to a Home Assistant MQTT broker every 60 seconds.
  • Implement Deep Sleep: For battery-powered deployments, replace time.sleep(2) with machine.deepsleep(60000). The RP2350's deep sleep current is roughly 1.5mA (compared to the RP2040's ~1.8mA), significantly extending the life of a 18650 Li-ion cell running through a buck converter.
  • Read Humidity and Pressure: The BME280 contains separate compensation formulas for relative humidity and barometric pressure. You can expand the read_calibration() function to pull the remaining 32 bytes of calibration data from registers 0xE1 through 0xE7 and apply the Bosch compensate_H and compensate_P math.

How to Simplify the Build

If I2C routing is causing layout headaches on a custom PCB, or if you only need rough ambient temperature and humidity without barometric pressure, swap the BME280 for a DHT22 (AM2302). The DHT22 uses a proprietary single-bus protocol that requires only one GPIO pin and a 10kΩ pull-up resistor, eliminating the need for SCL routing entirely. However, be aware that the DHT22 has a much slower sampling rate (0.5Hz) and lower temperature accuracy (±0.5°C) compared to the BME280's ±1.0°C rated precision and rapid I2C polling capabilities.

For deeper details on MicroPython's machine-level I2C implementation and FIFO handling on the RP2350, consult the MicroPython machine.I2C documentation.