UART (Universal Asynchronous Receiver-Transmitter) is the simplest serial protocol and the one you already use every time you open the Serial Monitor. It is a point-to-point, asynchronous protocol — no clock signal, no addresses, just two devices agreeing on a speed and exchanging bytes.
🔗How UART Works
UART sends data one byte at a time as a sequence of voltage-level transitions on a single wire. Because there is no shared clock, both sides must agree on the timing in advance. That agreed timing is the baud rate.
A single UART frame looks like this:
sequenceDiagram
participant Idle as Line Idle (HIGH)
participant Start as Start Bit (LOW)
participant D0 as Bit 0
participant D1 as Bit 1
participant D2 as Bit 2
participant D3 as Bit 3
participant D4 as Bit 4
participant D5 as Bit 5
participant D6 as Bit 6
participant D7 as Bit 7
participant Stop as Stop Bit (HIGH)
Note over Idle,Stop: One UART frame (8N1): 1 start + 8 data + 1 stop = 10 bits per byte| Part | Bits | Purpose |
|---|---|---|
| Start bit | 1 | Pulled LOW to signal the beginning of a byte |
| Data bits | 8 (usually) | The actual data, LSB first |
| Parity bit | 0 or 1 | Optional error check (usually omitted) |
| Stop bit | 1 (usually) | Pulled HIGH to signal the end of the byte |
The most common format is 8N1: 8 data bits, No parity, 1 stop bit. This is the default on virtually every device you will encounter.
🔗TX/RX Crossover
UART uses two data lines:
- TX (Transmit) — sends data out
- RX (Receive) — takes data in
The critical wiring rule: TX connects to RX, and RX connects to TX. You cross the wires because one device's output must feed the other device's input.
graph LR
subgraph ESP32
TX1["TX"]
RX1["RX"]
end
subgraph Peripheral
TX2["TX"]
RX2["RX"]
end
TX1 -- "data →" --> RX2
TX2 -- "data →" --> RX1Common mistake: Connecting TX-to-TX and RX-to-RX. If you get no data at all, try swapping the two wires.
🔗Baud Rate
The baud rate is the number of signal transitions (bits) per second. Both devices must use the exact same baud rate, or the data will be garbled.
Common baud rates:
| Baud Rate | Bits/sec | Effective Byte Rate | Typical Use |
|---|---|---|---|
| 9600 | 9,600 | ~960 B/s | GPS modules, some sensors |
| 19200 | 19,200 | ~1,920 B/s | Some industrial devices |
| 38400 | 38,400 | ~3,840 B/s | Bluetooth modules (AT mode) |
| 57600 | 57,600 | ~5,760 B/s | Faster peripherals |
| 115200 | 115,200 | ~11,520 B/s | Serial Monitor default, most common |
| 921600 | 921,600 | ~92,160 B/s | Fast data logging |
The effective byte rate is approximately $\frac{\text{baud rate}}{10}$ because each byte requires 10 bits on the wire (1 start + 8 data + 1 stop in 8N1 format):
$$\text{Byte rate} = \frac{\text{Baud rate}}{1 + 8 + 1} = \frac{\text{Baud rate}}{10}$$
At $115200\,\text{baud}$, you can transfer about $11{,}520$ bytes per second — roughly $11\,\text{KB/s}$.
🔗ESP32 UART Ports
The ESP32 has three hardware UART ports:
| Port | Arduino Name | Default TX | Default RX | Status |
|---|---|---|---|---|
| UART0 | Serial | GPIO 1 | GPIO 3 | Used by USB serial — do not reassign |
| UART1 | Serial1 | GPIO 10 | GPIO 9 | Connected to flash on most boards — remap before using |
| UART2 | Serial2 | GPIO 17 | GPIO 16 | Free to use |
🔗UART0 — USB Serial
UART0 is connected to the USB-to-serial converter (CP2102 or CH340) on your dev board. This is the port that Serial.begin(115200) opens and that the Serial Monitor reads from. Using GPIO 1 or GPIO 3 for anything else will break serial communication and may prevent uploading code.
🔗UART1 — Remap Required
The default UART1 pins (GPIO 9 and GPIO 10) are connected to the internal SPI flash on most ESP32 boards. Using them will crash the chip. Always remap UART1 to safe pins:
Serial1.begin(9600, SERIAL_8N1, 26, 27); // RX=26, TX=27🔗UART2 — Free to Use
UART2 on GPIO 16 (RX) and GPIO 17 (TX) is free and the easiest choice for connecting a peripheral:
Serial2.begin(9600); // Uses default pins: RX=16, TX=17🔗Data Format: 8N1
The SERIAL_8N1 constant passed to begin() sets the data format:
| Character | Meaning | Value |
|---|---|---|
| 8 | Data bits | 8 bits per byte |
| N | Parity | None (no error-check bit) |
| 1 | Stop bits | 1 stop bit |
Other formats exist (7E1, 8E2, etc.) but are rare in hobbyist electronics. If your device's datasheet does not mention a format, assume 8N1.
🔗Code Example
Connecting a peripheral device (such as a GPS module or sensor with UART output) on Serial2 while keeping the Serial Monitor on Serial:
// ESP32 UART2 example — read data from a peripheral device
#define PERIPHERAL_BAUD 9600 // Match your device's baud rate
void setup() {
Serial.begin(115200); // UART0: USB Serial Monitor
Serial2.begin(PERIPHERAL_BAUD, SERIAL_8N1, 16, 17); // UART2: RX=16, TX=17
delay(1000);
Serial.println("UART2 ready — forwarding data to Serial Monitor:");
}
void loop() {
// Forward data from peripheral to Serial Monitor
while (Serial2.available()) {
char c = Serial2.read();
Serial.print(c);
}
// Optionally send commands to the peripheral
if (Serial.available()) {
char c = Serial.read();
Serial2.print(c);
}
}This pattern — one UART for debugging and another for the peripheral — is the standard approach for UART devices.
🔗Signal Levels
UART does not define voltage levels, so you need to make sure both devices use the same logic level:
| Device | Logic Level |
|---|---|
| ESP32 | 3.3 V |
| Arduino Uno/Mega | 5 V |
| Most GPS/Bluetooth modules | 3.3 V |
| RS-232 serial ports | +/- 12 V |
Connecting a 5 V UART device directly to an ESP32 can damage the ESP32's GPIO pins. Use a logic-level converter or a voltage divider on the RX line. The TX line (3.3 V out from the ESP32) is usually accepted by 5 V devices because 3.3 V is above their logic-HIGH threshold.
🔗Common Issues
| Problem | Cause | Fix |
|---|---|---|
| Garbled text (random characters) | Baud rate mismatch | Ensure both devices use the exact same baud rate |
| No data received | TX and RX swapped | Swap the two wires — TX must connect to RX |
| No data received | Wrong UART port or pins | Verify you are using Serial2 and the correct GPIO numbers |
| ESP32 crashes on boot | Using UART1 default pins (GPIO 9/10) | Remap UART1 to safe pins in the begin() call |
| Cannot upload code | Peripheral connected to GPIO 1/3 (UART0) | Disconnect the peripheral before uploading; use UART2 instead |
| Data loss at high speed | No flow control, buffer overflow | Reduce baud rate, or read data more frequently in loop() |
| Partial messages | Reading before full message arrives | Buffer incoming data and parse only when a delimiter (like \n) is received |
🔗Used In
These articles on this site use UART: