Skip to main content
skill CX Codex

rp2040-pico — Claude Code skill for Pico firmware

Guidance for building Raspberry Pi Pico firmware, configuring PlatformIO, and diagnosing board-specific issues with GPIO, USB, ADC, and power.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add alexex1993/mcu-skills --skill rp2040-pico -a claude-code
Open on GitHub

What rp2040-pico does

The skill includes board references, PlatformIO and arduino-pico code recipes, and a project template in two variants with a scaffolding script. Its materials cover pin functions, peripherals, flashing, and common symptoms with their causes and fixes.

Use it to configure platformio.ini, choose the earlephilhower Arduino core, or troubleshoot USB CDC, ADC, PWM, I2C, and SPI. One important pin overlap: Wire1 defaults to GPIO26/27, which are also A0/A1. If you use both, remap the I2C pins before calling begin(). Serial uses USB CDC; GPIO0/1 are Serial1.

What it helps with

  • configure platformio.ini for a Raspberry Pi Pico
  • choose the earlephilhower core instead of Mbed
  • trace why serial output does not reach a UART adapter
  • move I2C off the A0/A1 pins
  • diagnose ADC or PWM behavior on the board

Example prompts for the agent

  • Set up my Raspberry Pi Pico firmware in PlatformIO and check the Arduino core choice.
  • Explain why Wire1 conflicts with A0 readings and show how to remap the bus.
  • Find why this firmware will not upload and explain how to enter BOOTSEL.

How to install rp2040-pico

1Skills CLI (Vercel)

npx skills add alexex1993/mcu-skills --skill rp2040-pico -a claude-code

2Manually: put the files in place

~/.claude/skills/rp2040-pico/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/rp2040-pico/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about rp2040-pico

How do I install rp2040-pico in Claude Code?

Run npx skills add alexex1993/mcu-skills --skill rp2040-pico -a claude-code. The skill file is at ~/.claude/skills/rp2040-pico/SKILL.md.

What does rp2040-pico do?

It provides board-specific help for Raspberry Pi Pico firmware, including project setup, peripheral configuration, flashing, and troubleshooting.

Why can Wire1 interfere with A0 readings?

By default, Wire1 uses GPIO26/27, which are also the A0/A1 analog inputs. The skill describes changing the I2C pins before calling begin().

Source text

SKILL.md · MIT
---
name: rp2040-pico
description: Firmware development for the Raspberry Pi Pico (original, RP2040 — dual Cortex-M0+ @ 133 MHz, 264 KB SRAM, 2 MB QSPI flash) — its 26 exposed GPIO, 4-channel 12-bit ADC, 16-channel PWM, 2× UART/I2C/SPI, PIO state machines, USB 1.1 CDC, VSYS/VBUS/SMPS power wiring, and the PlatformIO + arduino-pico (earlephilhower) setup around them. Use when working on this board, any Pico H or Pico clone, or a bare RP2040: project setup, platformio.ini, the earlephilhower-vs-Mbed core choice, pin mapping, USB CDC serial, analogRead/analogWrite behaviour, Wire/SPI defaults, battery power, EEPROM emulation, BOOTSEL/UF2 flashing, or debugging why something on the board does not work.
---

# Raspberry Pi Pico (RP2040)

Board-specific firmware knowledge. The Pico's failure modes are quiet: a
wrong `platformio.ini` line silently links a different Arduino core, `Serial`
goes to a port that does not exist yet, and an ADC pin doubles as a power
rail — so read the reference files rather than guessing.

- `reference/board-hardware.md` — the complete board reference: 40-pin map
  with alt functions, the four hidden GPIOs, power tree, clocks, memory map,
  relevant errata **plus** a development guide (Part II: §7 toolchain and
  the core-choice table, §8 peripheral cookbook, §9 flashing, §10
  symptom → cause → fix table).
- `reference/recipes.md` — copy-paste code: `platformio.ini`, `board.h`
  excerpts, USB CDC + Serial1, ADC with the VSYS ×3 math, PWM and the slice
  rule, Wire1 remapping, EEPROM with `commit()`, interrupts, core 1,
  BOOTSEL tricks.
- `template/` — a **project that builds clean**, in two variants, plus a
  scaffold script. See `template/README.md`.

## Orientation

