Skip to content
Chiplab logo
Chiplab logo
Back to Now

RTT vs SWO vs UART: choose the constraint, not the tutorial

Ask an engineer why their firmware prints over UART and you usually get a shrug. Ask a coding agent and you get the same answer with more confidence, because the last example in its context window used it.

Four common Cortex-M debug-output paths are UART, RTT, SWO, and semihosting, and they are not variations on a theme. One stops your program on every message. One needs a pin that vendor eval kits fail to route even when the probe and the core both support it. One needs no pin at all and reads your RAM while the core runs. One is fully specified as a protocol and completely unowned as debug infrastructure.

Choosing without knowing which is which is how you debug a timing bug through a transport that changes the timing.

The table

halts the CPU?bandwidthhardware neededextra pin?under emulation
UARTnobaud ratea free UART peripheral, plus a serial adapteryes, TX (and RX for input)yes
SEGGER RTTdepends on modeup to ~3.5 MB/sdebug probe with background memory accessnoneeds a host-side reader
SWO / ITMnoprescaler off the core clock; ~1.5 µs/char at 10 MHzprobe with SWO capture, and a core that has ityes, TRACESWOonly if the sink is modeled
Semihostingyes, every callmilliseconds to >100 ms per messagea debug agent, nothing elsenoyes, by design

Every row has a caveat that matters more than the row itself.

RTT has three modes and only one of them is free

RTT puts a control block in target RAM. The host probe locates it by scanning for an ID string, then reads and writes ring buffers, one per channel.1 Your firmware's write is a memcpy. That is the whole target-side cost.

Every summary flattens this: RTT runs in one of three modes. Background mode is the fast one, up to about 3.5 MB/s, available on all current J-Link and J-Trace Pro models. Legacy background mode is the same idea at lower speed on older probe firmware. Stop mode is what SEGGER calls "Pseudo RTT mode": the CPU is halted to read data, and SEGGER documents that it was introduced for CPUs that do not support background access, and that it affects real-time behavior.2

So "RTT never halts the core" holds right up until your part cannot do background memory access, at which point RTT halts the core and you have bought semihosting with extra steps. Throughput scales with the probe, not just the buffer: SEGGER's own numbers put Background mode up to roughly 3.5 MB/s, with Legacy background mode lower on older probe firmware.2 On an STM32F407 at 168 MHz, a line of output costs about a microsecond or less.3

And no extra pin. SEGGER is explicit: RTT needs no additional pin or hardware beyond a probe on the standard debug port.1

"You need a J-Link" is true of the tooling, not of the protocol

SEGGER states the requirement as any J-Link plus any supported target that allows background memory access.1 That describes SEGGER's stack.

It is not a description of the protocol, which is a documented RAM ring-buffer format that three independent projects have reimplemented against CMSIS-DAP-class probes:

  • OpenOCD ships rtt setup address size [ID], rtt start, and rtt server start port channel, over any adapter OpenOCD supports.4
  • pyOCD added RTT and SystemView support with a first-class session option, -O rtt=....56
  • probe-rs ships RTT support directly in its rtt module now. The standalone probe-rs-rtt crate was folded back into the main repo.7

Practical version: with a J-Link, use SEGGER's stack. With an ST-Link on a dev board, RTT is still on the table, not with SEGGER's tools or support.

Diagram of four Cortex-M debug transports showing UART using two pins, SWO using the TRACESWO pin, and RTT and semihosting riding the existing debug port

SWO is gated in three separate places

Serial Wire Output is Arm-architected infrastructure, not a bolt-on. Arm describes it as a trace sink similar to the TPIU that can trace exactly one source, the ITM, and outputs the stream off-chip through a single-pin interface; SWO plus its 8-bit ATB slave interface to the ITM are collectively the Serial Wire Viewer.8 Physically it is ten-bit packets with start and stop bits, in Manchester or UART encoding, and the capture device is expected to clock at the same speed as the TRACESWO pin.9 Arm's tooling docs name the use case: ITM stimulus ports exist for printf-style debugging without a communication interface like a UART.10

