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:
| Board | Peripheral | Clean? |
|---|---|---|
| STM32F4 Discovery | usart2 | Yes, no warnings. FreeRTOS task, repeats periodically |
| STM32F7 Discovery | usart2 | One RCC warning, output unaffected |
| STM32F103 Blue Pill | usart1 | UART clean, GPIO path in the same run was not |
| STM32L073 Nucleo | usart2 | Yes |
| STM32H745 Nucleo | usart3 | Yes, no warnings, dual-core board |
| nRF52840 DK | uart0 | Two PSEL pin-config warnings, output unaffected |
| STM32WBA52 Nucleo | usart1 | Benign RCC/PWR warnings, output unaffected. Embassy async example, prints once per second |

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.

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.

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.