| | |
|---|---|
| MCU | RP2040 — 2× ARM Cortex-M0+ @ **133 MHz** (arduino-pico default; SDK default 125 MHz), 40 nm QFN-56 |
| Memory | 264 KB SRAM (6 banks), **no internal flash** — 2 MB W25Q16JV QSPI, XIP from `0x10000000` + 16 KB cache. Max sketch 2,093,056 B (last 4 KB = emulated EEPROM) |
| LED / button | LED = GPIO25, **active-HIGH**, not on the header · BOOTSEL button, sampled **at power-up only** |
| USB | micro-B, FS device/host; `Serial` = **USB CDC**; 1200-baud touch reboots into BOOTSEL; enumerates as VID:PID `2E8A:000A` ("Raspberry Pi" / "Pico") once the sketch is running |
| ADC | 12-bit 500 ksps: A0=GPIO26, A1=27, A2=28, **A3=GPIO29 = VSYS/3 (internal)** + channel 4 = die temp; `analogRead` is **10-bit by default**; ENOB 8.7 bits |
| PWM | every GPIO, 16 ch (8 slices × 2); `analogWrite` default 8-bit @ 1 kHz; **GPIO 2n & 2n+1 share one slice** |
| Bus defaults | Wire = 4/5 · **Wire1 = 26/27 (= A0/A1!)** · SPI = 17/18/19/16 · Serial1 = 0/1 · Serial2 = 8/9 |
| Power | VBUS 5 V → diode → VSYS **1.8-5.5 V** → RT6150 buck-boost → 3.3 V; 3V3 pin out < 300 mA; 3V3_EN low = board "dead" |
| Debug | 3-pin SWD header (SWCLK/GND/SWDIO); boot ROM is mask ROM — **unbrickable** |
| Toolchain | PlatformIO + `raspberrypi` 1.19.0 + arduino-pico 5.6.0 (`board_build.core = earlephilhower`) |

## Rules that prevent the expensive mistakes

Each of these produces a failure that looks like something else.

1. **`board_build.core = earlephilhower` in `platformio.ini`.** Plain
   `framework = arduino` silently builds against the Arduino **Mbed** core:
   `#include <EEPROM.h>` fails to compile, `Wire1`/`Serial2` don't exist,
   `analogWrite` is 500 Hz with no frequency API — and none of the usual
   Pico tutorial code works. The error reads like a broken dependency, not
   a missing ini line.
2. **`Serial` is USB CDC, not UART0.** A USB-serial adapter wired to
   GPIO0/1 sees nothing from `Serial.print` — those pins are `Serial1`.
   The CDC port exists only once the host opens it: bytes printed before
   that are dropped, and `while (!Serial)` blocks startup until a monitor
   connects. Never gate bring-up on the port being open.
3. **Upload usually needs no button — until it does.** `pio run -t upload`
   touches the CDC port at 1200 baud and a *running* sketch reboots itself
   into BOOTSEL for picotool. If the firmware has crashed or its USB is
   dead, the upload dies with `Cannot find BOOTSEL disk` — hold BOOTSEL
   while replugging USB, then re-run. That recovery is always available;
   the boot ROM is mask ROM and cannot be overwritten.
4. **BOOTSEL only works at power-up.** Pressing it while the board runs
   does nothing (it grounds the flash CS line). To get the `RPI-RP2`
   drive: hold BOOTSEL, plug/replug USB, release. A running sketch can
   re-enter it from code (`reset_usb_boot(0, 0)` — recipe 13).
5. **`analogRead` returns 10-bit values (0-1023) by default** on a 12-bit
   ADC. Symptom: "stuck at 1023 at full scale" or calibration off by 4×.
   `analogReadResolution(12)` before sampling.
6. **A3/GPIO29 is not a free pin — it measures VSYS/3.** Multiply by 3 for
   the input voltage. GPIO23 (SMPS power-save), GPIO24 (VBUS sense), GPIO25
   (LED), GPIO29 are real GPIOs in code but **not on the 40-pin header** —
   wiring plans that use them compile and "work" while nothing appears on
   the breadboard.
7. **GPIO pairs share a PWM slice.** Slice = pin/2: 0&1, 2&3 … 28&29 share
   one counter and therefore one frequency — the last `analogWriteFreq()`
   on either pin retimes both, and a stepper/servo pair on one slice
   glitches in a way that looks like a power problem.
