Project Overview & Difficulty Rating

Difficulty: Intermediate | Time: 45 minutes | Cost: ~$12 USD

The Raspberry Pi Pico 2 W (featuring the RP2350 chip) solves the biggest bottleneck of the original Pico W: memory. The RP2040 had 264KB of SRAM, which frequently caused MemoryError crashes when loading the Infineon CYW43439 Wi-Fi firmware blob alongside MQTT and sensor libraries. The Pico 2 W doubles this to 520KB of SRAM, giving you plenty of headroom for network stacks and local data buffering.

This guide walks through building a robust, Wi-Fi-connected environmental monitor that publishes BME280 sensor data to an MQTT broker. We will also deep-dive into the most common networking crash on this board and how to fix it.

Hardware BOM & Pin Mapping

Before wiring, verify you have the exact board variant. This code targets the Raspberry Pi Pico 2 W (RP2350 with pre-soldered headers). Do not use the standard Pico 2 (which lacks the CYW43439 wireless chip) or the original Pico 1 W (which has half the SRAM and requires different memory management).

Parts List

  • MCU: Raspberry Pi Pico 2 W (SC7004302 or equivalent official variant)
  • Sensor: BME280 I2C Temperature/Humidity/Pressure breakout (3.3V logic variant, e.g., Adafruit 2652)
  • Power: USB-C cable (5V) or external 3.3V LDO feeding the VSYS pin
  • Wiring: 4x jumper wires (female-to-female)

Pin Mapping Table

BME280 Sensor Pin Pi Pico 2 W Pin RP2350 GPIO / Function
VIN / VCC Pin 36 (3V3 OUT) 3.3V Regulated Output
GND Pin 38 (GND) Ground Reference
SDA Pin 6 GP4 (I2C0 SDA)
SCL Pin 7 GP5 (I2C0 SCL)
Bench Tip: The Pico 2 W has an onboard switching regulator that is highly efficient, but it can introduce minor high-frequency noise on the 3V3(OUT) rail. If your BME280 readings jitter by ±0.2°C, solder a 100nF ceramic capacitor directly across the sensor's VCC and GND pins.

Step-by-Step Build & Compilable MicroPython Code

  1. Physical Wiring: Connect the BME280 to the Pico 2 W exactly as mapped in the table above. Ensure the sensor's I2C address is 0x76 or 0x77 (check the silkscreen on your specific breakout board; the code below defaults to 0x76 but includes a fallback).
  2. Flash MicroPython: Download the latest stable MicroPython UF2 for the Raspberry Pi Pico 2 from the official Raspberry Pi documentation. Hold the BOOTSEL button, plug in USB, and drag the UF2 file to the RPI-RP2 drive.
  3. Install Dependencies: Open Thonny IDE. Go to Tools > Manage Packages and install umqtt.simple and bme280. Alternatively, the script below uses mip to auto-install them on first boot if you are running MicroPython v1.20+.
  4. Upload Code: Copy the complete script below, update your Wi-Fi credentials and MQTT broker IP, and save it as main.py on the Pico 2 W.
# main.py - Pi Pico 2 W MQTT Environmental Node
import network
import machine
import time
import ubinascii
import sys

# Auto-install dependencies if missing (MicroPython 1.20+)
try:
    import umqtt.simple
    import bme280
except ImportError:
    import mip
    mip.install("umqtt.simple")
    mip.install("bme280")
    print("Dependencies installed. Hard resetting...")
    machine.reset()

from umqtt.simple import MQTTClient

# --- PIN & NETWORK DEFINITIONS ---
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
WIFI_SSID = "YourNetworkName"
WIFI_PASS = "YourNetworkPassword"
MQTT_BROKER = "192.168.1.100"
MQTT_TOPIC = b"home/livingroom/environment"

# Generate unique client ID from Pico's MAC address
client_id = ubinascii.hexlify(machine.unique_id()).decode()

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    # Disable Wi-Fi power save to prevent latency spikes during MQTT publish
    wlan.config(pm=0xa11140)
    wlan.connect(WIFI_SSID, WIFI_PASS)
    
    print("Connecting to Wi-Fi...", end="")
    for _ in range(20):
        if wlan.isconnected():
            break
        print(".", end="")
        time.sleep(0.5)
        
    if not wlan.isconnected():
        raise RuntimeError("Wi-Fi connection failed. Check SSID/Pass and 2.4GHz band.")
    print(f"\nConnected! IP: {wlan.ifconfig()[0]}")
    return wlan

def read_sensor():
    i2c = machine.I2C(0, sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=400000)
    # BME280 init (int_float is the standard bme280 library class)
    bme = bme280.BME280(i2c=i2c)
    # Read values (returns strings like "23.5C", "45.2%", "1013.2hPa")
    temp, pressure, humidity = bme.values
    return temp, pressure, humidity

