Loading header...

UART — Universal Asynchronous Receiver-Transmitter

UART is the oldest and simplest serial communication protocol. It is on every microcontroller ever made — it is how your Arduino talks to your PC, how a GPS module sends coordinates, and how a Bluetooth module accepts commands.


The Core Idea — Both Sides Agree on Speed

Most protocols send a clock signal so both sides stay in sync. UART does not. Instead, both sides agree on a speed before they start talking — this speed is called the baud rate. Once agreed, each device uses its own internal clock to time the bits.

flowchart LR classDef dev fill:#dbeafe,stroke:#2563eb,color:#1e3a5f classDef wire fill:#fef9c3,stroke:#ca8a04,color:#713f12 A["Device A\n'I will send at\n9600 bits/second'"]:::dev B["Device B\n'I will listen at\n9600 bits/second'"]:::dev W["TX → RX\nRX ← TX\nGND — GND\n\nNo clock wire needed"]:::wire A --- W --- B

This simplicity is why UART has survived 50+ years. The tradeoff: if the two clocks drift apart over time, bits get misread. At normal speeds this is never a problem.


The UART Frame — How One Byte is Sent

UART does not just blast bits onto the wire. Each byte is wrapped in a frame — a precise sequence that tells the receiver exactly where the byte starts and ends.

flowchart LR classDef idle fill:#f1f5f9,stroke:#94a3b8,color:#334155 classDef start fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef data fill:#dbeafe,stroke:#2563eb,color:#1e3a5f classDef stop fill:#dcfce7,stroke:#16a34a,color:#14532d IDLE1["Line idle\nHIGH"]:::idle START["Start bit\nALWAYS LOW\n'byte coming'"]:::start DATA["8 Data bits\nD0 → D7\nLSB sent first"]:::data STOP["Stop bit\nALWAYS HIGH\n'byte done'"]:::stop IDLE2["Line idle\nHIGH"]:::idle IDLE1 --> START --> DATA --> STOP --> IDLE2
Part Value Purpose
Idle HIGH Line sits high when nothing is being sent
Start bit LOW Tells receiver "a byte is about to arrive"
Data bits (D0–D7) 0 or 1 The actual byte, sent LSB first
Stop bit HIGH Tells receiver "that byte is done"

The shorthand 8N1 means: 8 data bits, No parity, 1 stop bit. You will see this everywhere — it is the standard for almost all UART communication.

Here is what the TX line actually looks like on an oscilloscope when sending the letter 'A' (ASCII 65 = binary 01000001):


What Baud Rate Actually Means

Baud = symbols per second. For UART each symbol is one bit, so baud = bits per second.

At 9600 baud, one bit takes about 104 microseconds. A full 10-bit frame (start + 8 data + stop) takes about 1 millisecond. That means you can send roughly 960 bytes per second at 9600 baud.

Common baud rates and when to use them:

Baud rate Bytes/second Typical use
9600 960 General debugging, GPS, GSM
19200 1,920 Slightly faster sensors
38400 3,840 Some GPS and radio modules
115200 11,520 Fast debugging, Bluetooth modules

[!TIP] When connecting two devices and seeing garbage characters in the terminal, baud rate mismatch is almost always the cause. Both sides must be set to exactly the same number.


How a UART Transmission Works — Step by Step

flowchart TD classDef tx fill:#dbeafe,stroke:#2563eb,color:#1e3a5f classDef rx fill:#dcfce7,stroke:#16a34a,color:#14532d classDef both fill:#fef9c3,stroke:#ca8a04,color:#713f12 A["Sender wants to send the letter 'A'\n(ASCII value 65, binary 01000001)"]:::tx B["Sender pulls TX line LOW\n→ Start bit — receiver wakes up"]:::tx C["Sender puts each bit on the wire\none at a time, LSB first\n1, 0, 0, 0, 0, 0, 1, 0"]:::tx D["Sender pulls TX line HIGH\n→ Stop bit — byte is complete"]:::tx E["Receiver was sampling the line\nat the agreed baud rate\nthe whole time"]:::rx F["Receiver has now collected\nall 8 bits and assembled 'A'"]:::rx A --> B --> C --> D B --> E --> F

Physical Wires

UART only needs 3 wires. Notice the cross connection — TX of one device goes to RX of the other, never TX to TX:

sequenceDiagram participant A as Device A participant B as Device B A->>B: TX → RX B->>A: RX → TX A-->>B: GND (shared)

Voltage Levels

Different devices use different voltage levels to represent HIGH and LOW. You must match them or use a level shifter in between.

flowchart TD classDef v5 fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef v33 fill:#fef9c3,stroke:#ca8a04,color:#713f12 classDef rs fill:#dbeafe,stroke:#2563eb,color:#1e3a5f V5["5V TTL\n────────────────\nLOGIC 1 = 5V\nLOGIC 0 = 0V\nUsed by: Arduino Uno, ATmega328P"]:::v5 V33["3.3V LVTTL\n────────────────\nLOGIC 1 = 3.3V\nLOGIC 0 = 0V\nUsed by: ESP32, STM32, Raspberry Pi"]:::v33 RS["RS-232\n────────────────\nLOGIC 1 = −3V to −25V\nLOGIC 0 = +3V to +25V\nOld PC serial ports — inverted!"]:::rs V5 --> V33 --> RS

How UART Configuration Works — In Plain Terms

When you set up UART on a microcontroller, you are answering four questions:

flowchart TD classDef q fill:#f3e8ff,stroke:#9333ea,color:#581c87 classDef a fill:#dbeafe,stroke:#2563eb,color:#1e3a5f Q1["How fast?\n→ Set the baud rate register\nThe chip divides its clock to match\ne.g. at 16 MHz CPU, for 9600 baud:\ndivide by 1667 to get ~9600 ticks/bit"]:::q Q2["Which direction?\n→ Enable transmit, receive, or both\nTX only, RX only, or full duplex"]:::q Q3["What frame format?\n→ Almost always 8N1\n(8 data bits, no parity, 1 stop bit)"]:::q Q4["How to send/receive?\n→ Write a byte to the data register to send\nRead the data register when a byte arrives\nCheck status flags first so you don't miss bytes"]:::q Q1 --> Q2 --> Q3 --> Q4

Sending and Receiving — The Logic Flow

Sending a byte:

Want to send a byte?
  → Check: is the transmit buffer empty?
      YES → Write your byte in
            Hardware takes over — sends start bit, 8 bits, stop bit automatically
      NO  → Wait and check again

Receiving a byte:

Want to read a byte?
  → Check: has a new byte arrived?
      YES → Read it from the data register
      NO  → Wait and check again

UART vs Other Protocols

UART SPI I²C
Wires 3 (TX, RX, GND) 4 + 1 per slave 2 + GND
Devices 1-to-1 only 1 master, many slaves Up to 127 on same bus
Clock None (async) Shared clock Shared clock
Speed Up to ~1 Mbps Up to 20+ Mbps 100 kHz – 1 MHz
Best for Debug console, GPS, GSM SD cards, displays Sensors, RTC, EEPROM

Common Mistakes

  • Wrong baud rate — see garbage instead of readable text in terminal
  • TX to TX wiring — nothing arrives because you connected transmitter to transmitter
  • Missing GND connection — voltage levels have no reference, everything is unreliable
  • 5V into 3.3V pin — works sometimes, damages the pin over time
  • Not waiting before sending — if you write a new byte before the previous one finished sending, the old byte gets cut off

Next: SPI — Serial Peripheral Interface →