courses

JTAG and SWD

Why this matters

UART gives you whatever the firmware chooses to print. SPI gives you what it says to its own peripherals. A debug port gives you the silicon, and that is a different category of access.

With working JTAG or SWD you can halt the CPU mid-instruction, read and write arbitrary memory including internal flash, single-step, set hardware breakpoints, and in most cases read out the entire firmware regardless of what the running code wants. When a debug port is available and unlocked, everything else on the board becomes unnecessary.

It is also the interface most likely to be silent. UART talks at boot; a debug port does nothing until you initiate. That silence is why it gets missed.

What JTAG actually was for

JTAG — IEEE 1149.1 — was not designed for debugging. It was designed for boundary scan: testing solder joints on assembled boards by shifting bits into a chain of cells placed around each pin, so a factory could verify connections without physical probes.

Debug access was bolted on afterwards, which is why the interface feels architecturally strange. You are not talking to a debugger; you are shifting bit patterns through a shift register that happens to be wired to debug logic.

The signals

Signal Purpose
TCK Test clock
TMS Test mode select — drives the state machine
TDI Test data in
TDO Test data out
TRST Test reset (optional, frequently absent)

Devices can be daisy-chained: TDO of one to TDI of the next, with TCK and TMS shared. That is why a scan may report several devices on one port.

The TAP state machine

The Test Access Port controller is a 16-state machine driven entirely by TMS, sampled on the rising edge of TCK. You do not need to memorise it, but two facts matter in practice:

  1. Five consecutive TMS-high clocks force Test-Logic-Reset from any state. This is the universal “I am lost, start over” sequence, and every tool begins with it.
  2. There are two symmetric paths: one for instruction register access (Shift-IR, choosing what the port does) and one for data register access (Shift-DR, moving data through it).

The practical shape is always: reset, shift an instruction, shift data, repeat.

SWD, and why you will usually meet it instead

Arm’s Serial Wire Debug replaces the four-wire interface with two, dropping boundary scan and keeping the debug half:

Signal Role
SWCLK Clock
SWDIO Bidirectional data

On virtually every Cortex-M device you will meet in this course, SWD is what is exposed. It is defined by the Arm Debug Interface Architecture Specification (ADIv5, with ADIv6 for newer parts).

Many parts implement SWJ-DP, which supports both and switches between them on a magic bit sequence — so a port that looks like JTAG may respond to SWD and vice versa. Tools try both.

Finding the pins

Unpopulated headers are the giveaway. Pin order is arbitrary and silkscreen labels are optional. Three approaches, in increasing order of effort:

1. Guess from the connector. Standard pinouts exist and cost nothing to try:

Connector Likely
10-pin 0.05” (1.27 mm) Arm Cortex Debug — SWD/JTAG, standard pinout
20-pin 0.1” Classic Arm JTAG
14-pin 0.1” Older Arm, or TI
4–5 loose pads near the SoC SWD broken out by hand

2. Brute force with a JTAGulator. The JTAGulator drives every permutation of candidate pins and watches for a valid response. It exists for exactly this and is very good at it. openocd’s swd_scan and similar tools do a narrower version.

3. Trace from the SoC. If you identified the part in hardware recon, its datasheet gives the debug pin numbers, and it becomes a continuity-tracing exercise from package to header.

The tell: IDCODE

Every compliant TAP returns a 32-bit IDCODE after reset. It is the confirmation that you have found a real port:

 31       28 27                12 11          1  0
[ version ][    part number    ][ manufacturer ][1]

Bit 0 is always 1. All-zeros or all-ones means you have not found a TAP — you are reading a floating pin or a pulled-down one.

For Arm Cortex-M3 and Cortex-M4 devices the SW-DP IDCODE is 0x2BA01477. Note carefully what that identifies: the debug port, not the specific microcontroller. Every Cortex-M4 from every vendor returns the same value. A student expecting a device-unique number and finding a generic Arm one has not failed — the part identity lives elsewhere, in a vendor-specific register (on STM32, DBGMCU_IDCODE at 0xE0042000).

Connecting

With a Black Magic Probe

The Black Magic Probe runs a GDB server on the probe itself, so there is no OpenOCD process in between:

