In short: UART flow control stops a fast transmitter from overrunning a slow receiver, either with two dedicated wires (RTS/CTS hardware handshake) that gate transmission in real time, or with in-band XON/XOFF control bytes carried in the data stream itself.
A UART has no clock line and no built-in acknowledgement of whether the other side is ready. Once you push the baud rate up or let an interrupt-starved CPU fall behind, bytes arrive faster than the receiver can drain its FIFO — and the hardware silently discards them. Flow control is the mechanism that closes that gap. If you have not already, it helps to understand UART baud rate and framing first, because flow control exists precisely to protect that framing from being lost to overrun.
Why overrun happens in the first place
Consider a link running at 115200 baud, 8-N-1. Each character is 10 bit-times (1 start + 8 data + 1 stop), so a new byte lands every 10 / 115200 ≈ 86.8 µs. A classic 16550 UART has only a 16-byte receive FIFO. If the receiving driver's interrupt latency ever exceeds 16 × 86.8 µs ≈ 1.39 ms — a plausible stall under a burst of higher-priority interrupts — the 17th byte overwrites unread data and the Overrun Error (OE) bit sets. The byte is simply gone; there is no retransmission at the UART layer.
Rule of thumb: your worst-case receive-service latency must stay below FIFO_depth × (bits_per_frame / baud). Above that, you either lower the baud rate, deepen the FIFO, or add flow control. Flow control is usually the cheapest and most robust of the three.
RTS/CTS: hardware handshake
Hardware flow control uses two extra signals. In the modern (and most useful) interpretation, RTS is an output meaning "I am Ready To Receive — keep sending," and CTS is an input meaning "the other side is Clear To Send to me." They cross over between the two devices: one end's RTS drives the other end's CTS.
The receiver asserts RTS while its FIFO has room. When the FIFO crosses a high-water mark, it de-asserts RTS. The transmitter samples CTS before (and ideally during) each byte and holds off the moment CTS goes inactive. Because the signals are physical wires, the reaction is nearly instantaneous — far tighter than any in-band scheme — which is why RTS/CTS is the default for high-rate links like Bluetooth HCI UART at 3 Mbps or a GNSS module streaming at 921600 baud.
XON/XOFF: software (in-band) flow control
When you only have three wires — TXD, RXD, GND — you can still throttle the link using two reserved control characters sent in the data channel itself: XOFF (0x13, Ctrl-S, "stop") and XON (0x11, Ctrl-Q, "resume"). A receiver nearing a full buffer transmits XOFF back to the sender; the sender pauses until it sees XON.
The catch is fundamental: because the control bytes travel in-band, your payload must be 7-bit or otherwise guaranteed never to contain 0x11 or 0x13. Sending raw binary — a firmware image, a protobuf frame — over XON/XOFF will randomly pause the link or corrupt data when a legitimate 0x13 byte is interpreted as XOFF. XON/XOFF also reacts more slowly: the XOFF must itself queue, transmit, and be parsed, so several more bytes can arrive before the sender stops. For a terminal or a text log it is fine; for a binary protocol, reach for RTS/CTS.
| Property | RTS/CTS (hardware) | XON/XOFF (software) |
|---|---|---|
| Extra wires | 2 (RTS, CTS) | 0 |
| Response time | <1 byte-time | Several byte-times |
| Binary-safe | Yes | No (0x11/0x13 reserved) |
| Typical use | High-rate / binary links | Text terminals, legacy modems |
Configuring it in software
On Linux, termios exposes both: set CRTSCTS in c_cflag for hardware handshake, or IXON | IXOFF in c_iflag for XON/XOFF. A frequent field bug is enabling CRTSCTS on a board whose RTS/CTS pins were never routed to the connector — the link then hangs forever waiting on a CTS that can never assert. Always confirm the pins physically exist before enabling hardware flow control.
Verification tip: in simulation, SystemVerilog UVM testbenches driven under Synopsys VCS or Siemens Questa should include a deliberate "receiver-stall" sequence that holds RTS inactive mid-burst, and assert that zero payload bytes are lost. Overrun bugs rarely reproduce on the bench but surface constantly in the field.
Designing the UART RTL
If you are implementing a UART controller in RTL rather than using one off the shelf, the flow-control logic is a small FSM around the FIFO occupancy counter: assert RTS while count < high_water, de-assert at the mark, and re-assert at a low_water mark to add hysteresis so RTS does not chatter. Simulate it with Synopsys VCS or Cadence Xcelium, and formally prove the "no byte dropped while CTS honored" property with a tool such as Cadence JasperGold or Siemens Questa Formal. Our team at Avecas VLSI design services routinely closes exactly this kind of handshake FSM for silicon-bound serial IP.
Pay attention to clock-domain crossing: CTS is asynchronous to your UART clock and must be double-flop synchronized before it gates the transmit state machine, or metastability will occasionally let one extra byte slip after a de-assert.
Where this fits in a VLSI curriculum
Flow control is a favourite interview question because it forces you to reason about FIFO depth, latency budgets, and in-band versus out-of-band signalling all at once — the same trade-offs that reappear in AXI back-pressure and credit-based NoC links. Pair this with UART error detection with parity and framing for the complete receive-side picture, and see the full track on our protocol and RTL courses.
Ready to build a flow-controlled UART from spec to synthesizable RTL and a self-checking testbench? The CourseTron Protocols & RTL Design track walks you through it line by line — enrol and tape out your first serial IP.
Frequently asked questions
Is RTS/CTS always faster than XON/XOFF?
In reaction time, yes. Hardware lines assert within a bit-time, while an XOFF character must be queued, transmitted and parsed, so several bytes can still arrive before the sender stops. XON/XOFF is only acceptable when you have spare buffer margin and a text-only payload.
Can I use XON/XOFF with binary data?
Not safely. The payload would have to escape or never contain 0x11 and 0x13, otherwise a data byte is mistaken for a control character — pausing the link or corrupting the stream. Use RTS/CTS for binary.
What is the OE bit and how do I recover from it?
The Overrun Error bit sets when a new byte arrives before the previous one was read from the FIFO; that byte is lost. Recovery is protocol-level: a higher-layer checksum or retransmit must detect the gap. Preventing it with flow control is far better than recovering from it.
Do I need flow control at low baud rates?
Often not — at 9600 baud a byte arrives only every ~1 ms, giving most CPUs ample service time. Flow control becomes essential as baud climbs, FIFOs stay shallow, or interrupt latency grows unpredictable.