Appearance
A Small Defence of the UART
A young engineer, who has joined the bench next to mine in the small contract-engineering office where I spend Tuesdays and Thursdays, asked me last week, in the slightly polite way young engineers ask senior people questions that they are afraid will make them look ignorant, whether anybody still bothered with the UART. He had been asked, by his project lead, to bring up a board that used a UART for its primary debug interface, and he wanted to know whether this was a sign that the project was using outdated tooling, or whether — and I am quoting him here — "people who know what they're doing actually still use serial."
I told him, in a kindly way that I hope did not come across as condescending, that the UART was the oldest peripheral on his chip, that it had been on every chip he would ever use, that it would be on every chip his children would ever use, and that he would, by the end of his career, have spent more time staring at UART output than at all of his other debug interfaces combined. He looked, briefly, disappointed. He had, I think, been hoping I would tell him to install some clever modern debugging framework.
I have been thinking, since, about why I gave him the answer I did, and what would have happened if I had given him a different one.
What the UART is
A UART — Universal Asynchronous Receiver-Transmitter, for any reader who has, somehow, missed the acronym — is a peripheral that sends and receives bytes over two wires, at a configurable rate, with no shared clock between the sender and the receiver. The sender clocks each bit out at the agreed-upon rate, marking the start of a byte with a low pulse and the end with one or more high stop bits. The receiver samples each bit at the middle of its expected window, assembles them into a byte, and pushes the byte into a register that the firmware can read. There is, in the basic form, no flow control, no error correction, no handshake. The two ends just have to agree, ahead of time, on the rate and the format.
This description has not changed, in any material way, since the first UART chips were sold by Western Electric in the mid-1960s. The standard rates — 9600, 19200, 57600, 115200 — are the same rates that were standard in 1990, which were the same rates (give or take) that were standard in 1975. The hardware is, by modern standards, almost embarrassingly simple. The peripheral on a modern Cortex-M chip occupies, on the die, perhaps a tenth of a square millimetre. The driver in the firmware is, in its minimal form, about forty lines of C.
This is, I think, why my young colleague was sceptical of it. The UART does not look like a serious piece of engineering. It looks like the kind of thing the chip designer included because they had a corner of die left over.
What the UART is for
The thing the UART is for, in the actual practice of embedded engineering, is being the one peripheral that always works. Every other peripheral on the chip, in my experience, has at some point failed me. The SPI bus has run at the wrong polarity. The I2C bus has hung because a slave forgot to release the line. The USB stack has failed to enumerate, in a way I never managed to debug, on three different boards. The Ethernet PHY has refused to come up after a soft reset. The CAN controller has, on one particularly memorable evening, transmitted a frame on a bus with no termination, with results I will not describe in print.
The UART, in fifteen years and several hundred boards, has never failed me. It has, on a handful of occasions, been wired up wrong — the transmit and receive lines reversed, the voltage levels mismatched, the baud rate misconfigured — and these problems have, in every case, been visible within thirty seconds of attaching a logic analyser. The peripheral itself, once correctly wired and configured, has done what it was supposed to do, every time.
This is not because the UART is, in some objective sense, the highest-quality peripheral on the chip. It is because the UART is the simplest. There is, in the UART, almost no surface area for things to go wrong. There is no clock to glitch. There is no bus to share. There is no protocol layer above the byte. If you can clock a bit out of the transmitter and read it back into the receiver, the UART works, and once it works it tends to keep working.
The other peripherals, by contrast, have enormous surface areas. SPI has clock polarity, clock phase, chip-select timing, frame length, MSB/LSB ordering, and (on most modern chips) a configurable interaction with DMA. I2C has clock stretching, arbitration, addressing modes, and a famously irritating set of failure modes when a slave misbehaves. USB has, by any honest accounting, several hundred pages of specification before you get to the descriptors. Every one of these surfaces is a place where a bug can hide. The UART has, essentially, no such surfaces.
The debug interface that always survives
The practical consequence of this, in the trade, is that the UART is the debug interface that always survives. When your USB-based debugger has stopped enumerating, the UART will still print. When your network stack has died, the UART will still print. When your JTAG probe has thrown an error you do not understand and the IDE has hung, the UART will still print. When the device has crashed so thoroughly that the entire RTOS is dead, a sufficiently paranoid firmware author will have arranged for the hard-fault handler to print the contents of the stack frame, byte by byte, polling the UART transmitter directly without any of the helpful but fragile abstractions the rest of the firmware uses. The hard-fault printout will appear on the engineer's terminal, even though nothing else on the device is functioning.
I have, in a moment of particular weariness, kept a project alive for an entire afternoon on UART prints alone, while every other debug facility was non-functional. The board had a hardware error that prevented the SWD interface from coming up. The USB port was, for reasons I never determined, dead. The Ethernet had not been bring-up yet. The UART was, in the literal sense, the only thing connecting me to the device. I added printfs. I deleted printfs. I added more printfs. I narrowed down the bug, found it, fixed it, and committed the fix. The whole afternoon was conducted at 115200 baud, over a $4 USB-to-serial adapter, with a terminal program I had not opened in three years and which, against all expectation, still worked.
The UART is the only peripheral on the chip that has never asked me to learn anything new about it. The chip vendor has not improved it. The standards body has not extended it. It has, in every sense that matters, stayed the same.
What the young engineer needs to learn
The reason I gave my young colleague the answer I did is that the UART, for all of its apparent simplicity, has a small set of practical lessons that I think every embedded engineer should learn early. The lessons are these.
The first lesson is that the baud rate is a contract between two devices that have no shared clock, and contracts of this kind are surprisingly fragile. If the receiver's clock is off by more than about two percent — which, on a chip with an internal RC oscillator, is entirely possible — the receiver will, on a long enough byte, sample one of the bits in the wrong place, and the byte will be received as a different byte than the one that was sent. The fix is to give the receiver a more accurate clock, usually by attaching a crystal. The lesson is that synchronous communication over an asynchronous channel always depends on some shared assumption, and the shared assumption is always somewhere you forgot to look.
The second lesson is that the UART has, on most chips, a tiny FIFO — sometimes eight bytes, sometimes sixteen, sometimes thirty-two. If the firmware does not read from the receive FIFO faster than the sender is writing to it, the FIFO will overflow, and the overflowed bytes will simply be lost. This lesson is, in some respects, the embedded version of the lesson every junior systems-programming engineer eventually learns about buffer management, only it is taught by the UART in a particularly hands-on way: the lost bytes are visible, on the logic analyser, going by on the wire, while the firmware is failing to receive them. There is no easier way to develop an instinct for buffer pressure than to watch the UART drop bytes in front of you.
The third lesson is that the UART has, in its handshaking lines (the now-deprecated RTS and CTS signals, which were standard on serial ports in the 1990s but are absent from most modern UART implementations), a small but important piece of history. The RTS/CTS lines existed because the people who designed the UART, sixty years ago, understood that asynchronous communication needed a way for the receiver to tell the sender to slow down. The lines were dropped from most modern hardware because they were "rarely used," and they were rarely used because most modern protocols have flow control built in at higher layers. But the lesson — that an asynchronous channel without flow control will, eventually, lose data — has not gone away. It has just moved up the stack, where it is now somebody else's problem.
These lessons, taken together, are most of what an embedded engineer needs to know about communication on real hardware. The lessons are easy to teach with a UART, because the UART is small enough that the entire surface is visible at once. They are hard to teach with USB or Ethernet, because in those protocols the lessons are buried under several layers of abstraction, where they are hidden but not absent.
A small confession
I will admit, here, that I have a personal fondness for the UART that goes beyond its practical usefulness. The first peripheral I ever brought up on a microcontroller, in a university lab in 2008, was a UART, sending a string of bytes that I had hard-coded into the firmware to a terminal program that I had configured to the wrong baud rate. The bytes came out as garbage. I fixed the baud rate. The bytes came out correctly. I felt, at the time, an entirely disproportionate sense of accomplishment. I had made a chip send bytes to a computer, and the bytes had been received, and I had done it with my own hands.
I have, since then, brought up many more sophisticated peripherals, on many more sophisticated chips, with many more sophisticated debugging tools. None of them has given me quite the same feeling. The UART is, in the slightly sentimental way that the first time one does anything always is, special.
The young engineer at the next bench has, I am happy to report, now successfully brought up his board. The UART is printing. He has, in the way of all engineers who have just made a UART print, been showing me the output with quiet pride. I have nodded approvingly, and have not, so far, told him that he is now committed, for the rest of his career, to this small and unfashionable peripheral. He will figure that out on his own.