Bandwidth is a prescaler off the core clock. TPIU_ACPR sets the divisor for the SWO baud rate.11 You are not choosing a speed, you are choosing a division of whatever the core runs at, so a clock change moves your trace port with it. The concrete cost, from SEGGER's measurements on that same STM32F407: roughly 1.5 µs per character at 10 MHz, about 120 µs for an eighty-character line.3 Two orders of magnitude slower per line than RTT.

Then the three gates. Your probe has to support SWO capture, which Arm's own CMSIS-DAP specification treats as optional: separate capability bits report SWO UART, SWO Manchester, and SWO streaming trace.12 Your silicon has to support it, and pyOCD says the quiet part plainly: Armv7-M and Armv8-M Mainline support SWO, while Armv6-M and Armv8-M Baseline do not, so Cortex-M0, M0+, M1 and M23 are out.13 And the board has to have routed the signal to the debug header, which pyOCD notes fails in a surprising number of cases, including on silicon vendor evaluation kits where probe and MCU both support it.13

Three independent things have to be true. A tutorial that worked for its author tells you nothing about whether they are true for you.

Semihosting stops the program, and that is the whole story

Semihosting is a trap. On M-profile Thumb the instruction is BKPT #0xAB, opcode 0xBEAB; A64 uses HLT #0xF000, and A32 can use either SVC #0x123456 or HLT #0xF000.14 The operation number goes in a register, and the set is a small POSIX-shaped file API: SYS_OPEN (0x01), SYS_WRITEC (0x03), SYS_WRITE0 (0x04), SYS_WRITE (0x05), SYS_READ (0x06), SYS_READC (0x07), up through SYS_EXIT_EXTENDED (0x20).14

SEGGER's description of the mechanism is the clearest one I have found: the target CPU is halted, the debug agent takes control and performs the action, then restarts the target, and the target stays halted for the duration of the operation.15 The price tag is in the same vendor's measurements: one message can take several milliseconds to more than a hundred.3

A hundred milliseconds of stopped core inside a control loop is not logging, it is a fault injection experiment you did not mean to run.

What semihosting buys you is that it needs nothing. No pin, no peripheral, no trace unit, no capture-capable probe. And it works under emulation: Zephyr documents it as a mechanism for code on Arm, RISC-V and Xtensa targets to use the host's I/O facilities under a debugger or an emulator.16

Timeline diagram contrasting continuous execution under RTT background mode and SWO with the halted execution gaps semihosting introduces at every call

Nobody owns UART, so nobody writes the fourth column

Here is the finding I care about most. I went looking for the document that compares these four. The closest thing I found is a SEGGER blog post, and it naturally favors RTT.

Arm documents SWO, ITM, the TPIU and semihosting thoroughly, because Arm architected all of them. Arm's documentation never mentions RTT and never will: RTT is SEGGER's technology, not part of the architecture. Arm could not produce a four-way matrix even in principle.

SEGGER can, and their blog post on getting printf output off a target is the closest thing that exists: UART and CDC, semihosting, SWO and RTT in one place.3 It is also SEGGER's blog, and it concludes that there is no reason to use SWO anymore when you can use RTT instead. UART gets a single sentence, which does at least name the real tradeoff: a UART may already be used by the application, and USB CDC requires a stack and a connector on the target.3 One dismissive sentence is not a comparison row.

That is the structural problem. Each vendor documents the transport it owns. Nobody owns UART. It is a convention, not architected debug infrastructure, so there is no single Cortex-M debug-output specification or vendor responsible column. You can watch a project work around the pin contention instead: Zephyr's GDB stub docs tell you to point zephyr,gdbstub-uart at a spare UART so printk and log messages do not collide with GDB, and say that on boards with one UART you must disable printk and logging entirely.17 That is the fourth column, written down as a workaround in someone else's feature docs.

The matrix is scattered by construction. Which is why engineers and agents default to whatever the last tutorial used. They are not being lazy. They went looking for the comparison, found four vendor pages each arguing for its own transport, and copied the example instead.

Grid graphic showing that Arm documents SWO and semihosting but never RTT, while SEGGER documents RTT and gives UART a single sentence, leaving no complete four-way comparison

The emulation column changes the ranking

Delete the board and the ordering inverts. Semihosting, the worst transport on hardware, becomes one of the easiest to get working, because a trap handled by a debug agent is a trap handled by an emulator.16 UART stays available, since a UART peripheral is one of the first things any machine model implements. SWO depends on whether the ITM and its trace sink were modeled at all.

RTT is the interesting one. The target side works fine under emulation, since it is just RAM. What disappears is the host-side reader polling that control block while the core runs. I wrote up that transport's format story separately in the RTOS trace post, including what came back when I ran Zephyr's CTF trace over UART on a simulated board.

Which is why UART still wins more often than it should on paper. It is the only one of the four with no probe requirement, no silicon requirement, no capability bit and no vendor.

Pick on the constraint, not on the example

Four questions, in order. Does the transport get to stop my core? Do I have a pin to spare? Do I have a probe with the right capability, and does my part have the peripheral at all? Am I running this on a model, on silicon, or both?

Answer those and the choice is usually forced. Semihosting for bring-up and for simulated CI where nothing has a deadline. SWO when you have a v7-M or v8-M Mainline part, a routed pin and a capable probe. RTT when you have background access and need throughput. UART when you want the thing that works everywhere.

None of that is hard. It is never written down in one place, so a choice with real timing consequences gets made by whichever page came up first. Simulation moves the tradeoffs around, and there is a separate list of things it still cannot catch, but it does not remove them. It changes which column you read.

Sources

Footnotes

  1. SEGGER, "About Real Time Transfer." https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/ 2 3

  2. SEGGER Knowledge Base, "RTT," §RTT operation Modes and buffer/speed figures. https://kb.segger.com/RTT 2

  3. SEGGER Blog, Johannes Lask, "Getting printf Output from Target to Debugger," 2016-10-21, updated 2022-10-14. https://blog.segger.com/getting-printf-output-from-target-to-debugger/ 2 3 4 5

  4. OpenOCD User's Guide, §15.6 "Real Time Transfer (RTT)." https://openocd.org/doc/html/General-Commands.html

  5. pyOCD documentation, "Session options" (rtt option). https://pyocd.io/docs/options.html

  6. pyOCD v0.43.0 release notes, "Add support for SEGGER's RTT and SystemView." https://github.com/pyocd/pyOCD/releases/tag/v0.43.0

  7. probe-rs, rtt module documentation, "Host side implementation of the RTT (Real-Time Transfer) I/O protocol over probe-rs". The standalone probe-rs-rtt crate now redirects here. https://docs.rs/probe-rs/latest/probe_rs/rtt/index.html

  8. Arm, CoreSight Technology System Design Guide (DGI0012), §Trace sinks. https://developer.arm.com/documentation/dgi0012/d/CoreSight-Components-and-Systems/CoreSight-components/Trace-sinks

  9. Arm, CoreSight Components Technical Reference Manual (DDI0314H), §Serial Wire Output, physical pin protocol. https://developer.arm.com/documentation/ddi0314/h/Serial-Wire-Output/SWO-trace-port/Physical-pin-protocol

  10. Arm, µVision User's Guide (101407), "Trace Features." https://developer.arm.com/documentation/101407/latest/Debugging/Code-and-Data-Trace--Cortex-M-/Trace-Features

  11. Armv7-M Architecture Reference Manual (DDI0403E), §C1.10 Trace Port Interface Unit: TPIU_ACPR is the SWO baud-rate prescaler. https://developer.arm.com/documentation/ddi0403/latest

  12. Arm, CMSIS-DAP DAP_Info capabilities and SWO commands. https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__Info.html and https://arm-software.github.io/CMSIS-DAP/latest/group__DAP__swo__gr.html

  13. pyOCD documentation, "SWO/SWV" (probe, MCU, architecture and board routing requirements). https://pyocd.io/docs/swo_swv.html 2

  14. Arm, ABI for the Arm Architecture, semihosting.rst (trap instructions and operation numbers). https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst 2

  15. SEGGER Knowledge Base, "Semihosting" (SEGGER's description of the halt-and-resume mechanism). https://kb.segger.com/Semihosting

  16. Zephyr documentation, "Semihosting Guide." https://docs.zephyrproject.org/latest/hardware/arch/semihost.html 2

  17. Zephyr documentation, "GDB stub" (zephyr,gdbstub-uart and single-UART boards). https://docs.zephyrproject.org/latest/services/debugging/gdbstub.html

Daniel Frassinelli
Published Aug 17, 2026