---
name: rp2350-flash
description: >
  SWD-only flashing/debugging of RP2350 targets via a CMSIS-DAP debug
  probe (PiProbe, Pico Debug Probe, J-Link in CMSIS-DAP mode).
  REQUIRES a probe physically wired to the target's SWD pins. Does
  NOT cover USB BOOTSEL drag-drop, UF2 install, OTA, or any non-probe
  flashing path. Covers the install (Pico SDK 2.x, picotool 2.x,
  raspberrypi/openocd fork), the canonical openocd flash command, the
  rp2350-bootsel SRAM-stub wrapper, and probe-side troubleshooting.
  Scope: host down to the probe's SWD pins — not target-board pinouts.
  Trigger words: flash rp2350 via swd, swd flash rp2350, swd program
  rp2350, piprobe flash, pico debug probe rp2350, cmsis-dap probe
  rp2350, openocd rp2350, rp2350 swd, debug probe rp2350, rp2350 gdb,
  reset_usb_boot via swd, force bootsel via swd, rp2350-bootsel,
  pico sdk rp2350 swd.
---

# rp2350-flash — flash RP2350 over SWD via a CMSIS-DAP debug probe

> ⚠ **Scope — SWD debug probe ONLY.** Use this skill *only* when the user has a CMSIS-DAP debug probe (PiProbe, Pico Debug Probe, J-Link in CMSIS-DAP mode) **physically wired to the RP2350 target's SWD pins**. Do **NOT** invoke this skill for:
>
> - USB BOOTSEL drag-drop / UF2 file copy
> - OTA / firmware-over-the-air updates
> - Self-bootloader flashing from running firmware
> - Any flash path that doesn't go through a CMSIS-DAP probe over SWD

Full setup + troubleshooting reference: **[flashing-rp2350.md](./flashing-rp2350.md)** (also published as the body of `skills/rp2350-flash` on the Adom Wiki).

## When to use this skill

- User has a CMSIS-DAP probe wired to a target's SWD pins **and** wants to flash an `.elf` onto the RP2350.
- User wants to build firmware with the Pico SDK for `PICO_PLATFORM=rp2350`, then flash via the probe.
- User wants to debug RP2350 via gdb-over-openocd, with the probe as the SWD bridge.
- User wants to force a target into USB BOOTSEL **via SWD** (the `rp2350-bootsel` wrapper) — only relevant when the target's USB lines are also wired to a host.
- User mentions "openocd rp2350", "piprobe flash", "pico debug probe", "swd flash", "swd program rp2350", etc.

## When NOT to use this skill

- User wants to drag-and-drop a `.uf2` onto the target's BOOTSEL drive over **USB** (no probe in the loop).
- User wants to update firmware over WiFi / BLE / OTA.
- User is flashing a running RP2350 from its own firmware (self-bootloader / dual-bank).
- There is no debug probe in the topology.

**Scope:** host → USB → probe → SWD pins. Target-board specifics (which GPIO is the LED, where SWCLK lands on castellations, USB-D± wiring) are out of scope — refer to the target board's own docs.

## Required downloads (the install is non-optional)

| # | What | Source | How |
|---|------|--------|-----|
| 1 | apt deps | distro | `apt-get install` |
| 2 | arm-none-eabi-gcc | distro `gcc-arm-none-eabi` | `apt-get install` |
| 3 | cmake | distro `cmake` | `apt-get install` |
| 4 | **Pico SDK 2.x** | https://github.com/raspberrypi/pico-sdk | `git clone` + submodule init |
| 5 | **picotool 2.x** | https://github.com/raspberrypi/picotool | `git clone` + cmake build |
| 6 | **OpenOCD (raspberrypi fork)** | https://github.com/raspberrypi/openocd, branch `sdk-2.0.0` | `git clone` + autotools build |

Stock distro OpenOCD 0.12.0 **does not work** — it predates RP2350 and ships no target config. This is the #1 cause of `no driver found for target rp2350`. The full install takes ~10 min on a fresh container.

