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
- 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.
- 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.
- 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.
- Wire I2C Data (SDA): Connect the orange wire from Pin 6 (GPIO4) to the BME280 SDA pin.
- Wire I2C Clock (SCL): Connect the yellow wire from Pin 7 (GPIO5) to the BME280 SCL pin.
- 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
- 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. UpdateBME280_ADDR = 0x77in the code. - 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.
- 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.
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
networkandumqtt.simplelibraries to push thetemp_cvariable to a Home Assistant MQTT broker every 60 seconds. - Implement Deep Sleep: For battery-powered deployments, replace
time.sleep(2)withmachine.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 registers0xE1through0xE7and apply the Boschcompensate_Handcompensate_Pmath.
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.






