Quick Cap
UART (Universal Asynchronous Receiver-Transmitter) is an asynchronous serial communication peripheral that provides two-wire (TX/RX) communication between devices using configurable baud rates, frame formats, and flow control. It is the most ubiquitous serial interface in embedded systems, used for debug consoles, GPS modules, Bluetooth radios, sensor bridges, and inter-processor communication.
Interviewers probe UART to test whether you understand asynchronous timing, frame structure, error detection mechanisms, and the difference between the UART peripheral and the electrical standards (RS-232, RS-485) that carry its signals.
Key Facts:
- Asynchronous communication: No shared clock signal — uses start/stop bits for synchronization
- Configurable framing: Start bit, data bits (5-8), optional parity bit, stop bits (1-2)
- Baud rate selection: Data transmission rate from 300 bps to several Mbps
- Flow control: Hardware (RTS/CTS) or software (XON/XOFF) flow control mechanisms
- DMA integration: Hardware-assisted data transfer for improved performance
- Error detection: Parity, framing, overrun, and timeout error detection
Deep Dive
At a Glance
| Feature | Detail |
|---|---|
| Wires | 2 minimum (TX, RX); 4 with flow control (+ RTS, CTS) |
| Clock | Asynchronous — no shared clock; both sides agree on baud rate |
| Duplex | Full-duplex (TX and RX are independent lines) |
| Topology | Point-to-point (one transmitter, one receiver per line) |
| Typical Speeds | 9600 bps - 921600 bps (up to several Mbps on some MCUs) |
| Data Format | Framed: start bit + data + optional parity + stop bit(s) |
| Error Detection | Parity bit, framing error, overrun detection (hardware flags) |
| Addressing | None — point-to-point only (multi-drop requires RS-485 or a protocol layer) |
How UART Works
UART communication is asynchronous — there is no shared clock wire between transmitter and receiver. Instead, both sides must independently agree on the same baud rate (bit timing) before communication begins. This is fundamentally different from synchronous protocols like SPI and I2C, where a master generates a clock that the slave follows. In UART, both sides run their own clocks and trust that they are close enough in frequency to stay synchronized for the duration of one frame.
Each byte is wrapped in a frame that provides synchronization:
- The line sits idle at logic high (called the mark state)
- The transmitter pulls the line low for one bit period — this is the start bit
- Data bits are clocked out LSB-first (most common) at the agreed baud rate
- An optional parity bit follows the data for basic error detection
- The line returns to high for 1 or 2 bit periods — the stop bit(s)
- The receiver uses the falling edge of the start bit to synchronize its internal sampling clock, then samples each subsequent bit at the center of the bit period
The receiver typically oversamples the line at 16x the baud rate. When it detects the falling edge of the start bit, it counts 8 oversampling ticks to reach the center of the start bit, verifies it is still low (to reject noise glitches), then samples each subsequent bit at 16-tick intervals. This oversampling provides noise immunity and allows the receiver to tolerate small differences in baud rate between the two sides.
Because TX and RX are separate wires, data can flow in both directions simultaneously (full-duplex). Unlike SPI, there is no master/slave relationship — both sides can transmit independently at any time.
Frame Structure
A UART frame contains the following fields, transmitted in order from left to right:
| Field | Bits | Value | Purpose |
|---|---|---|---|
| Idle | - | Logic 1 (high) | Line state between frames; can be any duration |
| Start bit | 1 | Always 0 (low) | Signals frame start; receiver synchronizes on this falling edge |
| Data bits | 5-8 (usually 8) | Payload | The actual data, transmitted LSB first |
| Parity bit | 0 or 1 | Computed | Even or odd parity for single-bit error detection |
| Stop bit(s) | 1 or 2 | Always 1 (high) | Guarantees line returns to idle; provides timing margin |
The most common configuration is 8N1 — 8 data bits, no parity, 1 stop bit — which gives 10 total bits per byte on the wire (1 start + 8 data + 1 stop). This means the effective data rate is 80% of the raw bit rate. At 115200 baud, the throughput is approximately 11,520 bytes per second.
Other common configurations:
| Notation | Data Bits | Parity | Stop Bits | Total Bits/Byte | Typical Use |
|---|---|---|---|---|---|
| 8N1 | 8 | None | 1 | 10 | Most common default |
| 8E1 | 8 | Even | 1 | 11 | Modbus RTU, industrial protocols |
| 8O1 | 8 | Odd | 1 | 11 | Some legacy systems |
| 8N2 | 8 | None | 2 | 12 | Extra timing margin for slow receivers |
| 7E1 | 7 | Even | 1 | 10 | ASCII-only protocols, legacy terminals |
Parity explained: Even parity sets the parity bit so the total number of 1s (data + parity) is even. Odd parity makes it odd. Parity catches single-bit errors but cannot correct them and misses even numbers of bit errors (e.g., two bits flipped). For stronger error detection, use a CRC or checksum at the application protocol layer. Many modern protocols (including the common 8N1 default) skip parity entirely and rely on higher-layer checksums instead.
UART Frame Diagram
- Idle (gray) — Line held HIGH (mark state) between frames.
- Start (red) — Always LOW. Receiver synchronizes on this falling edge.
- Data (blue) — 8 bits, transmitted LSB first. No clock — timed by baud rate.
- Parity (orange) — Optional even/odd parity for single-bit error detection.
- Stop (green) — Always HIGH. Returns line to idle, provides timing margin.
UART vs RS-232 vs RS-485
This is a frequent interview question. UART is a digital peripheral inside the MCU. RS-232 and RS-485 are electrical signaling standards that define voltage levels and wiring for carrying UART data over cables. They operate at different layers:
| Feature | UART (TTL) | RS-232 | RS-485 |
|---|---|---|---|
| What it is | MCU peripheral (logic-level signals) | Voltage standard for serial links | Differential voltage standard |
| Logic levels | 0V / 3.3V or 5V | +3 to +15V (space/0) / -3 to -15V (mark/1) | Differential pair, +/- 1.5V to 6V |
| Max distance | PCB-level (inches to a few feet) | ~15 m (50 ft) | ~1200 m (4000 ft) |
| Topology | Point-to-point | Point-to-point | Multi-drop bus (up to 32+ nodes) |
| Common connector | Header pins, no standard | DB-9 / DB-25 | Screw terminals (A/B differential pair) |
| Noise immunity | Low (single-ended, short distance) | Moderate (wider voltage swing) | High (differential signaling rejects common-mode noise) |
| Half/Full duplex | Full-duplex (separate TX/RX) | Full-duplex (separate TX/RX wires) | Half-duplex (shared differential pair) or full-duplex (2 pairs) |
| Typical use | MCU-to-MCU on same PCB, debug header | PC serial ports, legacy lab equipment | Industrial networks, Modbus RTU, building automation |
Key points for interviews:
- To connect a 3.3V UART MCU to an RS-232 device, you need a level shifter IC (e.g., MAX232, MAX3232). RS-232 uses inverted logic and +/- 12V swings that will damage an unprotected MCU pin.
- To connect to an RS-485 bus, you need a differential transceiver IC (e.g., MAX485, SN75176). The transceiver converts between single-ended UART and the differential A/B pair. On a half-duplex RS-485 bus, firmware must control the transceiver's direction enable (DE) pin — switching from receive to transmit mode before sending data and back to receive mode afterward.
- The UART peripheral configuration (baud rate, framing) is the same regardless of which electrical standard carries the signal. Only the external interface circuitry changes.
Connecting MCU UART pins (3.3V) directly to RS-232 signals (+/- 12V) will damage the MCU. Always use a level-shifting IC. This is a common bench mistake that interviewers specifically ask about.
Baud Rate
The baud rate is the number of bits transmitted per second. Both sides must use the same baud rate — there is no clock wire to enforce synchronization, so a mismatch produces garbage data. This is the single most common cause of "I see random characters on my terminal" problems.
Common baud rates:
| Baud Rate | Bit Period | Byte Time (8N1) | Throughput | Typical Use |
|---|---|---|---|---|
| 9600 | 104.2 us | 1.04 ms | ~960 B/s | GPS modules, low-speed sensors, Modbus |
| 19200 | 52.1 us | 521 us | ~1920 B/s | MIDI (31250 is also common) |
| 38400 | 26.0 us | 260 us | ~3840 B/s | Medium-speed peripherals |
| 115200 | 8.68 us | 86.8 us | ~11.5 KB/s | Debug consoles, general purpose |
| 460800 | 2.17 us | 21.7 us | ~46 KB/s | High-speed data logging |
| 921600 | 1.09 us | 10.9 us | ~92 KB/s | Fast firmware downloads |
How baud rate is generated: The MCU divides its peripheral clock by a prescaler to produce the bit-rate clock. The baud rate register value is typically calculated as:
BRR = F_CLK / (OVERSAMPLING * BAUD_RATE)
Because this division is integer (or fixed-point with limited fractional bits), the actual baud rate may not exactly match the target. For example, with a 72 MHz clock and 16x oversampling, requesting 115200 baud gives 72000000 / (16 * 115200) = 39.0625. Rounding to 39 gives an actual baud rate of 115384 bps — an error of 0.16%, which is well within tolerance.
The 3% tolerance rule: The accumulated timing error between transmitter and receiver must stay within approximately 3-5% of a bit period over the length of one frame (10-12 bits). Beyond this threshold, the receiver's sampling point drifts into the adjacent bit, causing framing errors. Practical implications:
- Each side should individually be within ~1.5% of the target baud rate
- Cheap internal RC oscillators (5-10% tolerance) often cannot sustain reliable UART, especially at high baud rates — use a crystal or ceramic resonator
- Higher baud rates are more sensitive to clock error because the absolute timing margin (in microseconds) shrinks proportionally
- Temperature drift matters: an RC oscillator that works at 25C on the bench may drift out of tolerance at 85C in the field
- Some MCUs support 8x oversampling (instead of 16x) to achieve higher baud rates from a given clock, but this halves the noise immunity margin
If you see seemingly random garbage on a UART terminal, the most common cause is a baud rate mismatch. Verify both sides are configured identically and that the MCU's clock source is accurate enough for the chosen baud rate.
Common Errors
UART hardware flags these errors automatically via status register bits. Understanding them is critical for debugging and is a frequent interview topic:
| Error | What Happened | Common Cause | How to Handle |
|---|---|---|---|
| Framing Error | Stop bit was not logic-high when expected | Baud rate mismatch, electrical noise, wrong data format (e.g., 7-bit vs 8-bit), or TX/RX lines swapped | Verify baud rate and frame settings on both sides; check wiring |
| Parity Error | Computed parity does not match received parity bit | Bit corruption from electrical noise or EMI | Flag and discard the byte; request retransmission at the protocol layer |
| Overrun Error | New byte arrived before the previous byte was read from the data register | CPU too slow to service the UART, or no interrupt/DMA configured | Use interrupt-driven or DMA reception with a ring buffer |
| Break Condition | RX line held low for longer than one complete frame period | Deliberate break signal from the remote side, or cable disconnected/cut | Detect and handle as a special event (e.g., reset protocol state machine) |
| Noise Error | Oversampling detected inconsistent bit values during a single bit period | Electrical noise, poor signal integrity, or marginal baud rate match | Check wiring, grounding, and signal quality; reduce baud rate if needed |
Overrun is the most commonly encountered error in practice and the most dangerous because it is silent — you lose data without any obvious symptom unless you explicitly check the overrun error flag. Every byte that arrives while the previous byte sits unread in the data register is lost. This is why interrupt-driven reception with a ring buffer is the standard approach for any non-trivial UART usage.
Framing errors are the second most common and are almost always caused by a baud rate mismatch or by the receiver starting mid-frame (e.g., after a reset while the other side is transmitting). If you see persistent framing errors, the first thing to check is that baud rate, data bits, parity, and stop bits match on both sides.
Flow Control
Flow control prevents a fast transmitter from overwhelming a slow receiver. Without it, if the receiver cannot process incoming bytes fast enough, data is lost to overrun errors. There are two approaches:
| Feature | Hardware (RTS/CTS) | Software (XON/XOFF) |
|---|---|---|
| Signals | RTS (output from receiver) and CTS (input to transmitter) — 2 extra GPIO lines | XON (0x11) and XOFF (0x13) characters sent in-band on the existing TX/RX lines |
| Response time | Near-instantaneous (hardware-level, ~1 bit period) | Delayed by software processing time (potentially multiple byte times) |
| Extra wiring | 2 additional wires | None |
| Bandwidth cost | None (out-of-band signaling) | Slight — XON/XOFF bytes consume bandwidth |
| Binary data safe? | Yes — does not interfere with data content | No — 0x11 and 0x13 cannot appear in the payload without escaping |
| Typical use | High-speed links, Bluetooth/Wi-Fi modules, any binary protocol | Legacy terminals, text-based protocols, simple ASCII streams |
| How it works | Receiver deasserts RTS when its buffer is nearly full; transmitter pauses when it sees CTS deasserted | Receiver sends XOFF character to pause remote transmitter; sends XON character to resume |
Hardware flow control (RTS/CTS) is preferred for any high-speed or binary-data link. Many Bluetooth modules (e.g., HC-05, RN42) and Wi-Fi modules (e.g., ESP32) require or strongly recommend hardware flow control. In MCUs, RTS and CTS are often handled automatically by the UART peripheral hardware — once enabled, the peripheral will pause transmission when CTS is deasserted without any software intervention.
Software flow control (XON/XOFF) is simpler to wire (no extra pins) but has significant limitations. The receiver must parse every incoming byte to detect flow control characters, and there is inherent latency — by the time the transmitter receives and processes an XOFF, it may have already sent several more bytes. Software flow control is only safe for ASCII/text streams where the XON and XOFF byte values (0x11, 0x13) do not appear in the payload.
Data Transfer Methods
| Method | CPU Overhead | Throughput | Latency | Best For |
|---|---|---|---|---|
| Polling | High (CPU busy-waits on status flags) | Low | Lowest (immediate check) | Simple single-byte debug prints, bare-metal prototyping |
| Interrupt | Medium (ISR fires per byte, ~1-5 us each) | Medium | Low (ISR responds within interrupt latency) | General-purpose RX/TX with ring buffer — the standard approach |
| DMA | Very low (hardware moves data to/from memory) | High | Higher (processes data in chunks) | Bulk transfers, continuous data logging, firmware downloads |
Polling is the simplest approach: the CPU reads the status register in a loop, waiting for the transmit-empty or receive-not-empty flag. This works for quick debug prints but wastes CPU cycles and is unsuitable for receiving data (the CPU must poll faster than bytes arrive, or data is lost).
Interrupt-driven with a ring buffer is the standard production approach. The UART receive interrupt fires each time a byte arrives. The ISR reads the byte from the hardware data register and pushes it into a circular (ring) buffer. The main loop or an RTOS task reads from the buffer at its own pace. This decouples reception timing from processing timing and prevents overrun errors as long as the buffer is large enough to absorb bursts.
/* Conceptual ring buffer — shows the pattern, not a complete driver.* In production, BUF_SIZE should be a power of 2 so the modulo* operation compiles to a bitwise AND.*/#define BUF_SIZE 256typedef struct {uint8_t data[BUF_SIZE];volatile uint16_t head; /* ISR writes here */volatile uint16_t tail; /* Main loop reads here */} ring_buf_t;/* Called from UART RX interrupt handler */void ring_buf_push(ring_buf_t *rb, uint8_t byte) {uint16_t next = (rb->head + 1) % BUF_SIZE;if (next != rb->tail) { /* buffer not full */rb->data[rb->head] = byte;rb->head = next;}/* If full, the byte is silently dropped.* In a real system, assert flow control (RTS) or increment* an overrun counter here.*/}/* Called from main loop or RTOS task */bool ring_buf_pop(ring_buf_t *rb, uint8_t *byte) {if (rb->head == rb->tail) {return false; /* buffer empty */}*byte = rb->data[rb->tail];rb->tail = (rb->tail + 1) % BUF_SIZE;return true;}
Buffer sizing: The ring buffer must be large enough to hold all bytes that can arrive during the longest period the main loop (or task) might be busy before it processes the buffer. For example, at 115200 baud (8N1), bytes arrive every 86.8 us. If the main loop has a worst-case busy period of 10 ms, you need at least 10ms / 86.8us = 116 bytes of buffer. Round up to the next power of 2 (128 or 256) for efficient modulo arithmetic. In an RTOS, the task's worst-case blocking time determines the minimum buffer size.
DMA is the right choice for high-throughput streams (GPS NMEA sentences, sensor data logging, firmware download over UART). Configure DMA to write incoming bytes directly into a memory buffer without CPU involvement. Use the half-transfer and transfer-complete DMA interrupts — or the UART peripheral's idle-line detection interrupt — to process data in chunks rather than byte-by-byte. Idle-line detection is particularly useful for variable-length messages: the UART peripheral detects when the RX line has been idle for one frame period and triggers an interrupt, telling you that a burst of data has finished.
For DMA transmit, load the source buffer and transfer length into the DMA channel, then start the transfer. The DMA engine feeds bytes to the UART transmit register automatically. A transfer-complete interrupt signals when all bytes have been sent, allowing you to load the next buffer or free the memory.
Low-Power Considerations
UART has unique power implications because the peripheral must remain active to detect incoming data:
- Idle current: Even when no data is being transmitted, the UART receiver consumes power to monitor the RX line for incoming start bits. In battery-powered systems, this always-on receiver is a significant power drain.
- Wake-on-UART: Many MCUs support waking from low-power sleep modes when activity is detected on the UART RX pin. The UART peripheral (or a GPIO edge interrupt on the RX pin) detects the falling edge of a start bit and triggers a wake-up. However, the first byte is often lost because the MCU takes time to wake and configure clocks — the transmitter should send a preamble or wake-up byte before the actual data.
- Baud rate and power: Lower baud rates keep the transmitter active longer per byte, consuming more energy per byte transferred. For battery-powered devices, it is often more power-efficient to use a higher baud rate (transmit quickly, then sleep) than a lower one.
- Peripheral clock gating: Disable the UART peripheral clock when not in use to save power. Re-enable it before transmission and ensure the baud rate generator is stable before sending data.
Debugging Story: The Automotive Data Corruption
A team was debugging an automotive sensor node that received corrupted UART data intermittently. The system worked perfectly on the bench at room temperature but produced garbled readings in the vehicle — roughly 1 in 20 messages arrived with bad data. The SPI and I2C buses on the same board were unaffected, which ruled out general EMI problems.
After connecting an oscilloscope to the UART lines, they measured the actual bit timing and compared it to the expected 115200 baud period (8.68 us). The transmitter was accurate (crystal-based), but the receiver's bit period was 9.03 us — a 4% error. The receiver MCU was clocked from its internal RC oscillator. At 25C on the bench, the RC oscillator was within 1% of nominal. But at 85C inside the engine compartment, thermal drift pushed it to 4%, exceeding the 3% tolerance window.
The fix was straightforward: switch the receiver MCU's clock source from the internal RC oscillator to an external 8 MHz crystal. Total cost: one crystal and two capacitors. The real lesson was deeper.
Lesson: For any UART link that must work across temperature ranges, use a crystal or ceramic resonator as the clock source — not the internal RC oscillator. The baud rate tolerance margin is tighter than most engineers assume, and temperature-induced clock drift is the most common cause of intermittent UART failures in deployed products. Always calculate and verify the worst-case baud rate error across your full operating temperature range during design review, not after field failures.
Wiring and Cross-Connection
UART wiring is simple but has one rule that trips up beginners and occasionally experienced engineers:
TX connects to RX, and RX connects to TX. This is a cross-connection — the transmitter of one device connects to the receiver of the other, and vice versa. Additionally, both devices must share a common ground (GND).
| Connection | Device A | Device B |
|---|---|---|
| Transmit data | TX (pin out) | RX (pin in) |
| Receive data | RX (pin in) | TX (pin out) |
| Ground | GND | GND |
| Flow control (optional) | RTS (out) | CTS (in) |
| Flow control (optional) | CTS (in) | RTS (out) |
Common wiring mistakes (and interview discussion points):
- TX-to-TX connection: Both sides drive the line simultaneously — causes bus contention, possible damage, definitely no communication. This is the most common beginner mistake.
- Missing ground: Without a shared ground reference, voltage levels are meaningless. The UART may appear to work intermittently or produce noise-corrupted data.
- Voltage mismatch: Connecting a 5V UART TX directly to a 3.3V UART RX can damage the 3.3V device. Use a level shifter or voltage divider.
- Swapped RTS/CTS: If hardware flow control is enabled but RTS and CTS are swapped, the link will hang immediately — the transmitter sees CTS deasserted and refuses to send.
Loopback testing: To verify that the UART peripheral and driver software are working, connect TX to RX on the same device (with flow control disabled). Any byte transmitted should be immediately received. This isolates the UART from the external wiring and remote device, making it the first step in debugging a non-working link.
Common UART-Based Protocols
UART is a transport layer — it moves bytes. Many higher-level protocols are built on top of UART and are commonly encountered in embedded systems:
| Protocol | Baud Rate | Frame Format | Description |
|---|---|---|---|
| Modbus RTU | 9600-115200 | 8E1 (8 data, even parity, 1 stop) | Industrial automation standard; uses device addresses and CRC-16 for multi-drop RS-485 communication |
| NMEA 0183 | 4800 or 9600 (default) | 8N1 | GPS sentence format; ASCII text lines starting with $ and ending with * + checksum |
| AT commands | 9600-115200 | 8N1 | Text-based command/response protocol for modems, Bluetooth (HC-05), Wi-Fi (ESP8266), cellular modules |
| CLI / Debug console | 115200 (common) | 8N1 | Human-readable command line interface for debugging and configuration |
| SBUS | 100000 | 8E2 (inverted) | RC receiver protocol; 25-byte frames at a non-standard baud rate with inverted signal polarity |
| DMX512 | 250000 | 8N2 | Stage lighting control; uses RS-485 physical layer with break-based framing |
Understanding these protocols helps in interviews because it shows you can connect UART knowledge to real applications. For example, knowing that Modbus RTU uses 8E1 framing (not the default 8N1) and that SBUS uses an inverted, non-standard baud rate demonstrates practical experience.
Debugging UART Issues — A Systematic Approach
UART debugging follows a logical elimination process. When communication fails, work through these steps in order:
Step 1: Verify physical connectivity
- Are TX and RX cross-connected (TX-to-RX, not TX-to-TX)?
- Is GND connected between both devices?
- Are voltage levels compatible (3.3V vs 5V)?
- Is a level shifter or transceiver present if needed (RS-232, RS-485)?
Step 2: Verify configuration match
- Same baud rate on both sides?
- Same data bits, parity, and stop bits (e.g., both 8N1)?
- Is flow control configured identically (both enabled or both disabled)?
Step 3: Use loopback to isolate
- Connect TX to RX on the suspect device (disconnect the other side)
- Transmit known data and verify it is received correctly
- If loopback fails, the problem is in the local UART configuration or driver
- If loopback works, the problem is in the wiring or the remote device
Step 4: Use an oscilloscope or logic analyzer
- Measure the actual bit period and compare to the expected value — this catches baud rate errors caused by clock inaccuracy
- Verify the start bit, data bits, and stop bit are present and correctly timed
- Check voltage levels (especially if crossing voltage domains)
- Look for noise, ringing, or incomplete transitions
Step 5: Check error flags
- Read the UART status register after each failed reception
- Framing errors point to baud rate or format mismatch
- Overrun errors point to software not reading data fast enough
- Parity errors point to electrical noise
If you receive the first few bytes correctly but then data becomes corrupted or missing, the most likely cause is an overrun error — the software is not reading received bytes fast enough. Switch from polling to interrupt-driven reception with a ring buffer.
UART in Embedded Linux
In embedded Linux, UART ports appear as tty device files (e.g., /dev/ttyS0, /dev/ttyAMA0, /dev/ttyUSB0). The kernel's serial subsystem handles the low-level UART peripheral configuration, and user-space applications interact with the port through the standard POSIX termios API.
Key concepts for interviews:
| Concept | Detail |
|---|---|
| Device node | /dev/ttySn for built-in UARTs, /dev/ttyUSBn for USB-to-serial adapters, /dev/ttyAMAn for ARM AMBA UARTs |
| termios API | tcgetattr() / tcsetattr() to configure baud rate, framing, flow control, and input/output processing |
| Canonical vs raw mode | Canonical mode buffers input until a newline (for interactive terminals); raw mode delivers bytes as they arrive (for binary protocols) |
| Console vs application port | The kernel may use one UART as the boot console (console=ttyS0,115200 in kernel command line); application UARTs need separate configuration |
| DTS (Device Tree) | UART pin assignments, clock sources, and default configuration are defined in the device tree; the driver reads these at probe time |
Common interview question: "How do you configure a UART port for a binary protocol in Linux?" The key answer points are: open the device file, use tcgetattr() to read current settings, set raw mode with cfmakeraw(), set baud rate with cfsetspeed(), configure 8N1, disable flow control, set VMIN/VTIME for read behavior, then apply with tcsetattr(). This is the standard pattern for GPS parsers, Modbus masters, and sensor bridges running on Linux SBCs.
Comparison: UART vs SPI vs I2C
| Criteria | UART | SPI | I2C |
|---|---|---|---|
| Clock | Asynchronous (no clock wire) | Synchronous (master generates clock) | Synchronous (master generates clock) |
| Wires | 2 (TX/RX) | 4+ (SCK/MOSI/MISO/CS) | 2 (SDA/SCL) |
| Speed | Up to ~1 Mbps typical | 1 - 50+ MHz | 100 kHz - 3.4 MHz |
| Duplex | Full-duplex | Full-duplex | Half-duplex |
| Topology | Point-to-point | Single master, multiple slaves (via CS) | Multi-master, multi-slave (via addresses) |
| Addressing | None (point-to-point only) | CS lines select the slave | 7-bit or 10-bit device addresses |
| Error detection | Parity bit (hardware) | None built-in | ACK/NACK per byte |
| Flow control | RTS/CTS or XON/XOFF | None (master controls clock) | Clock stretching (slave holds SCL low) |
| Typical use | Debug console, GPS, Bluetooth, sensor bridges | Flash, ADC/DAC, displays, RF transceivers | Sensors, EEPROM, RTC, GPIO expanders |
When to choose UART: You need a simple two-wire link between two devices that can transmit independently (no master/slave), or the peripheral only offers a UART interface (GPS, Bluetooth, many wireless modules). UART is also the standard choice for human-readable debug consoles. UART excels where the link is point-to-point, moderate speed, and does not need to be shared — exactly the scenario for a debug console, a GPS receiver, or a Bluetooth module.
When to choose SPI or I2C instead: When you need to connect multiple peripherals (SPI/I2C scale better), when you need higher speed (SPI can run at 10-50 MHz vs UART's typical 1 Mbps), or when you are extremely pin-constrained (I2C uses only 2 wires for any number of devices). If you need multi-drop communication over long cables, UART over RS-485 with a protocol like Modbus is the standard approach, but it requires half-duplex management and transceiver direction control.
Multi-UART systems: Most MCUs provide 2-6 UART peripherals. When a design needs more UART ports than the MCU has, options include:
- Using a UART multiplexer IC
- Using USB-to-UART bridge ICs (e.g., FTDI FT2232 provides 2 UARTs over a single USB port)
- Using a software UART (bit-banging) on GPIO pins — adequate for low baud rates but consumes significant CPU time
- Multiplexing at the protocol level (time-division or addressing) on a single physical UART with RS-485
What interviewers want to hear: You understand the UART frame structure and why asynchronous communication requires tight baud rate matching. You can explain the difference between the UART peripheral and the electrical standards (RS-232/RS-485). You know when to use flow control and which type. You can reason about polling vs interrupt vs DMA trade-offs for different throughput requirements. You understand that overrun is the most dangerous UART error because it is silent.
Interview Focus
Classic UART Interview Questions
Q1: "How do you configure UART for different baud rates, and what factors affect baud rate accuracy?"
Model Answer Starter: "I configure UART baud rates by calculating the baud rate divider value based on the UART clock frequency and desired baud rate. The formula is Baud_Rate_Divider = UART_Clock / (16 * Baud_Rate) for most UART implementations. Key factors affecting accuracy include the UART clock frequency stability, crystal oscillator accuracy, temperature effects, and the granularity of the baud rate divider. For high accuracy, I use precise crystal oscillators with low temperature coefficients, ensure the UART clock is derived from a stable source, and verify that the baud rate error is within acceptable limits (typically less than 3% for reliable communication). I also consider using oversampling techniques and error correction methods for critical applications."
Q2: "What's the difference between hardware and software flow control, and when would you use each?"
Model Answer Starter: "Hardware flow control uses dedicated RTS (Request to Send) and CTS (Clear to Send) signals to control data flow, providing immediate response and working at the hardware level. Software flow control uses XON/XOFF characters embedded in the data stream, requiring software interpretation and adding overhead. I use hardware flow control for high-speed communication, real-time systems, and when immediate flow control is needed. I use software flow control when hardware lines are not available, for simple applications, or when compatibility with legacy systems is required. Hardware flow control is more reliable and doesn't consume bandwidth, while software flow control is simpler to implement but can interfere with binary data transmission."
Q3: "How do you handle UART errors and data corruption, and what are the common error types?"
Model Answer Starter: "Common UART errors include parity errors (data corruption detected by parity bit), framing errors (incorrect start/stop bit detection), overrun errors (new data received before previous data was read), and timeout errors (no data received within expected time). I handle errors by implementing comprehensive error detection in interrupt handlers, using circular buffers to prevent data loss, implementing timeout mechanisms for reliable communication, and using checksums or CRC for data integrity verification. I also implement automatic retry mechanisms, proper buffer management to prevent overruns, and error logging for debugging. For critical applications, I use redundant communication paths or error correction codes."
Q4: "What causes UART communication failures, and how do you debug them?"
Model Answer Starter: "UART failures are typically caused by baud rate mismatches, incorrect frame format configuration, electrical issues like poor signal integrity or incorrect voltage levels, timing problems from clock inaccuracies, or buffer overruns from insufficient processing speed. I debug by using oscilloscopes to verify signal timing and voltage levels, checking baud rate calculations and clock accuracy, verifying frame format settings match between transmitter and receiver, implementing comprehensive error detection and logging, and using loopback tests to isolate hardware vs software issues. I also check for proper grounding, signal termination, and EMI interference that can cause communication failures."
Q5: "How do you implement UART with DMA, and what are the performance benefits?"
Model Answer Starter: "I implement UART with DMA by configuring DMA channels for both transmit and receive operations, setting up circular buffers for continuous data transfer, and using DMA interrupts for buffer management. DMA benefits include reduced CPU overhead by handling data transfer in hardware, improved system performance by allowing the CPU to perform other tasks, better real-time response by eliminating polling overhead, and more efficient buffer management for high-speed data streams. I configure DMA with appropriate transfer sizes, use double buffering for seamless data transfer, implement proper interrupt handling for buffer switching, and ensure proper synchronization between DMA and UART operations. This is especially beneficial for applications requiring continuous data logging or high-throughput communication."
Trap Alerts
- Don't say: "UART is just serial communication" — it has specific timing and framing requirements
- Don't forget: Baud rate accuracy requirements and clock stability considerations
- Don't ignore: Error handling and buffer management for reliable communication
Follow-up Questions
- "How would you implement UART communication in a system with multiple UART interfaces?"
- "What's your strategy for UART communication recovery after errors or timeouts?"
- "How do you handle UART communication in a system with mixed data types (binary and text)?"
Ready to test yourself? Head over to the UART Interview Questions page for a full set of Q&A with collapsible answers — perfect for self-study and mock interview practice.
Practice
❓ What is the minimum number of signals required for UART communication?
❓ What is the purpose of the start bit in UART communication?
❓ What is the main advantage of DMA with UART communication?
❓ What typically causes a UART framing error?
❓ What is the key difference between UART and RS-232?
Real-World Tie-In
Wireless Sensor Network Gateway
In the field, I worked on a gateway communicating with multiple sensor nodes via UART at different baud rates. I used DMA with idle-line detection for efficient reception, hardware flow control on the high-speed links, and ring buffers sized to absorb burst traffic. Proper crystal-based clocking and isolated RS-485 transceivers for the long cable runs gave us 99.9% data integrity in an electrically noisy industrial environment.
Industrial Process Monitoring
On the job, we built a monitoring system that needed a UART debug console, a sensor bridge at 115200 baud, and a Modbus RS-485 link at 9600 baud — all running simultaneously. The key challenges were managing three UART peripherals with different baud rates and frame formats, preventing overrun on the sensor stream by using DMA, and ensuring the RS-485 transceiver direction control (DE pin) toggled at the right time to avoid bus contention.