The Pimoroni Pico Plus 2 W pairs the Raspberry Pi RP2350 dual-core microcontroller with 16MB of QSPI flash, 8MB of PSRAM, and an Infineon CYW43439 Wi-Fi/BLE module. If you need to log environmental data over Wi-Fi without worrying about running out of memory for string buffers or TLS handshakes, this is the board to use. In this guide, we will wire up a BME280 sensor via the Qw/ST connector and write a robust MicroPython script to publish temperature, humidity, and pressure payloads to an MQTT broker.

Hardware Specifications and Pin Mapping

Before writing code, it is critical to understand how the Pimoroni Pico Plus 2 W differs from the baseline Raspberry Pi Pico 2 W. The addition of PSRAM and expanded flash changes how you handle memory allocation in MicroPython, particularly when buffering JSON payloads for MQTT.

Table 1: RP2350 Board Variant Comparison
Feature Pimoroni Pico Plus 2 W Raspberry Pi Pico 2 W Original Pico W (RP2040)
MCU RP2350 (Dual M33/RISC-V) RP2350 (Dual M33/RISC-V) RP2040 (Dual M0+)
SRAM 520 KB 520 KB 264 KB
PSRAM 8 MB (XIP capable) None None
Flash 16 MB QSPI 4 MB QSPI 2 MB QSPI
Wireless CYW43439 (Wi-Fi 4 / BLE 5.2) CYW43439 (Wi-Fi 4 / BLE 5.2) CYW43439 (Wi-Fi 4 / BLE 5.2)
Typical Price (2026) ~$16.00 USD ~$7.00 USD ~$6.00 USD

Pimoroni boards standardize on the Qw/ST (Qwiic/Stemma QT) connector for I2C peripherals. On the Pico Plus 2 W, this connector is hardwired to I2C0. Refer to the official Raspberry Pi Pico 2 datasheet for the full multiplexing matrix, but for this build, we only need the default I2C0 pins.

Table 2: Qw/ST I2C Pin Mapping
Function RP2350 GPIO Physical Pin (Qw/ST) Notes
SDA (Data) GPIO 4 Pin 3 Requires 4.7kΩ pull-up (on breakout)
SCL (Clock) GPIO 5 Pin 4 Max 400kHz Fast Mode
VCC (Power) N/A Pin 1 3.3V regulated from VSYS
GND N/A Pin 2 Common ground reference

Parts List and Wiring the Qw/ST Interface

This build requires minimal soldering because we are leveraging the Qw/ST ecosystem. Ensure your BME280 breakout has the Qw/ST connector pre-populated; if it only has raw pads, you will need to solder a 4-pin JST-SH connector or use standard Dupont jumper wires.

Component Selection Note: Do not confuse the BME280 with the BMP280. The BMP280 only measures temperature and pressure. The BME280 includes the humidity sensor required for our MQTT payload.

Required Materials

  • MCU: Pimoroni Pico Plus 2 W (RP2350 variant)
  • Sensor: Adafruit BME280 I2C Breakout (Product ID: 2652) or Pimoroni BME280 Breakout
  • Cable: 4-pin JST-SH to JST-SH Qw/ST cable (100mm or 200mm)
  • Power: 5V/1A USB-C power supply and cable
  • Prototyping: Half-size solderless breadboard (to mount the sensor if not using an enclosure)

Wiring Steps

  1. Seat the Pimoroni Pico Plus 2 W into the breadboard, ensuring the USB-C port faces the edge.
  2. Plug one end of the Qw/ST cable into the 4-pin connector on the Pico Plus 2 W.
  3. Plug the other end into the BME280 breakout. The connector is keyed and will only insert one way (Red wire to VCC, Black to GND, Blue to SDA, Yellow to SCL).
  4. Connect the USB-C cable to your computer to flash the firmware, then move to your deployment power supply later.

MicroPython Firmware and Complete MQTT Code

