How to use multiple Serial ports (UART) on ESP32
On Arduino Uno you basically get one hardware serial port. On ESP32 you get multiple UARTs, which is great when you want USB serial for debugging and a separate serial link for a GPS, Bluetooth module, RFID reader, etc.
Quick rules (don't skip these)
- Cross TX/RX: module TX → ESP32 RX, module RX → ESP32 TX.
- Share ground. Always. Serial without a shared ground is “random data generator.”
- 3.3V logic: ESP32 pins are 3.3V. If your module outputs 5V TX, use a level shifter or divider.
Example: Serial for debug + UART on custom pins
This uses UART2 (often called Serial2) on GPIO16/17. You can map UARTs to other pins too, but start here first.
#include <HardwareSerial.h>
// UART2 on custom pins
static const int RX2 = 16;
static const int TX2 = 17;
void setup() {
Serial.begin(115200); // USB debug
Serial2.begin(9600, SERIAL_8N1, RX2, TX2); // module UART
Serial.println("ESP32 dual UART demo");
Serial.println("Serial2 RX=GPIO16 TX=GPIO17 (baud 9600)");
}
void loop() {
// Forward module -> USB
while (Serial2.available()) {
Serial.write(Serial2.read());
}
// Forward USB -> module (optional)
while (Serial.available()) {
Serial2.write(Serial.read());
}
}Common gotchas
- Wrong baud rate looks like gibberish. Match the module's datasheet.
- Wrong pins (or a pin used by your board's built-in hardware). If it's flaky, try different GPIOs.
- Using Serial0 pins can interfere with USB/programming on some boards. Keep debug on
Serialand peripherals onSerial1/Serial2.
Bottom line
Use Serial for USB debug and Serial2 (or Serial1) for modules. Cross TX/RX, share ground, and keep everything at 3.3V logic levels.
Related: I2C vs SPI vs UART · 3.3V vs 5V level issues · ESP32 safe pins