Debugging

Debugging Firmware: JTAG, SWD, RTT, and Logic Analyzers

Every firmware engineer's debugging journey follows the same arc. You start with Serial.println(). You hit a bug where serial itself is broken (interrupts, timing, no hardware UART left). You graduate to a logic analyzer to look at the actual pins. Eventually somebody hands you a J-Link or ST-Link and you discover what real debugging feels like, and from that day on you cannot believe you spent weeks bisecting bugs by adding print statements.

This article is a tour of the toolchain. Each tool is described with what it actually does, what it costs, and when it is worth the setup time. The shortest path: skip ahead to SWD, buy a $5 ST-Link clone, and never go back.

The debugging hierarchy

flowchart TD Bug([Firmware misbehaving]) --> Q1{Can you
reproduce?} Q1 -->|No, intermittent| Logger[Persistent logging
RTT or flash] Q1 -->|Yes| Q2{Need to see
signal timing?} Q2 -->|Yes, electrical| LA[Logic analyzer
Saleae, DSLogic] Q2 -->|No, code logic| Q3{Compiler
support?} Q3 -->|ARM Cortex-M| SWD[SWD debugger
ST-Link, J-Link] Q3 -->|Other arch| JTAG[JTAG debugger] LA --> Solved([Bug found]) SWD --> Solved JTAG --> Solved Logger --> Solved style SWD fill:#dbeafe,stroke:#1e40af,color:#0c1e3b

Most bugs in 2026 are best attacked with SWD on Cortex-M. Logic analyzers are reserved for problems that involve actual electrical timing or protocol decode.

Level 0: printf and its limits

Printing values over UART has been the default firmware debugging technique since the 1970s. It works for surprisingly long. The limitations only become obvious when you hit one of three walls.

First, timing-sensitive code breaks under printf. A 9600-baud UART takes about 1 ms per character. Adding Serial.println() inside an interrupt handler that is supposed to run in microseconds destroys the timing. The bug you were debugging disappears, but only because the print itself perturbed the system enough to mask it.

Second, you eventually run out of UARTs. The Uno has one. The ESP32 has three but two are usually used for something. The chip you are debugging may have its UART pins shared with the peripheral you are trying to debug.

Third, print debugging cannot inspect anything that is not actively printed. Want to see the value of a struct member at the moment a crash happens? You have to predict in advance which fields might matter, add prints, recompile, reflash, and try to reproduce.

Level 1: SEGGER RTT (Real-Time Transfer)

RTT is a clever trick. The chip has a small ring buffer in RAM at a known location. The debugger reads that buffer over the SWD wire while the chip continues running. From the firmware side, RTT looks identical to printf but adds zero overhead — no UART, no interrupts, no timing perturbation.

RTT requires a SEGGER J-Link probe (J-Link EDU at $60 is the entry point) or a J-Link compatible firmware on a clone. Software is free. Setup is essentially:

#include "SEGGER_RTT.h"

void loop() {
  uint32_t t = millis();
  SEGGER_RTT_printf(0, "t=%u\n", t);  // zero-overhead print
}

The output appears in J-Link RTT Viewer in real time. We have run RTT inside hard real-time control loops at 10 kHz with no measurable timing impact. It is the answer for "I want printf-style logging but my chip is too tightly timed for UART".

Level 2: SWD (Serial Wire Debug)

SWD is ARM's two-wire debug protocol that replaces JTAG on Cortex-M and most modern Cortex-A chips. Two pins (SWDIO, SWCLK) plus power and ground. With a debugger attached, you get everything a desktop debugger gives you: breakpoints, single-step, variable inspection, memory inspection, register inspection, hardware watchpoints, and the ability to halt the chip mid-execution.

The cheapest way in: an ST-Link V2 clone from AliExpress for under $5. It works with STM32 chips natively and can flash other ARM chips with minor configuration. For better stability, the genuine ST-Link/V3 ($25), DAPLink-based probes (built into Nucleo, Discovery boards), or J-Link EDU/Mini ($20-60) are step-ups.

The toolchain on the host:

  • OpenOCD — open-source debug interface to most ARM chips. Daemon that exposes a GDB server.
  • arm-none-eabi-gdb — the GNU debugger. Connects to OpenOCD's GDB server.
  • VS Code with Cortex-Debug extension — modern visual frontend over GDB. Breakpoints with mouse clicks, variable hover, memory view, peripheral register view.

Once configured, you set a breakpoint by clicking next to a line, hit reset, and the chip stops at that line with the full call stack and every variable inspectable. The first time you do this you will close every Serial.println tab you have open.

