The puzzle
The console prints garbage. The wiring looks right, the code says 115 200 baud, and the other end says 115 200 too. On a different board the same code works, except that one byte in a few thousand arrives wrong. With no clock wire, how close do two UARTs have to be, and what else can lose a byte?
STEP 1
Wiring
A UART has a transmit line (TX) and a receive line (RX). Each side’s TX goes to the other’s RX, and the two boards must share a ground, because the receiver judges the level of RX against its own ground. Both sides must also use the same logic levels: a 3.3 V microcontroller pin talks directly to another 3.3 V UART or a USB-to-serial adapter set to 3.3 V, but not to a classic RS-232 port, which uses inverted signals at much larger positive and negative voltages and needs a level-shifting transceiver.
STEP 2
Making the baud rate
The UART divides its clock to produce 16 sample ticks per bit. The divisor is
and the hardware can only hold an approximation of it. The RP2040’s UART (an Arm PL011) stores a 16-bit integer part and a 6-bit fraction; the pico-sdk’s uart_set_baudrate() rounds to the nearest 1/64 and returns the rate it actually set. ST’s HAL computes the same quantity in fixed point with a 4-bit fraction for the STM32’s BRR register.
STEP 3
How much mismatch a frame tolerates
The receiver synchronises only on the start edge and then samples each bit where it expects the middle to be. Any difference between the two baud rates moves every sample a little further. The last sample, in the (first) stop bit, is taken n − ½ bit times after the edge, where n counts the bits up to and including the first stop bit (9.5 for 8N1; a second stop bit is not checked by the RP2040’s UART). It must stay within half a bit of the true centre. With 16× oversampling the start edge itself is found up to 1/16 bit late, so sampling stays correct as long as the total bit-period mismatch satisfies
This is the combined error of both ends and a theoretical limit (a fast receiver, whose worst case is an edge detected at once, tolerates a little more): slow edges, noise and receivers that take several samples per bit reduce it. Keep each end’s error well under 2 %.
The receiver starts timing at the start edge (detected up to 1/16 bit late with 16× oversampling) and samples each bit where it expects the centre. If its bit period differs from the transmitter’s, every sample slides a little further; by the stop bit the error has accumulated over the whole frame. A sample that crosses into the neighbouring bit reads that bit’s value, which corrupts the byte whenever the two differ. Only the first stop bit is sampled.
STEP 4
FIFOs and interrupts
A single-byte receive register must be read within one frame time (86.8 µs at 115 200 baud) or the next byte overwrites it. The PL011 has a 32-entry receive FIFO; the pico-sdk configures its interrupt to fire when the FIFO reaches 1/8 full (4 characters), plus a receive timeout interrupt when at least one character has waited and nothing more has arrived for 32 bit periods, so the last bytes of a message are not stranded below the threshold. The handler drains the FIFO into a ring buffer (unit 9, lesson 5).
On the transmit side, “FIFO empty” is not “finished”: the last byte is still in the shift register. The pico-sdk’s uart_tx_wait_blocking() waits for the BUSY flag, which stays set until the final stop bit has left, which matters before changing the baud rate, sleeping, or turning an RS-485 driver off (lesson 5).
STEP 5
Errors
| error | meaning | usual cause |
|---|---|---|
| framing | the stop bit was 0 | baud mismatch, wrong format, noise, or a break |
| parity | the parity bit does not match | noise, or mismatched parity setting |
| overrun | a byte arrived while the FIFO was full | software did not read in time |
| break | the line stayed low longer than a frame | cable disconnected, TX held low, or a deliberate break |
On the PL011 each received character carries its own error bits in the data register, so software can tell exactly which byte was damaged.
STEP 6
Flow control
When the receiver cannot keep up, hardware flow control lets it say so: each side drives an output (RTS) that the other reads as CTS, and a transmitter pauses while its CTS is deasserted. It prevents overruns when the receiver is busy, at the cost of two more wires. Software flow control (XON/XOFF characters) needs no wires but reserves two byte values, so raw binary data needs escaping to use it.
STEP 7
Worked example: the console divisor
At clk_peri = 125 MHz and 115 200 baud, the ideal divisor is 125 × 10⁶ / (16 × 115 200) = 67.817. The SDK computes div = ⌊8 × 125 × 10⁶ / 115 200⌋ = 8680, so the integer part is 8680 >> 7 = 67 and the fraction ((8680 & 127) + 1) / 2 = 52, giving 67 + 52/64 = 67.8125. The actual rate is
an error of +0.006 %, negligible. With a 12 MHz clock and 921 600 baud the ideal divisor is 0.81, below the minimum of 1, so the UART runs at 750 000 baud, 18.6 % slow: nothing readable arrives.
MYTHS AND FACTS
Common misconceptions
TX goes to TX
Each TX drives the other side’s RX.
If both ends say 115 200, they match
Each end is only as accurate as its divisor and its clock; the errors add.
An RC oscillator is fine for a UART
Only if its tolerance plus the other end’s error stays inside the budget; many internal oscillators are close to it, and temperature moves them.
The transmit FIFO is empty, so the byte has been sent
The shift register may still be sending it; wait for BUSY to clear.
Check yourself
Answer in your head, then open the card.
What is the total baud mismatch budget for an 8E1 frame (11 bits up to the stop bit) with 16× oversampling? Does 8E2 change it?
(0.5 − 1/16) / (11 − 0.5) = 0.4375 / 10.5 ≈ 4.2 %. 8E2 gives the same, because the receiver checks only the first stop bit.
A UART receives bytes correctly when the other device sends one at a time, but long bursts arrive with random bytes missing and the overrun flag set. What is wrong?
The software is not draining the receive FIFO fast enough during bursts. Read it from an interrupt into a ring buffer, lower the FIFO threshold, or use flow control or DMA.
Why does the pico-sdk enable the receive timeout interrupt together with the FIFO-level interrupt?
The level interrupt fires only at 4 characters; a message ending with 1–3 characters in the FIFO would wait there indefinitely. The timeout fires after 32 idle bit periods and lets the handler collect them.
A device’s 8 MHz clock is 1.5 % fast and the other end’s divisor error is −0.8 %. Will 8N1 work?
The mismatch is about 2.3 %, inside the 4.6 % theoretical budget but using half of it; it should work with clean edges, with little margin for temperature drift or slow edges.
Sources (4)
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_uart/uart.c — uart_set_baudrate: baud_rate_div = 8 × clk_peri / baudrate; ibrd = div >> 7; fbrd = ((div & 0x7f) + 1) / 2; ibrd clamped to 1..65535; returns 4 × clk_peri / (64 × ibrd + fbrd), the rate actually set
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_uart/uart.h — “There is no guarantee that the baudrate requested will be possible, the nearest will be chosen”; uart_set_irq_enables: “RX asserts when >=4 characters are in the RX FIFO (for RXIFLSEL=0). RT asserts when there are >=1 characters and no more have been received for 32 bit periods”; uart_tx_wait_blocking polls the BUSY flag
- Raspberry Pi Ltd, pico-sdk 1.5.1, rp2040/hardware_regs/uart.h (PL011 registers) — UARTDR error bits OE (overrun: data received while “the receive FIFO is already full”), BE, PE, FE; RXIFLSEL 0b000 = “Receive FIFO becomes >= 1 / 8 full” (4 characters per uart.h, so 32 entries); UARTFR BUSY “remains set until the complete byte, including all the stop bits, has been sent from the shift register”; LCR_H STP2: “The receive logic does not check for two stop bits being received”
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_uart.h — UART_DIV_SAMPLING16(PCLK, BAUD) = PCLK × 25 / (4 × BAUD), i.e. 100 × PCLK / (16 × BAUD), split into a mantissa and a 4-bit fraction for BRR: the same divide-by-16 principle in fixed point