Project Overview & Difficulty Rating
The Raspberry Pi Pico 2 W brings the dual-core RP2350 microcontroller (featuring both ARM Cortex-M33 and Hazard3 RISC-V cores) together with the Infineon CYW43439 Wi-Fi/Bluetooth chip. This combination offers a massive leap in SRAM (520KB) and security (Secure Boot, OTP) over the original Pico W, making it ideal for robust IoT edge nodes.
In this guide, we are building a Wi-Fi MQTT Relay Controller. This project connects to your local network, subscribes to an MQTT topic, and toggles a 2-channel relay module to control mains-voltage appliances (like a grow light or a water pump). We will also cover the exact network failure modes unique to the RP2350 architecture.
| Parameter | Specification |
|---|---|
| Difficulty | Intermediate (Requires basic MQTT broker setup) |
| Time to Build | 45 minutes (hardware) + 30 minutes (software) |
| Estimated Cost | $12 - $18 USD (Board + Relay + Misc) |
| Target Board | Raspberry Pi Pico 2 W (RP2350, 4MB QSPI Flash) |
| Firmware | MicroPython v1.24+ (RP2350 ARM Cortex-M33 build) |
Hardware BOM & Pin Mapping
Before wiring, ensure you have the exact Pi Pico 2 W variant. The standard Pico 2 lacks the Infineon CYW43439 radio and the necessary antenna shielding. For the relay module, use an optocoupler-isolated 5V module to prevent back-EMF from frying the Pico's 3.3V logic.
Parts List
- Microcontroller: Raspberry Pi Pico 2 W (RP2350, 4MB Flash, headers pre-soldered)
- Relay Module: 5V 2-Channel Relay Module with optocoupler isolation (e.g., Songle SRD-05VDC-SL-C)
- Power Supply: 5V 2A USB-C power supply (The CYW43439 Wi-Fi chip can draw up to 130mA during TX bursts; a weak USB port will cause brownouts)
- Wiring: 22 AWG solid core jumper wires
- Logic Level Note: The Pico 2 W outputs 3.3V. Most modern 5V relay modules will trigger reliably at 3.3V on the IN pins, but if yours does not, you will need a 2N2222 NPN transistor or a logic-level MOSFET (like the BSS138) to switch the 5V relay coil.
Pin Mapping Table
| Pico 2 W Pin | GPIO Number | Relay Module Pin | Function |
|---|---|---|---|
| VBUS | N/A | JD-VCC | 5V Power for relay coils (jumper removed) |
| 3V3(OUT) | N/A | VCC | 3.3V Power for optocoupler LEDs |
| GND | N/A | GND | Common Ground |
| GP16 | 16 | IN1 | Relay 1 Control (Active Low) |
| GP17 | 17 | IN2 | Relay 2 Control (Active Low) |
| GP25 | 25 (LED) | N/A | Onboard Status LED (Wi-Fi connected indicator) |
machine.Pin('LED', machine.Pin.OUT) instead of machine.Pin(25) to control the onboard status light.
Step-by-Step Build & MicroPython Code
This code targets the RP2350 ARM Cortex-M33 MicroPython build. Ensure you have installed the umqtt.simple library via Thonny's package manager (Tools > Manage Packages) before flashing.
1. Flash the Firmware
Download the latest stable MicroPython .uf2 file specifically for the Raspberry Pi Pico 2 (RP2350) from the official MicroPython downloads page. Hold the BOOTSEL button, plug in the USB, and drag the file to the RPI-RP2 drive.
2. Wire the Relay Module
Remove the jumper between VCC and JD-VCC on the relay module. Connect Pico VBUS to JD-VCC, Pico 3V3 to VCC, and GND to GND. Connect GP16 to IN1 and GP17 to IN2.
3. Upload the MicroPython Code
Copy the following complete, compilable code into your main.py file. Update the Wi-Fi credentials and MQTT broker IP address.
import network
import time
import ubinascii
from machine import Pin, unique_id
from umqtt.simple import MQTTClient
# --- PIN DEFINITIONS ---
RELAY_1 = Pin(16, Pin.OUT, value=1) # Active LOW (1 = OFF)
RELAY_2 = Pin(17, Pin.OUT, value=1) # Active LOW (1 = OFF)
STATUS_LED = Pin('LED', Pin.OUT, value=0)
# --- NETWORK CONFIG ---
WIFI_SSID = 'YourNetworkName'
WIFI_PASS = 'YourNetworkPassword'
MQTT_BROKER = '192.168.1.100'
MQTT_PORT = 1883
# Generate unique client ID from RP2350 silicon ID
CLIENT_ID = ubinascii.hexlify(unique_id()).decode()[:8]
TOPIC_1 = b'home/relays/1'
TOPIC_2 = b'home/relays/2'
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Prevent Wi-Fi from waking the device unnecessarily in sleep modes
wlan.config(pm = 0xa11140)
print(f'Connecting to {WIFI_SSID}...')
wlan.connect(WIFI_SSID, WIFI_PASS)
timeout = 20
while not wlan.isconnected() and timeout > 0:
STATUS_LED.toggle()
time.sleep(0.5)
timeout -= 1
if not wlan.isconnected():
raise RuntimeError('WIFI Failed to connect: Check 2.4GHz band and credentials')
STATUS_LED.on()
print(f'Connected! IP: {wlan.ifconfig()[0]}')
return wlan
def mqtt_callback(topic, msg):
print(f'Received: {topic} -> {msg}')
if topic == TOPIC_1:
RELAY_1.value(0 if msg == b'ON' else 1)
elif topic == TOPIC_2:
RELAY_2.value(0 if msg == b'ON' else 1)
def connect_mqtt(wlan):
client = MQTTClient(CLIENT_ID, MQTT_BROKER, port=MQTT_PORT, keepalive=60)
client.set_callback(mqtt_callback)
try:
client.connect()
print('MQTT Connected')
except OSError as e:
print(f'MQTT Connection Failed: {e}')
# Reset Wi-Fi if MQTT fails to connect to clear CYW43439 state
wlan.disconnect()
wlan.active(False)
raise
client.subscribe(TOPIC_1)
client.subscribe(TOPIC_2)
return client
def main():
wlan = connect_wifi()
client = connect_mqtt(wlan)
while True:
try:
client.check_msg() # Non-blocking check for new messages
time.sleep(0.1)
except OSError as e:
print(f'Network Error during loop: {e}')
print('Reconnecting...')
# Reconnect logic
while not wlan.isconnected():
connect_wifi()
client = connect_mqtt(wlan)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Program halted')
RELAY_1.value(1)
RELAY_2.value(1)
STATUS_LED.off()
Debugging: Network Failures & Exact Error Strings
The CYW43439 Wi-Fi chip on the Pi Pico 2 W communicates with the RP2350 over SPI. When network conditions degrade, the MicroPython network stack throws specific OS errors. Here is how to debug the most common failure modes.
Error 1: OSError: [Errno 118] EHOSTUNREACH
Context: This exact error string appears when the client.connect() function is called, the Wi-Fi is connected, but the MQTT broker cannot be reached.
Ranked Causes:
- Broker Firewall/Isolation: Your MQTT broker (e.g., Mosquitto on a Raspberry Pi 4) has a firewall blocking port 1883, or your router has 'AP Isolation' enabled, preventing Wi-Fi clients from talking to wired LAN devices.
- Incorrect IP Address: The
MQTT_BROKERvariable points to an offline device or a typo in the static IP. - Broker Service Down: The Mosquitto service crashed or isn't set to start on boot.
Error 2: RuntimeError: WIFI Failed to connect
Context: This is the custom exception thrown by our connect_wifi() function when the 20-second timeout expires.
First Three Things to Check:
- 2.4GHz vs 5GHz: The CYW43439 chip is strictly an 802.11n 2.4GHz radio. If your router uses a unified SSID for both bands and heavily steers devices to 5GHz, the Pico 2 W will fail to associate. Create a dedicated 2.4GHz IoT SSID.
- WPA3 Compatibility: While the Pico 2 W supports WPA2, some WPA3-Transition modes on modern mesh routers (like Eero or Ubiquiti) cause the CYW43439 firmware to hang during the handshake. Force WPA2-Personal (AES) for the IoT network.
- Power Brownout: The Wi-Fi chip draws peak current during the association phase. If you are powering the Pico 2 W from a PC USB hub, the voltage may dip below 4.5V, causing the RP2350 to silently reset the SPI bus. Use a dedicated 5V 2A wall adapter.
Extending or Simplifying the Build
Depending on your deployment environment, you may need to scale this project up for industrial use or down for battery-powered sensor nodes.
How to Simplify (Battery-Powered Deep Sleep)
If you want to run this off a 18650 Li-ion cell, you must utilize the RP2350's deep sleep capabilities. However, the CYW43439 chip will ruin your battery life if left active. To simplify for battery use:
- Replace the continuous
while TrueMQTT loop with a single HTTP GET request to a webhook (like IFTTT or Home Assistant). - Explicitly deinitialize the Wi-Fi interface before sleeping:
wlan.active(False)andwlan.deinit(). - Use
machine.deepsleep(3600000)to wake up once an hour. Properly deinitializing the Wi-Fi chip drops the Pico 2 W sleep current from ~20mA down to 1.8mA.
How to Extend (Secure Boot & TLS)
The RP2350 introduces hardware Secure Boot and a True Random Number Generator (TRNG). To extend this project for commercial or high-security deployments:
- Enable Secure Boot via the RP2350 OTP (One-Time Programmable) memory to prevent unauthorized firmware flashing.
- Upgrade the MQTT connection to use TLS (port 8883). The RP2350's ARM Cortex-M33 cores handle the cryptographic overhead of mbedTLS significantly faster than the original RP2040's Cortex-M0+, making encrypted MQTT viable without massive latency.
- Add an I2C BME280 sensor to the GP4 (SDA) and GP5 (SCL) pins to publish environmental data alongside the relay states.
Raspberry Pi Pico 2 W FAQ
Does the Pi Pico 2 W support 5GHz Wi-Fi or Wi-Fi 6?
No. The Pi Pico 2 W uses the Infineon CYW43439 chip, which is limited to 2.4GHz 802.11n (Wi-Fi 4) and Bluetooth 5.2. It does not support 5GHz or Wi-Fi 6 (802.11ax). If you require 5GHz to avoid congested 2.4GHz bands in industrial environments, you must use an external SPI Wi-Fi module like the Adafruit AirLift (ESP32-based) wired to the Pico 2 W's spare SPI bus.
Can I use the RISC-V cores on the Pi Pico 2 W for Wi-Fi tasks?
Technically yes, but practically no. The RP2350 contains two ARM Cortex-M33 cores and two Hazard3 RISC-V cores. However, the MicroPython Wi-Fi and Bluetooth stacks are currently optimized and compiled for the ARM architecture. If you flash the RISC-V MicroPython build, the CYW43439 Wi-Fi library will either fail to load or suffer severe performance penalties due to emulation layers. Always use the ARM build for wireless projects on the Pico 2 W.
Why is my Pi Pico 2 W drawing 20mA in deep sleep instead of microamps?
This is the most common debugging headache with the Pico W and Pico 2 W. The CYW43439 Wi-Fi chip has its own internal power domain. If you call machine.deepsleep() without first turning off the Wi-Fi radio, the chip remains in an idle listening state, drawing roughly 18-22mA. You must call wlan.active(False) and ideally network.WLAN(network.STA_IF).deinit() before entering deep sleep to cut power to the radio's MAC layer and achieve the ~1.8mA baseline sleep current.






