Skip to content

Fix overflow of the UART fractional baud rate divisor - #1012

Merged
thejpster merged 1 commit into
rp-rs:mainfrom
r3wretrhy:fix/uart-baud-divisor-carry-820
Oct 11, 2026
Merged

thejpster merged 1 commit into
rp-rs:mainfrom
r3wretrhy:fix/uart-baud-divisor-carry-820

Conversation

@r3wretrhy

Copy link
Copy Markdown
Contributor

Fixes #820.

calculate_baudrate_dividers() only rounded the fractional part of the divisor:

match (baudrate_div >> 7, (baudrate_div & 0x7F).div_ceil(2)) {

When the low 7 bits of 8 * clk / baud are 0x7F, the fractional part rounds up to 64. UARTFBRD.BAUD_DIVFRAC is only 6 bits wide, so it is written as 0 and the integer part is not incremented. For example 57871 baud at 125 MHz was programmed as 134 + 0/64 instead of 135 + 0/64 (the jump to ~58300 baud that @KisImre measured). The error is about 1 / divint, so it gets large at high baud rates.

This change rounds the whole divisor to the nearest 1/64 before splitting it, so the carry goes into the integer part. That gives the same result as the C SDK after raspberrypi/pico-sdk#1774 (baud_rate_div = 8 * clk / baud + 1; ibrd = baud_rate_div >> 7; fbrd = (baud_rate_div & 0x7f) >> 1). I checked that every value of 8 * clk / baud up to 2^20 gives the same result. The only values that change are the carry cases, which previously came out as (n, 64) and now come out as (n + 1, 0). The same change is applied to both rp2040-hal and rp235x-hal, which have identical copies of the function. The match is re-indented only because rustfmt now puts Ok(match ...) on one line.

I added host unit tests in both crates:

  • 115200 baud at 125 MHz still gives (67, 52).
  • 57871 baud at 125 MHz gives (135, 0). Before this change it gave (134, 64).
  • For every baud rate from 1200 to 1 000 000 (at 125 MHz for rp2040 and 150 MHz for rp235x), the fractional divisor fits in 6 bits. Before this change it failed at 1201 baud.

Tested:

  • cargo test --tests --features critical-section-impl and cargo test --doc --features critical-section-impl in rp2040-hal and rp235x-hal.
  • Builds for thumbv6m-none-eabi, thumbv8m.main-none-eabihf and riscv32imac-unknown-none-elf.
  • cargo clippy -- -D warnings and cargo fmt -- --check.

I have not tested this on hardware.

calculate_baudrate_dividers() rounded only the fractional part of the
divisor, so whenever the lower 7 bits of 8 * clk / baud were 0x7F the
fractional value became 64. UARTFBRD is 6 bits wide, so that got
truncated to 0 while the integer part was not incremented, e.g.
57871 baud at 125 MHz was programmed as 134 + 0/64 instead of 135 + 0/64.

Round the whole divisor to the nearest 1/64 before splitting it, as the
C SDK does since raspberrypi/pico-sdk#1774.

Fixes #820

@thejpster thejpster left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for this!

@thejpster
thejpster merged commit c5ada50 into rp-rs:main Oct 11, 2026
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Faulty UART baudrate divisor formula

2 participants