Level 3: JTAG

JTAG is the older standard SWD evolved from. Five pins: TCK, TMS, TDI, TDO, TRST. Used on chips that pre-date SWD and on multi-chip systems where you want a single debug chain across multiple devices (the J in JTAG stands for Joint Test Action Group).

For ARM Cortex-M development in 2026, you do not need JTAG. SWD does everything JTAG does for those chips, in fewer wires. JTAG is still relevant for FPGAs, older ARM7/ARM9 cores, RISC-V boards (some), and chips like the Raspberry Pi 4 and 5.

The same J-Link or ST-Link probes that do SWD also do JTAG with a different cable.

Level 4: Logic analyzers

A logic analyzer captures the digital state of multiple pins simultaneously and displays them as waveforms over time. Where SWD lets you debug code, a logic analyzer lets you debug signals. They solve different problems and you eventually want both.

Use cases that scream "use a logic analyzer":

  • I2C bus locking up. The address byte is going out fine but the device never responds. A logic analyzer shows you whether the device acknowledged, whether the timing meets spec, whether SDA was actually released.
  • SPI returning garbage. Wrong clock polarity, wrong clock phase, wrong endianness. A logic analyzer's protocol decoder tells you exactly what the bus saw.
  • UART framing errors. Wrong baud rate, missing stop bit. The waveform makes the problem obvious in three seconds.
  • Interrupt timing. ISR taking longer than expected? Toggle a GPIO at ISR entry and exit; the analyzer shows you the duration.

Hardware options:

  • Saleae Logic 8 (Pro 8) — $400, 8 channels, 100 MS/s, exceptional software, gold standard.
  • Saleae Logic clones — $10 on AliExpress. Hardware is FX2-based, works with Sigrok PulseView. Limited to 8 channels at 24 MS/s but covers 90% of hobby debugging.
  • DSLogic Plus — $150, 16 channels, 400 MS/s. Solid mid-range.
  • Built into modern oscilloscopes — mixed-signal scopes give you both at once.

Practical setup: VS Code + Cortex-Debug + ST-Link

Roughly five-minute setup for a typical STM32 project:

  1. Install VS Code, Cortex-Debug extension, arm-none-eabi-gcc toolchain, OpenOCD.
  2. Plug ST-Link V2 into USB. Plug 4 wires from ST-Link to target board: VCC, GND, SWDIO, SWCLK.
  3. Add a .vscode/launch.json with the standard Cortex-Debug template, pointing at your project's .elf file.
  4. Press F5. Code halts at main(). Set breakpoints with mouse clicks. Inspect variables in the side panel.

The configuration file is mostly boilerplate — PlatformIO generates a working launch.json for any board you select. Pure cmake projects need it written by hand once.

Frequently Asked Questions

Do I really need a $60 J-Link or is a $5 ST-Link clone enough?

For development on STM32 chips, the $5 clone is genuinely fine. We have used the same one for three years across dozens of projects. Genuine probes have advantages (faster flash speed on large binaries, RTT support without firmware mods, long-term reliability) that matter when you are doing this professionally for many hours daily.

Why does my debugger lose connection randomly?

Three usual culprits. First, ground bounce: a long jumper to GND introduces noise on the SWD signals. Use the shortest GND wire possible. Second, the chip enters a sleep mode that disables the debug clock; configure the chip to keep the debug clock alive (DBGMCU_CR on STM32). Third, the debugger USB cable is bad — surprisingly common.

Can I debug an ESP32 with SWD?

Yes. ESP32 supports JTAG (not SWD natively) via four pins. The ESP-Prog board ($10) is the easiest hardware. ESP-IDF supports it; the Arduino ESP32 core does not by default. Most ESP32 development still uses RTT-style logging via the ROM bootloader's UART because it is simpler.

What about debugging without a hardware probe at all?

Two options. SEGGER's J-Run lets you run a binary on a connected target without a full debug session. For pure simulation, QEMU and Renode emulate Cortex-M well enough to run firmware in software with full GDB attached. Useful for unit testing logic that does not depend on real peripherals.

Logic analyzer or oscilloscope — which first?

If you debug digital protocols (I2C, SPI, UART), the logic analyzer is more useful per dollar. If you debug analog signals, power supplies, or fast clock edges, the oscilloscope is essential. Most embedded engineers eventually own both; we covered scopes specifically in our oscilloscope buying guide.

Share your thoughts

Worked with this in production and have a story to share, or disagree with a tradeoff? Email us at support@mybytenest.com — we read everything.