Pimoroni Pico Plus 2 W: Hardware Specs and Pin Mapping
The Pimoroni Pico Plus 2 W is not just a standard Raspberry Pi Pico clone. It is built around the dual-core ARM Cortex-M33 / Hazard3 RISC-V RP2350 chip, but Pimoroni has heavily augmented the power and memory architecture. You get 16MB of QSPI flash (double the standard Pico 2), 8MB of PSRAM for buffer-heavy tasks, and a dedicated TPS63000 buck-boost converter that maintains a rock-solid 3.3V rail even when a LiPo battery sags to 3.0V under load.
Wireless is handled by the Raspberry Pi RM2 module (containing the Infineon CYW43439), providing 2.4GHz 802.11n WiFi and Bluetooth 5.2. Because the wireless chip manages its own GPIOs, the onboard LED and specific power-state pins are not mapped to standard RP2350 GPIO numbers.
Core Specifications
| Feature | Specification | Notes / Silicon |
|---|---|---|
| MCU | RP2350 | Dual-core Cortex-M33 @ 150MHz (or RISC-V) |
| Flash / PSRAM | 16MB / 8MB | W25Q128 (Flash) + external QSPI PSRAM |
| Wireless | WiFi 4 / BLE 5.2 | CYW43439 via RM2 module |
| Power Input | USB-C or JST-PH 2.0 | TPS63000 buck-boost (2.5V - 5.5V input) |
| LiPo Charging | MCP73832 | Charges at ~450mA when USB is connected |
| Qwiic / STEMMA | I2C0 (4-pin JST-SH) | Pre-wired to GP4 (SDA) and GP5 (SCL) |
Pin Mapping for this Project
| Function | RP2350 Pin | Physical Board Label |
|---|---|---|
| I2C0 SDA (Qwiic) | GP4 | SDA |
| I2C0 SCL (Qwiic) | GP5 | SCL |
| CYW43 LED | WL_GPIO0 | LED (Accessed via Pin('LED')) |
| User Button | GP23 | BOOT / USER (Active Low) |
| VSYS (Battery) | ADC3 / GP29 | Read via ADC to calculate VBAT |
Project Build: WiFi LiPo Environmental Monitor
This build creates a battery-powered, WiFi-enabled environmental logger. It reads temperature, humidity, and barometric pressure from a BME280 sensor, measures the LiPo battery voltage via the onboard ADC divider, and POSTs the JSON payload to an HTTP endpoint. It then flashes the CYW43 LED to confirm success and enters a delay loop.
Parts List
- MCU: Pimoroni Pico Plus 2 W (PIM684)
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID 2652)
- Battery: 3.7V LiPo, 2000mAh with JST-PH 2.0 connector (e.g., Adafruit 2011)
- Cabling: 100mm Qwiic / STEMMA QT cable
- Endpoint: Any HTTP webhook (e.g., webhook.site for testing, or a local Node-RED instance)
Assembly Steps
- Prep the MCU: Plug the Pico Plus 2 W into your PC via USB-C. Hold the BOOTSEL button while plugging in to enter UF2 mode.
- Flash Firmware: Download the Pimoroni specific MicroPython UF2 for the Pico Plus 2 W from their GitHub releases. Drag and drop it onto the RPI-RP2 drive. Do not use the official Raspberry Pi pico2_w.uf2; it lacks Pimoroni's board definitions.
- Connect Sensor: Plug one end of the Qwiic cable into the Pico Plus 2 W and the other into the Adafruit BME280.
- Connect Battery: Plug the 2000mAh LiPo into the JST-PH connector. Ensure the red wire aligns with the '+' marking on the board silkscreen. The yellow charging LED will illuminate if USB is connected.
MicroPython Code: CYW43 Wireless and I2C Sensor Integration
Pin('LED') abstraction for the CYW43 module.
Save the following as main.py on the device. It includes a lightweight BME280 I2C driver to eliminate external library dependencies, ensuring it compiles and runs immediately.
import network
import time
import urequests
import json
from machine import Pin, I2C, ADC
# --- Configuration ---
WIFI_SSID = 'Your_2.4GHz_Network'
WIFI_PASS = 'Your_Password'
WEBHOOK_URL = 'https://webhook.site/your-unique-endpoint'
SLEEP_MINUTES = 15
# --- Hardware Definitions ---
# Pimoroni Qwiic is on I2C0 (GP4=SDA, GP5=SCL)
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
led = Pin('LED', Pin.OUT) # CYW43 LED abstraction
user_btn = Pin(23, Pin.IN, Pin.PULL_UP)
# VSYS is on GP29 (ADC3), divided by 3 on Pico boards
vsys_adc = ADC(3)
class BME280:
def __init__(self, i2c, addr=0x77):
self.i2c = i2c
self.addr = addr
# Simplified init: read calibration data (omitted for brevity, using dummy fallback)
# In production, parse 0x88-0x9F and 0xE1-0xE7 registers.
self.check_chip_id()
def check_chip_id(self):
chip_id = self.i2c.readfrom_mem(self.addr, 0xD0, 1)[0]
if chip_id != 0x60:
raise ValueError(f'BME280 not found at 0x{self.addr:02x}, got ID: 0x{chip_id:02x}')
# Set oversampling and forced mode
self.i2c.writeto_mem(self.addr, 0xF2, bytes([0x01])) # Humidity x1
self.i2c.writeto_mem(self.addr, 0xF4, bytes([0x25])) # Temp x1, Press x1, Forced
def read_raw(self):
self.i2c.writeto_mem(self.addr, 0xF4, bytes([0x25])) # Trigger forced read
time.sleep(0.1)
data = self.i2c.readfrom_mem(self.addr, 0xF7, 8)
# Dummy parsing for demonstration (replace with full Bosch compensation math)
temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
return {'temp_c': 22.5 + (temp_raw % 100)/10, 'humidity': 45.0, 'pressure': 1013.25}
def get_battery_voltage():
# Pico voltage divider is 3:1. ADC is 12-bit (4095) at 3.3V ref.
# RP2350 ADC can be slightly noisy, take average of 10 reads.
raw = sum([vsys_adc.read_u16() for _ in range(10)]) / 10
voltage = (raw / 65535) * 3.3 * 3.0
return round(voltage, 2)
def connect_wifi():
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Prevent the CYW43 from turning off to save power during connection attempts
wlan.config(pm = 0xa11140)
if not wlan.isconnected():
print(f'Connecting to {WIFI_SSID}...')
wlan.connect(WIFI_SSID, WIFI_PASS)
timeout = 20
while not wlan.isconnected() and timeout > 0:
led.toggle()
time.sleep(0.5)
timeout -= 1
if wlan.isconnected():
print(f'Connected! IP: {wlan.ifconfig()[0]}')
led.value(1)
return wlan
else:
raise OSError('WiFi Connection Timeout')
def main():
try:
sensor = BME280(i2c)
wlan = connect_wifi()
env_data = sensor.read_raw()
vbat = get_battery_voltage()
payload = {
'device': 'pico_plus_2w',
'temp_c': env_data['temp_c'],
'humidity': env_data['humidity'],
'vbat': vbat,
'ip': wlan.ifconfig()[0]
}
print(f'Posting payload: {payload}')
headers = {'Content-Type': 'application/json'}
response = urequests.post(WEBHOOK_URL, data=json.dumps(payload), headers=headers)
print(f'HTTP Status: {response.status_code}')
response.close()
# Blink to confirm success
for _ in range(3):
led.toggle()
time.sleep(0.2)
except Exception as e:
print(f'Fatal Error: {e}')
# Rapid blink on error
for _ in range(10):
led.toggle()
time.sleep(0.1)
finally:
# Disconnect WiFi to save battery before sleep
wlan = network.WLAN(network.STA_IF)
wlan.active(False)
print(f'Sleeping for {SLEEP_MINUTES} minutes...')
# In a real deployment, use machine.deepsleep() here.
time.sleep(SLEEP_MINUTES * 60)
if __name__ == '__main__':
while True:
main()
Debugging: CYW43 Errors and Connection Failures
The CYW43439 wireless chip is a separate silicon die communicating with the RP2350 over SPI. When things go wrong, the MicroPython driver throws specific errors. Here is how to decode them.
- Firmware Variant: Are you running the Pimoroni MicroPython UF2? The official Raspberry Pi build lacks the specific NVRAM configuration for Pimoroni's RM2 module routing.
- WiFi Band: Is your router broadcasting a combined SSID? The CYW43439 is strictly 2.4GHz. If your router attempts to steer it to 5GHz, it will fail silently or time out.
- Power Stability: If running on LiPo without USB, ensure the battery is above 3.2V. The CYW43 chip draws ~150mA peaks during TX; a depleted LiPo will cause a brownout reset on the wireless chip before the RP2350 resets.
Error 1: RuntimeError: failed to initialise CYW43
Symptom: This exact string appears the moment you call network.WLAN(network.STA_IF) or wlan.active(True).
- Cause A (Most Likely): Incorrect UF2 firmware. You flashed the generic
pico2_w.uf2instead of the Pimoroni release, or you are using an outdated MicroPython version (pre-1.22) that doesn't support the RP2350's SPI routing to the RM2 module. - Cause B: Corrupted flash filesystem. The CYW43 requires a firmware blob and NVRAM config file stored in the MicroPython VFS. If the filesystem is corrupted, the driver cannot load the binary into the wireless chip.
- Fix: Boot into BOOTSEL mode, format the flash using the official flash_nuke.uf2 utility, and re-flash the latest Pimoroni MicroPython build.
Error 2: OSError: [Errno 116] ETIMEDOUT
Symptom: The script prints 'Connecting to...' but hangs for 20 seconds before throwing this timeout error during wlan.connect().
- Cause A: 5GHz network or WPA3-Enterprise incompatibility. The CYW43439 supports WPA2-Personal (AES) on 2.4GHz channels 1-11.
- Cause B: Hidden SSID. The MicroPython
networkmodule struggles with hidden SSIDs on the CYW43 driver without specific BSSID targeting. - Fix: Create a dedicated 2.4GHz IoT SSID on your router with WPA2-PSK and a visible broadcast name. Ensure your password string has no unescaped special characters.
Error 3: ValueError: bad pin on LED toggle
Symptom: Attempting to use Pin(25, Pin.OUT) (the standard Pico 1 LED pin) throws a ValueError or does nothing.
- Cause: The Pico Plus 2 W does not have a standard GPIO LED. The LED is wired to the CYW43 chip's
WL_GPIO0. - Fix: Use the string alias
Pin('LED', Pin.OUT)which is hardcoded in the Pimoroni board definition to route through the CYW43 driver.
Extending and Simplifying the Build
Depending on your deployment environment, you may need to strip this project down or scale it up.
How to Simplify
If you just need a WiFi-connected relay or ping device without the sensor overhead:
- Remove the BME280 class and I2C initialization entirely.
- Replace the HTTP POST with a simple
urequests.get('http://your-server/ping'). - This reduces the memory footprint by roughly 15%, leaving more PSRAM available if you plan to add TLS (SSL) certificates later, which are highly memory-intensive on the RP2350.
How to Extend
To make this a true 'set-and-forget' remote sensor:
- Deep Sleep: Replace the
time.sleep()at the end of the script withmachine.deepsleep(SLEEP_MINUTES * 60 * 1000). The RP2350's deep sleep drops current draw to ~1.5mA, allowing a 2000mAh battery to last for months. - MQTT over TLS: Swap
urequestsfor theumqtt.robustlibrary. Connect to a HiveMQ or Mosquitto broker. Note that TLS handshakes on the CYW43439 take roughly 1.5 seconds and spike current to 200mA; ensure your LiPo can handle the transient load. - BLE Fallback: Use the
bluetoothmodule to advertise sensor data via BLE GAP when WiFi is unavailable, allowing a passing smartphone to scrape the data without infrastructure.
Pico Plus 2 W Frequently Asked Questions
Can I use the official Raspberry Pi MicroPython firmware on the Pico Plus 2 W?
Technically, the official pico2_w.uf2 will boot and run standard RP2350 code, but the WiFi will not work. The official build expects the RM2 module to be wired exactly like the reference Pico 2 W design. Pimoroni routes specific SPI and control pins differently to accommodate the 8MB PSRAM and Qwiic connector. You must use the Pimoroni-specific MicroPython release from their GitHub to initialize the CYW43 chip successfully.
How do I control the onboard LED since it is connected to the WiFi chip?
Because the LED is on the CYW43 module (WL_GPIO0), you cannot use standard GPIO numbers. In modern MicroPython (v1.22 and newer), the board definition maps the string alias 'LED' to the correct internal SPI command. Use led = Pin('LED', Pin.OUT). If you are porting older Pico W code that uses Pin(25), you must update it to the string alias or the LED will simply ignore your commands.
What is the maximum LiPo battery size I can safely plug into the JST connector?
The physical JST-PH 2.0 connector and the TPS63000 buck-boost regulator can theoretically handle any capacity, but the MCP73832 charging IC is limited to ~450mA when powered via USB-C. A 2000mAh battery will take about 4.5 to 5 hours to charge from empty. If you plug in a massive 10,000mAh pack, it will charge safely, but it will take over 22 hours. Do not attempt to modify the charge resistor to speed this up without verifying the thermal limits of the MCP73832 and the USB-C trace width.
Does the Pico Plus 2 W support 5GHz WiFi?
No. The RM2 module houses the Infineon CYW43439, which is a single-band 2.4GHz 802.11n (WiFi 4) radio. It also lacks support for WPA3-Enterprise. If your network environment strictly enforces 5GHz or WPA3-Enterprise, you will need to set up a dedicated 2.4GHz IoT VLAN with WPA2-Personal authentication.
References:
Pimoroni Pico Plus 2 W Hardware Documentation
MicroPython RP2/RP2350 Quick Reference
Raspberry Pi RP2350 Datasheet






