ES: ESP32 UART

UART (Universal Asynchronous Receiver/Transmitter) is the simplest and most universal serial communication protocol. On the ESP32, UART is used for debug output, PC communication, GPS modules, GSM modems, and any device that speaks serial. This guide covers UART fundamentals, ESP32 UART interfaces, and practical usage with the Arduino framework.

UART is the bedrock of embedded communication. It requires only two wires, needs no shared clock, and is supported by virtually every microcontroller, sensor, and communication module ever made. On the ESP32, UART is the first tool you reach for when you need to debug, communicate with a PC, or talk to a GPS or GSM module.


What is UART?

UART (Universal Asynchronous Receiver/Transmitter) is a serial communication protocol that transmits data one bit at a time over a single wire in each direction, without a shared clock signal.

md
UART Frame (8N1 — 8 data bits, no parity, 1 stop bit)
------------------------------------------------------
Idle  START  D0  D1  D2  D3  D4  D5  D6  D7  STOP  Idle
HIGH  LOW    b   b   b   b   b   b   b   b   HIGH  HIGH

Key characteristics:

FeatureUART
Wires2 (TX and RX)
ClockNone (asynchronous — both sides agree on baud rate)
DirectionFull-duplex (simultaneous TX and RX)
Max speedTypically 115200 baud for standard use; up to 5 Mbaud on ESP32
TopologyPoint-to-point (one TX to one RX)

What is Baud Rate?

Baud rate is the number of symbol changes per second. For UART, 1 baud = 1 bit per second. Common baud rates:

Baud RateBits/secUse Case
96009600 bpsOlder devices, GPS modules
115200115200 bpsStandard debug output
921600921600 bpsESP32 firmware upload

Both sides of a UART connection must be configured to the same baud rate for communication to work.


ESP32 UART Interfaces

The ESP32 provides three hardware UART controllers:

InterfaceTX Pin (default)RX Pin (default)Notes
UART0GPIO1GPIO3Used for USB-to-serial, programming, and Serial in Arduino
UART1GPIO17GPIO16General-purpose serial
UART2GPIO17GPIO16General-purpose serial (configurable pins)

All UART pins on the ESP32 are software-remappable — any GPIO can be assigned as TX or RX for UART1 and UART2.


Using UART with the Arduino Framework

In the Arduino framework, UART is accessed through Serial, Serial1, and Serial2 objects.

UART0: Debug Output (Serial)

UART0 is the default serial port, connected to the USB-to-serial converter on most ESP32 development boards.

cpp
#include <Arduino.h>

void setup() {
    Serial.begin(115200);     // Initialize UART0 at 115200 baud
    Serial.println("ESP32 UART initialized");
}

void loop() {
    Serial.println("Hello from UART0");
    delay(1000);
}

UART1: General Purpose Serial

Use Serial1.begin() to configure UART1 with custom TX/RX pins:

cpp
#include <Arduino.h>

#define UART1_TX 17
#define UART1_RX 16

void setup() {
    Serial.begin(115200);   // Debug output on UART0
    Serial1.begin(9600, SERIAL_8N1, UART1_RX, UART1_TX);  // UART1 for device communication
    Serial.println("UART1 initialized");
}

void loop() {
    // Send data over UART1
    Serial1.println("AT");  // Example: send AT command to GSM module

    // Read response from UART1
    if (Serial1.available()) {
        String response = Serial1.readString();
        Serial.print("Response: ");
        Serial.println(response);
    }

    delay(1000);
}

Serial Configuration Options

cpp
Serial.begin(baud_rate, config, rx_pin, tx_pin);

ConfigData BitsParityStop Bits
SERIAL_8N18None1
SERIAL_8N28None2
SERIAL_8E18Even1
SERIAL_8O18Odd1

8N1 is the most common configuration and the default when only baud rate is specified.


Reading UART Data

Several methods are available for reading incoming data:

cpp
// Check if data is available
if (Serial.available()) {
    // Read one byte
    char c = Serial.read();

    // Read all available bytes as String
    String line = Serial.readString();

    // Read until newline character
    String line2 = Serial.readStringUntil('\n');

    // Read raw bytes into buffer
    uint8_t buf[64];
    size_t len = Serial.readBytes(buf, 64);
}


Complete UART Example: Echo Server

This example echoes back any data received over UART, a classic test for verifying bidirectional communication:

cpp
#include <Arduino.h>

void setup() {
    Serial.begin(115200);
    Serial.println("UART Echo Server Ready");
    Serial.println("Type something and press Enter...");
}

void loop() {
    if (Serial.available()) {
        String received = Serial.readStringUntil('\n');
        received.trim();  // Remove whitespace and newline characters

        Serial.print("Received: '");
        Serial.print(received);
        Serial.println("'");

        // Echo back with confirmation
        Serial.print("Echo: ");
        Serial.println(received);
    }
}

Open the Serial Monitor at 115200 baud and type a message. The ESP32 will echo it back.


UART Between Two Microcontrollers

When connecting two devices via UART:

md
Device A                        Device B
--------                        --------
TX (GPIO17) -----------------> RX (pin)
RX (GPIO16) <----------------- TX (pin)
GND        -----------------> GND

NOTE: Always connect GND between the two devices.
      Cross-connect TX to RX and RX to TX.

Critical: Always connect GND between the two devices — a shared reference ground is required for UART to function correctly.


UART for GPS Module (Example Application)

GPS modules like the NEO-6M communicate at 9600 baud using NMEA sentences:

cpp
#include <Arduino.h>

#define GPS_TX 17    // ESP32 TX to GPS RX
#define GPS_RX 16    // ESP32 RX to GPS TX

void setup() {
    Serial.begin(115200);        // Debug output
    Serial1.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);  // GPS module
    Serial.println("GPS UART initialized");
}

void loop() {
    while (Serial1.available()) {
        char c = Serial1.read();
        Serial.write(c);    // Print raw NMEA sentences to debug terminal
    }
}


UART Summary

OperationCode
Initialize UART0Serial.begin(115200)
Initialize UART1 with custom pinsSerial1.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN)
Send stringSerial.println("text")
Send bytesSerial.write(buffer, length)
Check for dataSerial.available()
Read one byteSerial.read()
Read string until newlineSerial.readStringUntil('\n')

Final Thoughts

UART is the simplest and most universally supported serial protocol. On the ESP32, it serves as the debug output channel, the firmware upload interface, and the communication bridge to external modules like GPS, GSM, and Bluetooth adapters.

Mastering UART usage — especially configuring custom pins for UART1/UART2 — enables the ESP32 to communicate with virtually any serial device in existence.

UART is how the ESP32 talks to the world one bit at a time.