UART
Devices do not always need a shared bus, and they do not always need complex arbitration. Debug ports, module links, and data exchange between an MCU and a host often have a simpler requirement: each side gets a transmit line and a receive line, no extra clock, no chip select, and no address broadcast, as long as bytes can be delivered reliably to the other side.
UART solves exactly that kind of point-to-point serial communication. It optimizes first for low implementation cost, direct wiring, and a low barrier for both hardware and software rather than for bus expansion or high throughput. That is why it has stayed in engineering use for decades: often what you need is not a more complex link, but a channel that works as long as both sides agree on the parameters.
The most stable first mental model for UART is this: UART keeps byte boundaries without a shared clock by using a start bit to re-align the sampling rhythm, then fixed data bits, an optional parity bit, and a stop bit to cut a continuous voltage stream into bytes. The later items like baud rate, data bits, parity, stop bits, and flow control are all answers to three questions: where does this frame start, how should the receiver sample it, and how do we avoid dropping data when the two ends are not perfectly matched?
UART First Solves Asynchrony, Not Sharing
SPI and I2C both put a clock directly on the wires, just with different sharing rules. UART deliberately does not transmit a clock. It assumes that the sender and receiver each have their own local clock, and that both sides agree in advance on a rate that is close enough.
The most basic wiring usually has only:
TX: transmit lineRX: receive lineGND: ground reference
If the link is full duplex, the lines are crossed:
- A’s
TXgoes to B’sRX - A’s
RXgoes to B’sTX
The advantages are obvious:
- simple wiring
- low controller implementation cost
- especially good for point-to-point device links and debug output
The tradeoffs are equally clear:
- no shared clock, so both ends must pre-agree on the timing
- it is not a natural multi-device shared bus
- the link layer is thin and does not handle addressing, arbitration, or complex error recovery
So UART’s core design is not “a serial port sends bytes.” It is that, without a clock wire, it uses the smallest possible mechanism to barely keep byte boundaries intact.
How Does the Receiver Know Where a Frame Starts
That is the key UART mechanism. When the line is idle, it usually stays high. When the sender begins a character, it first drives a start bit, which means pulling the line from high to low.
Once the receiver sees that high-to-low transition, it knows:
- a new frame has started
- it should sample the line according to the agreed baud rate
The rest of the frame follows a fixed format:
- 1 start bit
- 5 to 9 data bits, most commonly 8
- optional parity bit
- 1 or more stop bits
The stop bit is not there to carry content. It ends the frame and brings the line back to idle high, leaving a clean boundary for the next frame.
That gives UART its main flow:
- The line stays idle high
- The sender drives the start bit
- The receiver re-aligns its sampling timing from that start bit
- Both sides follow the agreed baud rate through the data bit interval
- The receiver checks parity and stop-bit validity
- The frame ends and the receiver waits for the next start bit
That flow explains why UART can work without a clock wire, and also why it is inherently more fragile: every frame has to re-find its rhythm from the start bit instead of relying on a shared clock all the way through.
Why Baud Rate Is the Fundamental Agreement
The most common UART parameter is the baud rate, such as 9600 or 115200. People often think of it as “bits per second,” which is fine. But in engineering terms, the more important meaning is this: it determines how often the receiver samples the line.
Because UART has no clock wire, the receiver can only estimate the center of each bit after it detects the start bit. If the baud rates differ too much, the sampling point drifts, and eventually you may see:
- a 0 sampled as a 1
- stop-bit sampling failure
- the back half of a frame becoming unstable
That is why UART may look “very simple” but still depends heavily on matching parameters. It is not enough for the rates to be roughly close; the error must stay within what one frame can tolerate.
The usual practical path is:
- both sides agree on a standard baud rate
- the controller uses divisors to get as close as possible
- the receiver usually oversamples, for example 8x or 16x, then makes the decision near the center of the bit
So “UART is asynchronous” does not mean “the two sides never need timing agreement.” It means “the line does not carry a clock, so the two sides must approximate synchronization with their local clocks.”
What Data Bits, Parity, and Stop Bits Actually Do
UART often gets written as combinations like 8N1. Many documents throw that acronym at the reader without unpacking it, but it maps directly to a concrete engineering choice:
8: 8 data bitsN: no parity1: 1 stop bit
These are not decoration. They balance efficiency, compatibility, and the minimum level of error detection.
The data bits decide how much useful information each frame can carry. Eight bits is the most common because it matches one byte.
The parity bit is the lightest possible error check, such as odd or even parity. It can help catch some single-bit errors, but it is very limited:
- it only detects some errors
- it does not correct errors
- it cannot replace checksums, CRC, or retransmission in a higher-layer protocol
The stop bit creates a clear end region at the end of the frame and gives the receiver some recovery margin. More stop bits usually improve tolerance a bit, but they reduce line efficiency.
So 8N1 is common not because it is absolutely mandatory, but because it is good enough for many general-purpose serial cases: one byte of payload, no extra link-layer checksum, and not too much overhead.
Why One Side Can Send While the Other Cannot Receive
This kind of bug is extremely common because the success conditions for UART look few, but in reality several basic agreements must hold at the same time:
- the electrical level standard must match
- the baud rate must match
- the data bits, parity, and stop bits must match
TX/RXmust be wired in the correct direction- both sides must share ground
If any one of those is wrong, the symptoms can be very confusing:
- one side looks like it is transmitting continuously, but the other side receives only garbage
- a few characters come through correctly, then the rest become corrupted
- short messages work but longer ones fail
- it looks like a software bug when the real problem is a wrong electrical level
It is especially important to separate “UART frame format” from “physical electrical standard.” People often say “serial port,” but what they are really mixing together are two layers:
- UART: how bytes are framed
- TTL, RS-232, RS-485, and similar: what voltage and driving scheme the line uses
If a MCU’s 3.3V TTL UART is connected directly to a traditional RS-232 interface, that is not a parameter mismatch. It is an electrical-layer mismatch.
Why UART Does Not Handle Message Boundaries
SPI has chip-select windows, I2C has START/STOP, and UART only cuts the continuous voltage stream into characters. It can tell you “this is one byte,” but it usually cannot tell you by itself “this is one full application message.”
So a higher layer almost always needs to define message boundaries itself, for example:
- fixed-length frames
- special terminators, such as newline
- a length field
- a checksum or CRC
That is the usual UART boundary: it is good at moving a character stream reliably, but it is not good at expressing complex message semantics on its own.
Once you see that clearly, many engineering choices become natural:
- debug logs fit UART because they are already character streams
- CLI command ports fit UART because newlines can define boundaries
- more complex device protocols can still run over UART, but they must add their own framing and checks
So UART does not lack protocol. The link layer is just intentionally thin, and most of the responsibility is pushed upward.
What Flow Control Is Solving
If the sender keeps sending and the receiver cannot keep up, UART does not have I2C-style clock stretching, and it does not have the master’s total clock control like SPI. So it either drops data or uses flow control to buy buffer time.
The common flow-control families are:
- hardware flow control:
RTS/CTS - software flow control: for example
XON/XOFF
Hardware flow control uses extra control lines to tell the other side “pause for a moment” or “you can continue now.” Software flow control puts control characters directly into the data stream.
Both solve the same problem: how do we avoid overflowing the receive buffer when the link layer itself does not have a built-in backpressure mechanism?
Flow control also brings costs:
- extra pins or extra protocol conventions
- more complicated debugging
- software flow control consumes special character semantics
It is important to separate two things: no flow control does not mean UART cannot be used. But if you run at a high baud rate, transmit continuously for long periods, or have an unstable receiver, lack of flow control will make dropped bytes much more likely.
What a Typical UART Usage Path Looks Like
Many real UART links look roughly the same:
- Both sides agree on parameters such as
115200 8N1 - After power-up, the line stays idle high
- The sender transmits characters one by one
- The receiver detects the start bit and reconstructs bytes with local sampling
- The upper layer combines the byte stream into messages using newline, length fields, or a protocol header
For a debug port, that may just be startup logs printed line by line. For a module link, it may be AT commands and responses. For a device-specific protocol, it may be frame header + length + payload + checksum.
This main path is worth remembering because it explains both UART’s strengths and limits:
- the strengths are simplicity, compatibility, and low cost
- the limits are that message semantics and reliability often need higher-layer support
What UART Is Most Easily Mistaken For
UART is often mistaken for “if you choose a baud rate, sending and receiving will naturally be reliable.” That is usually not enough in real engineering.
It is indeed simple, but that simplicity hides many assumptions:
- both sides use exactly the same default parameters
- local clock error stays within a tolerable range
- the electrical level standard is correct
- the receiver can keep up with the data
- the application layer defines boundaries and needed checks
So UART is not a naturally reliable byte channel. It is a point-to-point serial link that works because a few small agreements are enough for many cases. If you expect it to behave like a thicker protocol, you will eventually run into dropped bytes, garbled data, sticky frames, and boundary errors.
What To Check First in Engineering
When implementing, integrating, or debugging UART, the highest-value checks are usually not the business payload. Start with the basic link agreement.
First, confirm the electrical level and the wiring direction. Are TX/RX crossed correctly, are the grounds shared, and are you mixing TTL UART with RS-232 by mistake? Those are the first things to rule out.
Second, confirm that the parameters match exactly, including baud rate, data bits, parity, and stop bits. In garbled-output bugs, this is often more common than any software issue.
Third, when short messages work but longer ones fail, suspect baud-rate error, slow receive interrupt handling, FIFO/buffer overflow, or missing flow control.
Fourth, if you see occasional single-character corruption, do not trust parity too much. It only detects some errors and cannot replace higher-layer checksums and retransmission.
Fifth, if you are designing your own serial protocol, do not treat UART’s byte framing as message framing. Length fields, terminators, timeouts, and CRC should be defined clearly at the higher layer.
When It Fits and When It Does Not
UART is a good fit for:
- point-to-point device communication
- debug ports, log output, and command-line interaction
- cases where throughput is not the priority and you want low implementation cost and simplicity
UART is not a good fit for:
- trying to share one bus among many devices while expecting natural addressing and arbitration
- highly reliable, high-throughput communication where you do not want to add higher-layer support yourself
- long-distance wiring with raw TTL levels
So choosing UART is not about it being “old and simple.” It is about the fact that, on many point-to-point engineering links, it works with the fewest assumptions: no clock wire, no chip select, no address, just enough electrical and parameter agreement to move bytes reliably.