Use Raspberry Pi (2/3) as a JTAG/SWD adapter

Did you know that we can use the Raspberry Pi GPIO to bitbang the JTAG/SWD protocols? We can actually use our Raspberry Pi as a JTAG adapter for programming microcontrollers, FPGAs, or another Raspberry Pi! JTAG is a protocol similar to SPI, but works a little different. It’s commonly used for board bring-up, debugging, and programming in bare-metal environments. It’s incredibly useful because you have direct low-level access to hardware and peripherals, which allows things like reading and modifying CPU registers or reading/writing memory.

SWD is a newer technology developed around Cortex-based processors which boasts a reduced pin-count vs JTAG (2 vs 4) and can actually be used over JTAG pins if the device supports it. While SWD doesn’t support things like boundary-scan, it’s perfectly suited for debugging and bare-metal programming. Let’s get started!


We’ll need a couple of parts to get going:

  • Raspberry Pi (2/3) – I will be using the Pi 3 for my example
  • Rasbpian Jessie with latest firmware (I’m partial to Debian, sorry Red Hat guys)
  • Female to female jumper wires (3 or 4 for SWD, and 5-7 for JTAG, more information on this later)
  • A solid 2.5A 5V power supply (this is important always)

Let’s get our repositories and packages up to date:

sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get dist-upgrade -y

Once we’re updated, we’ll need to install some dependencies for OpenOCD:

sudo apt-get install git autoconf libtool make pkg-config libusb-1.0-0 libusb-1.0-0-dev telnet

This should provide all the pieces of the puzzle we need to compile OpenOCD. Next we’ll need to pull the latest source for OpenOCD so we can compile it:

git clone git://git.code.sf.net/p/openocd/code openocd

This will clone the OpenOCD repository to a directory called ‘openocd’ in your current working directory. Next we’ll need to enter this directory, and start the process of compiling from source:

cd openocd/
./bootstrap

During this process, the scripts will download JimTCL and other dependencies for OpenOCD. Once that’s complete, we’ll move on to configuring our options for OpenOCD:

./configure --enable-maintainer-mode --enable-bcm2835gpio --enable-sysfsgpio

If all goes successfully, we’re finally ready to compile:

make -j4

We will use ‘make -j4’ for the Pi 2/3 because of the four cores. This will improve the time it takes to compile OpenOCD by distributing the workload across all four cores (it’ll take a little while, so get comfortable.)

If we’ve finished without errors, our next step is to finally install it:

sudo make install

At this point, we have OpenOCD configured properly for our GPIO bitbanging on the Pi, in addition to compiled binaries and scripts that are now installed. Let’s take a side-step for a moment here and take a look at the pins we’ll need:


JTAG PINOUT:

FUNC GPIO PIN #
TCK GPIO11 23
TMS GPIO25 22
TDI GPIO10 19
TDO GPIO9 21
SRST* GPIO18 12
TRST* GPIO7 26
GND GND 20

* NOTE: Pins are optional, I recommend using SRST and ignoring TRST. The reasons why are out of the scope of this tutorial.

SWD PINOUT:

FUNC GPIO PIN
SWCLK GPIO25 22
SWDIO GPIO24 18
SRST GPIO18 12
GND GND 14

You’ll need to identify the pins on your target and connect them to your Raspberry Pi according to the charts above. Because target configurations are independent, you’ll need to find your configuration file for your target device in ‘/usr/local/share/openocd/scripts/target/’

We’ll need to make some quick modifications to the Raspberry Pi 2/3 interface configuration file depending on which protocol we will be using. Here are my examples:

JTAG:

#
# Config for using Raspberry Pi's expansion header
#
# This is best used with a fast enough buffer but also
# is suitable for direct connection if the target voltage
# matches RPi's 3.3V and the cable is short enough.
#
# Do not forget the GND connection, pin 6 of the expansion header.
#

interface bcm2835gpio

bcm2835gpio_peripheral_base 0x3F000000

# Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET
# These depend on system clock, calibrated for stock 700MHz
# bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET
bcm2835gpio_speed_coeffs 146203 36

# Each of the JTAG lines need a gpio number set: tck tms tdi tdo
# Header pin numbers: 23 22 19 21
bcm2835gpio_jtag_nums 11 25 10 9

