UART at 4 MHz: When Baud Rate Tolerance Actually Matters
Most UART tutorials show 115200 baud where framing errors are rare. At 4 Mbaud the 3% tolerance in the spec becomes a real engineering constraint.

Most UART tutorials stop at 115200 baud where a 3% clock error still frames cleanly. At 4 Mbaud on STM32H7, that same error puts you outside the receiver's sampling window — and the STM32 reference manual's "acceptable error" table assumes you read footnote 47. This note covers baud tolerance math, clock source choices, and scope verification at high speed.
Related: SPI bus debugging notes, oscilloscope trigger cheat sheet.
When 4 Mbaud shows up in real designs
- Fast sensor bridges (some lidar debug ports)
- FPGA configuration/status streams on short PCB traces
- Logic analyzer substitute links on bench (not production recommended)
- Bootloader protocols pushing flash programming limits
Not RS-232 — TTL UART, short wires, common ground assumed.
UART sampling recap
Receiver typically oversamples 16× (STM32 default). Effective bit center sampling tolerates baud mismatch between TX and RX if cumulative phase error stays within ~±2% to ±3% depending on frame length, number of bits, and receiver oversampling.
Rule of thumb from STM32 app notes: max total error ≈ 2% for 8N1 at 16× oversampling when both clocks independent.
At 115200: 2% is forgiving. At 4000000: one bit = 250 ns; error budget is tens of nanoseconds of slip over a frame.
Baud generator formula (STM32)
Baud = f_CK / USARTDIV
USARTDIV = f_CK / Baud
Fractional divider on H7 helps hit exact rates — still limited by f_CK choice.
Example: USART kernel clock = 200 MHz (after PLL divider)
USARTDIV = 200e6 / 4e6 = 50.0 → exact
Example: kernel clock = 48 MHz (HSI-based)
USARTDIV = 48e6 / 4e6 = 12.0 → exact
Problem case: 48 MHz clock, want 4 Mbaud — works if exact divider.
Problem case: 84 MHz APB clock, want 4 Mbaud:
USARTDIV = 84e6 / 4e6 = 21.0 → exact on paper
But if PLL jitter or incorrect USARTPRE prescaler on H7 — measured baud drifts.
Clock source ranking for high baud
| Source | Jitter | High baud suitability |
|---|---|---|
| HSE + PLL | Low | Best |
| HSI | Medium | Verify on scope |
| LSE | N/A for UART baud | No |
| Internal sync to USB | Low if locked | Good on parts with USB CLK recovery |
On STM32H743 project, switching USART kernel from HSI to PLL-derived 200 MHz fixed intermittent framing errors at 4 Mbaud — scope showed 1.2% baud run error until PLL locked fully after wake from stop mode.
Measuring actual baud
Scope on TX line, measure bit width:
Measured baud = 1 / bit_period
Error % = (measured - target) / target × 100
Alternatively count bits in known pattern over timer capture.
At 4 Mbaud, scope bandwidth matters — 200 MHz minimum for ~5% rise time fidelity on square-ish edges.
See oscilloscope trigger cheat sheet for edge triggering on single character 0x55 (alternating bits).
Test pattern: 0x55 and 0xAA
uint8_t pat[] = { 0x55, 0xAA, 0x55, 0xAA };
HAL_UART_Transmit(&huart, pat, 4, HAL_MAX_DELAY);
0x55 = max transition density — worst case for clock slip accumulation within frame.
Loopback RX interrupt counts framing errors (ORE, FE in ISR status register).
STM32 register check on framing errors
void USART1_IRQHandler(void) {
if (USART1->ISR & USART_ISR_FE) {
framing_errors++;
USART1->ICR = USART_ICR_FECF;
}
/* ... */
}
Sweep baud on TX partner ±3% — plot FE rate vs offset. Should match datasheet tolerance curve.
PCB / signal integrity at 4 Mbaud
UART is async but edge timing matters:
- Keep traces short (< 10 cm on 2-layer)
- Series resistor 22–33 Ω at TX pin reduces overshoot
- Do not route UART parallel to switching buck without gap
Scope eye: open receiver input at remote end — jitter visible as edge spread. If spread > 5% of bit period, fix hardware before tuning dividers.
Multi-drop and level shifters
5 V ↔ 3.3 V via auto-direction level shifter adds propagation delay asymmetry — effective sampling point shifts. At 4 Mbaud, avoided bit-banged level shifters; used TXS0102 with short wires or native 3.3 V both sides.
Bootloader implication
ST ROM UART bootloader often fixed at 115200 or 57600 — not this problem. Custom UART bootloader at 921600+ needs same tolerance analysis on host side (USB-UART adapter chip matters — FT232H vs CP2102).
Worked tolerance budget
Target 4 Mbaud, 8N1, 16× oversampling:
| Error source | Budget |
|---|---|
| TX clock | ±0.5% |
| RX clock | ±0.5% |
| Scope-measured partner adapter | ±0.3% |
| ISR latency causing late sample (sw) | ±0.2% |
| Total | ±1.5% |
Stay under 2% combined. If HSI alone is ±1% at temp corners, you have no margin — use HSE.
CubeMX footgun
Auto-calculated baud may use floating printout rounded in UI. Verify USART_BRR register:
printf("BRR=0x%08lx fck=%lu\n", USART1->BRR, HAL_RCC_GetPCLK2Freq());
Recompute manually.
What I would do next
Add production self-test: loopback during manufacturing, assert FE==0 at max baud for 1000 frames.
Document max baud on schematic note with required clock source — prevent future engineer from enabling HSI-only low-power mode without retest.
Related
SPI debugging for another serial bus with different tolerance model. Scope triggers for capturing framing glitches.
Manish Bookreader
Electronics enthusiast, Embedded Systems Expert, Linux/Networking programmer, and Software Engineer passionate about AI, electronics, books, and cooking.