8. **Wire1 defaults to GPIO26/27 — the same pins as A0/A1.** I2C bus
   corruption that starts as soon as analogRead is called is this, not
   electrical noise. `Wire1.setSDA()/setSCL()` to another I2C1 pair
   (2/3, 6/7, 10/11, 14/15, 18/19, 22/23) before `begin()`.
9. **GPIO26-29 are not 5 V tolerant and carry a reverse diode to 3V3.**
   Above ~3.6 V they damage the chip; any voltage on them while the board
   is unpowered back-powers the 3.3 V rail (a Pico that "ghosts" with no
   USB connected is being fed through an analog pin). GPIO0-25 tolerate
   applied voltage while unpowered.
10. **The ADC's accuracy budget is worse than its bit count.** Reference =
    filtered 3.3 V SMPS rail, ~30 mV inherent offset (150 µA through the
    200 Ω filter), ENOB 8.7 bits, DNL spikes at codes 512/1536/2560/3584.
    Naive absolute measurements are ±2-3 %. Average; cancel offset with a
    grounded channel; drive GPIO23 HIGH during sampling to quiet the SMPS
    (recipe 5), or fit an LM4040 3.0 V on ADC_VREF (range drops to 3.0 V).
11. **EEPROM lives in flash and only persists on `commit()`.** `begin(n)`
    maps a shadow, `put()` stages (update semantics), `commit()` burns one
    4 KB sector erase (100 k cycles). A sketch that never commits loses
    everything on reboot — silently, with no error.
12. **Flash writes stall both cores.** Code executes from the same QSPI
    chip (XIP), so `EEPROM.commit()` and filesystem writes pause the whole
    processor mid-stream. Keep them out of timing-critical and ISR-adjacent
    windows; the symptom is a periodic hiccup that looks like a scheduler
    bug.
13. **USB may not enumerate on a busy hub** (erratum E5, B0/B1 silicon —
    B2 fixes it): next to a chatty device on the same transaction
    translator, the port never leaves RESET. LED works, no COM port. Plug
    in directly or move ports before reinstalling drivers.
14. **Power wiring has three traps.** Feed VSYS (pin 39, 1.8-5.5 V) for
    battery/external power — a single Li-ion works. USB *host* mode needs
    5 V supplied to the VBUS pin (pin 40), not just VSYS. And a board that
    looks completely dead with USB connected usually has 3V3_EN (pin 37)
    shorted low — that pin disables the entire SMPS. The 3V3 pin (36) is an
    output (< 300 mA); never back-feed it. RUN (pin 30) shorted to GND is
    the reset button.
15. **LittleFS/FatFS silently gets zero bytes unless you ask for space.**
    The build always reserves the last 4 KB of flash for `EEPROM.h`
    (`maximum_sketch_size = flash_size - 4096 - filesystem_size`,
    `board_build.filesystem_size` default **`0MB`**), and any littlefs/fatfs
    region is carved out of flash *below* that, sized by that same option.
    `LittleFS.begin()` on the stock template returns `false` (or mounts an
    empty 0-byte volume) — not a build error, not a crash — because nothing
    was reserved. Set `board_build.filesystem_size = 1MB` (or `2MB`, `4MB`
    …) in `platformio.ini` before using `LittleFS`, `FatFS` or `SDFS`'s
    flash-backed mode, and re-flash with `pio run -t uploadfs` once for the
    initial image.

## When the task is analog measurement

The ADC is ratiometric to a filtered SMPS rail, not to a reference. What
that means in numbers: full scale is 3.3 V ± the SMPS tolerance (±1-2 %);
there is a built-in ~30 mV offset because the ADC's ~150 µA supply current
flows through the 200 Ω filter resistor (it varies ±20 µA with sampling);
effective resolution is 8.7 bits, with DNL spikes at four codes roughly
every 1024 counts (512, 1536, 2560, 3584 — erratum E11). So a single raw
12-bit reading implies far more accuracy than it has.

Work in that order: average many samples (free, fixes noise + DNL); tie a
spare ADC channel to AGND and subtract its reading (cancels the offset);
drive GPIO23 HIGH while sampling to force the SMPS into PWM mode, LOW after
(cuts ripple, costs light-load efficiency); and only then consider the
LM4040 3.0 V shunt on ADC_VREF — that also shrinks the input range to
3.0 V. For battery monitoring, `analogRead(A3) × 3` is well inside the
ADC's envelope — relative changes are trustworthy even when absolutes
aren't.

