UART - The Protocol That Needs No Introduction (But Gets One Anyway)
UART is the oldest serial protocol still in daily use: no clock wire, just two wires and a baud rate you both agree on. Here's how the framing works and why it still trips people up.
UART. The Protocol That Needs No Introduction (But Gets One Anyway)
UART has been around since the 1960s. Your Arduino has it. Your ESP32 has it. The GPS module on your drone uses it. So does the Bluetooth module, the modem, the barcode scanner, and the debug console on pretty much every embedded processor ever made. It's not exciting. It's just everywhere.
The reason it's survived so long is that it's nearly impossible to get wrong once you understand how it works. And when something does go wrong. garbled bytes, missing data, occasional bad characters. the cause is almost always one of three things.
The framing
No clock wire means both ends have to agree on a speed before anything gets sent. That speed is the baud rate: bits per second. Common values are 9600, 115200, 921600. The transmitter sends bits at exactly that rate, and the receiver samples at the same rate.
Every byte is wrapped in a frame:
Idle START D0 D1 D2 D3 D4 D5 D6 D7 [parity] STOP
1 [ 0 ] b b b b b b b b (opt) [ 1 ]
Rendering diagram...
The idle line is HIGH. The start bit is always LOW. that falling edge is how the receiver knows a byte is coming. Then 8 data bits LSB-first. Then one or two stop bits (HIGH) to let the receiver recover before the next frame.
The receiver doesn't sample continuously. It waits for the start edge, then waits half a bit period to get to the centre of bit 0, and samples from there at one-bit intervals. This is why baud rate accuracy matters: by bit 7, even a small error has accumulated.
Baud rate mismatch. the most common cause of garbage data
A 2% difference between sender and receiver is usually fine. The sample point drifts about 16% over 8 bits, which is still safely within the bit window. Beyond 5% mismatch, you get framing errors and garbled bytes. Beyond 10%, nothing works at all.
Sender: 9600 baud |bit0|bit1|bit2|...|bit7|
Receiver: 9500 baud | samples here β slight drift each bit β |
β
by bit 7, ~8% drift. still ok
When a microcontroller derives its baud rate from a crystal oscillator or PLL that isn't an exact multiple of the baud rate, there's always some residual error. Most datasheets specify the resulting baud rate error in the UART section. worth checking before you assume two devices will talk to each other reliably.
TX connects to RX. always cross the wires
This trips up more people than it should.
Rendering diagram...
Connect GND. Always. Logic levels are measured relative to ground. without a common reference, the voltages on the data lines are meaningless to the receiver.
When RS-232 enters the picture
If the other device has a DB9 connector and was made before 2010, it's probably RS-232. The electrical levels are inverted and amplified compared to TTL:
| TTL UART | RS-232 | |
|---|---|---|
| Logic 1 | 3.3V or 5V | β3V to β15V |
| Logic 0 | 0V | +3V to +15V |
A MAX232 or MAX3232 handles the conversion. The MAX232 needs Β±10V rails which it generates internally from 5V using charge pumps. Don't connect TTL UART directly to RS-232. the negative voltages will damage the MCU input.
When to use it and when not to
UART genuinely is the right call for: debug consoles, GPS/GNSS modules, Bluetooth and Wi-Fi AT-command modules, bootloader interfaces, any simple point-to-point link. It's simple, software support is universal, and you can hook a terminal emulator to it in 30 seconds.
It's the wrong call when you need to talk to multiple devices on one bus (there's no addressing), or when you need high throughput to something on the same PCB (SPI will be faster and more reliable).
The three things that cause 90% of UART problems: wrong baud rate, not crossing TX/RX, missing shared ground. Check those first before diving into anything else.