UART Baud Rate Calculator

Calculate USART_BRR and UBRR register values for STM32, nRF52, ESP32, AVR, and RP2040. Shows actual baud rate and error percentage for all standard baud rates.

Configuration

Oversampling
BRR = fCLK / (16 × baud)

Result

USART_BRR

0x005B

= 91 decimal

Actual baud

115,385

Error

0.16%

STM32F4 (168 MHz) reference manual →

Common baud rates — STM32F4 (168 MHz)

BaudUSART_BRRHexActualError
9,60010940x04469,5980.02%
19,2005470x022319,1960.02%
38,4002730x011138,4620.16%
57,6001820x00B657,6920.16%
115,200910x005B115,3850.16%
230,400460x002E228,2610.93%
460,800230x0017456,5220.93%
921,600110x000B954,5453.57%

How it works

UART clocks are derived from the peripheral clock by dividing it down to the target baud rate. The divisor is written to a register — USART_BRR on STM32, UBRR0 on AVR, UART_CLKDIV_REG on ESP32.

STM32 (USART_BRR)

// 16x oversampling (default)
USART1->BRR = SystemCoreClock / 115200;

// 8x oversampling (OVER8=1 in CR1) — use for higher baud rates
USART1->BRR = (2 * SystemCoreClock) / 115200;
// embassy-stm32 — 115200 baud on USART1
use embassy_stm32::usart::{Config, Uart};

let mut config = Config::default();
config.baudrate = 115_200;
// BRR = PCLK / baudrate computed automatically; swap to 8x oversampling
// by setting config.assume_noise_free = true on noisy lines
let uart = Uart::new(p.USART1, rx, tx, irqs, p.DMA2_CH7, p.DMA1_CH5, config);

// Or direct register write (16x oversampling):
// USART1::ptr().brr.write(|w| w.bits(pclk_hz / 115_200));
// 8x oversampling: w.bits((2 * pclk_hz) / 115_200)

The STM32 BRR register holds both the integer and fractional divider in a single 16-bit value. For 16x oversampling: BRR = fCLK / baud. The hardware rounds implicitly. Error below 2% is acceptable; below 0.5% is ideal.

AVR (UBRR0)

The AVR formula subtracts 1 because the counter counts from UBRR down to 0:

// Normal speed (U2X0 = 0)
UBRR0 = F_CPU / (16UL * 115200) - 1;

// Double speed (U2X0 = 1) — better accuracy at high baud rates
UBRR0 = F_CPU / (8UL * 115200) - 1;
// avr-hal — configure UART at 115200 baud
// UBRR = F_CPU / (16 * baud) - 1 is computed by the HAL automatically
let dp = arduino_hal::Peripherals::take().unwrap();
let pins = arduino_hal::pins!(dp);
let mut serial = arduino_hal::default_serial!(dp, pins, 115200);

// For double-speed mode, set the U2X bit manually before calling the HAL:
// dp.USART0.ucsr0a.modify(|_, w| w.u2x0().set_bit());
// UBRR = F_CPU / (8 * baud) - 1

At 16 MHz with 115200 baud: UBRR0 = 8 (0.2% error). At 8 MHz: UBRR0 = 3 (8.5% error — too high, use double speed mode or 57600 baud).

nRF52 (BAUDRATE register)

The nRF52 UARTE does not use a calculated divisor. The BAUDRATE register takes fixed magic constants:

NRF_UARTE0->BAUDRATE = UARTE_BAUDRATE_BAUDRATE_Baud115200; // 0x01D7E000
// embassy-nrf — UART at 115200 baud
use embassy_nrf::uarte::{Config, Uarte};
use embassy_nrf::uarte::Baudrate;

let config = Config {
    baudrate: Baudrate::BAUD115200, // hardware constant 0x01D7E000
    ..Default::default()
};
let mut uart = Uarte::new(p.UARTE0, irqs, rx, tx, config);

These values are defined in nrfx_uarte.h / nrf52840.h. The silicon generates the baud clock internally. You cannot use arbitrary baud rates — stick to the supported list in the datasheet (section 6.34).

ESP32 (UART_CLKDIV_REG)

ESP32 UART uses the APB clock (typically 80 MHz) divided directly:

// uart_ll.h internally does:
UART0.clk_div.div_int = APB_CLK_FREQ / baud_rate;
// esp-idf-hal — configure UART at 115200 baud
use esp_idf_hal::uart::{config::Config, UartDriver};
use esp_idf_hal::units::Hertz;

// APB_CLK / baudrate computed automatically; fractional divider used
// for sub-integer precision (4-bit field in UART_CLKDIV_REG)
let uart_config = Config::new().baudrate(Hertz(115_200));
let uart = UartDriver::new(
    peripherals.uart0, tx, rx, None::<AnyIOPin>, None::<AnyIOPin>,
    &uart_config,
)?;

The ESP-IDF uart_set_baudrate() handles this. The register also has a 4-bit fractional field for sub-integer precision.

Common mistakes

Feeding the wrong clock frequency. On STM32, USART1 is typically on APB2 (can run at system clock), while USART2/USART3 are on APB1 (often half the system clock). Check your clock tree in CubeMX or the RCC registers.

Forgetting the -1 on AVR. The UBRR formula is fCPU / (16 × baud) - 1, not fCPU / (16 × baud). At low baud rates the difference is small; at 9600 baud and 8 MHz it’s the difference between register value 51 and 52.

Using 16x oversampling at high baud rates on low clock MCUs. At 16 MHz and 921600 baud, the divisor rounds to 1 — no room for precision. Switch to 8x oversampling or a faster clock.

Not accounting for clock accuracy. Internal RC oscillators drift with temperature (typically ±1–3%). If your UART runs at the edge of the 2% error budget, a cold bench start might pass but a –40 °C field deployment fails.

nRF52 using calculated values. The BAUDRATE register is not a clock divider — it is a lookup table entry. Writing a calculated value will produce the wrong baud rate or garbage.

Frequently asked questions

Why does my STM32 UART have framing errors despite a correct baud rate calculation? +

The most common cause is feeding the wrong clock frequency into the formula. USART1 on STM32 is typically on APB2 (can run at full system clock), while USART2/USART3 are on APB1 which often runs at half the system clock. Check your clock tree in CubeMX or read the RCC registers directly.

What does the -1 mean in the AVR UBRR baud rate formula? +

The AVR UBRR counter counts from the register value down to 0, so the formula is fCPU / (16 × baud) - 1, not fCPU / (16 × baud). At 9600 baud and 8 MHz, omitting the -1 gives register value 52 instead of 51, producing a small baud rate error. At higher baud rates the error grows enough to cause framing errors.

Does the nRF52 BAUDRATE register accept calculated divisor values? +

No. The nRF52 UARTE BAUDRATE register is not a clock divider — it is a lookup table of fixed magic constants defined in nrfx_uarte.h and nrf52840.h. Writing a calculated value will produce the wrong baud rate or garbage output. Use the predefined constants such as UARTE_BAUDRATE_BAUDRATE_Baud115200 (0x01D7E000).

Newsletter

The embedded engineer's weekly cheat sheet

Register tricks, timing gotchas, and tool updates. One email per week. No fluff.

No spam. Unsubscribe anytime.