An 8-bit AVR that debugs an ARM Cortex-M4. Bit-banged SWD, bare metal, no probe chip and no level shifter. One resistor and four jumper wires, plus one more for reset.
20.1KB flash · 339B RAM on the AVR. Halt, step, breakpoints, watchpoints, flash
programming, RTT, and a GDB server.
Every practical SWD host leans on dedicated hardware: an ST-Link, a CMSIS-DAP probe, or at minimum a level shifter between 5V and 3.3V logic. This removes all of it and implements ADIv5 directly against the target's 5V-tolerant pins.
Bit-banged SWD from a small host is not new (pirate-swd, Black Magic Probe). What is unusual here is the combination: an 8-bit host with 2KB of RAM and no USB, carrying the protocol, the Cortex-M debug logic, the flash driver, XMODEM and a GDB server all at once, with no probe silicon and no level shifter in the path.
- PA13/PA14 are 5V-tolerant. SWDIO is never driven high; low is driven, high is released and the resistor pulls it to 3.3V, so it never contends with the target.
- NRST is optional, needed only for
cr, connect under reset. It is the pin markedRon the header. Same treatment as SWDIO, pulled low or released, no resistor needed. Why that matters even though the pin is 5V-tolerant: docs/NOTES.md. - A bad ground makes every logic level meaningless and looks exactly like a protocol bug.
make flash # build and flash the AVR host
make target-flash # build target/blink.bin and program the STM32
screen /dev/ttyACM0 115200 # or drive it by handOr debug it from gdb, the same hardware driven through tools/gdbserver:
make gdbserver # listens on localhost:3333
gdb-multiarch target/blink.elf -ex 'target remote localhost:3333'> i
DPIDR 0x2BA01477 ARM SW-DP
AP IDR 0x24770011 AHB-AP
CPUID 0x410FC241 Cortex-M4 r0p1
DBGMCU 0x10006431
DEV 0x00000431 rev 0x00001000 STM32F411 flash: ok
flowchart TB
shell["shell.c · command line over UART"]
xm["xmodem.c · load and save images"]
rtt["rtt.c · target printf"]
cortex["cortex.c · halt, step, registers"]
fpb["fpb.c · breakpoints"]
dwt["dwt.c · watchpoints"]
flash["flash.c · erase, program, verify"]
ap["ap.c · MEM-AP, memory access"]
dp["dp.c · Debug Port, power, errors"]
swd["swd.c · bit-banged physical layer"]
pins(["PB0 SWCLK · PB1 SWDIO"])
shell --> xm & rtt & cortex & flash
cortex --> fpb & dwt
xm --> flash
rtt --> ap
cortex --> ap
flash --> ap
fpb --> ap
dwt --> ap
ap --> dp --> swd --> pins
| Layer | Does |
|---|---|
swd.c |
Clock and data bit-banging, turnaround, line reset, transfers |
dp.c |
Debug Port: connect, power domains, sticky error recovery |
ap.c |
MEM-AP: memory at byte, halfword and word size, block transfers |
cortex.c |
Halt, resume, reset-halt, stepping, core registers |
fpb.c dwt.c |
Hardware breakpoints and data watchpoints |
flash.c |
Geometry probe, sector and mass erase, program, verify, option bytes |
xmodem.c rtt.c |
Image transfer both ways, and target printf |
shell.c |
The command line |
Every protocol quirk that cost real time to find (turnaround timing, CSW Prot bits, sticky errors, TAR boundaries, and more) is written up in docs/NOTES.md.
Addresses and values are hex. Counts, sizes and slots are decimal. ? prints this list.
c cr |
connect, or holding NRST | b / b <addr> |
list, or set a breakpoint |
i |
DPIDR, AP IDR, CPUID, DBGMCU | k [slot] |
clear breakpoints |
s |
status, halted and lockup | a / a <addr> [rwb] [len] |
list, or set a watchpoint |
r <addr> [sz] |
read, size 1, 2 or 4 | j [slot] |
clear watchpoints |
w <addr> <val> [sz] |
write, same sizes | f |
flash size and sector map |
d <addr> [n] |
dump n words | u |
unlock flash |
h g |
halt, resume | e <sect|addr> |
erase a sector |
t q |
reset and halt, reset and run | p <addr> <val> |
program a word |
n [n] m [n] |
step, step with interrupts masked | z v |
mass erase, drop protection |
x [reg] [val] |
core registers | o |
option bytes and protection |
l <addr> |
load a binary over XMODEM | y <addr> <len> |
save memory to a file |
rtt [base] [len] |
stream target output | force |
run flash on an unrecognised part |
tools/swdflash <file.bin> [addr] # what `make target-flash` runs
tools/swdmon [seconds] # dump whatever the board sends
tools/gdbserver [port] # RSP server, defaults to 3333PORT sets the serial device (default /dev/ttyACM0), DEBUG=1 logs every gdb packet.
Both swdflash and gdbserver fall back to cr when a plain connect or reset fails, so a
target whose firmware has taken the SWD pins is still reachable.
info registers, memory access (x, set {int}...), break/hbreak, watch/rwatch/
awatch with a length, stepi, continue, Ctrl-C, load, detach all work. break
on flash uses a hardware breakpoint automatically; RAM needs hbreak since software
breakpoints aren't supported. Details and caveats in docs/NOTES.md.
| Used | Of | |
|---|---|---|
| Flash | 20558 B | 32 KB |
| SRAM | 339 B | 2 KB |
- Tested against an STM32F411 (Black Pill). SWD, DP, AP and Cortex-M are architectural and
should hold for any Cortex-M, but
flash.cis written to the F4 flash controller only. - The device id from DBGMCU gates the flash commands, so an unrecognised part is reported
and refused rather than risked.
forceoverrides that, for porting. - One target, one AP, no multidrop and no JTAG.
- SWD only, at whatever rate the bit-bang loop manages: around 4.4KB/s for bulk writes.
- RDP level 2 is not reachable from here, on purpose.
src/ include/ AVR firmware
target/ a small STM32 blinky, and steal.c, which takes the SWD pins to test `cr`
tools/swdflash host side flasher: drives the shell, sends the image
tools/swdmon dumps whatever the board sends
tools/gdbserver GDB remote serial protocol, bridged onto the shell
tools/test_*.py tests for the above, no hardware needed
docs/NOTES.md engineering notes: every quirk, bug, and why behind the code
hardware/kicad/ schematic and PCB for the wiring
.github/ builds both toolchains, runs the tests, checks it still fits
- ARM Debug Interface v5 (ARM IHI 0031A). DP and AP registers, packet format, ACK
encoding, parity, turnaround, CSW. Predates the JTAG-to-SWD sequence, so
0xE79Eis not in it. - ARMv7-M Architecture Reference Manual (ARM DDI 0403). DHCSR, DEMCR, AIRCR, the
0xA05Fkey, DCRSR and DCRDR, FPB and DWT. - STM32F411 reference manual (RM0383) and datasheet. Flash controller, option bytes, sector layout, PA13/PA14 defaults, 5V-tolerant pins, BOOT0.
- ATmega328P datasheet. DDR, PORT and PIN semantics, USART, U2X baud.

