Firmware Development7 min read

Debugging Firmware Using JTAG and SWD

Lafiz Maruf Rahman
Lafiz Maruf Rahman
February 25, 2025
Debugging Firmware Using JTAG and SWD

The first time my firmware crashed with a hard fault, I had no idea what to do. There was no error message. No stack trace. The device just stopped working. I spent two days adding printf statements everywhere trying to find the problem.

Then I learned to use a hardware debugger. I found the bug in 10 minutes.

What JTAG and SWD actually are

JTAG and SWD are hardware interfaces that give you direct access to a microcontroller's internals while it is running. Through these interfaces, you can:

  • Pause the processor at any point
  • Step through code one instruction at a time
  • Read and write any memory address
  • Inspect all CPU registers
  • Set breakpoints that pause execution when a specific line of code runs
  • Set watchpoints that pause execution when a specific memory address is read or written

JTAG uses 4-5 pins. SWD uses just 2 pins (SWDIO and SWDCLK) and is the preferred interface for ARM Cortex-M microcontrollers. Most modern STM32, nRF52, and similar chips use SWD.

The hardware you need

You need a debug probe — a small device that connects between your PC and the microcontroller.

ST-Link is the most common for STM32 chips. It is built into every STM32 Nucleo and Discovery development board. You can also buy standalone ST-Link probes for a few dollars.

J-Link from SEGGER is the professional choice. It works with almost any ARM chip, is very fast, and has excellent software support. It is more expensive but worth it for serious work.

CMSIS-DAP is an open standard. Many development boards include a CMSIS-DAP compatible debugger.

Setting up with VS Code and Cortex-Debug

The Cortex-Debug extension for VS Code gives you a graphical debugging experience:

json
// .vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug (ST-Link)",
            "type": "cortex-debug",
            "request": "launch",
            "servertype": "openocd",
            "configFiles": ["interface/stlink.cfg", "target/stm32f4x.cfg"],
            "executable": "build/firmware.elf",
            "runToEntryPoint": "main"
        }
    ]
}

Press F5 and you are debugging. Set breakpoints by clicking in the gutter. Hover over variables to see their values. Step through code with F10 (step over) and F11 (step into).

Debugging a hard fault

Hard faults are the most common crash in firmware. They happen when you access invalid memory, divide by zero, or execute an invalid instruction.

When a hard fault occurs, the processor saves its state before crashing. You can read this to find exactly where the crash happened:

c
void HardFault_Handler(void) {
    // Get the stack pointer at the time of the fault
    uint32_t *stack;
    __asm volatile (
        "TST LR, #4\n"
        "ITE EQ\n"
        "MRSEQ %0, MSP\n"
        "MRSNE %0, PSP\n"
        : "=r" (stack)
    );
    
    // stack[6] is the PC (program counter) at the time of the fault
    uint32_t fault_pc = stack[6];
    
    // Log it or display it
    printf("Hard fault at PC: 0x%08lX\n", fault_pc);
    
    while (1); // halt
}

With a debugger attached, you can set a breakpoint in HardFault_Handler and inspect the stack to see exactly which line of code caused the crash.

Printf debugging still has its place

Hardware debugging is powerful, but sometimes you just want to print values over serial. This is called printf debugging and it is still useful, especially for timing-sensitive code where stopping the processor changes the behavior.

Invest in a good debug probe. The time you save on the first serious bug will pay for it many times over.

FirmwareJTAGSWDDebuggingEmbedded Systems

Ready to build something great?

Let's talk about your project. We will give you honest advice, a clear plan, and a fair price. No pressure, no sales pitch.

Free consultation
No commitment required
Response within 24 hours