When makers search for the raspberry pico w 2, they are looking for the RP2350-based wireless board officially named the Raspberry Pi Pico 2W. Released to address the power-consumption and security limitations of the original RP2040 Pico W, the Pico 2W pairs dual ARM Cortex-M33 cores with the Infineon CYW43439 Wi-Fi/Bluetooth module. It is a massive upgrade for battery-powered IoT nodes, but its new architecture introduces specific firmware quirks.
This guide walks through building a Wi-Fi MQTT greenhouse climate node. We will cover the hardware decision matrix, exact wiring, production-ready MicroPython code, and the specific error strings you will hit when the RP2350’s wireless stack misbehaves.
The Verdict: Which Board Variant Should You Actually Buy?
Before ordering parts, you need to select the right microcontroller for your power and security constraints. The naming convention can be confusing, so here is a decision path to terminate your board selection.
| Use Case | Board Pick | Why? |
|---|---|---|
| Mains-powered IoT, high GPIO count needed | ESP32-S3 (WROOM-1) | More raw I/O, mature ESP-IDF ecosystem, no deep-sleep battery requirements. |
| Ultra-low cost, simple USB serial logger | Raspberry Pi Pico 1 (RP2040) | $4 price point, no Wi-Fi overhead, massive community support. |
| Battery-powered, secure Wi-Fi, needs OTA | Raspberry Pi Pico 2W (RP2350) | Hardware security (OTP/secure boot), 4MB flash, deeper sleep states than ESP32. |
Hardware BOM and Pin Mapping
This build avoids complex I2C multiplexing by using the RP2350’s internal temperature sensor alongside an external ADC-based capacitive soil moisture probe. This keeps the BOM cheap and the code self-contained.
Parts List
- MCU: Raspberry Pi Pico 2W (RP2350, 4MB Flash) — ~$7.00
- Sensor: Capacitive Soil Moisture Sensor v1.2 (Analog output) — ~$2.50
- Power: 18650 Li-ion cell + Adafruit PowerBoost 1000C (or generic 5V USB power bank for bench testing)
- Wiring: 22 AWG solid core hookup wire, half-size breadboard
Pin Mapping Table
| Pico 2W Pin | RP2350 Function | Connected To | Notes |
|---|---|---|---|
| GP26 (Pin 31) | ADC0 | Soil Sensor AOUT | Reads 0-3.3V analog moisture level |
| 3V3 (Pin 36) | Power Out | Soil Sensor VCC | Do not use VBUS (5V) for this sensor |
| GND (Pin 38) | Ground | Soil Sensor GND | Common ground required for ADC |
| Internal ADC4 | Temp Sensor | N/A (On-chip) | RP2350 internal die temperature |
Step-by-Step Wiring Procedure
- De-energize the board: Ensure the Pico 2W is unplugged from USB and the battery is disconnected.
- Mount the MCU: Press the Pico 2W into the breadboard, straddling the center trench so pins on both sides are accessible.
- Wire Power: Connect a jumper from the Pico’s
3V3pin (Physical Pin 36) to the breadboard’s positive rail. ConnectGND(Physical Pin 38) to the negative rail. - Connect the Soil Sensor:
- Sensor
VCCto breadboard positive rail (3.3V). - Sensor
GNDto breadboard negative rail. - Sensor
AOUTto PicoGP26(Physical Pin 31).
- Sensor
- Verify Voltages: Before plugging in USB, use a multimeter in continuity mode to verify there is no short between the 3V3 and GND rails. Set the meter to DC Voltage, plug in USB, and confirm 3.25V–3.35V on the positive rail.
MicroPython Code: MQTT Publishing with Error Handling
Target Board Variant: This code is written specifically for the Raspberry Pi Pico 2W (RP2350) running MicroPython v1.23.0 or newer for RP2350. Do not flash the RP2040 Pico W firmware; the Wi-Fi chip initialization sequence differs.
The script connects to Wi-Fi, reads the internal die temperature and external soil moisture ADC, and publishes to an MQTT broker. It includes robust retry logic and exception handling to prevent the watchdog from resetting the board during network hiccups.
import network
import time
import machine
import ubinascii
from umqtt.simple import MQTTClient
import sys
# --- PIN & CONFIG DEFINITIONS ---
SOIL_ADC_PIN = 26
WIFI_SSID = "YourNetworkSSID"
WIFI_PASS = "YourNetworkPassword"
MQTT_BROKER = "192.168.1.50"
MQTT_PORT = 1883
MQTT_TOPIC_SOIL = b"greenhouse/soil"
MQTT_TOPIC_TEMP = b"greenhouse/temp"
SLEEP_INTERVAL_SEC = 300 # 5 minutes
# Initialize ADC for Soil Moisture (GP26)
soil_adc = machine.ADC(SOIL_ADC_PIN)
# Initialize Internal Temperature Sensor (ADC4 on RP2350)
temp_adc = machine.ADC(4)
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Prevent Wi-Fi from waking up the core unnecessarily during sleep
wlan.config(pm = 0xa11140)
if not wlan.isconnected():
print(f"Connecting to {WIFI_SSID}...")
wlan.connect(WIFI_SSID, WIFI_PASS)
max_retries = 15
for i in range(max_retries):
if wlan.isconnected():
break
time.sleep(1)
print(".", end="")
if not wlan.isconnected():
raise RuntimeError("WIFI connect failed: Timeout after 15s")
print(f"\nConnected! IP: {wlan.ifconfig()[0]}")
return wlan
def read_sensors():
# Soil Moisture (16-bit raw value, higher = wetter for most capacitive sensors)
soil_raw = soil_adc.read_u16()
# Internal Temperature calculation for RP2350
# Formula: 27 - (ADC_voltage - 0.706)/0.001721
temp_raw = temp_adc.read_u16()
adc_voltage = temp_raw * 3.3 / 65535
temp_c = 27 - (adc_voltage - 0.706) / 0.001721
return soil_raw, round(temp_c, 2)
def main():
client_id = ubinascii.hexlify(machine.unique_id())
try:
wlan = connect_wifi()
client = MQTTClient(client_id, MQTT_BROKER, port=MQTT_PORT, keepalive=60)
client.connect()
print(f"Connected to MQTT Broker: {MQTT_BROKER}")
soil, temp = read_sensors()
print(f"Publishing -> Soil: {soil}, Temp: {temp}C")
client.publish(MQTT_TOPIC_SOIL, str(soil))
client.publish(MQTT_TOPIC_TEMP, str(temp))
client.disconnect()
wlan.active(False) # Shut down radio to save power
except OSError as e:
print(f"Network/MQTT OSError: {e}")
sys.exit(1) # Let external watchdog or RTC alarm handle restart
except Exception as e:
print(f"Unexpected Error: {e}")
sys.exit(1)
# Deep sleep implementation would go here using machine.deepsleep()
print(f"Sleeping for {SLEEP_INTERVAL_SEC}s...")
time.sleep(SLEEP_INTERVAL_SEC)
if __name__ == "__main__":
main()
Debugging: Exact Error Strings and First 3 Checks
The RP2350’s wireless stack is newer than the ESP32’s, meaning MicroPython error handling can sometimes throw opaque OS-level errors. When your node fails, here are the first three things to check, mapped to the exact error strings Thonny will output.
1. Error: OSError: [Errno 113] EHOSTUNREACH
What it means: The Pico connected to Wi-Fi, but the TCP socket cannot route to the MQTT broker’s IP address.
- Cause A (Most Likely): Your MQTT broker (e.g., Mosquitto on a Pi) is on a different VLAN or subnet, and the router is dropping the packets.
- Cause B: The broker IP in the code is a typo, or the broker service crashed.
- Fix: Ping the broker IP from your PC. Verify Mosquitto is listening on
0.0.0.0:1883, not just127.0.0.1.
2. Error: RuntimeError: WIFI connect failed: Timeout after 15s
What it means: The CYW43439 chip initialized, but the WPA2 handshake failed or timed out.
- Cause A (Most Likely): You are trying to connect to a 5GHz or Wi-Fi 6 (802.11ax) network. The Pico 2W only supports 2.4GHz 802.11n.
- Cause B: WPA3 enterprise security is enabled on your router, which the Pico’s firmware does not support.
- Fix: Create a dedicated 2.4GHz IoT SSID on your router with WPA2-Personal (AES) security.
3. Error: ValueError: ADC pin 4 is not valid (or similar ADC read failure)
What it means: The code is trying to read the internal temperature sensor, but the firmware doesn't recognize ADC4.
- Cause A (Most Likely): You accidentally flashed the RP2040 (Pico 1) MicroPython firmware onto your Pico 2W. The RP2040 uses ADC4 for temp, but early RP2350 builds mapped it differently or require specific SDK flags.
- Fix: Download the correct
RP2350specific.uf2file from the official MicroPython downloads page. Hold the BOOTSEL button, plug in USB, and drag the correct file over.
1. Is the router broadcasting a 2.4GHz signal?
2. Is the correct RP2350
.uf2 firmware flashed? 3. Is the MQTT broker actually running and accessible on port 1883?
Extending or Simplifying the Build
Depending on your infrastructure, you may need to strip this project down or scale it up. Here is exactly how to modify the architecture.
How to Simplify (No MQTT Broker)
If you do not want to maintain a Mosquitto broker on a local server, drop the MQTT protocol entirely. Replace the umqtt logic with the urequests library to send a simple HTTP POST request to a free service like IFTTT Webhooks or a local Node-RED HTTP endpoint.
Trade-off: HTTP requires more radio-on time than MQTT, which will reduce your 18650 battery life by roughly 30% per cycle.
How to Extend (Add Deep Sleep and a 12V Pump)
To make this a true autonomous greenhouse node, you need to control a water pump and sleep between reads.
- Add Deep Sleep: Replace
time.sleep()at the end of the script withmachine.deepsleep(SLEEP_INTERVAL_SEC * 1000). The RP2350’s power gating will drop current draw from ~20mA to microamps. Note that deep sleep resets the RAM; you will need to use the RTC or write state to the onboard flash if you need to persist variables across sleeps. - Add a 12V Pump: Never connect a 12V inductive load directly to the Pico’s GPIO. Use a logic-level MOSFET (like an IRLZ44N) or an opto-isolated relay module. Connect the Pico’s
GP15through a 1kΩ gate resistor to the MOSFET gate, and wire the pump across your 12V supply and the MOSFET drain. Add a flyback diode (1N4007) across the pump terminals to prevent voltage spikes from frying the RP2350 when the pump turns off.
For further reading on the RP2350's security and power architecture, refer to the official Raspberry Pi RP2350 Datasheet and the MicroPython network.WLAN documentation.






