{
  "schema_version": 1,
  "type": "skill",
  "slug": "rp2350-flash",
  "title": "Flashing RP2350 via SWD Debug Probe (PiProbe / CMSIS-DAP)",
  "brief": "SWD-only flashing/debugging of RP2350 targets via a CMSIS-DAP debug probe (PiProbe, Pico Debug Probe). REQUIRES a probe physically wired to the target's SWD pins. Does NOT cover USB BOOTSEL drag-drop,",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "discovery_triggers": [
    "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",
    "raspberrypi openocd fork",
    "rp2350 flash probe"
  ],
  "discovery_pitch": "SWD-only flashing/debugging of RP2350 via a CMSIS-DAP debug probe (PiProbe, Pico Debug Probe). REQUIRES a probe wired to the target's SWD pins — does NOT cover USB BOOTSEL drag-drop, UF2 install, OTA, or any non-probe path.",
  "sample_prompts": [
    {
      "label": "Flash via SWD",
      "prompt": "Flash my RP2350 firmware.elf via SWD using the PiProbe"
    },
    {
      "label": "Install probe toolchain",
      "prompt": "Install Pico SDK 2.x and the raspberrypi/openocd fork so I can flash RP2350 via SWD probe"
    },
    {
      "label": "Probe pre-flight",
      "prompt": "Verify my PiProbe + RP2350 SWD connection is up"
    },
    {
      "label": "SWD gdb debug",
      "prompt": "Set up gdb debugging for my RP2350 over the SWD probe"
    },
    {
      "label": "Force BOOTSEL via SWD",
      "prompt": "Force my RP2350 into USB BOOTSEL via the SWD probe (no physical jumper)"
    },
    {
      "label": "Explain probe path",
      "prompt": "Explain the host → PiProbe → SWD → RP2350 path"
    }
  ],
  "source_path": "SKILL.md",
  "readme": "# Flashing RP2350 via SWD Debug Probe (PiProbe / CMSIS-DAP)\n\n> ⚠ **Scope — SWD debug probe ONLY.** This guide applies *only* when you have a CMSIS-DAP debug probe (PiProbe, Pico Debug Probe, J-Link in CMSIS-DAP mode, etc.) **physically wired to the RP2350 target's SWD pins**. It does **NOT** apply to:\n>\n> - USB BOOTSEL drag-drop / UF2 file copy\n> - OTA / firmware-over-the-air updates\n> - Self-bootloader flashing from already-running firmware\n> - Any flash path that doesn't go through a CMSIS-DAP probe over SWD\n>\n> If you don't have a debug probe wired to SWD, this guide is the wrong tool — close the page.\n\nEnd-to-end guide for building, flashing, and debugging an **RP2350 target** using a **PiProbe** (RP2040 running the raspberrypi/debugprobe firmware) or any other CMSIS-DAP probe, over SWD.\n\n## Hardware path (scope of this guide)\n\n```\nhost (Linux) ──USB──► PiProbe (CMSIS-DAPv2, 2e8a:000c)\n                         │\n                         │ SWD: SWCLK + SWDIO + GND (+ optional nRESET)\n                         ▼\n                      RP2350 target\n```\n\nThis guide covers everything **from the host down to the SWD signals at the probe's target connector** — toolchain, probe firmware, OpenOCD config, flash command, debug session, USB BOOTSEL-via-SWD. It does NOT cover target-board-specific pinouts (which GPIO is the LED, where SWCLK lands on castellations, etc.) — that belongs in the target board's own documentation.\n\n## Required downloads at a glance\n\n| # | What | Source | Install method |\n|---|------|--------|----------------|\n| 1 | apt build deps | distro packages | `apt-get install` |\n| 2 | arm-none-eabi-gcc 13.x | distro `gcc-arm-none-eabi` | `apt-get install` |\n| 3 | cmake ≥ 3.13 | distro `cmake` | `apt-get install` |\n| 4 | **Pico SDK 2.x** | https://github.com/raspberrypi/pico-sdk | `git clone` + submodule init |\n| 5 | **picotool 2.x** | https://github.com/raspberrypi/picotool | `git clone` + cmake build |\n| 6 | **OpenOCD (raspberrypi fork)** | https://github.com/raspberrypi/openocd, branch `sdk-2.0.0` | `git clone` + autotools build |\n\nAll three RPi-hosted repos must be cloned and built from source. Distro `openocd` will NOT work — see the \"Why this exact stack\" section.\n\n## Why this exact stack\n\n| Component | Why this version |\n|-----------|------------------|\n| **Pico SDK 2.x** | RP2350 support landed in SDK 2.0; SDK 1.x will not compile for `PICO_PLATFORM=rp2350`. |\n| **picotool 2.x** | Required by SDK 2.x for binary post-processing (UF2 generation, OTP packing). |\n| **raspberrypi/openocd fork** | Stock OpenOCD 0.12.0 predates RP2350 and ships **no** `target/rp2350.cfg`. Distro-installed OpenOCD will fail with `no driver found for target rp2350`. The raspberrypi fork (`sdk-2.0.0` branch) is the canonical RP2350 OpenOCD. |\n| **arm-none-eabi-gcc 13.x** | Toolchain. Older 10.x also compiles fine. |\n| **cmake ≥ 3.13** | Pico SDK CMake floor. |\n\n## Install steps — fresh container\n\n### Step 1: apt deps\n\n```bash\nsudo apt-get update -qq\nsudo apt-get install -y --no-install-recommends \\\n  build-essential pkg-config git \\\n  libusb-1.0-0-dev libhidapi-dev libftdi1-dev \\\n  libtool autoconf automake texinfo \\\n  python3 python3-pip \\\n  gcc-arm-none-eabi cmake\n```\n\n`gcc-arm-none-eabi` and `cmake` may already be present from the base image — `apt-get install` is a no-op when satisfied.\n\n### Step 2: Pico SDK 2.x\n\n```bash\ncd ~\ngit clone --depth 1 https://github.com/raspberrypi/pico-sdk.git\ncd pico-sdk\ngit submodule update --init --depth 1\necho 'export PICO_SDK_PATH=$HOME/pico-sdk' >> ~/.bashrc\nexport PICO_SDK_PATH=$HOME/pico-sdk\n```\n\nVerify ≥ 2.0.0:\n\n```bash\ngrep 'set(PICO_SDK_VERSION_MAJOR' ~/pico-sdk/pico_sdk_version.cmake\n# expect:  set(PICO_SDK_VERSION_MAJOR 2)\n```\n\n### Step 3: picotool 2.x\n\n```bash\ncd ~\ngit clone --depth 1 https://github.com/raspberrypi/picotool.git\ncd picotool && mkdir -p build && cd build\ncmake ..\nmake -j\"$(nproc)\"\nsudo make install\npicotool version    # → picotool v2.2.x (Linux, ...)\n```\n\n### Step 4: OpenOCD with RP2350 support (raspberrypi fork)\n\n```bash\ncd ~\ngit clone --depth 1 --branch sdk-2.0.0 https://github.com/raspberrypi/openocd.git openocd-rp\ncd openocd-rp\n./bootstrap\n./configure --enable-cmsis-dap --disable-werror --prefix=/usr/local\nmake -j\"$(nproc)\"\nsudo make install\nhash -r\nopenocd --version 2>&1 | head -1   # → Open On-Chip Debugger 0.12.0+dev-...\nls /usr/local/share/openocd/scripts/target/ | grep rp2350\n# expect: rp2350.cfg, rp2350-riscv.cfg, rp2350-rescue.cfg,\n#         rp2350-dbgkey-secure.cfg, rp2350-dbgkey-nonsecure.cfg\n```\n\nInstalls to `/usr/local/bin/openocd`, which takes precedence over any stock `/usr/bin/openocd`.\n\n## Building firmware for RP2350\n\nMinimal project:\n\n```\nmyapp/\n├── CMakeLists.txt\n└── myapp.c\n```\n\n`CMakeLists.txt`:\n\n```cmake\ncmake_minimum_required(VERSION 3.13)\nset(PICO_BOARD pico2 CACHE STRING \"\")\nset(PICO_PLATFORM rp2350 CACHE STRING \"\")\ninclude($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)\nproject(myapp C CXX ASM)\npico_sdk_init()\nadd_executable(myapp myapp.c)\ntarget_link_libraries(myapp pico_stdlib)\npico_add_extra_outputs(myapp)\n```\n\nThe two critical settings are:\n- `PICO_BOARD=pico2` — board headers for the RP2350A reference design (Pico 2). Override individual GPIO assignments in your code if your board differs.\n- `PICO_PLATFORM=rp2350` — tells the SDK to target the RP2350 architecture (vs `rp2040`).\n\nBuild:\n\n```bash\nmkdir -p build && cd build && cmake .. && make -j\"$(nproc)\"\n```\n\nProduces `myapp.elf` (used for SWD flashing) and `myapp.uf2` (used for USB BOOTSEL drag-drop, if applicable).\n\n## Flashing via SWD — the canonical command\n\n```bash\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \\\n  -c \"adapter speed 5000; program firmware.elf verify reset exit\"\n```\n\nSuccessful output ends with:\n\n```\n** Programming Started **\n** Programming Finished **\n** Verify Started **\n** Verified OK **\n** Resetting Target **\n```\n\n5 MHz SWD is comfortable on clean wiring; the PiProbe + RP2350 can also run at 24 MHz reliably with short SWD leads.\n\nThe target executes the new firmware immediately after `Resetting Target` — no power cycle needed.\n\n## Debugging via gdb over the probe\n\nTerminal 1 (leave running):\n\n```bash\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg\n```\n\nOpenOCD listens on `:3333` for gdb.\n\nTerminal 2:\n\n```bash\narm-none-eabi-gdb firmware.elf \\\n  -ex 'target extended-remote :3333' \\\n  -ex 'monitor reset halt'\n```\n\nThen `load`, `b main`, `c`, etc. as normal.\n\n## Watching live `printf` output via ARM semihosting\n\nSemihosting routes the target's `printf` (and other libc I/O) through the SWD probe back into OpenOCD's stdout — no UART, no USB-on-the-target needed. Useful when SWD is the *only* physical link to the target.\n\n### Firmware side\n\nLink against semihosting stdio and disable the other stdio backends:\n\n```cmake\ntarget_link_libraries(myapp pico_stdlib)\npico_enable_stdio_semihosting(myapp 1)\npico_enable_stdio_uart(myapp 0)\npico_enable_stdio_usb(myapp 0)\n```\n\nAfter `stdio_init_all()`, every `printf()` becomes a semihosting BKPT 0xAB call that OpenOCD intercepts.\n\n### Host side — the canonical \"watch the target run\" command\n\n```bash\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \\\n  -c \"adapter speed 5000\" \\\n  -c \"init\" \\\n  -c \"reset halt\" \\\n  -c \"arm semihosting enable\" \\\n  -c \"resume\"\n```\n\nLeave it running. Every `printf` from the firmware streams to stdout. Ctrl-C to stop.\n\n**Order matters:**\n\n1. `init` — attach to the probe and examine cores.\n2. `reset halt` — reset the target and **leave it halted**. Plain `reset` runs after reset, which leaves the cores in an undefined state for the next step.\n3. `arm semihosting enable` — turn on the BKPT 0xAB intercept *while the target is halted*.\n4. `resume` — let the firmware run with semihosting now wired up.\n\nReordering or skipping `halt` produces the failure modes in the troubleshooting table below (`not halted` / `context restore failed`).\n\n## Forcing USB BOOTSEL via SWD (optional)\n\n`picotool reboot -u -f` works only over USB — it cannot drive a CMSIS-DAP probe in the upstream picotool build. To force a target into BOOTSEL via SWD only, load a tiny SRAM stub that calls the bootrom's `reset_usb_boot()`.\n\nStub source — `~/tools/force-bootsel/force_bootsel.c`:\n\n```c\n#include \"pico/bootrom.h\"\nint main(void) {\n    reset_usb_boot(0, 0);\n    while (1) { __asm volatile(\"wfi\"); }\n}\n```\n\n`CMakeLists.txt` uses `pico_set_binary_type(force_bootsel no_flash)` so the binary runs entirely from SRAM and doesn't disturb flash.\n\nWrapper at `/usr/local/bin/rp2350-bootsel`:\n\n```bash\n#!/usr/bin/env bash\nset -e\nSTUB=${RP2350_BOOTSEL_STUB:-/home/adom/tools/force-bootsel/build/force_bootsel.elf}\nENTRY=$(arm-none-eabi-readelf -h \"$STUB\" | awk '/Entry point/ {print $NF}')\nexec openocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \\\n  -c \"init; reset halt; load_image $STUB; reg sp 0x20040000; reg pc $ENTRY; resume; sleep 300; exit\"\n```\n\n**Caveat.** BOOTSEL puts the chip into USB MSC mode, but a USB host must be connected to the target's USB D± lines to see the resulting drive. If the only physical link between host and target is the probe's SWD, **BOOTSEL has nowhere to go** — flash directly via SWD instead.\n\n## Pre-flight: confirm probe + target are alive\n\nBefore touching anything else, run:\n\n```bash\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"init; targets; exit\"\n```\n\nExpect, in order:\n\n1. `Info : CMSIS-DAP: FW Version = 2.0.0` — PiProbe is talking.\n2. `Info : SWD DPIDR 0x4c013477` — SWD link is up and the DAP IDCODE matches RP2350.\n3. `Info : [rp2350.dap.core0] Cortex-M33 r1p0 processor detected`\n4. `Info : [rp2350.dap.core1] Cortex-M33 r1p0 processor detected`\n\nIf you see the probe (1) but no DPIDR (2): SWD wires are broken/swapped, target is unpowered, or there's no common ground between probe and target.\n\nIf you don't see the probe at all (1 missing): `lsusb | grep 2e8a:000c` — if absent, the probe isn't on USB. Check cables / hub.\n\n## Troubleshooting\n\n| Symptom | Cause | Fix |\n|---------|-------|-----|\n| `error: no driver found for target rp2350` | Stock OpenOCD 0.12.0 (predates RP2350) | Build raspberrypi/openocd fork (Step 4 above) |\n| `Error: Could not find a USB device` | Probe missing or `/dev/bus/usb` permissions | `lsusb \\| grep 2e8a:000c`; if device node exists but is root-only, fix udev or run with sudo |\n| `Error: error submitting USB read: Input/Output Error` (flooded repeatedly) | A previous openocd process is still holding the probe (often a wedged session from an earlier resume failure) | `pgrep -af openocd` to find the stale PID; `kill <PID>` that specific process. **Do NOT `pkill openocd`** if other openocd sessions are needed elsewhere. |\n| `SWD DPIDR 0x00000000` | Bad SWD wiring, target unpowered, or no common GND | Verify continuity on SWCLK/SWDIO/GND, verify target's +3V3 rail |\n| `Error: timed out while waiting for target halted` | SWD clock too fast for the cable | Drop `adapter speed 5000` to `adapter speed 1000` |\n| `Error: target was in unexpected state X` | Target stuck in a bad loop / WFI | Add `reset halt` before `program` |\n| `Error: [rp2350.dap.core0] not halted` / `context restore failed, aborting resume` after `arm semihosting enable` | Used `reset` (which runs after reset) instead of `reset halt` before enabling semihosting and calling `resume`. The semihosting `resume` requires a defined halted start state. | Reorder commands to `init; reset halt; arm semihosting enable; resume`. Always halt before enabling semihosting. |\n| `resume of a SMP target failed, trying to resume current one` followed by both cores halting unexpectedly | Raspberry Pi OpenOCD fork's SMP resume path is fragile on RP2350 | Either use `reset halt; resume` (avoids the SMP race), or set `USE_CORE 0` via `-c \"set USE_CORE 0\"` before the target config to debug only core 0 |\n| `picotool: No accessible RP-series devices in BOOTSEL mode were found` | Target's USB isn't on the host bus | Don't use picotool over USB — flash via OpenOCD/SWD instead |\n| Verify fails after flash | Wrong `PICO_BOARD` / `PICO_PLATFORM` | Confirm CMake cache has `PICO_BOARD=pico2` and `PICO_PLATFORM=rp2350`; nuke `build/` and rebuild |\n| `VECTRESET is not supported on this Cortex-M core, using SYSRESETREQ instead` | Cosmetic | Ignore — RP2350 cores don't implement VECTRESET; SYSRESETREQ is the correct path |\n\n## File map (after a full install)\n\n| Path | Purpose |\n|------|---------|\n| `~/pico-sdk/` | Pico SDK 2.x source (download #4) |\n| `~/picotool/` | picotool source (download #5) |\n| `~/openocd-rp/` | OpenOCD raspberrypi fork source (download #6) |\n| `~/tools/force-bootsel/` | BOOTSEL-trigger SRAM stub source |\n| `/usr/local/bin/picotool` | Installed binary, picotool 2.x |\n| `/usr/local/bin/openocd` | Installed binary, raspberrypi fork (overlays any stock) |\n| `/usr/local/bin/rp2350-bootsel` | SWD → BOOTSEL wrapper |\n| `/usr/local/share/openocd/scripts/target/rp2350.cfg` | OpenOCD target config |\n| `/usr/local/share/openocd/scripts/interface/cmsis-dap.cfg` | OpenOCD interface config |\n\n## Quick command reference\n\n```bash\n# Pre-flight\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"init; targets; exit\"\n\n# Flash an .elf via SWD\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \\\n  -c \"adapter speed 5000; program firmware.elf verify reset exit\"\n\n# Halt + reset only (no flash)\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \\\n  -c \"init; reset halt; exit\"\n\n# Start gdb-server (terminal 1)\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg\n\n# Attach gdb (terminal 2)\narm-none-eabi-gdb firmware.elf -ex 'target extended-remote :3333'\n\n# Stream printf via ARM semihosting through the probe (no UART/USB needed)\nopenocd -f interface/cmsis-dap.cfg -f target/rp2350.cfg \\\n  -c \"adapter speed 5000\" -c \"init\" -c \"reset halt\" \\\n  -c \"arm semihosting enable\" -c \"resume\"\n\n# Probe stuck after a wedged session? Kill the SPECIFIC stale openocd PID\npgrep -af openocd            # find the PID\nkill <PID>                   # only that PID — never `pkill openocd`\n\n# Force BOOTSEL via SWD (only if target USB is wired to a host)\nrp2350-bootsel\n\n# Build a Pico SDK project for RP2350\ncd myapp && mkdir -p build && cd build && cmake .. && make -j$(nproc)\n```\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "kyle@adom.inc"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "metadata": {},
  "created_at": "2026-05-28T05:29:25.218Z",
  "updated_at": "2026-05-28T05:29:25.218Z",
  "sub_skills": [],
  "parent_app": null
}