The code below targets the Pimoroni Pico Plus 2 W specifically. While standard Raspberry Pi MicroPython builds will run on the RP2350, Pimoroni provides a custom MicroPython build (v1.23.0+) that includes pre-compiled C drivers for their hardware and properly maps the 8MB PSRAM. Download the Pimoroni RP2350 MicroPython UF2 from their GitHub releases, hold the BOOTSEL button, plug in USB, and drag the UF2 file to the RPI-RP2 drive.

We use the umqtt.simple library, which is built into the Pimoroni MicroPython image. The script includes robust error handling for Wi-Fi drops and MQTT broker timeouts, which are common failure modes in the CYW43439 driver.


# TARGET BOARD: Pimoroni Pico Plus 2 W (RP2350, 16MB Flash, 8MB PSRAM)
# FIRMWARE: Pimoroni MicroPython RP2350 Build v1.23.0+

import network
import time
import json
import gc
from machine import Pin, I2C
from umqtt.simple import MQTTClient
import bme280

# --- PIN DEFINITIONS & CONFIG ---
I2C_SDA = 4
I2C_SCL = 5
I2C_FREQ = 400_000

WIFI_SSID = "YourNetwork_2.4GHz"
WIFI_PASS = "YourSecurePassword"
MQTT_BROKER_IP = "192.168.1.50"
MQTT_PORT = 1883
MQTT_TOPIC = b"home/lab/environment"

# Initialize I2C0 for Qw/ST
i2c = I2C(0, sda=Pin(I2C_SDA), scl=Pin(I2C_SCL), freq=I2C_FREQ)
sensor = bme280.BME280(i2c=i2c)

# Initialize Wi-Fi Station Interface
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Disable Wi-Fi power saving to prevent CYW43439 sleep-dropouts
wlan.config(pm = 0xa11140)

def connect_wifi():
    print(f"Connecting to {WIFI_SSID}...")
    wlan.connect(WIFI_SSID, WIFI_PASS)
    timeout = 15
    while not wlan.isconnected() and timeout > 0:
        time.sleep(1)
        timeout -= 1
    
    if wlan.isconnected():
        ip = wlan.ifconfig()[0]
        print(f"Connected! IP: {ip}")
        return True
    else:
        print("Wi-Fi connection failed.")
        return False

def publish_sensor_data(client):
    try:
        # Read sensor values (returns tuples/strings depending on driver)
        temp_c = sensor.temperature[:-1]  # Strip 'C' suffix
        humidity = sensor.humidity[:-1]   # Strip '%' suffix
        pressure = sensor.pressure[:-3]   # Strip 'hPa' suffix
        
        payload = json.dumps({
            "temp": float(temp_c),
            "hum": float(humidity),
            "pres": float(pressure),
            "free_mem": gc.mem_free()
        })
        
        client.publish(MQTT_TOPIC, payload, qos=1)
        print(f"Published: {payload}")
    except Exception as e:
        print(f"Sensor/Publish Error: {e}")

# --- MAIN EXECUTION LOOP ---
if not connect_wifi():
    machine.reset()

try:
    client = MQTTClient("pico_plus_2w_logger", MQTT_BROKER_IP, port=MQTT_PORT, keepalive=60)
    client.connect()
    print("Connected to MQTT Broker")
except OSError as e:
    print(f"MQTT Connection Failed: {e}")
    machine.reset()

while True:
    try:
        if not wlan.isconnected():
            print("Wi-Fi dropped. Reconnecting...")
            if not connect_wifi():
                machine.reset()
        
        publish_sensor_data(client)
        gc.collect()  # Force garbage collection to manage 520KB SRAM
        time.sleep(30)
        
    except OSError as e:
        print(f"Network Error in loop: {e}")
        time.sleep(5)
        try:
            client.disconnect()
            client.connect()
        except:
            machine.reset()

Debugging RP2350 Wireless and MQTT Failures

