The Raspberry Pi Pico WH (SKU: SC0918) is the header-equipped sibling of the standard Pico W. It pairs the RP2040 microcontroller with the Infineon CYW43439 WiFi/Bluetooth chip, but saves you the tedious 15-minute job of soldering 40 tiny 0.1-inch pins. If you are prototyping on a breadboard and need wireless connectivity, the Pico WH is the definitive starting point. However, the CYW43439 WiFi stack introduces specific memory and power quirks that trip up makers migrating from the ESP32 or the original wired Pico.

This guide walks through building a robust, WiFi-enabled MQTT environmental sensor. We will cover the exact hardware decisions, provide production-ready MicroPython code with memory management, and debug the most common wireless failure modes.

The Decision Path: Pico WH vs. Pico W vs. ESP32

Before buying, confirm the Pico WH is actually the right board for your bench. The 'H' simply denotes pre-soldered headers, but the underlying wireless architecture dictates your firmware limits.

Criteria Raspberry Pi Pico WH Raspberry Pi Pico W ESP32-C3 SuperMini
Price (2026 Avg) ~$8.00 ~$6.00 ~$4.50
Headers Pre-soldered 0.1" None (Castellated) Pre-soldered 0.1"
WiFi Chip CYW43439 (2.4GHz) CYW43439 (2.4GHz) Integrated (2.4GHz)
SRAM 264 KB 264 KB 400 KB
Deep Sleep Current ~1.2 mA ~1.2 mA ~5 µA
The Concrete Pick: If you are building a custom PCB and want to save $2 per unit, buy the Pico W. If you are building a battery-powered node that must sleep for months, buy the ESP32-C3. For 90% of breadboard IoT prototypes where you want the Raspberry Pi ecosystem and zero soldering, buy the Pico WH.

Hardware Spec Sheet & Pin Mapping

This build targets the Raspberry Pi Pico WH running MicroPython (v1.22+). We are reading a BME280 temperature/humidity sensor via I2C and publishing to an MQTT broker. A critical hardware note: on the Pico W and WH, the onboard LED is not connected to GP25 like the original Pico. It is wired to the WiFi chip's GPIO0.

Parts List

  • MCU: Raspberry Pi Pico WH (SC0918)
  • Sensor: BME280 Breakout (Adafruit 2652 or generic 3.3V I2C variant)
  • Prototyping: 830-point breadboard, 22 AWG solid core jumper wires
  • Power: 5V/2A USB-C power supply (do not use a PC USB 2.0 port; the WiFi chip needs current headroom)

Pin Mapping Table

Pico WH Pin GPIO / Function BME280 Breakout Pin Notes
Pin 36 3V3(OUT) VIN / VCC Max draw ~300mA total
Pin 38 GND GND Common ground
Pin 6 GP4 (I2C0 SDA) SDA Requires 4.7k pull-up (usually on breakout)
Pin 7 GP5 (I2C0 SCL) SCL Clock speed set to 400kHz
Internal WL_GPIO0 N/A Onboard LED (Accessed via 'LED' alias)

Step-by-Step Build & MicroPython Firmware

Flash your Pico WH with the latest MicroPython UF2 for the Raspberry Pi Pico W (the firmware is identical for the WH). Ensure you allocate the maximum filesystem space during the Thonny IDE setup.

Step 1: Wire the I2C Bus

Connect GP4 to SDA and GP5 to SCL. Keep I2C wires under 6 inches to avoid capacitance issues at 400kHz. If your generic BME280 breakout lacks pull-up resistors, the bus will hang silently.

Step 2: Upload the BME280 Library

MicroPython does not include a native BME280 driver. Download the community-standard bme280.py float library from the robert-hh/BME280 GitHub repository and save it to the root directory of your Pico WH.

Step 3: Flash the Main Application

The following code handles WiFi connection, memory management, sensor polling, and MQTT publishing. It includes explicit error handling to prevent the node from hanging on network drops.


import time
import gc
import machine
import network
import ubinascii
from umqtt.simple import MQTTClient
import bme280

# --- PIN & CONFIG DEFINITIONS ---
I2C_SDA = 4
I2C_SCL = 5
WIFI_SSID = 'Your_2.4GHz_Network'
WIFI_PASS = 'Your_Password'
MQTT_BROKER = '192.168.1.100'
MQTT_PORT = 1883
MQTT_TOPIC = b'pico_wh/sensor/env'
SLEEP_INTERVAL = 30  # seconds

# Force garbage collection before loading the heavy WiFi firmware blob
gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())

# Initialize I2C and Sensor
i2c = machine.I2C(0, sda=machine.Pin(I2C_SDA), scl=machine.Pin(I2C_SCL), freq=400000)
bme = bme280.BME280(i2c=i2c)

# Initialize Onboard LED (WL_GPIO0 on Pico W/WH)
led = machine.Pin('LED', machine.Pin.OUT)

