Parts List and Pin Mapping: The RPI Pico W I2C Setup
The Raspberry Pi Pico W pairs the RP2040 dual-core Cortex-M0+ with an Infineon CYW43439 WiFi/Bluetooth chip. While the RP2040 handles standard I2C effortlessly, the CYW43439 introduces specific power and timing quirks that cause most beginners to abandon their first wireless sensor build. This guide targets the standard RPI Pico W with pre-soldered headers. Do not use the Pico WH ( castellated edges) for breadboard prototyping unless you plan to solder pin headers first.
Required Hardware
- MCU: Raspberry Pi Pico W (with headers) — ~$6.00
- Sensor: Adafruit BME280 I2C Breakout (Product ID 2652) — ~$14.95. This specific variant includes onboard 10kΩ pull-up resistors, which are mandatory for stable I2C on the RP2040.
- Wiring: 22 AWG solid-core jumper wires (4 required).
- Prototyping: 400-point solderless breadboard.
Pin Mapping Table
The RP2040 features multiple I2C controllers. We are using I2C0 on the default GPIO pins. According to the official Pico W datasheet, GPIO 4 and 5 are the standard routing for I2C0.
| Pico W Pin | RP2040 GPIO | Function | BME280 Breakout Pin |
|---|---|---|---|
| Pin 6 | GP4 | I2C0 SDA | SDI / SDA |
| Pin 7 | GP5 | I2C0 SCL | SCK / SCL |
| Pin 36 | 3V3(OUT) | Power (3.3V) | VIN / VCC |
| Pin 38 | GND | Ground | GND |
Decision Tree: Local Mosquitto vs. Cloud MQTT Brokers
Before writing firmware, you must decide where the Pico W will publish its sensor data. Microcontrollers struggle with heavy TLS handshakes, making broker selection critical for memory-constrained devices like the RP2040 (264KB SRAM).
| Criteria | Local Mosquitto (Docker/Pi) | Adafruit IO (Cloud Free) | HiveMQ Cloud |
|---|---|---|---|
| Setup Time | 45+ mins (Docker, certs) | 5 mins (Web UI) | 15 mins (Web UI) |
| TLS/SSL Support | Hard (Requires custom certs) | Native (Port 8883) | Native (Port 8883) |
| Privacy | 100% Local | Data on Adafruit servers | Data on HiveMQ servers |
| RP2040 RAM Impact | Low (Plain TCP port 1883) | High (TLS overhead) | High (TLS overhead) |
The Concrete Pick
Default Recommendation: Use Adafruit IO Free Tier. For a standalone RPI Pico W project where you want secure MQTT without managing local server infrastructure or generating custom SSL certificates for the microcontroller, Adafruit IO provides the path of least resistance. It natively supports the umqtt library's standard TLS implementation without requiring external crypto chips.
Complete MicroPython Firmware and Error Handling
This firmware targets MicroPython v1.22.0+ for the Pico W. It uses the modern mip package manager to pull the BME280 driver and umqtt.simple directly from the MicroPython ecosystem, ensuring you don't need to hunt for GitHub repositories.
Save this as main.py on your Pico W. Replace the WiFi and Adafruit IO credentials with your own.
import network
import time
import sys
from machine import Pin, I2C
# --- 1. Install Dependencies via MIP (MicroPython 1.20+) ---
try:
import bme280
import umqtt.simple as mqtt
except ImportError:
import mip
mip.install('bme280')
mip.install('umqtt.simple')
import bme280
import umqtt.simple as mqtt
# --- 2. Configuration ---
WIFI_SSID = 'YourNetwork_2.4GHz'
WIFI_PASS = 'YourPassword'
MQTT_BROKER = 'io.adafruit.com'
MQTT_PORT = 1883 # Use 8883 for TLS, but 1883 is easier for initial debugging
MQTT_USER = 'your_adafruit_username'
MQTT_KEY = 'your_adafruit_active_key'
MQTT_TOPIC_TEMP = f'{MQTT_USER}/feeds/pico-weather.temperature'
# --- 3. Hardware Initialization ---
# I2C0 on GP4 (SDA) and GP5 (SCL) at 400kHz
i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
# Onboard LED for status indication (CYW43439 WL_GPIO0)
led = network.WLAN(network.STA_IF).active(True) # Init WLAN to access LED
wlan = network.WLAN(network.STA_IF)
def connect_wifi():
wlan.active(True)
wlan.config(pm = 0xa11140) # Disable WiFi power save to prevent dropouts
wlan.connect(WIFI_SSID, WIFI_PASS)
print('Connecting to WiFi...', end='')
timeout = 15
while not wlan.isconnected() and timeout > 0:
print('.', end='')
time.sleep(1)
timeout -= 1
if not wlan.isconnected():
raise OSError('WiFi connection failed. Check SSID/Pass and 2.4GHz band.')
print('\nConnected:', wlan.ifconfig()[0])
def read_sensor():
# Scan I2C bus to verify hardware connection before reading
devices = i2c.scan()
if 0x77 not in devices and 0x76 not in devices:
raise OSError('BME280 not found on I2C bus. Check wiring.')
bme = bme280.BME280(i2c=i2c)
# bme280 library returns strings like '23.5C', '1013.2hPa'
temp_str = bme.temperature
temp_val = float(temp_str.split('C')[0])
return temp_val
def main():
connect_wifi()
client = mqtt.MQTTClient('pico_w_node', MQTT_BROKER, port=MQTT_PORT, user=MQTT_USER, password=MQTT_KEY)
try:
client.connect()
print('Connected to MQTT Broker')
except Exception as e:
print(f'MQTT Connection Failed: {e}')
sys.exit()
while True:
try:
temp = read_sensor()
print(f'Publishing: {temp}C')
client.publish(MQTT_TOPIC_TEMP, str(temp))
time.sleep(30) # 30 second publish interval
except OSError as e:
print(f'Hardware/Network Error: {e}')
time.sleep(10)
except Exception as e:
print(f'Unexpected Error: {e}')
time.sleep(10)
if __name__ == '__main__':
main()
Debugging the Big Three: Exact Error Strings and Fixes
When your RPI Pico W fails, it rarely fails silently. The MicroPython REPL will throw specific OS errors. Before tearing apart your breadboard, run through these first three things to check:
- I2C Pull-ups and Address: Run
i2c.scan()in the REPL. It must return[118](0x76) or[119](0x77). If it returns an empty list[], your wiring or pull-ups are wrong. - WiFi Band: The CYW43439 chip is strictly 2.4GHz. If your router uses a unified SSID for 2.4GHz and 5GHz, the Pico W will often fail to associate. Create a dedicated 2.4GHz IoT SSID.
- Broker Auth: If using Adafruit IO, ensure you are using your Active Key as the password, not your web login password.
Error 1: OSError: [Errno 110] ETIMEDOUT
Where it happens: During wlan.connect() or client.connect().
Ranked Causes:
- WiFi Power Save Mode: The Pico W's default WiFi driver aggressively sleeps the CYW43439 chip. The code above includes
wlan.config(pm = 0xa11140)to disable this. If you omitted this, the chip drops packets during the MQTT handshake. - 2.4GHz vs 5GHz Steering: The router kicked the Pico W off because it didn't respond to 5GHz steering probes fast enough. Fix: Disable 802.11k/v/w on your router's IoT VLAN.
- DNS Resolution Failure: The Pico W connected to the AP but couldn't resolve
io.adafruit.com. Fix: Hardcode the broker IP or set a static DNS inwlan.ifconfig().
Error 2: OSError: [Errno 121] EREMOTEIO or ValueError: bad SDA pin
Where it happens: During i2c.scan() or bme280.BME280() initialization.
Ranked Causes:
- Missing Pull-up Resistors: The RP2040 I2C pins are not internally pulled up. If using a bare BME280 chip or cheap clone board, add 4.7kΩ external pull-ups to 3.3V.
- Wrong I2C Bus: You wired to GP0/GP1 (I2C0 default alt) or GP8/GP9 (I2C0 alt) but initialized
I2C(0, sda=Pin(4), scl=Pin(5)). Match the physical wires to the software GPIO numbers. - 3.3V vs 5V Logic: You powered the BME280 with 5V but are using 3.3V logic lines. The BME280 is strictly a 3.3V device; 5V will fry the sensor's internal voltage regulator.
Error 3: MQTTException: 5 or Connection Refused
Where it happens: Immediately after client.connect() succeeds at the TCP level but fails at the MQTT protocol level.
Ranked Causes:
- Wrong Password Field: You used your Adafruit IO account password instead of the API Active Key.
- Client ID Collision: Another device is already connected to the broker with the client ID
'pico_w_node'. MQTT brokers drop existing connections when a duplicate ID connects. Fix: Append the Pico's MAC address or a random integer to the client ID. - Topic ACL Restrictions: You are trying to publish to a topic your user doesn't own. Ensure the topic string exactly matches
username/feeds/feedname.
Extending or Simplifying the Build
Depending on your end goal, you may need to strip this project down to its bare essentials or scale it up for a permanent installation.
How to Simplify: Drop MQTT for HTTP GET
If setting up an MQTT broker feels like overkill and you just want to log data to a local server, strip out the umqtt library entirely. Use the built-in urequests library to send a simple HTTP GET request to a local Flask or Node-RED server:
import urequests
url = f'http://192.168.1.50:5000/log?temp={temp_val}'
response = urequests.get(url)
response.close()
Trade-off: HTTP uses significantly more RAM per request than MQTT and requires the server to be actively polling or listening, whereas MQTT pushes data asynchronously.
How to Extend: Deep Sleep and Battery Power
To run this node on a 18650 Li-Ion cell, you must implement deep sleep. However, the RPI Pico W has a notorious quirk: the CYW43439 WiFi chip does not automatically wake the RP2040 from deep sleep.
To extend this build for battery operation:
- Use the RP2040's internal RTC alarm to wake the main core.
- You must physically route a wake signal or use the
machine.deepsleep()method combined with a timer. - Crucially, you must toggle the CYW43439 power enable pin (GPIO 23 on the Pico W board) to ensure the WiFi chip boots cleanly after wake, otherwise it will hang in a high-current fault state.
Final Bench Note: Always measure your Pico W's current draw with a multimeter in series with the 5V USB line. A healthy idle Pico W with WiFi connected draws ~65mA. If you see >120mA continuously, your WiFi driver is stuck in a transmit loop or the CYW43439 chip has crashed. A hard power cycle (unplug USB) is the only fix for a crashed WiFi coprocessor.






