Bench note

UART baud rates that match what you soldered

The silent frame error

You've wired TX to RX, set both ends to 115200, and still see occasional corruption. The problem isn't your connections—it's the accumulated timing error between two independent clocks trying to agree on when a bit starts and stops.

UART has no shared clock line. Each side generates its own baud timing from a crystal or internal oscillator, and those never match perfectly. A cheap 16 MHz crystal might drift ±100 ppm. Your microcontroller's internal RC oscillator? Easily ±2%. When both sides run fast or slow in opposite directions, the bit windows walk past each other until a frame fails.

Pick baud rates your hardware can hit

Most micros derive UART timing by dividing a base clock. If that division doesn't land on a whole number, you get a baud rate close to your target—but not exact.

Check your datasheet's baud-rate table. An ATmega328P at 16 MHz hits 115200 baud with 0.2% error using a divisor of 8. Clean. But 230400? That needs a divisor of 3.5, which rounds to 4, giving you 3.5% error. You'll see glitches within a few dozen frames.

Instead: use 250000 baud. It divides evenly (divisor of 4, zero error), and it's faster. The "standard" rates exist because RS-232 hardware had fixed divisors; your embedded UART doesn't care.

Add the tolerances, not the datasheets

Even with perfect divisors, crystal tolerance stacks. If one board runs +50 ppm and the other runs –50 ppm, you're 100 ppm apart before temperature drift.

UART can tolerate about ±2% accumulated error over a ten-bit frame (start + 8 data + stop). That sounds generous until you add:

  • Crystal initial accuracy: ±50 ppm typical, ±100 ppm cheap
  • Temperature drift: ±30 ppm over 0–70°C
  • Aging: ±5 ppm per year
  • Divisor rounding error: sometimes 3%

Two boards with 1% baud error each and 100 ppm crystal mismatch will fail intermittently under temperature swings. I've debugged this exact failure mode in a robot control loop where one board sat near a motor driver and ran 15°C hotter than the other.

What I actually do

I pick baud rates that divide cleanly from my clock, then confirm both sides land within 0.5% of each other. For production, I specify ±50 ppm crystals and test at temperature extremes. For one-offs, I avoid the problem:

  • Use SPI or I²C when both boards share a clock domain
  • Drop to 57600 or even 9600 when cable length or noise matters
  • Add parity bits and check them, or frame with start/end markers you can verify

If you must run fast async serial, log your actual baud divisor and error in the firmware comments. When you troubleshoot at 2 a.m., you'll know whether to suspect baud drift or the watchdog timer you forgot to pet.

The math you can skip

You don't need to calculate ppm error by hand. Set your target baud, compute divisor = F_CPU / (16 × baud), round it, then reverse the formula to see what you actually get. If the error is over 1%, pick a different rate.

Or use a rate that divides evenly and never think about it again. 250000 baud at 16 MHz, 1000000 baud at 48 MHz—these just work. Save your attention for the control loops that matter.

← Full log