def main():
    try:
        wlan = connect_wifi()
        temp, pressure, humidity = read_sensor()
        
        # Format payload
        payload = f'{{"temp": "{temp}", "hum": "{humidity}", "press": "{pressure}"}}'
        
        print(f"Publishing to {MQTT_BROKER}: {payload}")
        client = MQTTClient(client_id, MQTT_BROKER)
        client.connect()
        client.publish(MQTT_TOPIC, payload)
        client.disconnect()
        print("Success!")
        
    except OSError as e:
        print(f"Network/OS Error caught: {e}")
        # Trigger a watchdog reset on network failure
        machine.reset()
    except Exception as e:
        print(f"Unexpected error: {e}")
        machine.reset()

if __name__ == "__main__":
    main()

Debugging the "OSError: [Errno -2] ENOTFOUND" Crash

When working with the CYW43439 wireless chip on the Pico 2 W, the most notorious failure during MQTT or HTTP operations is the DNS resolution crash. If your serial monitor outputs the following exact string, your board has connected to Wi-Fi but cannot resolve the broker's hostname:

OSError: [Errno -2] ENOTFOUND

This happens inside the socket.getaddrinfo() call within the umqtt library. If you passed an IP address (like 192.168.1.100) to the MQTT client, you will rarely see this. It almost exclusively triggers when using a hostname (e.g., mqtt.myhome.local or broker.hivemq.com).

The First Three Things to Check

  1. DNS Server Assignment: The CYW43439 driver relies on the DHCP server to pass a valid DNS address. If your router is handing out a local DNS (like Pi-hole) that blocks external MQTT domains, or if the DHCP lease omitted the DNS field, the Pico will fail to resolve. Fix: Hardcode your MQTT broker's IP address in the script, or configure your router to push 8.8.8.8 as the secondary DNS.
  2. mDNS / .local Domains: MicroPython's lightweight network stack does not support Multicast DNS (mDNS) out of the box. If your broker is at homeassistant.local, the Pico 2 W cannot resolve it. Fix: Use the static IP of your Home Assistant server instead of the .local hostname.
  3. Memory Fragmentation: Though less common on the 520KB RP2350 than the RP2040, if you allocate large buffers before connecting to Wi-Fi, the CYW43439 firmware may fail to allocate the contiguous SRAM block it needs for the DNS socket buffer, resulting in a silent failure that bubbles up as ENOTFOUND. Fix: Run gc.collect() immediately before calling client.connect().

Extending and Simplifying the Build

Depending on your deployment environment, you may need to strip this project down to its bare essentials or scale it up for production.

How to Simplify

  • Drop the Sensor: If you just need a Wi-Fi heartbeat node to test network stability, remove the bme280 import and I2C setup. Publish a simple incrementing integer or the board's internal temperature (machine.ADC(machine.ADC.CORE_TEMP)) to save memory and boot time.
  • Switch to HTTP: If setting up an MQTT broker is a hurdle, replace the umqtt block with MicroPython's built-in urequests library to send a simple HTTP POST to a free service like IFTTT or a Home Assistant Webhook.

How to Extend

  • Dual-Core Architecture: The RP2350 has two cores. Use the _thread module to run the Wi-Fi/MQTT stack on Core 1, while Core 0 handles high-speed I2C polling or drives a local OLED display via SPI without network latency interrupting the UI.
  • Deep Sleep Integration: For battery-powered nodes, use the RP2350's dormant mode. Note that the CYW43439 chip draws ~20mA even when idle. To achieve true micro-amp deep sleep, you must cut power to the wireless chip via its enable pin or use an external hardware timer to physically gate the VSYS rail.
Code Note: For more advanced networking features like TLS encryption (MQTTS), refer to the MicroPython RP2 Quick Reference. The Pico 2 W's RP2350 includes a hardware True Random Number Generator (TRNG) which significantly speeds up TLS handshakes compared to software-based entropy gathering.

Pi Pico 2 W FAQ

Is the Pi Pico 2 W pinout identical to the original Pico W?

Yes, the physical footprint and GPIO pinout (GP0 through GP28, plus ADC and power pins) are 100% identical to the original Pico W and Pico 1. You can drop the Pico 2 W into an existing breadboard or custom PCB designed for the Pico W without changing your wiring. The internal architecture (RP2350 vs RP2040) is different, but the hardware abstraction layer in MicroPython handles the translation seamlessly.

Why does my Pi Pico 2 W get hot when transmitting over Wi-Fi?

The Infineon CYW43439 Wi-Fi/Bluetooth combo chip and the RP2350's onboard switching regulator generate localized heat. During heavy continuous transmission (like flooding an MQTT broker with 100 messages a second), the board can reach 45-50°C (113-122°F) to the touch. This is normal and within the silicon's thermal limits. However, if you are reading the RP2350's internal core temperature sensor, you must calibrate for this ambient heat offset, or your room temperature readings will be artificially high by 3-5°C.

Can I use the RISC-V core on the Pi Pico 2 W for Wi-Fi tasks?

The RP2350 features a unique dual-core, dual-architecture design (Arm Cortex-M33 and Hazard3 RISC-V). However, the MicroPython firmware and the CYW43439 Wi-Fi driver binaries are currently compiled exclusively for the Arm architecture. While you can technically boot the board into RISC-V mode for raw compute tasks, the wireless stack and standard MicroPython networking libraries require the Arm cores. For Wi-Fi projects, ensure your UF2 firmware is the standard Arm build.