Universal Asynchronous Receiver-Transmitter (UART) is a two-wire, point-to-point, asynchronous serial communication protocol. Unlike SPI or I2C, UART does not use a shared clock line. Instead, both devices agree on a timing speed (baud rate) beforehand and sample the data line at precise intervals. It remains the foundational backbone for debugging consoles, GPS modules, cellular modems, and basic inter-microcontroller telemetry on the bench.

If you are asking what is the uart in the context of modern embedded design: it is the simplest, most robust way to move text or binary data between exactly two devices without the overhead of addressing or clock synchronization. Below is the hardware-level breakdown of how it works, how to wire it without frying your silicon, and how to debug it when the terminal spits out garbage.

Bus Mechanics: Protocol Fit for Distance, Speed, and Device Count

Choosing the right protocol depends entirely on your physical constraints. UART is strictly a point-to-point topology. If you need to connect multiple sensors to one microcontroller, you must use a multiplexer, dedicate multiple hardware UART peripherals, or switch to a bus protocol. Here is how UART stacks up against the other common bench protocols.

Protocol Wires (Excl. GND) Max Speed (Typical) Addressing Max Distance (TTL) Topology
UART 2 (TX, RX) 1 - 3 Mbps None < 15 meters Point-to-Point
I2C 2 (SDA, SCL) 100kHz - 3.4MHz 7-bit / 10-bit < 1 meter Multi-Master / Multi-Slave
SPI 3 shared + CS 10 - 50+ MHz Hardware CS lines < 1 meter Master-Slave
CAN Bus 2 (CANH, CANL) 1 Mbps (Classic) 11-bit / 29-bit IDs 40m (at 1Mbps) Multi-Master Bus

Note: UART distance limits apply to raw TTL logic (0-3.3V or 0-5V). If you pass UART through an RS-485 transceiver (like the MAX485), you can push the same serial data over 1200 meters using differential signaling.

Physical Layer: Wiring, Logic Levels, and the Pull-Up Myth

The physical wiring of UART is notoriously counterintuitive for beginners: TX connects to RX, and RX connects to TX. The transmitter of device A must feed the receiver of device B. Both devices must also share a common Ground (GND).

The 5V vs 3.3V Logic Level Hazard

The most common way makers destroy ESP32s or STM32 boards is by connecting a 5V Arduino TX pin directly to a 3.3V MCU RX pin. While the 3.3V TX to 5V RX direction is usually safe (the 3.3V HIGH signal often crosses the 5V chip's logic HIGH threshold), the reverse will overvoltage the 3.3V input protection diodes, leading to immediate or degraded failure.

Callout Tip: The Resistor Divider Hack
If you lack a dedicated logic level converter (like the TXS0102 or BSS138 MOSFET board), you can drop a 5V TX line to a safe 3.3V RX input using a simple resistor divider. Place a 1kΩ resistor in series with the 5V TX line, and a 2kΩ pull-down resistor from the RX pin to GND.
Math: Vout = 5V × (2kΩ / (1kΩ + 2kΩ)) = 3.33V.

Do UART Lines Need Pull-Up Resistors?

Unlike I2C, which uses open-drain outputs requiring external pull-ups to achieve a HIGH state, UART transceivers use push-pull outputs. The TX pin actively drives the line both HIGH and LOW. Therefore, UART does not require pull-ups to function.

However, a floating RX pin (before the transmitting device boots, or if a cable is unplugged) acts as an antenna. It can pick up electromagnetic interference (EMI), causing the receiving MCU's UART peripheral to trigger endless receive interrupts, corrupt memory, or cause brownouts. As a robust bench practice, place a 10kΩ pull-up resistor to VCC on any RX pin that might be left floating during system startup.

Classic Failures and How to Sniff the Bus

When a UART link fails, it almost always falls into one of three categories. Here is how to identify and fix them.

  1. Baud Rate Mismatch: If Device A transmits at 115200 baud and Device B listens at 9600 baud, the receiver will sample the bitstream at the wrong intervals, resulting in garbage characters (e.g., ÿÿÿ). Fix: Verify both .begin() statements. Note that UART baud rate error tolerance is typically ±2%. If your MCU's clock divider cannot achieve an exact baud rate (common with certain 8MHz AVRs at high speeds), drop to a lower, more stable baud rate like 38400.
  2. Crossed TX/RX or Missing Ground: If the terminal is completely dead, swap the TX and RX wires. If the data is highly intermittent or corrupts when a motor turns on, you likely have a missing common ground or a ground loop. Fix: Run a dedicated, thick GND wire between the two boards. Never rely on parasitic ground paths through USB shields or chassis.
  3. Parity and Stop Bit Errors: The standard configuration is 8N1 (8 data bits, No parity, 1 stop bit). If communicating with legacy industrial equipment, you may need to configure Even/Odd parity or 2 stop bits in your serial initialization.

How to Sniff and Debug UART

Do not rely solely on Serial.print() to debug hardware UART issues. Use a Logic Analyzer. A basic $12 24MHz 8-channel clone (compatible with Saleae Logic 2 software) is more than sufficient, as a 115200 baud signal only requires a ~1MHz sample rate to capture cleanly.

Connect the logic analyzer ground to your circuit ground, and clip the CH0 and CH1 probes to the TX and RX lines. In the software, add the "Async Serial" analyzer, set it to your target baud rate, and it will decode the raw voltage transitions into ASCII or HEX characters in real-time. This immediately reveals if a device is transmitting malformed bytes or if the line is being held low by a short circuit.

Minimal Working Exchange: ESP32 to Arduino Uno

Below is a complete, compilable example of an ESP32 sending sensor telemetry to an Arduino Uno. We use HardwareSerial on the ESP32 and SoftwareSerial on the Uno so the Uno can forward the data to your PC via its primary USB serial port.

Wiring Map (Assuming 5V Uno and 3.3V ESP32):

ESP32 Pin (3.3V) Intermediate Arduino Uno Pin (5V)
GPIO 17 (TX2) 1kΩ Series + 2kΩ Pull-down Pin 10 (Software RX)
GPIO 16 (RX2) Direct Wire Pin 11 (Software TX)
GND Direct Wire GND

ESP32 Sender Code:

// ESP32 Hardware UART Sender
#define TX_PIN 17
#define RX_PIN 16
HardwareSerial ESP_UART(2);

void setup() {
  // Initialize UART2 at 115200 baud, 8N1 format
  ESP_UART.begin(115200, SERIAL_8N1, RX_PIN, TX_PIN);
}

void loop() {
  float temp = 24.5; // Replace with actual sensor read
  ESP_UART.print("T:");
  ESP_UART.println(temp);
  delay(1000);
}

Arduino Uno Receiver Code:

// Arduino Uno SoftwareSerial Receiver
#include <SoftwareSerial.h>

// RX on Pin 10, TX on Pin 11
SoftwareSerial UNO_UART(10, 11);

void setup() {
  Serial.begin(115200);  // Hardware serial to PC USB
  UNO_UART.begin(115200); // Software serial to ESP32
}

void loop() {
  // Forward bytes from ESP32 to PC Serial Monitor
  if (UNO_UART.available()) {
    Serial.write(UNO_UART.read());
  }
}

Frequently Asked Questions

What is the UART maximum cable length and distance?

Raw TTL UART (0-3.3V or 0-5V) is unbalanced and highly susceptible to capacitive loading and EMI. Over standard copper jumper wires, you should keep TTL UART runs under 1 meter. Using shielded twisted pair (STP) cable and lowering the baud rate to 9600 can push TTL to roughly 15 meters. For anything longer, or for noisy industrial environments, you must convert the UART TTL signal to a differential standard like RS-485 (up to 1200m) or RS-232 (up to 15m).

What is the UART idle state and why does it matter?

The UART idle state is Logic HIGH. When no data is being transmitted, the TX line sits at VCC. This historical design choice (dating back to telegraphy) ensures that a broken wire or disconnected cable (which floats or gets pulled low) is immediately recognized as a "break" condition or continuous start bit, rather than being silently ignored as idle. If your logic analyzer shows the line idling LOW, your TX/RX wires are likely swapped, or the transmitter is unpowered.

What is the UART difference from RS-232?

UART is the logic-level protocol (the timing and framing of the bits), while RS-232 is a physical layer standard. RS-232 uses the exact same asynchronous start/stop bit framing as UART, but it changes the voltage levels. In RS-232, a Logic 1 (Mark) is represented by -3V to -15V, and a Logic 0 (Space) is +3V to +15V. You cannot connect an RS-232 port directly to a microcontroller UART pin without a level-shifting driver IC like the MAX232, or you will instantly destroy the microcontroller.

For deeper hardware specifications, refer to the SparkFun Serial Communication Tutorial and the official Espressif ESP-IDF UART API Guide.