JTAG and SWD
- 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:
- 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.
- 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 — butmonitor helpon 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.
With st-link tools
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:
- Never set. The most common by a distance. Setting it is a manufacturing step that costs time and can brick units, so it gets skipped — or skipped on one production lot.
- Set, but reversible by mass erase. Useless if you want the firmware. Exactly what you want if you want to run your own code on the device.
- Set on the main core, absent on a second. Multi-core SoCs and radio co-processors frequently expose a debug port nobody remembered to lock.
- Set, but bypassable by fault injection. The protection check is logic that runs at boot. Glitch the supply rail or the clock at the right microsecond and the check can be skipped. This is the Project C final-project option.
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
- A debug port gives access to the silicon rather than to what firmware chooses to expose; when it is unlocked, nothing else on the board is necessary.
- JTAG was designed for boundary scan; debug was added later, which is why the interface is shaped the way it is.
- On Cortex-M parts you will nearly always meet SWD’s two wires rather than JTAG’s four.
- A valid IDCODE confirms a real port; all-zeros or all-ones means you have not found one. Bit 0 is always 1.
0x2BA01477identifies a Cortex-M3/M4 debug port, not a specific microcontroller — the part identity is in a vendor register.- Debug lockout most often fails by never having been enabled.
- RDP Level 2 is permanent. Never set it on hardware you want back.
References
- IEEE 1149.1, Test Access Port and Boundary-Scan Architecture — https://standards.ieee.org/ieee/1149.1/4484/
- Arm Debug Interface Architecture Specification (ADIv5) — https://developer.arm.com/documentation/ihi0031/latest/
- Arm CoreSight Architecture Specification — https://developer.arm.com/documentation/ihi0029/latest/
- OpenOCD User’s Guide, Flash Commands — https://openocd.org/doc/html/Flash-Commands.html
- Black Magic Probe documentation — https://black-magic.org/
- stlink tools — https://github.com/stlink-org/stlink
- JTAGulator — https://grandideastudio.com/portfolio/security/jtagulator/
- STM32F3 reference manual RM0316, option bytes and read protection — https://www.st.com/resource/en/reference_manual/rm0316-stm32f303xbcde-stm32f303x68-stm32f328x8-stm32f358xc-stm32f398xe-advanced-armbased-mcus-stmicroelectronics.pdf
Related course pages: Protocols · Schedule · UART · SPI · Dumping flash · Attacks · Final Project
🛠️ Maintenance note: OpenOCD’s interface config filenames change between releases —
interface/stlink.cfgwasinterface/stlink-v2.cfgnot 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.