UART
Asynchronous serial communication
UART Fundamentals
UART (Universal Asynchronous Receiver/Transmitter) is a serial, point-to-point interface with one defining trait: there is no shared clock line. SPI has SCLK, I²C has SCL — UART has neither. Instead, both sides independently agree in advance on a timing rate, the baud rate, and each side uses its own internal clock to send and sample bits at that agreed pace.
Why asynchronous?
Because transmitter and receiver never share a clock signal, "asynchronous" here specifically means each side must independently reconstruct bit timing from a known baud rate and the start bit — not that data can arrive at arbitrary, unsynchronized moments within a byte.
Why UART at all?
Extremely simple wiring — just TX, RX and GND Full-duplex without any extra signal lines
No addressing overhead — ideal point-to-point Universally supported — debug consoles, bootloaders, GNSS/cellular/Bluetooth modules Not built for multiple devices
Basic UART has no addressing mechanism at all — it's fundamentally a two-device link. Talking to several devices from one UART peripheral requires extra hardware (a switch/mux) or an application-level protocol layered on top; UART itself provides no arbitration for that.
The UART Frame
UART's idle line sits HIGH. A frame begins the instant the line is pulled LOW — that's the start bit — followed by the data bits, an optional parity bit, and finally a stop bit that returns the line HIGH.
8-N-1 — the most common configuration This shorthand is one of the first things to confirm when two UART devices won't talk — both sides must use the exact same configuration.
Why the start bit is LOW Since idle is defined as HIGH, a HIGH→LOW transition is unambiguous — it can only mean a new frame is beginning. That single guaranteed transition is what lets the receiver synchronize its sampling to this specific frame, with no shared clock required.
Bit Order — LSB First
UART transmits the least significant bit first — the opposite of how the byte is usually written down. This trips people up constantly when comparing a logic-analyzer capture against the intended byte value.
Worked example — 0x53 If you ever see a decoded capture that looks "bit-reversed" compared to the byte you expected, this is almost always why.
Baud Rate
Baud rate is the agreed signaling rate — for standard binary UART where one symbol equals one bit, baud rate and bit rate are numerically the same thing. Common values: 9600, 19200, 38400, 57600, 115200.
Baud rate must match If one side is set to 115200 and the other to 9600, the receiver samples at completely the wrong moments — the result is framing errors and garbage bytes, not just "a bit off." Baud rate ≠ bytes per second
115200 baud does not mean 115200 bytes/sec. With 8-N-1, every byte costs 10 transmitted bits (start + 8 data + stop), so real throughput is roughly baud ÷ 10 — about 11,520 bytes/sec. This is a very common interview calculation.
Where the baud rate actually comes from UART has no external clock pin, but the peripheral still needs an internal one — it derives the baud rate from the MCU's peripheral clock through a divider/oversampling scheme. The exact formula differs by MCU, and the divider often can't hit the requested baud exactly, leaving a small baud-rate error that both sides' frame timing and oscillator accuracy must tolerate.
Receiver Operation & Sampling
The receiver idles, watching RX. The instant it sees a HIGH→LOW transition, it assumes a start bit has begun and starts timing from there using its own baud-rate clock — there's no external clock edge to lean on.
Why sample mid-bit?
Signal edges are the least reliable place to read a value — reflections, propagation delay and timing jitter are worst right at a transition. Sampling near the center of the bit period gives the most margin against small timing mismatches.
Many UART peripherals internally clock at 8× or 16× the bit rate, so they can locate the start-bit edge precisely and then count forward to hit the center of every following bit — improving noise tolerance and timing accuracy. The exact scheme is MCU-specific.
Error Detection
Parity detects, it doesn't correct Parity can flag that something looks wrong, but it can't say which bit is wrong or reconstruct the original byte — and if exactly two bits flip together, the parity can still come out looking correct. It's a cheap first line of defense, not a substitute for a real checksum or CRC.
Flow Control
Basic UART is a simple 3-wire link (TX, RX, GND) with no built-in backpressure — if the receiver can't keep up, data is simply lost. Flow control adds a way for the receiver to tell the transmitter to pause.
Hardware flow control — RTS/CTS RTS (Request To Send) and CTS (Clear To Send) let the receiver signal, out of band, whether it's currently able to accept more data. Exact signal semantics can vary by implementation, but the goal is always the same: stop the transmitter before the receiver's buffer overflows.
Software flow control — XON/XOFF Special in-band bytes tell the transmitter to pause (XOFF) and resume (XON) — no extra wires needed, but it consumes part of the data stream itself and requires those special bytes to never appear in ordinary payload data.
3-wire vs full interface Simple TX/RX/GND UART is common and sufficient for many uses, but offers no hardware backpressure at all. Adding RTS/CTS turns it into a more complete interface capable of protecting a slower receiver during bursts of data.
Physical Interfaces
UART is a digital protocol/peripheral concept — it says nothing about voltage levels or connectors. Those are defined by a separate physical-layer standard sitting underneath it.
Logic-level UART directly from the MCU — commonly 3.3 V or 5 V. Fine for short board-level or module connections, but not interchangeable with RS-232 voltage levels without a transceiver.
An electrical standard defining its own voltage levels and signal polarity — MCU UART pins cannot connect directly to an RS-232 line; an RS-232 transceiver sits between them.
A differential physical layer (A/B pair) suited to longer, noisier, multidrop runs than raw logic-level UART can handle. UART still provides the framing and byte stream; an RS-485 transceiver converts it to differential signaling. Half-duplex RS-485 also typically needs explicit direction control — enabling the transmitter, waiting for transmission-complete (not just TX-empty), then switching back to receive, or the final byte can be truncated.
Half-duplex vs full-duplex With separate TX and RX wires, standard UART is naturally full-duplex — both sides can transmit at once. Some implementations share a single line and operate half-duplex instead, where only one side may transmit at a time and extra coordination is required to avoid collisions.
UART Peripheral
TX shift register Software loads a byte into the TX data register; the hardware moves it into an internal shift register and serializes it — start bit, data bits, optional parity, stop bit — onto the TX pin.
TX empty vs transmission complete "TX register empty" means another byte can usually be loaded — it does not mean the current byte has finished physically leaving the pin. "Transmission complete" means the whole frame has actually gone out. This distinction matters before disabling the UART or switching an RS-485 transceiver's direction.
FIFO and baud generator TX/RX FIFOs absorb short bursts and reduce interrupt frequency. The baud-rate generator derives the requested baud from the peripheral clock through an MCU-specific divider/oversampling scheme — always consult the specific reference manual rather than assuming a universal formula.
Driver Development
Write TX register → wait until ready → write next byte. Simple, but the CPU is busy the whole time waiting on both TX and RX.
A byte arrives → UART interrupt fires → ISR reads RX and stores it in a buffer. Transmission works the same way in reverse. Much better CPU efficiency than polling.
Decouples UART timing (bytes can arrive any time) from application timing (the app reads when it's ready) — without a ring buffer, a busy application risks losing bytes to overrun.
DMA and a packet state machine
Protocol Design on Top of UART
UART only guarantees a byte stream — it has no concept of "message," "command" or "checksum." Any structure above that is something your application defines.
The receiver reads a length byte and then knows exactly how many payload bytes to collect — generally more robust than relying purely on inter-byte timing.
Reserved START/END byte values (e.g. 0x7E / 0x7F) mark message boundaries — simple, but if those values can legitimately appear inside payload data, byte-stuffing/escaping is needed to avoid false boundaries.
RX timeout as an alternative Some UART peripherals can flag a gap in incoming bytes after a burst, which a variable-length protocol can treat as "the packet is probably finished" — useful when there's no explicit end delimiter, though it's a timing heuristic rather than a guarantee.
Don't forget endianness Multi-byte values like a 16-bit sensor reading still need a defined byte order on the wire (e.g. 0x1234 sent as 12 34 vs 34 12) — UART transports bytes; it never decides this for you.
UART vs SPI vs I²C vs CAN
The short version UART — the simplest possible link between exactly two devices, no shared clock, ideal for debug consoles and modules. SPI and I²C are local peripheral buses for talking to chips on the same board. CAN is a different category entirely — a robust network connecting separate ECUs, not a peripheral bus at all.
Practical UART Debugging
Reading a logic-analyzer capture A decoder needs the same 8-N-1-style configuration your firmware uses (baud, data bits, parity, stop bits) to make sense of START/DATA/STOP correctly — mismatched decode settings will make perfectly good data look corrupted.
Real Applications
UART's place in an automotive stack UART is common for ECU debugging, bootloaders, manufacturing interfaces and talking to external modules — but it is generally not the primary ECU-to-ECU vehicle network, a role that belongs to CAN/CAN FD where multi-node arbitration, error confinement and a defined network architecture are required.