To get started with MicroPython on the Raspberry Pi Pico, you need the Raspberry Pi Pico W variant, a solderless breadboard, an I2C OLED display, and the Thonny IDE. Flash the latest UF2 firmware, wire the I2C0 bus to GP4/GP5, and run a hardware-abstracted script. This guide skips the generic overviews and gives you the exact part numbers, pinouts, and debugging paths to get a working sensor node on your bench in under an hour.
The Hardware Decision: Which Pico Board to Pick?
The Raspberry Pi Foundation now offers several Pico variants. Choosing the wrong one means either paying for unused silicon or realizing halfway through your build that you lack the necessary radio or compute headroom. Use this decision matrix to terminate your selection process.
| If your project needs... | Choose this board | Why (The Engineering Reality) |
|---|---|---|
| Basic GPIO, ADC, or offline data logging | Raspberry Pi Pico (RP2040) | Cheapest option (~$4). No RF overhead, lower idle current draw. |
| WiFi, MQTT, BLE, or remote telemetry | Raspberry Pi Pico W (Default Pick) | Adds Infineon CYW43439 2.4GHz radio for ~$2 more. Essential for 90% of modern IoT builds. |
| Heavy math, dual-core 150MHz, more SRAM | Raspberry Pi Pico 2 (RP2350) | Features ARM/RISC-V switchable cores and 520KB SRAM. Overkill for simple sensor polling. |
Parts List and Pin Mapping for the Pico W
Below is the exact bill of materials (BOM) and the physical pin mapping required for the code provided later in this guide. We are using the I2C0 bus to keep I2C1 free for future sensor expansion.
| Component | Exact Variant / Spec | Approx. Cost |
|---|---|---|
| Microcontroller | Raspberry Pi Pico W (RP2040, with headers) | $6.00 |
| Display | 0.96" SSD1306 OLED, I2C interface (128x64, 4-pin) | $4.50 |
| Prototyping | 830-point solderless breadboard + 20AWG jumper wires | $6.00 |
| Power | 5V/2A USB-C wall adapter + USB-A to Micro-USB data cable | $8.00 |
Pico W to SSD1306 I2C Pin Mapping
| Pico W Pin (Physical) | GPIO / Function | SSD1306 OLED Pin | Wire Color (Standard) |
|---|---|---|---|
| Pin 1 | GP0 (TX) / I2C0 SDA | SDA | Blue |
| Pin 2 | GP1 (RX) / I2C0 SCL | SCL | Yellow |
| Pin 3 | GND | GND | Black |
| Pin 36 | 3V3(OUT) | VCC | Red |
Note: While GP4/GP5 are the default I2C0 pins in the datasheet, the RP2040's PIO allows mapping I2C0 to GP0/GP1. We use GP0/GP1 here as they are physically adjacent on the top-left of the board, making breadboard routing cleaner. The code reflects this mapping.
Flashing Firmware and Wiring the Circuit
- Download the Firmware: Go to the official MicroPython download page and grab the latest stable
.uf2file for the Pico W (e.g., v1.23.0). - Enter Bootloader Mode: Press and hold the white
BOOTSELbutton on the Pico W while plugging the Micro-USB cable into your PC. Release the button when a USB mass storage drive namedRPI-RP2appears. - Flash the Board: Drag and drop the
.uf2file onto theRPI-RP2drive. The drive will disconnect automatically, and the Pico W will reboot into MicroPython. - Configure Thonny: Open Thonny IDE. Go to Tools > Options > Interpreter. Select "MicroPython (Raspberry Pi Pico)" and choose the correct COM/tty port.
- Install the OLED Driver: The
ssd1306module is not always frozen into the base firmware. Downloadssd1306.pyfrom the MicroPython-SSD1306 GitHub repository, open it in Thonny, and save it to the Pico W's root directory asssd1306.py. - Wire the Circuit: Connect the SSD1306 to GP0 (SDA), GP1 (SCL), GND, and 3V3(OUT) exactly as mapped in the table above. Ensure the OLED VCC is connected to 3V3, not VBUS (5V), to avoid frying the I2C pull-ups.
The Code: I2C Sensor Reading with Error Handling
The following script targets the Raspberry Pi Pico W (RP2040). It initializes the I2C0 bus on GP0/GP1, scans for the OLED, and reads the RP2040's internal temperature sensor (ADC channel 4) to display live data. This avoids external sensor dependencies while proving the I2C bus is functional.
import machine
import ssd1306
import time
import sys
# --- Pin Definitions for Raspberry Pi Pico W ---
# Using GP0 and GP1 for I2C0 to keep physical wiring clean
I2C_SDA = machine.Pin(0)
I2C_SCL = machine.Pin(1)
LED_PIN = machine.Pin("LED") # Pico W specific onboard LED (controlled via WL_GPIO0)
# Initialize I2C0 at 400kHz
i2c = machine.I2C(0, sda=I2C_SDA, scl=I2C_SCL, freq=400000)
def scan_i2c():
"""Scans the I2C bus and returns a list of found device addresses."""
devices = i2c.scan()
if not devices:
raise OSError("[Errno 19] ENODEV: No I2C devices found. Check wiring.")
return devices
def get_internal_temp():
"""Reads the RP2040 internal temperature sensor on ADC channel 4."""
sensor_temp = machine.ADC(4)
reading = sensor_temp.read_u16()
# Conversion formula from RP2040 datasheet (Section 4.9.5)
temperature = 27 - (reading - 0.706) / 0.001721
return temperature
def main():
try:
# 1. Scan for OLED (usually 0x3C or 0x3D)
devices = scan_i2c()
oled_addr = 0x3C if 0x3C in devices else 0x3D
oled = ssd1306.SSD1306_I2C(128, 64, i2c, addr=oled_addr)
# 2. Boot sequence indication
LED_PIN.on()
oled.fill(0)
oled.text("Flux Node OK", 0, 0)
oled.show()
time.sleep(1)
# 3. Main telemetry loop
while True:
temp_c = get_internal_temp()
oled.fill(0)
oled.text("Pico W Telemetry", 0, 0)
oled.text(f"Core: {temp_c:.2f} C", 0, 20)
oled.text(f"I2C: {hex(oled_addr)}", 0, 40)
oled.show()
time.sleep(2)
except OSError as e:
# Catch I2C and hardware faults specifically
print(f"Hardware Fault Caught: {e}")
sys.print_exception(e)
except KeyboardInterrupt:
print("\nHalted by user (Ctrl+C).")
finally:
# Ensure safe state on exit
LED_PIN.off()
print("System safely powered down.")
if __name__ == "__main__":
main()
Debugging: First 3 Checks and the ENODEV Error
When working with I2C on the RP2040, the most common failure mode is the bus failing to acknowledge a device. If your script crashes, you will likely see this exact error string in the Thonny shell:
OSError: [Errno 19] ENODEV
or
OSError: [Errno 110] ETIMEDOUT
The First 3 Things to Check When It Fails
- Verify the Power Rail Mismatch: The SSD1306 OLED requires 3.3V logic. If you accidentally wired VCC to the Pico's VBUS (Pin 40, 5V), the OLED might light up, but the I2C pull-up resistors will pull the SDA/SCL lines to 5V. The RP2040 GPIO pins are strictly 3.3V tolerant. This won't immediately fry the Pico, but it will cause I2C ACK failures. Move VCC to Pin 36 (3V3 OUT).
- Check for SDA/SCL Swap: I2C is not auto-negotiating. If SDA and SCL are reversed, the clock line won't pulse correctly, resulting in an
ETIMEDOUT. Swap the blue and yellow jumper wires and hard-reset the Pico (unplug and replug). - Confirm the I2C Address: Cheap clone OLEDs sometimes ship with the I2C address hardcoded to
0x3Dinstead of the standard0x3C. Runprint(i2c.scan())in the Thonny REPL. If it returns[61], your address is0x3D(61 in hex). The provided code handles this automatically, but manual scripts often miss it.
Ranked Causes for ENODEV / ETIMEDOUT
| Rank | Cause | Fix / Measurement |
|---|---|---|
| 1 | Missing or weak I2C pull-up resistors | Measure SDA/SCL to 3V3 with a multimeter. Should read ~3.3V. If floating, add 4.7kΩ pull-ups. |
| 2 | Breadboard contact fatigue | Move the Pico W to a different breadboard row. Pre-soldered headers often lose contact in worn boards. |
| 3 | I2C bus locked by previous crash | The RP2040 I2C peripheral can lock up if interrupted mid-transaction. Power cycle the board completely. |
Extending or Simplifying the Build
Once the base telemetry loop is stable, you need to decide whether to scale the project up for production or strip it down for a low-power edge node.
How to Extend: Adding WiFi and MQTT
Because we selected the Pico W, adding network telemetry is a software-only change.
1. Import the network and umqtt.simple modules.
2. Connect to your 2.4GHz WiFi network (the CYW43439 chip does not support 5GHz).
3. Publish the temp_c variable to an MQTT broker like Mosquitto or Home Assistant.
Warning: The Pico W's WiFi stack draws ~120mA during transmission. If you are powering this from a 1000mAh LiPo, expect less than 8 hours of runtime unless you implement deep sleep (machine.deepsleep()) between MQTT publishes.
How to Simplify: Baremetal and Low Power
If you don't need the display or WiFi, strip the BOM down to just the Pico and a 3V coin cell. Remove the ssd1306 imports and the OLED initialization. To maximize battery life, disable the onboard LED, lower the CPU frequency to 18MHz using machine.freq(18000000), and put the board into machine.lightsleep() for 5 minutes between ADC reads. This drops the average current draw to under 2mA, allowing a CR2032 coin cell to run the node for months.
For deeper hardware specifications and register-level details, always refer to the Raspberry Pi Pico Datasheet and the MicroPython RP2 Quick Reference.






