SWD and JTAG Debugging for ARM: Probes, Wiring, OpenOCD and GDB

Programming a chip through a serial bootloader is enough to get started, but it gives you no view inside the running program. A hardware debugger lets you stop the CPU at any line, step through code, read and change variables and registers, and see exactly which instruction caused a fault. ARM chips provide this through JTAG or Serial Wire Debug (SWD). This tutorial explains both interfaces, how to wire them, which probes to use, and how to debug from the command line with OpenOCD and GDB.

JTAG Versus SWD

JTAG (IEEE 1149.1) was originally designed for boundary-scan testing of circuit boards and was later used for CPU debugging. It needs four or five signals. ARM7 and ARM9 chips such as the LPC2148 debug only through JTAG. SWD is an ARM-defined two-wire protocol introduced with the CoreSight debug architecture. Almost all Cortex-M chips support SWD, and many support both, sharing the same pins.

SignalJTAGSWDFunction
ClockTCKSWCLKClock driven by the probe
Data / modeTMSSWDIOBidirectional data in SWD, mode select in JTAG
Data inTDINot usedSerial data into the chip
Data outTDOSWO (optional)Serial data out in JTAG; trace output in SWD
Test resetnTRST (optional)Not usedResets the debug logic
System resetnRESETnRESETLets the probe reset the target
ReferenceVTrefVTrefTarget I/O voltage, used by the probe for level shifting

SWD saves pins and is the default for new designs. JTAG is still needed for ARM7 parts, and for daisy-chaining several devices on one board for boundary-scan testing.

Connectors and Probes

Two connectors are common. The older 20-pin 0.1 inch ARM JTAG header is found on LPC2148 boards and works with ULINK2 and J-Link. The 10-pin 0.05 inch Cortex Debug connector is used on most modern Cortex-M boards.

10-pin Cortex Debug connector (0.05 inch pitch)

 VTref  1  2  SWDIO / TMS
 GND    3  4  SWCLK / TCK
 GND    5  6  SWO / TDO
 KEY    7  8  NC / TDI
 GNDDet 9 10  nRESET
ProbeInterfacesNotes
CMSIS-DAP / DAPLinkSWD, JTAGOpen standard, cheap, supported by OpenOCD, pyOCD and Keil
SEGGER J-LinkSWD, JTAGFast, supports ARM7 and Cortex-M, own GDB server software
Keil ULINK2SWD, JTAGIntegrates with Keil uVision, common in college labs
ST-LINKSWDBuilt into ST boards; officially intended for ST devices

Debugging with OpenOCD and GDB

OpenOCD talks to the probe and opens a GDB server on TCP port 3333. GDB from the GNU Arm toolchain connects to it. The example uses a CMSIS-DAP probe and an LPC1768; OpenOCD ships configuration files for many probes and targets in its interface and target folders.

Terminal 1:
openocd -f interface/cmsis-dap.cfg -c "transport select swd" -f target/lpc17xx.cfg

Terminal 2:
arm-none-eabi-gdb app.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) load                      # program flash
(gdb) break main
(gdb) continue
(gdb) next                      # step over one line
(gdb) step                      # step into a function
(gdb) print ms_ticks
(gdb) info registers
(gdb) watch error_count         # stop when the variable changes
(gdb) x/8xw 0x2007C000          # dump 8 words of memory
(gdb) backtrace

Build your program with -g so that GDB has line and variable information, and use -Og or -O0 while debugging; higher optimisation reorders code and removes variables, which makes stepping confusing. Hardware breakpoints are limited, typically between two and eight depending on the core, so GDB may refuse to add more breakpoints in flash.

Built-in Debug Helpers: DWT and ITM

Cortex-M3, M4 and M7 contain a Data Watchpoint and Trace unit (DWT) with a 32-bit cycle counter, and an Instrumentation Trace Macrocell (ITM) that can send printf-style text through the SWO pin with almost no CPU cost. The code below measures how many cycles a function takes and sends text over ITM channel 0.

debug_tools.c: DWT cycle counting and ITM text output using CMSIS
#include "LPC17xx.h"
#include <stdio.h>

static void cycle_counter_init(void)
{
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;  /* enable DWT and ITM */
    DWT->CYCCNT = 0;
    DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;            /* start counting */
}

static void itm_print(const char *s)
{
    while (*s)
        ITM_SendChar((uint32_t)*s++);   /* no-op if the debugger has not enabled ITM */
}

static volatile uint32_t sink;

static void work(void)
{
    for (uint32_t i = 0; i < 1000u; i++)
        sink += i * i;
}

int main(void)
{
    char msg[48];
    uint32_t start, cycles;

    SystemCoreClockUpdate();
    cycle_counter_init();

    while (1) {
        start  = DWT->CYCCNT;
        work();
        cycles = DWT->CYCCNT - start;    /* unsigned subtraction handles wrap */

        snprintf(msg, sizeof msg, "work: %lu cycles, %lu us\n",
                 (unsigned long)cycles,
                 (unsigned long)(cycles / (SystemCoreClock / 1000000u)));
        itm_print(msg);

        for (volatile uint32_t d = 0; d < 1000000u; d++)
            ;
    }
}

To see ITM output, enable SWO trace in your debugger with the correct core clock frequency, for example in Keil under Debug Settings, Trace, or with OpenOCD's tpiu or swo configuration commands. The DWT counter works without SWO; just read cycles in the watch window. Cortex-M0 and M0+ do not have the DWT cycle counter or ITM.

Troubleshooting Connection Problems

  • Probe reports no target or wrong voltage: VTref is not connected or the board is unpowered.
  • Connects once, then fails after flashing new code: the program reconfigured the SWD pins as GPIO or entered deep sleep. Connect under reset (hold nRESET while attaching) or use the chip's ISP mode to erase it.
  • Random communication errors: wires are too long or the SWD clock is too fast; lower the adapter speed, for example adapter speed 1000 in OpenOCD.
  • Breakpoints are skipped: code was built with optimisation and no debug information, or the ELF file in GDB does not match the code in flash.
  • LPC2148 with JTAG does not connect: the JTAG pins on Port 1 must be enabled for debugging; check the board's debug jumper and that the program does not reassign P1.26 to P1.31.

Summary

JTAG uses TCK, TMS, TDI and TDO and is required for ARM7 parts like the LPC2148, while SWD needs only SWDIO and SWCLK and is standard on Cortex-M. Connect VTref, GND, the signal lines and nRESET to a probe such as CMSIS-DAP or J-Link, run OpenOCD as a GDB server, and debug with breakpoints, stepping and watchpoints. On Cortex-M3 and above, the DWT cycle counter and ITM add precise timing and low-cost text output.