# or if you have both connected,
# reset_config trst_and_srst srst_push_pull

# Each of the SWD lines need a gpio number set: swclk swdio
# Header pin numbers: 22 18
# bcm2835gpio_swd_nums 25 24

# If you define trst or srst, use appropriate reset_config
# Header pin numbers: TRST - 26, SRST - 12

# bcm2835gpio_trst_num 7
# reset_config trst_only

bcm2835gpio_srst_num 18
reset_config srst_only srst_push_pull

# or if you have both connected,
# reset_config trst_and_srst srst_push_pull

SWD:

#
# Config for using Raspberry Pi's expansion header
#
# This is best used with a fast enough buffer but also
# is suitable for direct connection if the target voltage
# matches RPi's 3.3V and the cable is short enough.
#
# Do not forget the GND connection, pin 6 of the expansion header.
#

interface bcm2835gpio

bcm2835gpio_peripheral_base 0x3F000000

# Transition delay calculation: SPEED_COEFF/khz - SPEED_OFFSET
# These depend on system clock, calibrated for stock 700MHz
# bcm2835gpio_speed SPEED_COEFF SPEED_OFFSET
bcm2835gpio_speed_coeffs 146203 36

# Each of the JTAG lines need a gpio number set: tck tms tdi tdo
# Header pin numbers: 23 22 19 21
# bcm2835gpio_jtag_nums 11 25 10 9

# or if you have both connected,
# reset_config trst_and_srst srst_push_pull

# Each of the SWD lines need a gpio number set: swclk swdio
# Header pin numbers: 22 18
bcm2835gpio_swd_nums 25 24

# If you define trst or srst, use appropriate reset_config
# Header pin numbers: TRST - 26, SRST - 12

# bcm2835gpio_trst_num 7
# reset_config trst_only

bcm2835gpio_srst_num 18
reset_config srst_only srst_push_pull

# or if you have both connected,
# reset_config trst_and_srst srst_push_pull

You can save either of these files in your home directory (/home/pi/) and name them whatever you want, I chose rpi_swd.cfg and rpi_jtag.cfg (Remember, you’ll need to be in the directory where your configuration files are when you execute OpenOCD, at least to make it easy.)

While OpenOCD has this script already, you may have problems if you need to modify it because of permissions. It’s easier to modify it locally if you need to. I also noticed an error in the RPi2 interface configuration they provide; they state SRST is pin 18/GPIO24, but it’s actually pin 12/GPIO18.

Now that our preparation is done, we can start debugging. Remember to find the target configuration file for your device first. Here’s what I would do for my STM32L476RG:

sudo openocd -f rpi_jtag.cfg -f target/stm32l4x.cfg

This launches our OpenOCD/gdb server which we’ll need to connect to using telnet. You can use netcat or the telnet client, I personally prefer the telnet client. We’ll open a new terminal window or SSH connection on the Pi and run this command to connect to OpenOCD:

telnet localhost 4444

That’s it! We can debug our devices using our Raspberry Pi and OpenOCD. If you have any questions or need further clarification, leave a comment.

20 Comments

  1. tatery

    Hi,
    This tutorial is really great, thank you so much for it.

    I have some issue with reset using RPi as SWD programmer. It seems like reset does not work or at least my MCU (SAM4E8C) does not perform reset.
    This is my configuration file:

    source [find rpi_swd.cfg]
    transport select swd
    set CHIPNAME AT91SAM4E8C
    source [find target/at91sam4XXX.cfg]
    set _FLASHNAME $_CHIPNAME.flash
    flash bank $_FLASHNAME at91sam4 0x00400000 0 1 1 $_TARGETNAME
    init
    targets
    reset halt
    program firmware.bin verify reset 0x410000

    log from execution:
    sudo openocd
    Open On-Chip Debugger 0.10.0-dev-00371-g81631e4 (2016-08-29-12:46)
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    BCM2835 GPIO nums: swclk = 25, swdio = 24
    BCM2835 GPIO config: srst = 18
    srst_only separate srst_gates_jtag srst_push_pull connect_deassert_srst
    adapter speed: 100 kHz
    adapter_nsrst_delay: 100
    cortex_m reset_config sysresetreq
    Info : BCM2835 GPIO JTAG/SWD bitbang driver
    Info : SWD only mode enabled (specify tck, tms, tdi and tdo gpios to add JTAG mode)
    Info : clock speed 100 kHz
    Info : SWD DPIDR 0x2ba01477
    Info : AT91SAM4E8C.cpu: hardware has 6 breakpoints, 4 watchpoints
    TargetName Type Endian TapName State
    — —————— ———- —— —————— ————
    0* AT91SAM4E8C.cpu cortex_m little AT91SAM4E8C.cpu running
    AT91SAM4E8C.cpu: target state: halted
    target halted due to debug-request, current mode: Thread
    xPSR: 0x21000000 pc: 0x00442aae psp: 0x20007b28
    AT91SAM4E8C.cpu: target state: halted
    target halted due to debug-request, current mode: Thread
    xPSR: 0x21000000 pc: 0x00442aae psp: 0x20007b28
    ** Programming Started **
    auto erase enabled
    Info : sam4 does not auto-erase while programming (Erasing relevant sectors)
    Info : sam4 First: 0x00000008 Last: 0x00000026
    Info : Erasing sector: 0x00000008
    Info : Erasing sector: 0x00000009
    Info : Erasing sector: 0x0000000a
    Info : Erasing sector: 0x0000000b
    Info : Erasing sector: 0x0000000c
    Info : Erasing sector: 0x0000000d
    Info : Erasing sector: 0x0000000e
    Info : Erasing sector: 0x0000000f
    Info : Erasing sector: 0x00000010
    Info : Erasing sector: 0x00000011
    Info : Erasing sector: 0x00000012
    Info : Erasing sector: 0x00000013
    Info : Erasing sector: 0x00000014
    Info : Erasing sector: 0x00000015
    Info : Erasing sector: 0x00000016
    Info : Erasing sector: 0x00000017
    Info : Erasing sector: 0x00000018
    Info : Erasing sector: 0x00000019
    Info : Erasing sector: 0x0000001a
    Info : Erasing sector: 0x0000001b
    Info : Erasing sector: 0x0000001c
    Info : Erasing sector: 0x0000001d
    Info : Erasing sector: 0x0000001e
    Info : Erasing sector: 0x0000001f
    Info : Erasing sector: 0x00000020
    Info : Erasing sector: 0x00000021
    Info : Erasing sector: 0x00000022
    Info : Erasing sector: 0x00000023
    Info : Erasing sector: 0x00000024
    Info : Erasing sector: 0x00000025
    Info : Erasing sector: 0x00000026
    wrote 253952 bytes from file firmware.bin in 20.702446s (11.979 KiB/s)
    ** Programming Finished **
    ** Verify Started **
    verified 248528 bytes in 3.864183s (62.808 KiB/s)
    ** Verified OK **
    ** Resetting Target **

    When I run openocd second time (after manual MCU reset) I’ve got this:
    sudo openocd
    Open On-Chip Debugger 0.10.0-dev-00371-g81631e4 (2016-08-29-12:46)
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    BCM2835 GPIO nums: swclk = 25, swdio = 24
    BCM2835 GPIO config: srst = 18
    srst_only separate srst_gates_jtag srst_push_pull connect_deassert_srst
    adapter speed: 100 kHz
    adapter_nsrst_delay: 100
    cortex_m reset_config sysresetreq
    adapter_nsrst_delay: 100
    adapter_nsrst_assert_width: 100
    Info : BCM2835 GPIO JTAG/SWD bitbang driver
    Info : SWD only mode enabled (specify tck, tms, tdi and tdo gpios to add JTAG mode)
    Info : clock speed 100 kHz
    Info : SWD DPIDR 0x2ba01477
    Info : AT91SAM4E8C.cpu: hardware has 6 breakpoints, 4 watchpoints
    TargetName Type Endian TapName State
    — —————— ———- —— —————— ————
    0* AT91SAM4E8C.cpu cortex_m little AT91SAM4E8C.cpu running
    Error: timed out while waiting for target halted
    TARGET: AT91SAM4E8C.cpu – Not halted
    in procedure ‘reset’ called at file “openocd.cfg”, line 21
    in procedure ‘ocd_bouncer’
    Info : Halt timed out, wake up GDB.

    Do you have any idea what is wrong with my configuration?

    • ryan

      Judging from the second part of the log, it looks like a possible issue with the SRST line, whether it’s on the target or the Pi I’m not sure. How are you powering the Pi and the target device? I’ll investigate the configurations to ensure consistency on my end.

      EDIT: What is your reset configuration?

  2. tatery

    Hi again,

    When i was able to program MCU only once and all next tries failed I decided to use jtag mode but unfortunately I have more errors than for SDW.

    My openocd.cfg configuration:

    source [find rpi_jtag.cfg]
    transport select jtag
    set CHIPNAME AT91SAM4E8C
    source [find target/at91sam4XXX.cfg]
    set _FLASHNAME $_CHIPNAME.flash
    flash bank $_FLASHNAME at91sam4 0x00400000 0 1 1 $_TARGETNAME
    reset_config srst_only
    init
    targets
    reset halt

    console ouptut:

    sudo openocd
    Open On-Chip Debugger 0.10.0-dev-00371-g81631e4 (2016-08-29-12:46)
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    BCM2835 GPIO config: tck = 11, tms = 25, tdi = 10, tdo = 9
    BCM2835 GPIO config: srst = 18
    srst_only separate srst_gates_jtag srst_push_pull connect_deassert_srst
    adapter speed: 4000 kHz
    adapter_nsrst_delay: 100
    jtag_ntrst_delay: 100
    cortex_m reset_config sysresetreq
    srst_only separate srst_gates_jtag srst_push_pull connect_deassert_srst
    Info : BCM2835 GPIO JTAG/SWD bitbang driver
    Info : JTAG only mode enabled (specify swclk and swdio gpio to add SWD mode)
    Info : clock speed 4061 kHz
    Error: JTAG scan chain interrogation failed: all zeroes
    Error: Check JTAG interface, timings, target power, etc.
    Error: Trying to use configured scan chain anyway…
    Error: AT91SAM4E8C.cpu: IR capture error; saw 0x00 not 0x01
    Warn : Bypassing JTAG setup events due to errors
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Could not initialize the debug port
    TargetName Type Endian TapName State
    — —————— ———- —— —————— ————
    0* AT91SAM4E8C.cpu cortex_m little AT91SAM4E8C.cpu unknown
    Error: JTAG scan chain interrogation failed: all zeroes
    Error: Check JTAG interface, timings, target power, etc.
    Error: Trying to use configured scan chain anyway…
    Error: AT91SAM4E8C.cpu: IR capture error; saw 0x00 not 0x01
    Warn : Bypassing JTAG setup events due to errors
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Invalid ACK (0) in DAP response
    Error: Could not initialize the debug port

    • ryan

      Investigating this, but double check your pinout and connection. The scan chain is all 0s so my guess would be either a power issue or pinout issue. There’s some differences in the configurations for the original Pi and Pi2/3 for OpenOCD adapter configurations. I’ll keep you updated.

  3. tatery

    Finally both SWD and JTAG configurations work. They have started working after I added following lines to my openocd.cfg file:

    reset_config srst_only
    adapter_khz to 250

    Thanks once again ryan for great tutorial.

    • ryan

      Interesting, glad you were able to solve the problem. Sounds like either the clock was too high, or your wires are a little too long.

  4. Fernando

    Hi there

    Thanks for this tutorial.

    Knowing what JTAG is but only seeing someone else use commercial off-the-shelf dedicated JTAG programmers before, I wonder, what kind of electric protection there is on the RasPi GPIO pins? in other words what is the potential of misconnecting something and frying the raspi or the target device?

    Just curious / cautious :)…
    😉
    FC
    PS: I have a Raspi 2 model B and a PiZero, if it makes any difference

    • ryan

      As far as I know, there’s no protection on the pins. They should be 3.3v tolerant, but there’s no telling what will happen if something were to be connected incorrectly. However, I’ve done this process dozens of times, even with incorrect connections (for debugging purposes) on numerous devices and have had zero issues. Most non-commercial JTAG programmers are simply FTDI chips like the FT2232H. Always double check your pinouts, but again, I haven’t had any issues on my Pi1, 2 or 3.

  5. George

    Great tutorial Ryan! Thank you so much.

    I was just wondering which would be the next steps to program a FPGA via JTAG after following this tutorial?

    Thank you!
    George

  6. sK

    Hey Ryan,

    Note: I am new to OpenOCD
    Could you explain why I might be getting an error saying “Error: Cannot find AICE Interface! Please check connection and permissions” when I use the command “sudo openocd -f andes_config.cfg -f target/nds32v3m.cfg”
    I am trying to work with Andes nds32 config file. Most of the settings for the Interface and the target are already present for the Andes uC.

    andes_config.cfg:
    ————————
    if { [info exists CHIPNAME] } {
    set _CHIPNAME $CHIPNAME
    } else {
    set _CHIPNAME nds32_v3m
    }

    if { [info exists _CPUTAPID] } {
    set _CPUTAPID $CPUTAPID
    } else {
    set _CPUTAPID 0x1000063d
    }

    if { [info exists _ENDIAN] } {
    set _ENDIAN $ENDIAN
    } else {
    set _ENDIAN little
    }

    source [find interface/nds32-aice.cfg]
    —————————————————–

    Output when I run the command:
    ———————————————
    Open On-Chip Debugger 0.10.0+dev-00224-g19f8f58c (2017-12-15-17:42)
    Licensed under GNU GPL v2
    For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
    Info : only one transport option; autoselect ‘aice_jtag’
    trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
    adapter speed: 24000 kHz
    nds32_v3m.cpu
    Error: Cannot find AICE Interface! Please check connection and permissions.
    ——————————————————————-

    Let me know if anyone of you have faced a similar problem.

    Thanks,
    sK

    • ryan

      sK,

      The problem appears to be OpenOCD attempting to use AICE as the transport, as opposed to the Pi’s JTAG/SWD pins. The problematic line is here ‘source [find interface/nds32-aice.cfg]’ as that configures the interface as AICE. To remedy this, remove that line in andes_config.cfg. We will modify your arguments to select the Pi GPIO to bit-bang JTAG instead of the AICE interface. It will look like this:
      sudo openocd -f pi_jtag.cfg -f andes_config.cfg -f target/nds32v3m.cfg

      You can simply copy the Pi JTAG configuration file I have posted on this tutorial and name it pi_jtag.cfg

  7. Derrick

    Hi! Ryan,
    Thank you for the tutorial. I wonder if you can show me an example on how to toggle a GPIO on raspberry pi as a slow clock, like 200kHz?

    Thanks,
    Derrick

  8. Dan

    Hi,

    I got a problem with an Cisco WLC 2504 that I got. The item arrived bricked (don’t worry, I got a full refund), and if I didn’t know better, the previous owner tried to tamper with the firmware of it, or powered off the unit mid “BIOS” update (I forget the technical name they give it).

    If you check inside the device, it does appear that they use a JTAG in the form of a 14 pin “Com” header. Seeing as how they also had the Linksys brand, which appears to use a similar design in their kernel where the Kernel and initrd etc are all packed into a single image, I wonder if there is a chance that they are using the same exact approach for the WLC.

    My question is if I could possibly use my Pi’s to guess the pin outs and memory locations so that I could possibly pull the current firmware off of the device, and verify it, and then maybe even push a corrected one back onto it.

    While I don’t know the procedures, I suspect it isn’t terribly difficult. So I turn to you guys to see if anyone has any advice, or wouldn’t mind holding my hand a bit while I experiment. I do have another identical unit to pull an image from, so hopefully someone is up for a challenge? 🙂

    Thanks,

    Dan

  9. Ahmed

    Thanks for the tutorial,

    By any chance, can I change the GPIO of the RaspberryPi for JTAG operation or not?

  10. Andrew

    Would it be possible to use a Zero/W for JTAG?
    I don’t see why not. I’ve tried an image intended for JTAG on my Zero W, but it’s only been tested on 1B+, 2, and 3B, and I believe my Zero W may be damaged, so it’s difficult to confirm.

    • Andrew

      I should mention that my issue with the JTAG image is a bootloop and with a stock image, it does not detect its own Wifi+BT chip.

Leave a Reply

Your email address will not be published. Required fields are marked *