Skip to content

Repository files navigation

ADIv5-AVR

build

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.

gdb attached over the AVR bridge: registers, a hardware breakpoint, and a real flash load

20.1KB flash · 339B RAM on the AVR. Halt, step, breakpoints, watchpoints, flash programming, RTT, and a GDB server.

Why

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.

Wiring

Wiring schematic: Arduino Uno to Black Pill, SWCLK and SWDIO with a 2.2k pull-up, optional NRST

  • 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 marked R on 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.

Quick start

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 hand

Or 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

Architecture

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
Loading
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.

Commands

Shell, over UART at 115200

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

Host tools

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 3333

PORT 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.

From gdb

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.

Footprint

Used Of
Flash 20558 B 32 KB
SRAM 339 B 2 KB

Scope

  • Tested against an STM32F411 (Black Pill). SWD, DP, AP and Cortex-M are architectural and should hold for any Cortex-M, but flash.c is 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. force overrides 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.

Layout

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

References

  • 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 0xE79E is not in it.
  • ARMv7-M Architecture Reference Manual (ARM DDI 0403). DHCSR, DEMCR, AIRCR, the 0xA05F key, 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.

License / MIT. See LICENSE.

About

An 8-bit AVR debugging an ARM Cortex-M4. Bit-banged SWD with hardware breakpoints, watchpoints, flash programming and RTT in 18KB. One resistor, four wires, no probe chip.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages