Skip to content
Chiplab logo
Chiplab logo
Back to Now

Why UART is still the best debug interface in 2026

I have a coding agent that reads a datasheet, writes a peripheral driver, builds it, and runs it on a virtual chip without touching hardware. Very modern. Very 2026.

The way it finds out whether any of that worked is a serial port whose design predates the microcontroller.

Nobody planned this. The UART debug interface won by outliving everything that was supposed to replace it, and it keeps winning for a reason that has nothing to do with nostalgia.

A very short history of watching firmware

Debug interfaces have gone through roughly four generations.

First there were lights. Front-panel LEDs and switches, and the debugging technique was "blink the LED in a pattern I can count." People still do this. It's the only interface guaranteed to work when nothing else does.

Then serial. A device to shift bytes out one wire and in on another, so a machine could talk to a teletype. The acronym is older than the chips we run it on. Firmware borrowed it immediately, because a print statement is the cheapest debugging tool ever invented and it worked here too.

Then the dedicated debug port. JTAG arrived as an IEEE standard at the start of the nineties, built for boundary scan and quickly repurposed into halt-the-core, read-the-registers debugging. ARM later squeezed it down to two pins as SWD, which is what your probe is actually speaking today.

Then trace. SWO, ITM, ETM. Real instrumentation: timestamped events streamed out of the core without stopping it, so you can watch timing instead of guessing at it.

Each generation was strictly more capable than the last. And every single one of them shipped next to a UART, not instead of it.

Why the old one didn't die

Five things, and none of them are features. They're all absences.

It needs two wires. TX, RX, ground. Often you only wire TX, because all you want is for the board to talk. There is no clock line to route, no impedance to worry about, no probe that needs to be within a few inches of the target.

It needs no special tooling. A three dollar USB-serial adapter, or the virtual COM port your debug probe already exposes. Compare that to the software stack between a trace port and a readable timeline.

The output is human-readable with no decoder. printf, and the string comes out the other end as a string. No symbol table, no map file, no vendor GUI that only runs on Windows. You do not have to reconstruct meaning; the firmware author already wrote it down.

It has no host-side state. cat /dev/ttyACM0. It's a file. Nothing to attach to, nothing to lose sync with, nothing that dies when you unplug and replug.

It works when the rest of the chip doesn't. Clock tree half configured, watchdog barking, peripherals unenumerated, no debugger connected. One shift register and a baud clock is a very low bar to clear, so it clears early in boot and keeps clearing it while everything downstream is on fire.

There's a sixth thing that only became load-bearing recently: text goes into a pipe. An agent can't attach to your trace GUI. It can read a line of ASCII and act on it. When we talk about the loop breaking at the machine, the thing that closes it in practice is almost always a serial log.

Seven boards, one afternoon

Here's the claim I get to make that most of what we publish can't: today, UART output was captured on every single board Chiplab supports. Not a representative sample. All seven.

Verbatim, the line each board printed:

STM32F4 Discovery
08:04:34.9793 [INFO] usart2: [...] Hello world!

STM32F7 Discovery
08:09:04.6720 [INFO] usart2: [...] Hello world!

STM32F103 Blue Pill
08:07:06.4365 [INFO] usart1: [...] bluepill-hello: $2 board, zero hardware, still boots

STM32L073 Nucleo
08:07:06.8859 [INFO] usart2: [...] datasheet-agent: never seen this board before, reading DBGMCU_IDCODE

STM32H745 Nucleo
08:09:05.2628 [INFO] usart3: [...] Hello world!

nRF52840 DK
08:09:06.3444 [INFO] uart0: [...] Hello world!

STM32WBA52 Nucleo
08:09:05.9954 [INFO] usart1: [...] Hello world!

And how each run went:

BoardPeripheralClean?
STM32F4 Discoveryusart2Yes, no warnings. FreeRTOS task, repeats periodically
STM32F7 Discoveryusart2One RCC warning, output unaffected
STM32F103 Blue Pillusart1UART clean, GPIO path in the same run was not
STM32L073 Nucleousart2Yes
STM32H745 Nucleousart3Yes, no warnings, dual-core board
nRF52840 DKuart0Two PSEL pin-config warnings, output unaffected
STM32WBA52 Nucleousart1Benign RCC/PWR warnings, output unaffected. Embassy async example, prints once per second

Table showing UART debug output captured on all seven Chiplab supported boards

I want to be precise about the two imperfect ones, because "all seven were flawless" would be the more marketable sentence and it isn't true.

On the F7, Chiplab logged [WARNING] rcc: Unhandled read/write from offset 0x90 before the firmware got going. That's a clock-control register bit the model doesn't track. On the nRF52840 there were two warnings about unhandled PSEL bits. On Nordic parts the PSEL registers select which physical pins route to the UART peripheral, and our model didn't track two of the reserved bits. The WBA52, the newest chip in the set, produced a handful of RCC and PWR register warnings.

In all three cases the warnings were about registers next to the UART, not the UART. The output came out anyway.

The Blue Pill run is the most interesting of the seven for exactly this reason. The UART path worked cleanly, while the GPIO path in that same run hit bit-band addresses Chiplab's model doesn't implement. Same firmware, same run, same simulator: serial fine, pin toggling broken. That story gets its own post in Blue Pill in 2026.

Why simulators get UART right first

That pattern isn't luck, and it's the actual argument of this post.

A UART is one shift register and a baud clock. There's no DMA requirement, no handshaking to negotiate, no descriptor rings, no clock-domain trickery you have to model to get a byte out. You can implement a usable one in an afternoon. The behavior a simulator has to reproduce is "bytes appear in order," and bytes appearing in order is not hard.

Now compare that to what's sitting one register block over. A clock tree with a dozen interdependent enables. A pin mux where the reserved bits still do something on this revision. A power controller with modes that change what other peripherals see. That is where our warnings came from, on all three boards.

Diagram contrasting a simple UART with the complex peripherals around it

So UART ends up as the lowest common denominator that real silicon and simulators actually agree on. It's the first peripheral implemented, the first one tested, and the one least likely to have drifted since. On a new board, printf over serial is the closest thing to a guarantee you get.

That cuts both ways, and I'd rather say it here than let you discover it: agreeing on UART is not the same as agreeing on the chip. What simulation can't catch is a list of the places our stack will hand you a plausible number instead of an error. A clean serial log tells you the firmware reached that print statement. It does not tell you the peripheral underneath it behaves like silicon.

When you actually need more than UART

UART is a debug interface. It is not a debugger. Some things it will never do for you.

Real-time trace. SWO and ITM stream timestamped events out of the core while it keeps running. A printf at 115200 baud costs real cycles and shifts the timing of the thing you're measuring. If you're chasing jitter or an interrupt-latency problem, serial logging is part of the problem.

Register-level inspection. JTAG and SWD let you halt the core and read memory and registers as they are, right now. No breakpoints over UART. No stepping. No "what is in r3 at this moment." The only state you can see is state the firmware author decided to print, which means you can only debug problems you already anticipated.

High-bandwidth and timing-critical buses. A logic analyzer on SPI or I2C shows you edges, setup times, and who drove the line low. Serial output shows you what your driver believed happened. Those are different, and when they disagree the logic analyzer is right.

Anything before your UART is initialized. If the fault happens during clock setup, the interface that reports faults isn't up yet. That's what a debug probe is for.

Diagram comparing what UART shows you against what SWO, SWD and logic analyzers show you

None of that is an argument for skipping serial. It's an argument for knowing what layer you're looking at. Serial answers "did it get here and what did it think." The probe answers "what is actually in the machine." Most bugs die to the first question, which is why the first question is worth making trivially cheap on every target.

The boring conclusion

Two wires and a print statement is still the fastest way to find out whether firmware works, on real silicon and in simulation, and now also for an agent that can only act on what it can read. When we needed a first-run sanity check across seven different chips from two vendors, including a part that came out recently and a dollar-store clone of a part from 2007, we didn't debate the approach. We printed a line.

Every one of those boards had a different clock tree, a different pin mux, and a different level of model maturity behind it. The UART was the only thing they all agreed on. That agreement is the whole value, and it's why the interface everyone predicted would be retired thirty years ago is the one I'd pick first if you gave me an unfamiliar board and an afternoon.

It's also why going from datasheet to running firmware starts with a serial banner and not a debugger session. Get the board talking first. Everything else is easier once it does.