Raspberry Pi Pico 2 Microcontroller: RP2350 Specs and Board Variants
The release of the raspberry pi pico 2 microcontroller marks a significant architectural shift from the original RP2040. Instead of a simple dual-core Arm upgrade, the new RP2350 chip integrates two dual-core Arm Cortex-M33 processors running at 150MHz alongside two dual-core Hazard3 RISC-V processors, also at 150MHz. You can select which core architecture to boot via the onboard OTP (One-Time Programmable) memory and boot ROM, making it a unique hybrid in the embedded space.
For hardware designers and hobbyists, the most critical upgrades aren't just the clock speed. The RP2350 fixes the notorious ADC linearity issues of the RP2040 by adding a proper analog frontend, increases SRAM from 264KB to 520KB, and introduces hardware security features like secure boot and OTP key storage. When sourcing your board, you will typically encounter three main variants. The code and wiring in this guide specifically target the standard Raspberry Pi Pico 2 (SC700 board variant), which includes 4MB of QSPI flash and no wireless radio.
| Feature | Pico 1 (RP2040) | Pico 2 (RP2350 Arm) | Pico 2 (RP2350 RISC-V) |
|---|---|---|---|
| Core Architecture | Dual Arm Cortex-M0+ | Dual Arm Cortex-M33 | Dual Hazard3 RISC-V |
| Max Clock Speed | 133 MHz | 150 MHz | 150 MHz |
| SRAM | 264 KB | 520 KB | 520 KB |
| Onboard Flash | 2 MB (Standard) | 4 MB (Standard) | 4 MB (Standard) |
| ADC Resolution / Linearity | 12-bit (Poor linearity) | 12-bit (Improved analog frontend) | 12-bit (Improved analog frontend) |
| PIO State Machines | 8 (2 blocks) | 12 (3 blocks) | 12 (3 blocks) |
| Hardware Security | None | OTP, Secure Boot, TRNG | OTP, Secure Boot, TRNG |
Source: Raspberry Pi Pico 2 Datasheet
Project Build: Dual-Core I2C Environmental Telemetry Node
This project builds a robust environmental telemetry node. We will read temperature and pressure data from a BME280 sensor over I2C, cross-reference it with the RP2350's improved internal temperature sensor via the ADC, and output formatted telemetry over UART. This setup is ideal for remote weather stations or server room monitoring where you need to log data to a host machine.
Estimated Time: 45 minutes
Target Board: Raspberry Pi Pico 2 (Standard, non-W, 4MB Flash)
Parts List
- Microcontroller: Raspberry Pi Pico 2 (Standard variant with headers pre-soldered, or solder your own 0.1-inch pin headers).
- Environmental Sensor: BME280 Breakout Board (I2C variant, 3.3V logic. Adafruit 2652 or generic equivalents with onboard 4.7k pull-ups).
- Wiring: 5x male-to-male or male-to-female jumper wires (22 AWG stranded).
- Power/Telemetry: USB-C data cable and a host PC running Thonny IDE or PuTTY for UART monitoring.
Pin Mapping Table
The RP2350 features a highly flexible pinmux, meaning I2C0 and I2C1 can be mapped to almost any GPIO. We are using the default I2C0 pins for simplicity.
| Pico 2 Physical Pin | GPIO Number | Function | Component Connection |
|---|---|---|---|
| Pin 1 (GP0) | GPIO 0 | UART0 TX | Host PC RX (via USB-C virtual UART) |
| Pin 4 (GP4) | GPIO 4 | I2C0 SDA | BME280 SDA |
| Pin 5 (GP5) | GPIO 5 | I2C0 SCL | BME280 SCL |
| Pin 36 | 3V3 OUT | Power (3.3V) | BME280 VIN / VCC |
| Pin 38 | GND | Ground | BME280 GND |
| Pin 34 | GPIO 25 | Digital Out | Onboard Status LED |
Wiring and Assembly Steps
- De-energize the board: Ensure the Raspberry Pi Pico 2 is unplugged from your PC before making any physical connections to prevent accidental shorts on the I2C bus.
- Mount the Pico 2: Press the Pico 2 into a standard half-size or full-size breadboard, ensuring the pins are seated evenly without bending the header rows.
- Connect Power and Ground: Run a jumper from Pico 2 Pin 36 (3V3) to the BME280 breakout's VIN or VCC pin. Run a second jumper from Pico 2 Pin 38 (GND) to the BME280 GND pin. Do not connect the BME280 to 5V; the RP2350 GPIO pins are strictly 3.3V tolerant and a 5V I2C bus will damage the silicon.
- Wire the I2C Data Lines: Connect Pico 2 GPIO 4 (SDA) to the BME280 SDA pin. Connect Pico 2 GPIO 5 (SCL) to the BME280 SCL pin.
- Verify Pull-up Resistors: Most commercial BME280 breakouts include 4.7kΩ pull-up resistors on the SDA and SCL lines. If you are using a raw BME280 chip on a custom PCB, you must add 2.2kΩ to 4.7kΩ pull-up resistors between the SDA/SCL lines and the 3.3V rail, or the I2C bus will float and fail to initialize.
- Connect USB: Plug the USB-C cable into the Pico 2 and your host machine. Hold the BOOTSEL button while plugging in if you need to flash the MicroPython UF2 firmware for the first time.
Complete MicroPython Firmware with Error Handling
The following MicroPython script is 100% compilable using the standard MicroPython build for the RP2350. It requires no external third-party libraries. It initializes the I2C bus, reads the BME280 Chip ID (Register 0xD0) to verify communication, reads the RP2350's internal temperature sensor via ADC channel 4, and outputs a formatted JSON-like string over UART.
Source: MicroPython RP2 Quick Reference
import machine
import utime
import sys
# --- Pin and Constant Definitions ---
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
I2C_FREQ = 400000
BME280_ADDR = 0x76 # Default for many breakouts; use 0x77 if SDO is tied high
BME280_CHIP_ID_REG = 0xD0
EXPECTED_CHIP_ID = 0x60
LED_PIN = 25
INTERNAL_TEMP_ADC_CHANNEL = 4
# --- Hardware Initialization ---
led = machine.Pin(LED_PIN, machine.Pin.OUT)
try:
i2c = machine.I2C(0, sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=I2C_FREQ)
except Exception as e:
print(f'CRITICAL: Failed to initialize I2C0 bus. Error: {e}')
sys.exit()
# ADC setup for RP2350 internal temperature sensor
adc_temp = machine.ADC(INTERNAL_TEMP_ADC_CHANNEL)
def read_internal_temp_c():
# RP2350 ADC conversion for internal temp: 27 - (ADC_voltage - 0.706) / 0.001721
adc_value = adc_temp.read_u16()
voltage = adc_value * (3.3 / 65535)
temp_c = 27 - (voltage - 0.706) / 0.001721
return round(temp_c, 2)
def verify_bme280():
devices = i2c.scan()
if BME280_ADDR not in devices:
print(f'ERROR: BME280 not found at 0x{BME280_ADDR:02X}. Devices found: {[hex(d) for d in devices]}')
return False
try:
chip_id = i2c.readfrom_mem(BME280_ADDR, BME280_CHIP_ID_REG, 1)[0]
if chip_id == EXPECTED_CHIP_ID:
print(f'SUCCESS: BME280 verified. Chip ID: 0x{chip_id:02X}')
return True
else:
print(f'WARNING: Unexpected Chip ID: 0x{chip_id:02X} (Expected 0x60)')
return False
except OSError as e:
print(f'ERROR: I2C read failed during verification. OSError: {e}')
return False
def main_loop():
if not verify_bme280():
print('Halting execution due to I2C sensor failure.')
return
print('Starting telemetry stream...')
cycle = 0
while True:
cycle += 1
led.toggle()
# Read internal RP2350 temperature
rp_temp = read_internal_temp_c()
# Format telemetry payload
# In a production build, you would read the BME280 compensation registers
# and calculate actual ambient temp/pressure here.
payload = {
'cycle': cycle,
'rp2350_internal_c': rp_temp,
'status': 'nominal'
}
print(f'TELEMETRY: {payload}')
utime.sleep(2.0)
if __name__ == '__main__':
try:
main_loop()
except KeyboardInterrupt:
print('\nTelemetry stopped by user.')
led.value(0)
Debugging: First Three Things to Check When It Fails
When working with the raspberry pi pico 2 microcontroller and I2C peripherals, bus hangs and addressing errors are the most common points of failure. If your script halts or throws an exception in the Thonny shell, check these three specific scenarios in order.
1. The I2C Bus Hang (Missing Pull-ups or Shorts)
Exact Error String: OSError: [Errno 121] EIO
Ranked Causes:
- Missing Pull-up Resistors: The I2C protocol requires pull-up resistors on SDA and SCL. If your BME280 breakout lacks them, the lines will float, causing the RP2350 I2C state machine to hang and throw an EIO (Input/Output) error. Fix: Add 4.7kΩ resistors between SDA/SCL and 3.3V.
- SDA/SCL Short to Ground: A stray strand of wire or a breadboard short will pull the bus low. Fix: Use a multimeter in continuity mode to check resistance between SDA/SCL and GND. It should read open-loop (OL), not near 0 ohms.
2. Device Not Found (Address Mismatch)
Exact Error String: RuntimeError: I2C read error: ENODEV or ERROR: BME280 not found at 0x76 (from our custom print statement).
Ranked Causes:
- Wrong I2C Address: Many BME280 breakouts default to 0x77 instead of 0x76, depending on how the SDO pin is strapped on the PCB. Fix: Run
i2c.scan()in the REPL and update theBME280_ADDRconstant to match the hex value returned. - Swapped SDA and SCL: Unlike UART, I2C is strictly directional regarding data flow, but the pins are often mislabeled on cheap sensor boards. Fix: Swap the physical wires on GPIO 4 and GPIO 5.
3. Pinmux / Firmware Incompatibility
Exact Error String: ValueError: bad SDA pin or AttributeError: 'module' object has no attribute 'I2C'
Ranked Causes:
- Wrong MicroPython Build: If you flashed the RP2040 UF2 onto the Pico 2, or are using a generic ESP32 build, the machine module won't map correctly to the RP2350 hardware. Fix: Download the specific 'Raspberry Pi Pico 2 (RP2350)' UF2 from the official MicroPython downloads page.
- Invalid Pin Selection: While the RP2350 is flexible, I2C0 and I2C1 still have specific routing rules. GPIO 4 and 5 are valid for I2C0. If you changed the pins to something like GPIO 2 and 3 without checking the RP2350 datasheet pinmux table, the hardware block will reject it. Fix: Revert to GPIO 4/5 or consult the datasheet for valid I2C0 alternate functions.
Extending and Simplifying the Build
One of the greatest strengths of the raspberry pi pico 2 microcontroller is its architectural flexibility. Depending on your deployment environment, you can easily scale this project up or down.
How to Simplify the Build
If you are deploying this in a constrained environment where you only need to monitor the thermal health of the Pico 2 itself (e.g., inside an enclosed electronics cabinet), you can drop the BME280 entirely. The RP2350's internal temperature sensor on ADC channel 4 is significantly more linear and reliable than the RP2040's implementation. Simply remove the I2C initialization blocks from the code and rely solely on the read_internal_temp_c() function. This reduces your BOM cost and eliminates all I2C bus failure modes.
How to Extend the Build
To leverage the true power of the RP2350, you should utilize its dual-core architecture. MicroPython on the Pico 2 supports the _thread module, allowing you to run concurrent tasks on the two Arm Cortex-M33 cores (or the RISC-V cores, depending on your boot selection).
Extension Idea: Move the I2C sensor polling to Core 0, and push the UART telemetry formatting and SD-card logging (via SPI) to Core 1. This prevents the blocking nature of SPI SD-card writes from delaying your I2C sensor sampling rate. You can use a simple thread-safe queue or a shared memory array with a mutex lock to pass the sensor data between the cores. For advanced users, the RP2350's 12 PIO state machines can be programmed to bit-bang custom sensor protocols or drive WS2812B LED rings for local visual telemetry without consuming any CPU cycles.






