The Pico Pin Decision Matrix: Which GPIO for Which Peripheral?
The RP2040 chip inside the Raspberry Pi Pico features 30 multi-function GPIO pins (GP0 to GP29). However, not every pin can do everything. The silicon routes specific peripherals (I2C, SPI, UART, PWM) to specific pin pairs. If you assign a peripheral to the wrong pin, your code will compile but fail at runtime. Use this decision tree to lock in your pico pin assignments before you wire a single breadboard.
| If you need... | Then choose these Pico Pins... | Why this is the definitive pick |
|---|---|---|
| Default I2C (I2C0) | GP4 (SDA) & GP5 (SCL) | Matches the default MicroPython I2C0 bus mapping. Keeps GP8/GP9 free for a secondary I2C1 bus. |
| Default SPI (SPI0) | GP16 (RX), GP17 (CSn), GP18 (SCK), GP19 (TX) | Hardware SPI0 block. Essential for high-speed displays (like ST7789) or SD card modules. |
| Analog Input (ADC) | GP26 (ADC0), GP27 (ADC1), GP28 (ADC2) | These are the only pins physically wired to the RP2040's internal 12-bit SAR ADC. GP29 is tied to VSYS/3 for voltage monitoring. |
| PWM Output | Any GP pin except 23, 24, 25, 29 | The RP2040 has 8 PWM slices (A and B channels). Avoid GP23-25 (used for onboard SMPS and LED on non-W models) and GP29. |
| High Current (>16mA) | None directly. Use GP15 to drive an IRLZ44N MOSFET. | RP2040 GPIOs max out at 50mA total across all banks. Never drive motors or high-power LEDs directly from a pico pin. |
Hardware Build: BME280 I2C and PWM LED on the Pico W
To demonstrate proper pin allocation, we will wire a BME280 environmental sensor (I2C) and a status LED (PWM). This build targets the Raspberry Pi Pico W (the variant with the Infineon CYW43439 WiFi/BLE chip), though the GPIO mapping is identical to the original Pico.
Parts List
- Microcontroller: Raspberry Pi Pico W (with pre-soldered 0.1" headers)
- Sensor: Adafruit BME280 I2C/SPI Breakout (Product ID: 2652) - includes onboard 10kΩ pull-ups
- Indicator: 5mm Diffuse Red LED
- Current Limiting: 330Ω 1/4W carbon film resistor
- Prototyping: Half-size 400-point breadboard, 22 AWG solid-core jumper wires
Pin Mapping Table
| Pico W Pin | Function | Connects To |
|---|---|---|
| 3V3(OUT) (Pin 36) | Power | BME280 VIN |
| GND (Pin 38) | Ground | BME280 GND & LED Cathode |
| GP4 (Pin 6) | I2C0 SDA | BME280 SDI/SDA |
| GP5 (Pin 7) | I2C0 SCL | BME280 SCK/SCL |
| GP15 (Pin 20) | PWM Slice 7B | 330Ω Resistor → LED Anode |
Wiring Steps
- Insert the Pico W into the breadboard, straddling the center trench so pins 1-20 are on one side and 21-40 are on the other.
- Wire the 3V3(OUT) pin to the breadboard's red power rail, and any GND pin to the blue ground rail.
- Connect the BME280 breakout board. Route the red power rail to VIN, blue ground rail to GND, GP4 to SDA, and GP5 to SCL.
- Place the 330Ω resistor in series with the LED anode (long leg). Connect the free resistor leg to GP15, and the LED cathode (short leg) to the ground rail.
- Verify all connections with a multimeter in continuity mode before applying USB power.
Complete MicroPython Code with Pin Definitions and Error Handling
The following code is written for MicroPython v1.22.1+ on the Pico W. It initializes the I2C bus, scans for the BME280, and pulses the LED via PWM. It includes robust error handling to catch common I2C failures without crashing the REPL.
Note: You must upload a compatible bme280.py library to your Pico's root directory before running this script. The robert-hh/BME280 MicroPython library is highly recommended.
import machine
import time
import bme280
# --- PIN DEFINITIONS ---
I2C_SDA_PIN = 4
I2C_SCL_PIN = 5
LED_PWM_PIN = 15
I2C_FREQ = 400000 # 400kHz Fast Mode
# --- HARDWARE INITIALIZATION ---
led_pwm = machine.PWM(machine.Pin(LED_PWM_PIN))
led_pwm.freq(1000)
try:
i2c = machine.I2C(0, sda=machine.Pin(I2C_SDA_PIN), scl=machine.Pin(I2C_SCL_PIN), freq=I2C_FREQ)
except ValueError as e:
print(f'FATAL: Invalid pin assignment. {e}')
machine.reset()
# --- I2C DEVICE SCANNING & ERROR HANDLING ---
def scan_and_init_sensor():
devices = i2c.scan()
if not devices:
raise OSError('No I2C devices found. Check wiring and pull-ups.')
# BME280 default I2C address is 0x76 or 0x77
bme_addr = 0x76 if 0x76 in devices else (0x77 if 0x77 in devices else None)
if not bme_addr:
raise OSError(f'Found devices {devices}, but BME280 address (0x76/0x77) missing.')
return bme280.BME280(i2c=i2c, address=bme_addr)
try:
sensor = scan_and_init_sensor()
print('BME280 initialized successfully.')
except OSError as e:
print(f'I2C Initialization Failed: {e}')
# Fallback: Blink LED rapidly to indicate hardware fault
while True:
led_pwm.duty_u16(65535)
time.sleep(0.1)
led_pwm.duty_u16(0)
time.sleep(0.1)
# --- MAIN LOOP ---
try:
while True:
temp_c = sensor.temperature[:-1] # Strip 'C' character
humidity = sensor.humidity[:-1] # Strip '%' character
print(f'Temp: {temp_c}C | Humidity: {humidity}%')
# Map temperature (15C to 30C) to PWM duty cycle (0 to 65535)
temp_float = float(temp_c)
duty = int(max(0, min(65535, ((temp_float - 15.0) / 15.0) * 65535)))
led_pwm.duty_u16(duty)
time.sleep(2.0)
except KeyboardInterrupt:
print('Stopping script. Turning off LED.')
led_pwm.duty_u16(0)
Debugging Pico Pin Errors: Exact Strings and Ranked Causes
When your Pico W refuses to talk to a peripheral, the MicroPython REPL will throw specific exceptions. Here is how to decode them and fix the underlying hardware or software fault.
Error 1: OSError: [Errno 121] EIO or ENODEV
This is the classic I2C NACK (Not Acknowledged) error. The Pico sent a clock pulse, but the sensor did not pull the SDA line low to respond.
- Cause 1 (Most Likely): SDA and SCL are swapped. The RP2040 is strict about I2C pin pairs. If you wired GP5 as SDA and GP4 as SCL, the hardware I2C block will fail. Swap the wires.
- Cause 2: Power mismatch. You wired the sensor's VIN to a 5V rail, but the Pico's I2C pins are 3.3V. The sensor might be pulling the SDA line high to 5V, which the Pico cannot read reliably (and risks damaging the GPIO).
- Cause 3: Missing Pull-up Resistors. I2C requires pull-up resistors on SDA and SCL. The Adafruit BME280 breakout has them onboard. If you are using a bare sensor module, you must add 4.7kΩ resistors between the SDA/SCL lines and 3.3V.
Error 2: ValueError: bad SCL pin or bad SDA pin
This error occurs during the machine.I2C() instantiation. The MicroPython firmware checks the pin multiplexer table and rejects the pin.
- Cause 1: Pin does not support the requested I2C block. For example, trying to use
machine.I2C(0, scl=Pin(8)). I2C0 SCL must be on GP1, GP5, GP9, GP13, GP17, or GP21. GP8 is for I2C1. Consult the RP2040 Datasheet GPIO function table. - Cause 2: Pin is already claimed. If you initialized SPI0 on GP5 earlier in your script, the pin mux is locked to SPI. You must deinitialize the SPI bus or choose a different pin.
- Run an I2C Scan: Execute
i2c.scan()in the REPL. If it returns an empty list[], your issue is physical (wiring, power, or pull-ups). If it returns a hex address, your issue is software (wrong address in code or missing library). - Verify 3V3(OUT) vs VBUS: Ensure your sensor is powered by Pin 36 (3V3 OUT), not Pin 40 (VBUS/5V). VBUS will fry the logic levels.
- Check Jumper Wire Continuity: Breadboard contacts wear out. Use a multimeter to beep-test the exact jumper wire connecting GP4 to the sensor's SDA pin.
Extending and Simplifying Your Pico Pin Layout
As your project grows, you will inevitably run out of dedicated pico pins or I2C addresses. Here is the definitive path forward based on your specific bottleneck.
| Your Bottleneck | The Concrete Solution | Implementation Detail |
|---|---|---|
| Out of I2C Addresses (Multiple identical sensors) | Use a TCA9548A I2C Multiplexer. | Wire the TCA9548A to GP4/GP5. It acts as an 8-channel switch, allowing you to connect up to 8 identical BME280 sensors (all at address 0x76) and toggle them via software. |
| Out of GPIO Pins entirely | Use a 74HC595 Shift Register or MCP23017 I2C Expander. | The MCP23017 adds 16 extra GPIO pins over your existing I2C bus. Use the mcp23017 MicroPython library to control them exactly like native Pico pins. |
| Need to drive 5V/12V loads | Use an IRLZ44N Logic-Level MOSFET. | Connect the Pico GP pin to the MOSFET Gate via a 100Ω resistor. Add a 10kΩ pull-down resistor from Gate to Ground. Wire your 12V load between the Drain and 12V supply. |
If you are building a permanent installation and want to simplify the physical layout, abandon the breadboard and order a custom PCB using the Raspberry Pi Pico W footprint. KiCad 8 includes the official Pico W component library out-of-the-box. Routing your I2C traces at 90-degree angles with 4.7kΩ 0603 pull-up resistors placed within 5mm of the SDA/SCL pads will eliminate 99% of the signal integrity issues that plague breadboard prototypes.
For deep-dive specifications on the RP2040 GPIO banks, internal pull-up/pull-down resistor values (typically 50kΩ to 60kΩ), and pad control registers, refer directly to the official Raspberry Pi Pico hardware documentation. When writing performance-critical code that requires precise timing beyond standard I2C/SPI blocks, investigate the RP2040's PIO (Programmable I/O) state machines, which allow you to create custom hardware protocols on any pico pin without CPU intervention.