## When the task is powering the board

The chain is VBUS → Schottky D1 → VSYS → RT6150 buck-boost → 3V3. The
buck-boost accepts 1.8-5.5 V at VSYS, which makes power the most flexible
part of the board: USB, one Li-ion cell (with protection!), 3×AA, or any
2.3-5.5 V supply with an ORing diode/P-FET into VSYS. The traps: USB host
mode must see 5 V on the **VBUS pin** (VSYS power alone leaves the host
port dead); USB-only setups may bridge VBUS to VSYS to remove the diode
drop; and never feed the 3V3 pin — it's an output, and > 3.3 V there goes
straight into the RP2040 and flash. VSYS current draw: ~10 mA idle in
BOOTSEL, ~1.3 mA sleep, ~0.8 mA dormant, 90+ mA loaded.

## Starting a new project

Do not hand-assemble one. `template/` builds clean; scaffold from it:

```sh
~/.claude/skills/rp2040-pico/template/variants/new-project.sh <target-dir> [--full|--minimal]
cd <target-dir> && pio run -t upload -t monitor
```

- `--minimal` — classic Blink on GPIO25. **58,172 B** flash / 8,732 B RAM.
  Flash this first on an unfamiliar board: if the LED does not blink after
  upload, the problem is the cable, the BOOTSEL dance or rule 1, not the
  code. (The floor is TinyUSB CDC — a bare blink costs ~58 KB flash here,
  vs ~4 KB flash / ~40 KB RAM on the Mbed core.)
- `--full` (default) — non-blocking heartbeat, USB-CDC report, A0 at 12
  bits, die temperature, VSYS via A3, VBUS sense, EEPROM boot counter.
  61,112 B flash / 9,036 B RAM.

Both build as-is with platform-raspberrypi 1.19.0 + arduino-pico 5.6.0
(verified). Nothing is generated and no paths are embedded, so copying the
tree by hand works identically. `template/README.md` maps files to
subsystems.

When the user already has a project, prefer adding
`board_build.core = earlephilhower` to their `platformio.ini` over
rewriting their code — that one line is usually the whole fix.

## Flashing

Over the micro-USB cable:

```sh
pio run -t upload -t monitor
```

Normally nothing is pressed. PlatformIO opens the CDC port at 1200 baud,
the running sketch reboots into BOOTSEL, picotool flashes and reboots the
board — the LED blinks again within a second of `SUCCESS`. The board
enumerates as a CDC port (`/dev/cu.usbmodem*`, `COMx`); the monitor's baud
rate is ignored.

When the running firmware cannot respond (crashed, USB disabled, power-only
cable), the keystroke sequence is: hold **BOOTSEL** → plug/replug USB →
release → a 128 MB `RPI-RP2` drive appears → either re-run `pio run -t
upload` (picotool picks up the BOOTSEL device), or drag
`.pio/build/pico/firmware.uf2` onto the drive — the board reboots itself
when the copy completes. `pio run -t erase` copies a `flash_nuke.uf2` that
erases all 2 MB (including EEPROM) — the clean-slate recovery. A
power-only USB cable is the most common cause of "no RPI-RP2 drive".

The alternative route is SWD over the 3-pin bottom header with a second
Pico as Picoprobe (`upload_protocol = picoprobe`) — needed only when flash
itself is wedged; the mask-ROM bootloader survives everything.

## Reporting

State honestly what was verified on hardware and what came from the
datasheet. In this skill: both template variants and the recipes marked
compile-verified build clean against platform-raspberrypi 1.19.0 +
arduino-pico 5.6.0; the `--minimal` blink is the user project this skill
was extracted from. **No analog measurement, power draw or timing figure
was measured on hardware by the author** — the ADC accuracy envelope, VSYS
divider, SMPS behaviour and current figures are datasheet-derived
(Pico datasheet §3-4, RP2040 datasheet errata), and core-behaviour claims
(read resolution defaults, Wire1 pins, upload touch) come from the
arduino-pico 5.6.0 sources. Anything over 133 MHz F_CPU a user requests is
overclocking — label it as outside spec.

© alexex1993 · MIT · alexex1993/mcu-skills

Similar skills

SKILL.md skills catalog →

From the same set22

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor