Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jtag-finder

Find the JTAG and SWD pins on any board, using a €11 STM32 Nucleo.

Open-source firmware that turns an STM32G431RB Nucleo-64 into a JTAG / SWD pinout discoverer in the same spirit as Joe Grand's JTAGulator. Wire the mystery pads from a target PCB to a row of pins on the Nucleo, run SCAN ALL in a serial terminal, and the firmware brute-forces every JTAG / SWD pin combination until something replies with a valid IDCODE or DPIDR.


Quick start

git clone https://github.com/<you>/jtag-finder
cd jtag-finder
make -C firmware && make -C firmware flash      # build + st-flash
miniterm /dev/ttyACM0 115200                    # any serial terminal

Then in the terminal:

> PINS 4
OK pins=4
> SCAN ALL
JTAG TCK=PB0 TMS=PB1 TDI=PB2 TDO=PB3 IDCODE=0x4BA00477 MFG=ARM
JTAG_DONE hits=1
SWD  SWCLK=PB0 SWDIO=PB1 DPIDR=0x2BA01477 MFG=ARM
SWD_DONE hits=1

That's the whole workflow. Wire 4-8 pads to PB0..PB7 on the Nucleo, set PINS to the number of pads, run SCAN, read the hits.


Why

Joe Grand's JTAGulator pioneered this — a small standalone box that brute- forces every plausible JTAG / SWD pin assignment until a device responds. It costs ~$199, has closed firmware, and is the right answer if you need 24 channels and UART discovery.

jtag-finder is the cheap, open-source, 8-channel, JTAG+SWD-only alternative for hobbyists, students, CTF players, and quick PCB investigations.

Feature JTAGulator jtag-finder
Channels 24 8
JTAG IDCODE scan yes yes
SWD DPIDR scan yes yes
UART pin discovery yes no (planned)
Adjustable target voltage yes (1.4-5.5 V) no (3.3 V only)
Firmware closed MIT, ~5.9 KB
Hardware cost ~$199 ~€11 Nucleo + jumper wires

If you need any of the JTAGulator-only rows above, buy the JTAGulator. If you don't, build this in 20 minutes.


What you need

  • STM32G431RB Nucleo-64 board (USB Type-C connector).
  • A handful of female-to-female jumper wires (or grabber leads) to reach the target's pads.
  • A common GND between the Nucleo and the target. USB ground usually suffices.
  • A 3.3 V tolerant target. This firmware does not level-shift.
  • A serial terminal on the host (miniterm, picocom, PuTTY, screen).

Toolchain to build:

  • arm-none-eabi-gcc (13.x tested)
  • arm-none-eabi-objcopy, arm-none-eabi-nm, arm-none-eabi-readelf
  • stlink-tools (st-flash binary)
  • GNU Make
  • Python 3.10+ with pyserial for the test suite

Commands

All commands end with \r; replies are line-based ASCII.

Command Description
V Print firmware version
PINS <n> Set the number of active scan pins (1-8)
SCAN JTAG Try every (TCK, TMS, TDI, TDO) permutation
SCAN SWD Try every (SWCLK, SWDIO) ordered pair
SCAN ALL JTAG first, then SWD
TRY JTAG a b c d Try a single tuple (debugging)
TRY SWD c d Try a single pair (debugging)
VALIDATE IDCODE 0x... Run the matcher on a hex value, no GPIO involved
VALIDATE DPIDR 0x... Same for DPIDR
FAKE START [0xhex] Turn on the internal fake TAP (self-test)
FAKE STOP Turn it off

A match looks like this:

JTAG TCK=PB0 TMS=PB1 TDI=PB2 TDO=PB3 IDCODE=0x4BA00477 MFG=ARM
SWD  SWCLK=PB0 SWDIO=PB1 DPIDR=0x2BA01477 MFG=ARM

MFG=… is decoded from the JEP106 7-bit manufacturer code in the IDCODE / DPIDR field. Thirteen vendors are recognised (ARM, ST, NXP, Atmel, Xilinx, Cypress, Intel, Microchip, TI, ON Semi, Silicon Labs, Maxim, Toshiba). Floating-pin readings that happen to have bit 0 set but land on an unknown JEDEC byte get rejected — that's the cheapest false-positive filter.


Pinout

Default scan pins are PB0..PB3 (PINS 4). Extend to PB0..PB7 with PINS 8 for 1680 JTAG permutations (~30 s scan).

                STM32G431RB Nucleo-64

         CN7 (left)              CN10 (right)
         ┌─────────────┐         ┌─────────────┐
   pin 8 │ GND         │   pin 9 │ GND         │
   pin 11│ (PB7)       │   pin 11│ PB7         │ <- scan #7
   pin 13│ (PB6)       │   pin 13│ PB6         │ <- scan #6
   pin 15│ PB3         │  scan #3 │ ...        │
   pin 17│ (PB5)       │   pin 22│ PB2         │ <- scan #2
   pin 34│ PB0         │ scan #0  │ pin 24 PB1 │ <- scan #1
         └─────────────┘         └─────────────┘

For the fake-target self-test (no external target required), four wires loop the scanner back to the on-board fake TAP:

PB0  ──── PA8     TCK
PB1  ──── PA9     TMS
PB2  ──── PA10    TDI
PA11 ──── PB3     TDO     (PA11 drives, PB3 reads)

How it works

JTAG IDCODE discovery

For each ordered tuple (TCK, TMS, TDI, TDO):

  1. Drive TMS=1 for 5+ TCK cycles to force Test-Logic-Reset.
  2. Walk TMS through Run-Test/Idle → Select-DR → Capture-DR → Shift-DR.
  3. Clock 32 bits out of TDO. After reset, the IDCODE register is loaded into DR by default (per IEEE 1149.1).
  4. Validate: bit 0 must be 1; bits [11:1] must decode to a known JEP106 manufacturer.

SWD DPIDR discovery

For each ordered (SWCLK, SWDIO) pair:

  1. ≥50 SWCLK pulses with SWDIO=1 → line reset.
  2. Send the JTAG-to-SWD switching sequence 0xE79E (LSB first).
  3. Another ≥50 SWCLK reset.
  4. Issue an SWD read of DP register 0 (DPIDR): start=1, APnDP=0, RnW=1, addr=00, parity=1, stop=0, park=1 → request byte 0xA5.
  5. Sample 3-bit ACK; expect 0b001 (OK).
  6. Read 32 data bits + 1 parity bit, validate.

Self-test

EXTI line 8 fires on every rising edge of PA8. The ISR samples PA9 (TMS), advances a 16-state TAP state machine, and in Shift-DR shifts the configured IDCODE out on PA11. With four jumper wires, the same Nucleo acts as both scanner and target.

Why no DMA, no HAL

The whole thing is ~5.9 KB and reads register-for-register from RM0440. Bit-banged GPIO is fast enough — the protocol-level limit is target response time, not host CPU speed.


Limitations (be honest)

  • 3.3 V only. Targets running below ~2.5 V may not register a logic high; targets running above 3.6 V will damage the Nucleo.
  • Single-tap chains only. Multi-device JTAG chains aren't decoded.
  • No UART discovery. Other tools (JTAGulator, glasgow) do that better.
  • No ARM or RISC-V boundary scan beyond IDCODE. Once you have the pinout, hand off to openocd.
  • Bit-banged. ~10 kHz TCK, ~50 kbps SWD. Plenty for discovery; not enough for live debugging.

Tests

70 host-side integration tests across 8 suites, run against real hardware (Nucleo on /dev/ttyACM0):

python3 host/run_all.py
Suite # Coverage
test_build.py 6 Binary size, ELF entry, SP, no libc
test_cmd.py 14 Every command + every error path
test_idcode.py 6 IDCODE/DPIDR matcher against real chips
test_validate_extensive.py 10 13 JEDEC vendors + hex edge cases + 50× stress
test_pins.py 11 PINS boundaries 0-9, TRY duplicates / out-of-range
test_adversarial.py 14 Prefix isolation, RX burst, multi-cmd batch
test_stability.py 6 100×V, 50×VALIDATE, 20×FAKE, 10×SCAN+V
test_self_loopback.py 3 End-to-end fake-TAP self-test (needs 4 jumpers)

Master run completes in ~5 minutes. Each suite resets the board so order doesn't matter.


Roadmap

  • UART pin scan (autobaud + framing detection).
  • SWD AP scan after DPIDR — AP IDR, MEM-AP CSW, base addr, ROM table.
  • JSON output for tooling integration (openocd cfg generation).
  • Target Vcc sensing via ADC before driving pins.
  • More JEDEC manufacturers in the matcher.

PRs welcome. Run host/run_all.py before opening one — it must finish 70/70.


Acknowledgements

  • Joe Grand / Grand Idea Studio for JTAGulator, which set the bar for this category of tool.
  • OpenOCD / sigrok / glasgow for openly documenting the protocols this firmware speaks.

License

MIT — see LICENSE.

About

Open-source JTAG and SWD pinout discoverer for STM32G431RB Nucleo. Bare-metal C in 5.9 KB, 70 integration tests. JTAGulator-style brute-force IDCODE / DPIDR scan.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages