Every time you open the Serial Monitor, you are using a UART. It is the oldest and simplest serial link in electronics, two wires and an agreed speed, and it is how the ESP32 talks to your computer, GPS modules, GSM modems, fingerprint readers, mmWave radar sensors, other microcontrollers and a long list of AT-command devices.
The ESP32 has three hardware UARTs, which is a luxury compared with the single serial port on an Arduino Uno. You can print debug messages to your computer on one, read a GPS on another and talk to a sensor on the third, all at different baud rates and all in hardware, with no SoftwareSerial hacks. The catch is knowing which pins each one uses by default, which ones are unavailable, and how to remap them. This guide covers all of it, with working examples.
How UART Works
A UART connection needs just two signal wires plus a shared ground:
- TX (transmit): the pin that sends data.
- RX (receive): the pin that listens.
- GND: a common reference. Forgetting this is a classic cause of garbage output.
The connection is a crossover: the ESP32's TX goes to the other device's RX, and the ESP32's RX goes to the other device's TX. Wiring TX to TX is the most common UART mistake and produces total silence.
There is no clock line. Both sides must agree on the baud rate (bits per second, for example 9600 or 115200) and the frame format, almost always 8N1: 8 data bits, no parity, 1 stop bit. If the baud rate is wrong, you receive unreadable characters.
ESP32 UART Pins
| UART | Arduino object | Default TX | Default RX | Notes |
|---|---|---|---|---|
| UART0 | Serial | GPIO 1 | GPIO 3 | Connected to the USB chip. Used for uploading and the Serial Monitor. |
| UART1 | Serial1 | GPIO 10 | GPIO 9 | Default pins are flash pins on WROOM-32 boards. You must remap it. |
| UART2 | Serial2 | GPIO 17 | GPIO 16 | Free to use on standard boards. The usual choice for a second device. |
Two practical points follow from this table:
- Leave UART0 alone. GPIO 1 and 3 are wired to the programming chip. Attaching a device there interferes with uploads and floods your device with boot messages. Use it only for the computer.
- UART2 is the easy second port. On a DevKit V1 it is labeled RX2 (GPIO 16) and TX2 (GPIO 17). See the ESP32 pinout guide. On WROVER modules GPIO 16 and 17 are used by PSRAM, so remap Serial2 there.
Thanks to the GPIO matrix, any UART can be moved to almost any pin, as the next section shows.
Using Serial2 on the Default Pins
void setup() {
Serial.begin(115200); // to the computer
Serial2.begin(9600, SERIAL_8N1, 16, 17); // baud, format, RX pin, TX pin
}
void loop() {
// forward anything from the device to the computer
while (Serial2.available()) {
Serial.write(Serial2.read());
}
}
Note the argument order for begin(): baud, config, RX pin, TX pin. Mixing up RX and TX in this call is as easy as mixing up the wires.
Remapping a UART to Any Pins
Create a UART on any free pins by naming the port and giving it the pin numbers:
HardwareSerial sensorSerial(1); // use hardware UART1
void setup() {
Serial.begin(115200);
sensorSerial.begin(9600, SERIAL_8N1, 25, 26); // RX = 25, TX = 26
}
This is the standard fix for UART1, whose default pins sit on the flash chip. You can also move Serial2 the same way: Serial2.begin(115200, SERIAL_8N1, 32, 33);.
Use output-capable pins for TX. GPIO 34, 35, 36 and 39 are input only, so they work for RX but not TX.
Test Your UART With a Loopback
You can verify the pins and code without any device. Connect GPIO 17 (TX2) to GPIO 16 (RX2) with one jumper wire, so the ESP32 hears itself.
void setup() {
Serial.begin(115200);
Serial2.begin(115200, SERIAL_8N1, 16, 17);
delay(500);
Serial2.println("Hello UART");
delay(100);
if (Serial2.available()) {
String reply = Serial2.readStringUntil('\n');
Serial.println("Received: " + reply);
} else {
Serial.println("Nothing received. Check the jumper.");
}
}
void loop() {
}
You should see Received: Hello UART. If you see nothing, the jumper is not seated or the pins do not match your board.
Example: Reading a GPS Module
A NEO-6M GPS module outputs standard NMEA text at 9600 baud by default. Wire it to Serial2:
| GPS module | ESP32 |
|---|---|
| VCC | 3.3V or 5V (check your module) |
| GND | GND |
| TX | GPIO 16 (RX2) |
| RX | GPIO 17 (TX2) |
Install TinyGPSPlus from the Library Manager, then:
#include <TinyGPSPlus.h>
TinyGPSPlus gps;
void setup() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, 16, 17);
}
void loop() {
while (Serial2.available()) {
gps.encode(Serial2.read());
}
if (gps.location.isUpdated()) {
Serial.printf("Lat: %.6f Lon: %.6f Sats: %d\n",
gps.location.lat(), gps.location.lng(), gps.satellites.value());
}
}
A cold start can take several minutes outdoors with a clear sky view, and indoor reception is usually nothing at all. If nothing prints, first confirm raw NMEA text arrives by echoing Serial2.read() to the monitor.
Example: A USB-to-AT-Command Bridge
Many modules (SIM800 GSM, HC-05 Bluetooth, ESP-01, LoRa modems) are configured by typing AT commands. A bridge sketch passes everything you type in the Serial Monitor to the module and prints its replies:
void setup() {
Serial.begin(115200);
Serial2.begin(9600, SERIAL_8N1, 16, 17);
Serial.println("Bridge ready. Type AT commands.");
}
void loop() {
if (Serial.available()) Serial2.write(Serial.read());
if (Serial2.available()) Serial.write(Serial2.read());
}
Set the Serial Monitor line ending to Both NL & CR so the module sees a proper command terminator. Type AT and expect OK.
Talking to an Arduino: Mind the Voltage
Linking an ESP32 to an Arduino Uno is common, but the two run at different logic levels: the ESP32 at 3.3V, the Uno at 5V.
- ESP32 TX to Arduino RX: safe. The Uno reads 3.3V as a valid HIGH.
- Arduino TX to ESP32 RX: not safe without protection. A 5V signal into the ESP32 can damage it. Use a voltage divider (for example 1 kΩ from the Arduino TX to the ESP32 RX and 2 kΩ from that point to GND, which gives about 3.3V) or a level shifter.
Share a ground between the boards, and match the baud rate on both sides.
Handling More Data: Buffers and Parsing
The default receive buffer is 256 bytes. If a device sends bursts faster than your loop reads them, bytes are lost. Enlarge the buffer before calling begin():
Serial2.setRxBufferSize(2048);
Serial2.begin(115200, SERIAL_8N1, 16, 17);
Reading tips:
- Always check
available()before reading. - Do not block the loop with long
delay()calls while data is arriving. - To read a full line, use
readStringUntil('\n'). For binary protocols, read exact byte counts withreadBytes(buffer, length). - Set
Serial2.setTimeout(50)so read calls do not wait a full second for a missing terminator.
Common Problems
Garbage characters. The baud rates do not match, or the device is not using 8N1. Check the device datasheet.
Complete silence. TX and RX are not crossed, ground is not shared, or the module is not powered. Try swapping the two data wires.
Upload fails when a device is attached. The device is on GPIO 1 or 3 and interferes with flashing. Move it to Serial2, or disconnect it during upload.
Boot loops or odd behavior. A device is pulling a strapping pin during boot. The UART2 pins 16 and 17 are safe, so prefer them.
Data drops at high speed. Increase the receive buffer and read more often. Use 115200 or lower unless the wiring is short and clean.
Works for a while, then freezes. The loop is too slow to empty the buffer, or the module is browning out the supply. Check power.
Frequently Asked Questions
How many UARTs does the ESP32 have? Three on the original ESP32 (UART0, UART1 and UART2). The ESP32-S3 also has three, and the ESP32-C3 has two.
Can I use SoftwareSerial on ESP32? You do not need it. Use a hardware UART. A SoftwareSerial library for ESP32 exists, but hardware ports are more reliable at high speed.
What is the maximum baud rate? Up to 5 Mbps in theory. For breadboard wiring, 115200 to 921600 is practical.
Can I use Serial2 with WiFi? Yes, there is no conflict.
Which pins should I use for a second device? GPIO 16 and 17 for Serial2, or remap to any free pair such as 25 and 26. See the ESP32 pinout guide for safe pins.
Where next? Learn the other two buses in the I2C guide and the SPI guide.