A network-attached SWD debug probe on a Raspberry Pi Pico 2 (RP2350). It
speaks pyOCD's remote-probe protocol over a software 10BASE-T Ethernet link, so
a remote pyocd — and therefore GDB — can debug an RP2040/RP2350 target's SWD as
if the probe were plugged into the dev machine over USB. Everything runs on one
Pico 2; nothing at the remote site but the probe, power, and an Ethernet drop.
dev machine remote site
─────────── ───────────
GDB ⇄ pyocd ──(line-delimited JSON / TCP)──▶ Pico 2 probe
│ over software 10BASE-T ├─ smoltcp TCP servers
└─ gdb-multiarch ──(GDB RSP / TCP :3333)──────▶ │ :5555 pyOCD · :3333 GDB
(no host tool) │ :5556 console · :5557 OpenOCD
nc / RTT viewer ──(raw RTT / TCP :5558)─────────▶ │ :5558 RTT (host-free)
(no host tool) └─ SWD engine (PIO) ──▶ target SWCLK/SWDIO
It also runs a native GDB stub on-probe (:3333): point a bare gdb-multiarch
straight at it — no pyOCD, no host gdbserver — and debug both RP2350 cores,
set hardware breakpoints + watchpoints, single-step, flash firmware (load),
and reset/run, all over the network. That's the Black Magic Probe model: the probe
itself speaks GDB's protocol. See Build & use and
DESIGN.md §12.
There's also a native on-probe RTT viewer (:5558): the probe finds the
target's SEGGER RTT control block in RAM and streams up-channel 0 to the socket, so
nc <probe-ip> 5558 tails a running target's RTT log with nothing on the host —
no J-Link, no pyOCD, no OpenOCD. RTT is non-intrusive (it polls the ring buffers via
background memory reads while the core runs; it never halts). See
DESIGN.md §13.
By default: no Wi-Fi, no USB-Ethernet dongle, no host at the remote end — just
bit-banged 10BASE-T (PIO + DMA + an RS-485 transceiver + magnetics) bridged to
smoltcp, with a PIO SWD engine on the
side. Pure Rust, no_std, on the RP2350's Hazard3 RISC-V cores.
On a Pico 2 W there's also an optional Wi-Fi transport (--features wifi)
that replaces the 10BASE-T link with the onboard CYW43439 (station mode, DHCP) —
same pyOCD/GDB-over-the-network experience, no Ethernet drop required. The SWD
engine and all five TCP servers are transport-agnostic, so only the link changes.
See Build & use.
Working and hardware-validated against a real RP2350 target: pyOCD connects over the network, does SWD multidrop selection, halts the dual Cortex-M33 cores, reads/writes registers and memory, flashes firmware, and runs a full interactive GDB session (breakpoints, stepping, memory). The probe can even self-rescue a gated RP2350.
The on-probe GDB stub (:3333) is complete and hardware-validated too: a bare
gdb-multiarch does the whole debug+flash cycle with no host tool — dual-core
threads, hardware breakpoints, DWT data watchpoints, single-step, load to flash
(via the RP2350 bootrom), and reset/run. Also reachable: pyOCD/OpenOCD/probe-rs and
a serial console, all concurrently.
The native on-probe RTT viewer (:5558) is hardware-validated too: nc the
port and a running target's RTT log streams out, host-free (the probe scans RAM for
the control block and bridges up-channel 0). RTT also works host-driven through the
existing pyOCD/probe-rs paths on :5555 (pyocd rtt, or probe-rs-net --rtt).
The RP2350's RISC-V side is covered too: an OpenOCD remote_bitbang server
(:5557) lets OpenOCD debug the Hazard3 cores through the same probe — both
cores examine, halt, and read registers (connect ~10–15 s with the bundled
read-batching patch; see host/README.md). And a target
serial console (:5556) bridges the target's UART to TCP, running concurrently
with any debug session. The four SWD-using front ends (pyOCD, GDB, OpenOCD, RTT)
share the wire one-owner-at-a-time; the console is independent.
Performance is modest by design — it's a software PHY: ~3 ms round-trip, ~100 KB/s block memory reads (~10–15 KB/s writes), ~0.4 kB/s flash programming. Great for a low-rate remote debug box, not a fast bench flasher.
⚠️ An open debug port is remote code execution on the target. Keep it on an isolated segment or VPN.
A Pico 2 (RP2350) + an ISL3177E transceiver + HR911105A RJ45 magnetics (the pico-10base-t-rs board), plus SWD wiring to the target: SWCLK→GP2, SWDIO→GP3, shared GND. Ethernet on GP13/GP14. Force the peer NIC to 10 Mbit half-duplex.
Optional extras: GP6→target nRESET (driven open-drain, active-low) for
reset control, and GP0/GP1↔target UART (115200 8N1) for the serial console
on :5556.
The 10BASE-T transport comes from pico-10base-t-rs as a Cargo path dependency, so clone the two repos side by side. Flashing uses picotool over USB BOOTSEL.
git clone https://github.com/mattdeeds/pico-10base-t-rs
git clone https://github.com/mattdeeds/pico-remote-probe
cd pico-remote-probe
rustup target add riscv32imac-unknown-none-elf # the firmware runs on the Hazard3 cores
cargo run --release # build + flash via picotool over USB BOOTSEL
# Or, on a Pico 2 W, the Wi-Fi build (replaces the 10BASE-T link; 2.4 GHz only).
# Credentials are baked in at build time (nothing stored in the repo):
WIFI_SSID='YourSSID' WIFI_PASSWORD='YourPass' cargo run --release --features wifi
# The probe joins the AP + DHCPs; its USB-CDC heartbeat prints the acquired IP.Then, from a host on the probe's network — either drive it through pyOCD:
pyocd gdbserver -t rp2350 -u remote:<probe-ip>:5555 # plain pyOCD; cores auto-discover…or attach GDB directly to the on-probe stub, no host tool in between:
gdb-multiarch -ex 'set architecture armv8-m.main' \
-ex 'target extended-remote <probe-ip>:3333' your.elf
# then: load · break/watch · stepi · continue · monitor reset · run…or tail the target's RTT log straight from the probe, no host tool:
nc <probe-ip> 5558 # streams SEGGER RTT up-channel 0 (probe scans RAM for it)…or the target's serial console (nc <probe-ip> 5556), or OpenOCD against
the RISC-V side (openocd -f host/rbb-rp2350-riscv.cfg), or
probe-rs via the host/probe-rs-net
network driver.
See DESIGN.md for the architecture and milestones, host/README.md for the pyOCD/GDB/OpenOCD recipes (incl. the RP2350 rescue + flash gotchas and the OpenOCD speed patch), and docs/swd-port-notes.md for the SWD engine + protocol reference.
Builds on pico-10base-t-rs (the software 10BASE-T NIC, itself a port of kingyoPiyo's Pico-10BASE-T) for transport, and Raspberry Pi's debugprobe for the SWD engine. Speaks pyOCD's remote-probe protocol.
Licensed under either of MIT or Apache-2.0 at your option.