$ arm-none-eabi-gdb firmware.elf
(gdb) target extended-remote /dev/ttyACM0
(gdb) monitor swd_scan
Target voltage: 3.3V
Available Targets:
No. Att Driver
 1      STM32F3 M4
(gdb) attach 1
(gdb) info registers

monitor jtag_scan does the same for JTAG targets.

🛠️ If a tutorial says monitor swdp_scan, it predates the rename. Current Black Magic firmware registers that spelling only as “Deprecated: use swd_scan instead”. It still works, so nothing breaks today — but monitor help on the probe in front of you is the authority, because these command names have changed before.

With OpenOCD

More configuration, far broader hardware support:

$ openocd -f interface/stlink.cfg -f target/stm32f3x.cfg
Info : clock speed 1000 kHz
Info : STLINK V2J27S15 (API v2) VID:PID 0483:374B
Info : Target voltage: 2.905595
Info : [stm32f3x.cpu] Cortex-M4 r0p1 processor detected

Then from a second terminal, or via -c:

$ openocd -f interface/stlink.cfg -f target/stm32f3x.cfg \
    -c init -c "reset halt" \
    -c "flash read_bank 0 firmware.bin 0 0x40000" \
    -c reset -c shutdown

The target config file has to match the part — stm32f3x.cfg for an STM32F3, stm32f4x.cfg for an F4, and so on. Using the wrong one usually fails at flash-bank probing rather than at connect, which makes the error message point at the wrong thing.

flash read_bank <bank> <file> <offset> <length> is the command that dumps internal flash. dump_image <file> <address> <size> reads an arbitrary address range instead, which is what you want for SRAM or peripheral registers.

For STM32 specifically, simplest of all. Note that st-info reports the flash and SRAM sizes it read off the device, which is a free cross-check on whatever the boot log told you:

$ st-info --probe
Found 1 stlink programmers
  version:    V2J27S15
  serial:     0000000000000000000000
  flash:      262144 (pagesize: 2048)
  sram:       40960
  chipid:     0x422
  dev-type:   STM32F302_F303_358

$ st-flash read firmware.bin 0x08000000 0x40000

Lockout, and how it fails

Vendors can disable debug access. On most microcontroller families this is a fuse, a lock bit, or a protection level in one-time-programmable memory. The STM32 mechanism is representative:

Level Effect Reversible?
RDP 0 No protection. The default —
RDP 1 Flash unreadable via debug Yes — but reverting triggers a mass erase
RDP 2 Debug interface permanently disabled No. Irreversible.

⚠️ RDP Level 2 is a one-way door. ST cannot undo it, you cannot undo it, and the board can never be reflashed or debugged again. Never set it on hardware you want back. Level 1 gives you a legitimately locked target for teaching and is recoverable.

Real-world failure patterns, roughly in order of frequency:

Worked example: a locked port, diagnosed

$ st-info --probe
Found 1 stlink programmers
  chipid:     0x422
  dev-type:   STM32F302_F303_358

$ st-flash read dump.bin 0x08000000 0x40000
st-flash 1.8.0
2026-09-03T14:22:01 INFO common.c: STM32F302_F303_358: 40 KiB SRAM, 256 KiB flash
2026-09-03T14:22:01 ERROR common.c: Failed to read memory at 0x08000000

Read that carefully, because the distinction matters: the probe connected and identified the part, then the flash read failed. That is not a wiring problem and not a missing port — it is read protection doing its job. RDP Level 1 or 2 is set.

A wiring problem looks different: st-info --probe finds no target at all, or reports chipid: 0x0000.

Having distinguished the two, the follow-up is to read the option bytes to see which level, and then decide whether a mass erase (to run your own code) or fault injection (to recover the firmware) is the appropriate next step.

Key takeaways

References


Related course pages: Protocols · Schedule · UART · SPI · Dumping flash · Attacks · Final Project

🛠️ Maintenance note: OpenOCD’s interface config filenames change between releases — interface/stlink.cfg was interface/stlink-v2.cfg not long ago — so verify the exact invocation against the installed version before demonstrating it. ADIv6 is current for newer Arm parts but ADIv5 still describes everything in the course kit. The RDP warning must stay prominent wherever this material is taught.