The CYW43439 wireless chip on the RP2350 is highly capable but sensitive to power delivery and network configuration. If your serial monitor halts or throws exceptions, follow this decision path.

The First Three Things to Check

  1. Wi-Fi Band: The CYW43439 is strictly a 2.4GHz radio. If your router uses a unified SSID for 2.4GHz and 5GHz, the Pico may fail to negotiate the handshake. Create a dedicated 2.4GHz IoT SSID.
  2. Broker IP vs. Hostname: The umqtt.simple library struggles with mDNS (.local) resolution. Always pass a static IPv4 address (e.g., 192.168.1.50) to the MQTTClient constructor rather than a hostname like homeassistant.local.
  3. USB Power Delivery: The Wi-Fi radio draws up to 130mA during transmission spikes. If your PC USB port or wall wart sags below 4.7V, the RP2350 brownout detector will reset the board silently. Use a known-good 5V/2A power supply.

Common Error Strings and Ranked Causes

Error: OSError: [Errno 113] EHOSTUNREACH

This occurs during the client.connect() phase. The Pico has an IP address, but cannot route to the broker.

  • Cause 1 (Most Likely): The MQTT broker service (Mosquitto, HiveMQ) is not running on the target IP, or the port 1883 is blocked by the host OS firewall (e.g., UFW on Linux).
  • Cause 2: The Pico and the broker are on different VLANs, and inter-VLAN routing for port 1883 is dropped.
  • Cause 3: The DHCP lease expired, and the Pico's network stack failed to renew it before attempting the TCP handshake.

Error: MemoryError: memory allocation failed, allocating 4096 bytes

This happens when formatting the JSON payload or during TLS handshakes (if using umqtt.robust with SSL).

  • Cause 1: MicroPython's garbage collector hasn't run. The CYW43 driver allocates large RX buffers. You must call gc.collect() before building JSON strings.
  • Cause 2: You are using standard strings instead of pre-allocated byte arrays. In memory-constrained environments, use uio.BytesIO to build payloads.
Safety Note: Never power the Pico Plus 2 W via the VSYS pin with more than 5.5V. If you are integrating this into a 12V or 24V solar setup, use a buck converter (like a Pololu D24V50F5) stepped down to 5.0V before feeding VSYS.

Extending and Simplifying the Build

Depending on your deployment environment, you may need to optimize this logger for power consumption or add local feedback.

How to Simplify (Lower Power / Offline)

If you do not need real-time cloud monitoring and want to run this off a 2000mAh LiPo battery for months, drop the Wi-Fi entirely. The Pico Plus 2 W has 16MB of flash. You can use the littlefs filesystem to log CSV data locally. Put the RP2350 into machine.deepsleep() for 15 minutes between BME280 reads. The RP2350's deep sleep current is roughly 1.5mA, compared to the 25mA+ idle draw of the Wi-Fi radio.

How to Extend (Local Display and Edge Alerts)

To add a local display without using a second I2C bus, daisy-chain an I2C OLED (like the Adafruit SSD1306 128x64) onto the same Qw/ST bus. I2C supports up to 112 devices, provided their addresses do not clash. The BME280 defaults to 0x76 or 0x77, while the SSD1306 is typically 0x3C. Alternatively, utilize the RP2350's Programmable I/O (PIO) blocks to drive a strip of WS2812B addressable LEDs. Because the PIO handles the strict timing requirements of the LEDs in hardware, it frees up both Cortex-M33 cores to handle Wi-Fi interrupts and MQTT parsing without flickering the LEDs. Refer to the MicroPython RP2 Quick Reference for PIO state machine syntax.

By leveraging the 8MB PSRAM on the Pimoroni Pico Plus 2 W, you can also buffer weeks of MQTT payloads locally in RAM during internet outages, publishing them in a batch once the CYW43439 reconnects to your router. This makes it an exceptionally resilient node for off-grid or unreliable network environments.