UART and baud rates
Two wires crossed over, one agreed speed, and no clock between them. It is the oldest bus here and the one that fails most quietly - a GPS that drops half its sentences is not a bad GPS, it is a buffer that overflowed.
The buffer nobody sizes
Serial data does not wait for you. It lands in the Arduino driver's receive buffer — 256 bytes by default, on top of the chip's 128-byte hardware FIFO — whether your loop is ready or not.
Reading lines without blocking
The pattern above is worth copying: read whatever is available, build a line
character by character, act on it when the newline arrives. Never
while (!Serial1.available()) {} — that is a loop that waits forever the one
time the device does not answer.
Three UARTs, and which are free
The classic ESP32 has three. UART0 is the USB console — using it for anything else means losing your debug output. UART1 and UART2 are yours, on any pins.
The C3 and C6 have two, and on a native-USB board the console is separate hardware, so both UARTs are free once the chip has booted (on the S3 the ROM boot log still comes out of UART0's TX pin by default). That is a small but real advantage when you have a GPS and a fingerprint reader on the same board.
The code
A second UART on any two pins, with the buffer sized before begin. That order matters - setRxBufferSize after begin is ignored, and the error it logs is hidden at the default debug level.
void setup() {
Serial.begin(115200); // the USB console
Serial1.setRxBufferSize(2048); // BEFORE begin, or it is ignored
Serial1.begin(9600, SERIAL_8N1, 16, 17); // baud, format, RX, TX
}
void loop() {
static char line[120];
static int n = 0;
while (Serial1.available()) {
char c = Serial1.read();
if (c == '\n' || n == sizeof(line) - 1) {
line[n] = 0;
n = 0;
if (strncmp(line, "$GPGGA", 6) == 0) Serial.println(line);
} else if (c != '\r') {
line[n++] = c;
}
}
}The classic ESP32 and the S3 have three UARTs, the C3 has two, and the C6 has two plus a low-power one. UART0 is the console on the classic ESP32 and on an S3 with USB CDC off; on a native-USB board Serial is USB. Pass your own pins to Serial1 and Serial2.
Same shape. rxbuf is a constructor argument here, so it is harder to get wrong than the Arduino version.
from machine import UART, Pin
uart = UART(1, baudrate=9600, tx=Pin(17), rx=Pin(16), rxbuf=2048)
while True:
line = uart.readline()
if line and line.startswith(b'$GPGGA'):
print(line.decode().strip())readline returns None rather than blocking when there is no complete line, so this loop never stalls waiting for a sentence that has not arrived yet.
When it does not work
The two ends disagree about baud. There is no clock wire, so each side times the bits itself and a mismatch produces plausible-looking nonsense rather than an error.
The receive buffer overflowed while your loop was busy. Nothing reports it. Raise the buffer size, stop blocking in the loop, or read the port from its own task.
TX and RX are not crossed. The board's TX goes to the device's RX. Two devices both transmitting into each other's transmit pin is completely silent and looks like a dead module.
Long wires, or a 5 V device. Long or badly loaded wires slow the edges; shorten them or lower the baud.
UART reaches a desk. The next page is the same protocol over hundreds of metres of factory cable.
RS485 and Modbus →Edit this page — content/esp32/uart-and-baud-rates.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.