Step-by-step install commands: [flashing-rp2350.md § Install steps](./flashing-rp2350.md#install-steps--fresh-container).

## Pre-flight check — always run this first

Before doing anything, confirm probe + target are alive:

```bash
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "init; targets; exit"
```

Expected: `CMSIS-DAP: FW Version = 2.0.0`, `SWD DPIDR 0x4c013477`, and both `rp2350.dap.core0` + `core1` as Cortex-M33 r1p0. If you see those, the toolchain is installed and the wiring is good — skip to "Flash via SWD" below.

Failure modes:
- `no driver found for target rp2350` → stock OpenOCD, install the raspberrypi fork (download #6).
- `Could not find a USB device` → probe isn't on USB. `lsusb | grep 2e8a:000c`. Fix the cable.
- `SWD DPIDR 0x00000000` → target unpowered, SWD wires bad/swapped, or no common ground between probe and target.

## Build firmware for RP2350

`CMakeLists.txt` must set:

```cmake
cmake_minimum_required(VERSION 3.13)
set(PICO_BOARD pico2 CACHE STRING "")
set(PICO_PLATFORM rp2350 CACHE STRING "")
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(myapp C CXX ASM)
pico_sdk_init()
add_executable(myapp myapp.c)
target_link_libraries(myapp pico_stdlib)
pico_add_extra_outputs(myapp)
```

Build:

```bash
mkdir -p build && cd build && cmake .. && make -j$(nproc)
```

Produces `myapp.elf` (for SWD flash) and `myapp.uf2` (for USB BOOTSEL drag-drop, if applicable).

## Flash via SWD — the recommended path

```bash
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
  -c "adapter speed 5000; program firmware.elf verify reset exit"
```

Successful run ends with `** Verified OK **` and `** Resetting Target **`. The new firmware starts running immediately.

**Don't bother with BOOTSEL when you have a probe.** SWD flashes work even if the target's USB pins aren't wired anywhere.

## Force USB BOOTSEL via SWD (rare path)

`picotool reboot -u -f` only works when the target is already on USB — it cannot drive a CMSIS-DAP probe in the upstream build. If you need to force BOOTSEL via SWD only:

```bash
rp2350-bootsel
```

This wraps OpenOCD + a tiny SRAM stub (`~/tools/force-bootsel/`) that calls the bootrom's `reset_usb_boot(0, 0)`.

**Caveat**: BOOTSEL is only useful if the target's USB D± lines are physically wired to a USB host. In a probe-only setup (SWD between probe and target, no USB cable on the target), BOOTSEL puts the chip into MSC mode but there's no host to see the drive. **Use the SWD flash path instead** — that works regardless.

## Debug via gdb over the probe

Terminal 1 (leave running):

```bash
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg
```

Terminal 2:

```bash
arm-none-eabi-gdb firmware.elf -ex 'target extended-remote :3333'
```

## Stream `printf` via ARM semihosting through the probe

When the target has no UART/USB wired, route `printf` through SWD via semihosting:

Firmware CMake:
```cmake
pico_enable_stdio_semihosting(myapp 1)
pico_enable_stdio_uart(myapp 0)
pico_enable_stdio_usb(myapp 0)
```

Host command (leave it running):
```bash
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
  -c "adapter speed 5000" -c "init" -c "reset halt" \
  -c "arm semihosting enable" -c "resume"
```

**Order matters:** `init → reset halt → arm semihosting enable → resume`. Plain `reset` (without `halt`) leaves the target running, then `arm semihosting enable` and `resume` will fail with `not halted` / `context restore failed`.

## Common failure modes (one-liner triage)

| Error | Likely cause |
|-------|--------------|
| `no driver found for target rp2350` | Stock OpenOCD; install raspberrypi fork (download #6) |
| `Could not find a USB device` | PiProbe missing from `lsusb`; fix cabling |
| `error submitting USB read: Input/Output Error` (flood) | Another openocd is holding the probe — `pgrep -af openocd` then `kill <PID>` that specific PID (never `pkill openocd`) |
| `SWD DPIDR 0x00000000` | Target unpowered, SWD wires bad, or no common GND |
| `timed out while waiting for target halted` | SWD clock too fast — drop `adapter speed` to 1000 |
| `not halted` / `context restore failed, aborting resume` (after `arm semihosting enable`) | Used `reset` instead of `reset halt` — reorder to `init; reset halt; arm semihosting enable; resume` |
| `resume of a SMP target failed` | RP2350 fork's SMP resume is fragile — use `reset halt; resume` or `set USE_CORE 0` |
| `picotool: No accessible RP-series devices in BOOTSEL` | Target USB not on host bus — use OpenOCD/SWD instead |
| Verify fails after flash | Wrong `PICO_BOARD` / `PICO_PLATFORM`; nuke `build/` and rebuild |
| `VECTRESET is not supported... using SYSRESETREQ` | Cosmetic warning, ignore |

Full troubleshooting table: [flashing-rp2350.md § Troubleshooting](./flashing-rp2350.md#troubleshooting).

## Quick command reference

```bash
# Pre-flight
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c "init; targets; exit"

# Flash an .elf via SWD (canonical)
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
  -c "adapter speed 5000; program firmware.elf verify reset exit"

# Start a gdb-server (terminal 1)
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg

# Attach gdb (terminal 2)
arm-none-eabi-gdb firmware.elf -ex 'target extended-remote :3333'

# Stream printf via ARM semihosting through the probe (no UART/USB needed)
openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \
  -c "adapter speed 5000" -c "init" -c "reset halt" \
  -c "arm semihosting enable" -c "resume"

# Probe stuck after a wedged session? Kill the SPECIFIC stale openocd PID
pgrep -af openocd            # find the PID
kill <PID>                   # only that PID — never `pkill openocd`

# Force BOOTSEL via SWD (only if target USB is wired to host)
rp2350-bootsel

# Build a Pico SDK project for RP2350
cd myapp && mkdir -p build && cd build && cmake .. && make -j$(nproc)
```