def connect_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    # Prevent power-save mode from dropping MQTT connections
    wlan.config(pm = 0xa11140) 
    
    if not wlan.isconnected():
        print('Connecting to WiFi...')
        wlan.connect(WIFI_SSID, WIFI_PASS)
        max_wait = 15
        while max_wait > 0:
            if wlan.status() < 0 or wlan.status() >= 3:
                break
            max_wait -= 1
            led.toggle()
            time.sleep(1)
            
    if wlan.status() != 3:
        raise RuntimeError('WiFi connection failed')
    
    led.value(1)
    print('Connected:', wlan.ifconfig())
    return wlan

def publish_telemetry(client):
    try:
        temp, press, hum = bme.values
        # Format payload (simple CSV for lightweight parsing)
        payload = f'{temp},{press},{hum}'
        client.publish(MQTT_TOPIC, payload)
        print(f'Published: {payload}')
    except Exception as e:
        print(f'Sensor/Publish Error: {e}')

try:
    wlan = connect_wifi()
    client_id = ubinascii.hexlify(machine.unique_id())
    client = MQTTClient(client_id, MQTT_BROKER, port=MQTT_PORT, keepalive=60)
    client.connect()
    
    while True:
        publish_telemetry(client)
        # Deep sleep is preferred for battery, but light sleep works for USB
        time.sleep(SLEEP_INTERVAL)
        
except OSError as e:
    print(f'Network OS Error: {e}')
    machine.reset()
except Exception as e:
    print(f'Fatal Error: {e}')
    machine.reset()

Debugging: Fixing CYW43 WiFi & Memory Errors

The CYW43439 chip requires a ~300KB firmware blob to operate. Because the RP2040 only has 264KB of SRAM, this firmware is executed in place (XIP) from the external flash chip, but the runtime state and WiFi buffers heavily fragment the MicroPython heap. When the Pico WH fails, it usually fails in one of three specific ways.

The First Three Things to Check When It Fails

  1. Power Rail Sag: The CYW43 chip draws peaks of ~130mA during TX bursts. If your USB cable is thin or your breadboard power rails have high contact resistance, the 3.3V LDO on the Pico WH will brownout, resetting the board silently. Measure the 3V3(OUT) pin with a multimeter; it must stay above 3.1V during WiFi connection.
  2. SSID Band Mismatch: The CYW43439 is strictly an 802.11n 2.4GHz radio. It cannot see 5GHz or 6GHz networks. If your router uses a unified SSID for both bands, the Pico WH may attempt to associate with the 5GHz band and time out. Create a dedicated 2.4GHz IoT SSID.
  3. Heap Fragmentation: If you allocate large buffers after initializing the WiFi stack, MicroPython will fail to find contiguous memory blocks, throwing memory errors.

Troubleshooting: OSError: [Errno 12] ENOMEM

Symptom: During wlan.connect() or when initializing the MQTT client, the REPL throws:

OSError: [Errno 12] ENOMEM

Ranked Causes & Fixes:

Rank Cause Fix
1 Heap fragmented by WiFi driver load Call gc.collect() and gc.threshold() before calling network.WLAN() (as shown in the code above).
2 Importing heavy modules after WiFi init Move all import statements to the very top of main.py before any hardware initialization.
3 Retaining large string payloads in RAM Use byte strings (b'topic') for MQTT topics and avoid concatenating large JSON strings in memory.

For deeper architectural details on the Pico W memory layout, refer to the official Raspberry Pi Pico W Datasheet and the MicroPython network.WLAN documentation.

Extending and Simplifying the Build

Once the base node is stable, you will likely need to adapt it for your specific environment. Here is how to pivot the design without rewriting the core architecture.

How to Simplify (The 'Dumb' Sensor Node)

If you do not want to maintain an MQTT broker (like Mosquitto) on your network, strip the umqtt library out entirely. Replace the publish_telemetry() function with a simple HTTP GET request using the urequests library to push data to a local Node-RED endpoint or a cloud service like ThingsBoard. This reduces the firmware footprint by ~15KB and eliminates the need for client.check_msg() polling loops.

How to Extend (Battery & Deep Sleep)

The Pico WH's ~1.2mA sleep current is mediocre for coin-cell or small LiPo applications. To extend battery life:

  • Add a Pico SHIM: Use a LiPo shim (like the Pimoroni LiPo SHIM) to manage charging and boost conversion.
  • Use RTC Alarms: Instead of time.sleep(), configure the RP2040's RTC to wake the board. Note that the Pico WH does not support true deep sleep (where RAM is lost) if you want to retain WiFi credentials without re-reading flash. Use machine.lightsleep() instead, which drops current to ~1.5mA while retaining state.
  • Disable the LED: Ensure led.value(0) is called before sleeping. A lit LED draws ~2mA, completely ruining your sleep current budget.
Safety & Code Caveat: When deploying IoT nodes on mains-powered relays or HVAC controllers, always use optical isolation or mechanical relays rated for your specific load. The Pico WH GPIO pins output 3.3V at a maximum of 12mA; they cannot drive relay coils directly without a logic-level MOSFET or transistor driver.