UART (Universal Asynchronous Receiver-Transmitter) is a 2-wire, point-to-point, asynchronous serial protocol used to move data between two microcontrollers, or between a microcontroller and a PC via a USB-to-TTL adapter. Because it lacks a shared clock line, both devices must agree on a timing speed (baud rate) beforehand. If you need to connect exactly two devices over a short distance without the overhead of addressing or clock synchronization, UART is your default choice.
The Physical Layer: Wiring UART Without Frying Your Board
UART requires a minimum of three connections: TX (Transmit), RX (Receive), and GND (Ground). The golden rule of UART wiring is that the TX pin of Device A must always connect to the RX pin of Device B, and vice versa.
The Pull-Up Misconception
A common point of confusion for makers transitioning from I2C is the search for pull-up resistors. Standard UART does not require pull-up resistors. The TX lines are push-pull outputs that actively drive the line high and low. If you are seeing advice about 4.7kΩ pull-ups, you are looking at I2C documentation, not UART. The only exception is RS-232, which uses different voltage levels and termination rules, but for standard 3.3V/5V TTL UART, leave the pull-ups off.
UART vs I2C vs SPI vs RS-485: Which Protocol Fits Your Build?
Choosing the right bus depends entirely on your distance, speed, and device count requirements. Here is how UART stacks up against the alternatives.
| Feature | UART (TTL) | I2C | SPI | RS-485 (UART-based) |
|---|---|---|---|---|
| Wires Required | 2 (TX, RX) + GND | 2 (SDA, SCL) + GND | 4 (MOSI, MISO, SCK, CS) | 2 (A, B differential) + GND |
| Typical Speed | 9600 - 115200 bps | 100kHz - 3.4MHz | 10MHz - 50MHz+ | Up to 10 Mbps |
| Addressing | None (Point-to-Point) | 7-bit / 10-bit I2C | Hardware CS lines | Software (e.g., Modbus) |
| Max Distance | ~15 meters (at 9600) | ~1 meter | ~1 meter | ~1200 meters |
| Best Use Case | Debugging, GPS modules, 2-device links | Sensors, OLEDs, low-speed multi-drop | SD cards, high-speed displays | Industrial, long-run HVAC, DMX |
The Verdict: Choose UART when you only need to talk to one peripheral (like a GPS receiver or a cellular modem) and want simple code. Choose RS-485 if you need UART's simplicity but must run the cable across a building. Choose I2C or SPI when you need to connect multiple sensors on the same breadboard.
Minimal Working Exchange: ESP32 to Arduino Nano via UART
Let's build a reliable link between a 3.3V ESP32 DevKit V1 and a 5V Arduino Nano. Because of the voltage mismatch, we will use a passive voltage divider on the Nano's TX line to protect the ESP32's RX pin.
Wiring and Voltage Divider
To drop 5V down to a safe 3.3V, we use a 1kΩ series resistor and a 2kΩ pull-down resistor. This yields exactly 3.33V, which is well within the ESP32's safe input range while remaining high enough to register as a logic '1'.
| ESP32 DevKit V1 | Intermediate Component | Arduino Nano |
|---|---|---|
| GND | Direct Wire | GND |
| GPIO 16 (RX2) | Voltage Divider (1kΩ series, 2kΩ to GND) | D11 (SoftwareSerial TX) |
| GPIO 17 (TX2) | Direct Wire (3.3V is safe for 5V TTL input) | D10 (SoftwareSerial RX) |
ESP32 Code (Hardware Serial 2)
// ESP32 DevKit V1 - Hardware Serial2
#define RXD2 16
#define TXD2 17
void setup() {
Serial.begin(115200); // USB debug
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // UART to Nano
Serial.println("ESP32 Ready. Waiting for Nano...");
}
void loop() {
if (Serial2.available()) {
String incoming = Serial2.readStringUntil('\n');
Serial.print("Received from Nano: ");
Serial.println(incoming);
// Send acknowledgment back
Serial2.println("ESP32 ACK");
}
}
Arduino Nano Code (Software Serial)
// Arduino Nano - Software Serial
#include <SoftwareSerial.h>
#define RX_PIN 10
#define TX_PIN 11
SoftwareSerial nanoUART(RX_PIN, TX_PIN);
unsigned long lastSend = 0;
void setup() {
Serial.begin(115200); // USB debug
nanoUART.begin(9600); // UART to ESP32
}
void loop() {
// Send a heartbeat every 2 seconds
if (millis() - lastSend > 2000) {
lastSend = millis();
nanoUART.println("Nano Heartbeat");
}
// Read response from ESP32
if (nanoUART.available()) {
String ack = nanoUART.readStringUntil('\n');
Serial.print("ESP32 says: ");
Serial.println(ack);
}
}
Debugging the Bus: Sniffing TX/RX and Fixing Classic Failures
Unlike I2C, where a missing pull-up resistor or an address clash will silently halt the bus, UART's classic failures are strictly physical and timing-based. When UART fails, it usually fails loudly with garbage data or not at all.
The Classic Failures
- Baud Rate Mismatch: If the sender transmits at 115200 bps and the receiver listens at 9600 bps, you will see garbage characters like
ÿor???in your serial monitor. Always verify both.begin()calls match exactly. - Missing Common Ground: UART is single-ended. The voltage on the TX pin is measured relative to GND. If you forget to connect the GND wire between the two boards, the RX pin sees a floating, noisy voltage and will read random bits. Always wire GND to GND.
- TX to TX Wiring: Connecting TX to TX instead of TX to RX results in total silence. Neither device is listening to the other's transmit line.
How to Sniff and Debug
When your serial monitor shows nothing, stop guessing and look at the physical signal. Connect a logic analyzer (a $12 generic 24MHz 8-channel clone works perfectly for UART speeds) to the TX and RX lines. Set your analyzer software (like PulseView or Saleae Logic) to decode the 'UART/Serial' protocol at your target baud rate.
For deeper electrical faults, an oscilloscope will reveal if your signal is suffering from severe ringing, slow rise times due to excessive cable capacitance, or if your voltage divider is dropping the logic-high level below the receiver's threshold.
UART Communication FAQ
Why is my UART communication outputting garbage characters?
Garbage characters almost always indicate a baud rate mismatch between the transmitter and receiver. If the transmitter is sending bits faster than the receiver is sampling them, the receiver's internal state machine loses track of the start and stop bits, resulting in misaligned byte reads. Double-check that both devices are initialized to the exact same baud rate (e.g., 9600 or 115200). A secondary cause is a missing common ground wire, which causes the receiver to sample electrical noise instead of clean logic levels.
Can I connect multiple devices to a single UART TX pin?
Technically, yes, but with strict limitations. Because UART is point-to-point and lacks addressing, you can wire one master TX pin to multiple slave RX pins (a multi-drop configuration). However, the slaves cannot talk back on the same wire without causing data collisions. If multiple slaves need to transmit data back to the master, you must either use an RS-485 transceiver network, use a multiplexer chip, or switch to a true multi-master protocol like I2C or CAN bus.
What is the maximum reliable distance for UART communication?
Standard TTL UART (0-3.3V or 0-5V) is highly susceptible to capacitive loading and electromagnetic interference. At 9600 baud, you can reliably push TTL UART over standard twisted-pair cable up to about 15 meters. At 115200 baud, that distance drops to roughly 2 meters before signal degradation causes bit errors. If you need to run serial communication further than 15 meters, you must use differential signaling by adding RS-485 transceiver modules (like the MAX485) to both ends of the cable, which extends the range up to 1200 meters.






