ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

串口波特率误差的完整计算与工程验证方法

串口波特率误差的完整计算与工程验证方法

一、误差从哪里来,先把公式钉死

UART 异步通信没有时钟线,收发双方各自靠本地时钟分频出采样节拍,约定在相同的时刻对数据位做判决。既然没有共享时钟,双方波特率的任何偏差都会累积成采样点的相位漂移,误差的定义因此非常直接

误差 = (实际波特率 - 目标波特率) / 目标波特率 × 100%

实际波特率由分频寄存器决定。以常见的 16 倍过采样为例

实际波特率 = fCK / (16 × USARTDIV)

其中 USARTDIV 是写入 BRR 寄存器的定点数,高 12 位是整数部分(Mantissa),低 4 位是小数部分(Fraction),所以最小分辨率是 1/16 = 0.0625。这个 0.0625 的分辨率是后面所有量化误差的根源,寄存器只能表达 1/16 的整数倍,理论值落在两个刻度之间时就必须取舍。

USARTDIV 理论值 = fCK / (16 × 目标波特率)

1.1 一个整除的算例

STM32F103,PCLK2 = 72MHz,目标 115200

USARTDIV = 72_000_000 / (16 × 115200) = 39.0625

39.0625 恰好能被 0.0625 整除,写入 BRR 的整数部分 39(0x27),小数部分 0.0625/0.0625 = 1(0x1),BRR = 0x271。实际波特率 = 72_000_000 / (16 × 39.0625) = 115200,误差 0%。这种情况属于时钟频率与波特率存在整除关系,寄存器刚好能精确表达,量化误差为零。

1.2 一个不整除的算例

现实中 PCLK 往往不是 115200 的整数倍关系。8MHz 晶振经 PLL 倍频到 72MHz 没问题,但如果系统时钟配置成 64MHz,PCLK2 = 64MHz

USARTDIV = 64_000_000 / (16 × 115200) = 34.7222

小数部分 0.7222 × 16 = 11.555,寄存器只能取 11(向下取整)或 12(四舍五入)。取 12 时 USARTDIV = 34.75,实际波特率 = 64_000_000 / (16 × 34.75) = 115107.9,误差 = (115107.9 - 115200)/115200 = -0.08%。取 11 时 USARTDIV = 34.6875,实际波特率 = 115315.3,误差 +0.10%。两种取法都远小于 2.5%,可用。

取 12 还是取 11,取决于四舍五入策略。四舍五入让误差绝对值最小,向下取整让误差恒为正向。工程上优先四舍五入,因为正负误差在收发两端可能相互抵消也可能叠加,绝对值小才是硬指标。

1.3 误差为什么必须算,不能靠试

误差算错,板子上的串口就是随机丢字节,而且往往在温度变化或换批次芯片后才暴露。原因是晶振的频率漂移会叠加到分频误差上,常温下勉强能通的链路,到了高低温边界就跌出容忍窗口。计算的意义是在打板之前就知道余量还剩多少,而不是等样机阶段用示波器救火。

二、误差的容忍边界在哪里

接收端的采样机制决定了容忍度。16 倍过采样下,接收器在起始位下降沿后第 8、9、10 个采样点做三取二判决,理论上允许累计相位偏移不超过半个位宽,即 50%/16 ≈ 3.1%(单边)。考虑起始位检测本身有 1 个采样周期的量化误差,工程上把收发双方误差之和控制在 2%~3% 以内是安全区,取 2.5% 作为设计红线。

2.1 红线是两端之和,不是单端

这个 2.5% 是收发两端误差的绝对值之和,不是单端。如果 MCU 误差 -0.64%,对端 USB 转串口芯片误差 +1.5%,合计 2.14%,仍在红线内但余量只剩 0.36%。所以做产品时不能只看自己这端。对端芯片的误差典型值在 ±1%~±2% 量级,具体取决于其内部时钟源是晶振还是 RC 振荡器,RC 方案的温漂会明显更大。

2.2 一帧累计偏移的算法

一帧 10 位(1 起始 + 8 数据 + 1 停止)累计偏移的计算

累计偏移 = 帧位数 × 单端误差

以 -0.64% 为例,10 位累计偏移 6.4% 个位宽,即 0.064 位。第 10 位(停止位)的采样点相对理想位置偏移 0.064 × 16 ≈ 1.02 个采样周期,三取二判决仍能正确落在数据位中心附近。当单端误差达到 3% 时,10 位累计偏移 0.3 位,停止位采样点偏移 4.8 个采样周期,已经越过三取二窗口,开始出现帧错误。

2.3 为什么帧越长越危险

上面的计算针对 10 位帧。如果启用了校验位变成 11 位,或者用了 9 位数据模式,累计偏移按比例放大。更长的帧意味着最后一位的相位漂移更大,这也是为什么高波特率长帧场景对误差更敏感。反过来,如果协议层能容忍偶发重传,误差红线可以适当放宽到 3%,但要在协议里准备好重传机制。

三、晶振选型直接决定误差下限

上面 8MHz→72MHz 的例子误差为 0,是因为 72 能被 115200 的相关因子整除。换一组常见参数对比

晶振系统时钟PCLK目标波特率USARTDIV 理论实际写入实际波特率误差
8MHz72MHz72MHz11520039.062539.06251152000%
12MHz72MHz72MHz11520039.062539.06251152000%
8MHz64MHz64MHz11520034.722234.75115107.9-0.08%
25MHz72MHz36MHz11520019.531319.5115384.6+0.16%
7.3728MHz72MHz36MHz11520019.531319.5115384.6+0.16%
8MHz48MHz48MHz9600312.5312.596000%
8MHz48MHz48MHz9216003.25523.25923076.9+0.16%

注意 7.3728MHz 这一行,这个频率是 115200 × 64 的整数倍关系(7.3728M / 115200 = 64),配合合适的分频能拿到极低误差,这是老工程师偏爱 7.3728MHz、11.0592MHz 这类「串口晶振」的原因。但上表里它经 PLL 到 72MHz 后反而没优势,说明晶振选对还不够,时钟树配置同样决定结果。晶振频率是原料,PLL 和分频是加工工艺,原料好但工艺不对照样出废品。

3.1 11.0592MHz 的经典算例

11.0592M / (16 × 115200) = 6.0,整数,误差 0%。这就是 51 单片机时代 11.0592MHz 晶振统治串口应用的原因。这个频率同时是 9600、19200、38400、57600、115200 的整数倍关系源,一组晶振覆盖全系列波特率,代价是系统主频不是整数 MHz,对需要精确延时的场景不友好。

3.2 晶振精度与分频误差是两回事

分频误差是寄存器量化带来的,可以通过换频率消除。晶振本身的频率精度是另一层误差,常见无源晶振精度在 ±10ppm~±30ppm 量级,换算成百分比是 ±0.001%~±0.003%,远小于分频误差。但温漂会在这个基础上叠加,工业级场景要按全温区的最差精度评估。两层误差叠加后才是链路真实的单端误差。

四、用代码把误差算出来

手算容易错,直接写个 C 函数,输入 PCLK 和目标波特率,输出最优 BRR 和误差

#include<stdio.h>#include<stdint.h>#include<math.h>typedefstruct{uint16_tbrr;// 写入 BRR 的值doubleactual;// 实际波特率doubleerror_pct;// 误差百分比}uart_div_t;uart_div_tcalc_brr(uint32_tpclk,uint32_tbaud){uart_div_tr;doublediv=(double)pclk/(16.0*(double)baud);// 四舍五入到 1/16 分辨率uint32_tmant=(uint32_t)div;uint32_tfrac=(uint32_t)((div-mant)*16.0+0.5);if(frac>=16){frac=0;mant+=1;}r.brr=(mant<<4)|(frac&0x0F);doublereal_div=(double)mant+(double)frac/16.0;r.actual=(double)pclk/(16.0*real_div);r.error_pct=(r.actual-(double)baud)/(double)baud*100.0;returnr;}intmain(void){uint32_tpclk_list[]={72000000,64000000,48000000,36000000};uint32_tbaud_list[]={9600,115200,921600};for(inti=0;i<4;i++){for(intj=0;j<3;j++){uart_div_tr=calc_brr(pclk_list[i],baud_list[j]);printf("PCLK=%lu BAUD=%lu BRR=0x%03X actual=%.2f err=%+.3f%%\n",pclk_list[i],baud_list[j],r.brr,r.actual,r.error_pct);}}return0;}

这段代码可直接用 gcc 编译(需 -lm)。实测输出中 PCLK=36000000、BAUD=921600 时 BRR=0x002,实际 1125000,误差 +22%,远超红线。这说明高波特率下低 PCLK 根本不可行,必须提高时钟或降低波特率。这类组合在选型阶段就该被代码筛掉。

4.1 把筛选逻辑做成批量扫描

单点计算只能验证一组参数,选型阶段需要的是全表扫描。下面这段代码遍历一组候选 PCLK 和波特率,把超过红线的组合直接标红,方便在时钟树设计阶段一次性排除不可行方案

#include<stdio.h>#include<stdint.h>#include<math.h>#defineERR_REDLINE2.5// 单端误差红线,单位 %// 返回 1 表示该组合可用,0 表示超红线intcheck_combo(uint32_tpclk,uint32_tbaud,double*err_out){doublediv=(double)pclk/(16.0*(double)baud);if(div<1.0){// USARTDIV 不能小于 1*err_out=999.0;return0;}uint32_tmant=(uint32_t)div;uint32_tfrac=(uint32_t)((div-mant)*16.0+0.5);if(frac>=16){frac=0;mant+=1;}doublereal_div=(double)mant+(double)frac/16.0;doubleactual=(double)pclk/(16.0*real_div);doubleerr=(actual-(double)baud)/(double)baud*100.0;*err_out=err;returnfabs(err)<=ERR_REDLINE;}intmain(void){// 候选 PCLK 覆盖常见时钟树结果uint32_tpclks[]={8000000,16000000,24000000,32000000,36000000,48000000,64000000,72000000};// 候选波特率覆盖常用档位uint32_tbauds[]={9600,19200,38400,57600,115200,230400,460800,921600};intnp=sizeof(pclks)/sizeof(pclks[0]);intnb=sizeof(bauds)/sizeof(bauds[0]);printf("PCLK\\BAUD");for(intj=0;j<nb;j++)printf("%9lu",bauds[j]);printf("\n");for(inti=0;i<np;i++){printf("%9lu",pclks[i]);for(intj=0;j<nb;j++){doubleerr;intok=check_combo(pclks[i],bauds[j],&err);// 可用打勾,超红线打叉并标误差if(ok)printf(" OK");elseprintf(" %+6.2f%%",err);}printf("\n");}return0;}

这段代码的价值在于把「这个时钟能不能跑这个波特率」变成一张可视化的矩阵表。表格里 OK 的格子是安全区,带误差数字的格子是危险区。选型时把时钟树方案套进去,一眼就能看出哪几档波特率需要换晶振或者改 PLL 倍频。

4.2 代码里的边界条件

上面两段代码都处理了两个边界。第一是 frac 进位到 16 时 mant 加一、frac 归零,避免小数部分溢出到整数位。第二是 USARTDIV 小于 1 时直接判不可用,因为 16 倍过采样下接收器需要至少 16 个采样点完成起始位检测,分频值过低会导致采样窗口不足。这两个边界在手工计算时最容易被忽略,写进代码就变成了强制检查。

五、过采样倍率对误差的影响

STM32 的 USART 支持 8 倍过采样(OVER8=1),公式变为

实际波特率 = fCK / (8 × USARTDIV)

8 倍过采样时 USARTDIV 的小数部分只有 3 位(分辨率 1/8 = 0.125),量化误差比 16 倍时大一倍,但换来的是同样 PCLK 下能跑到更高波特率。以 72MHz、921600 为例

16 倍:USARTDIV = 72M/(16×921600) = 4.8828,取 4.875,实际 923076.9,误差 +0.16%
8 倍:USARTDIV = 72M/(8×921600) = 9.7656,取 9.75,实际 923076.9,误差 +0.16%

本例两者相同,因为 72M 对 921600 的整除性较好。但在 36MHz、921600 下

16 倍:USARTDIV = 2.4414,取 2.4375,实际 923076.9,误差 +0.16%
8 倍:USARTDIV = 4.8828,取 4.875,实际 923076.9,误差 +0.16%

仍然相同。真正拉开差距的是 8 倍过采样允许 USARTDIV 低至 1 附近,16 倍时最小 USARTDIV 受限于起始位检测需要至少 16 个采样点。实际选型时优先用 16 倍,除非波特率超出范围。

5.1 8 倍过采样的代价

8 倍过采样的量化分辨率从 1/16 降到 1/8,意味着同样的 PCLK 和目标波特率下,能选到的分频值更粗糙。在整除性差的组合里,8 倍过采样的误差可能比 16 倍大一倍。代价换来的是同样 PCLK 下最高波特率翻倍,以及 USARTDIV 可以取到更小的值。什么时候该用 8 倍,判断标准是 16 倍模式下 USARTDIV 是否已经小于 2,如果小于 2 说明分频余量不足,换 8 倍能腾出空间。

5.2 过采样倍率与容忍度的关系

过采样倍率还影响接收端的三取二判决窗口宽度。16 倍过采样下窗口是 3 个采样周期,8 倍过采样下窗口同样是 3 个采样周期但每个周期更宽,折算成位宽比例反而更大。这意味着 8 倍过采样的单边容忍度在理论上略宽,但量化误差更大,两者部分抵消。工程上不需要纠结这个,按 16 倍优先、误差红线统一按 2.5% 控制即可。

六、实测验证方法

理论算完必须实测。用示波器抓 TX 引脚发 0x55(0b01010101,方波最规整),测 10 个位的时间。115200 下单位宽 8.68μs,10 位 86.8μs。若实测 87.4μs,反推实际波特率 = 10/87.4μs = 114416,误差 -0.68%,与计算值 -0.64% 吻合(差值来自示波器时基精度)。

选 0x55 是因为它的位模式是 01010101,起始位之后的每一位都在翻转,边沿密集,测量 10 个位的总时间时边沿定位误差最小。换成 0x00 或 0xFF 这种连续相同位的模式,边沿少,测量基准点不好找。

6.1 误码统计法

更省事的办法是让 MCU 持续发 0x55,用另一块已知准确的板子接收,统计 10000 字节的误码数。误码率 0 说明误差在容忍区内,出现偶发帧错误,先把误差算一遍再怀疑接线。统计法比示波器法更贴近真实链路,因为它同时验证了收发两端的误差叠加效果,而示波器只测了单端。

6.2 温度循环验证

误差在常温下合格不代表全温区合格。晶振温漂典型值在 ±10ppm~±50ppm 量级,宽温晶振能压到 ±10ppm 以内,普通晶振在 -40℃~+85℃ 区间可能漂到 ±30ppm 以上。把分频误差和温漂最差值叠加,才是链路在最坏情况下的单端误差。产品如果要在宽温区工作,常温实测通过后还要做温度循环下的误码统计。

七、常见误区

7.1 误区一,只算自己这端的误差

很多人算完 MCU 侧误差 -0.64%,看到远小于 2.5% 就认为链路安全。正确做法是把对端芯片的误差也加进来。对端如果是 USB 转串口芯片,其内部时钟可能是 RC 振荡器,误差典型值在 ±1.5%~±2% 量级,叠加后可能逼近红线。设计时按两端误差绝对值之和评估,余量至少留 1%。

7.2 误区二,认为晶振频率选对就万事大吉

7.3728MHz 是公认的串口友好频率,但如果 PLL 配置把它倍频到一个与目标波特率整除性差的主频,误差照样超标。正确做法是先确定系统主频需求,再反推晶振频率,最后用代码扫描验证。晶振频率、PLL 倍频、APB 分频三者共同决定 PCLK,任何一环没配对都会让误差失控。

7.3 误区三,用理论值代替寄存器实际值

有人算误差时直接用 USARTDIV 理论值代入公式,得出误差为零的结论,却忽略了寄存器只能存 1/16 的整数倍。正确做法是先把理论值量化到寄存器分辨率,再用量化后的值反算实际波特率。理论值和寄存器值之间的差就是量化误差,这一步不能省。

八、参数对照表

下表汇总常见参数在选型时的典型取值与影响方向,供快速查阅

参数典型值范围选大了会怎样选小了会怎样可突破场景
单端波特率误差±0.1%~±1%累计相位漂移大,停止位采样越界无实际危害,越接近零越好协议层有重传时可放宽到 ±3%
收发合计误差≤2.5%偶发帧错误,误码率上升余量充足但可能牺牲时钟灵活性短帧、低波特率可到 3%
USARTDIV 分辨率1/16(16 倍过采样)无法调整,硬件固定无法调整,硬件固定切 8 倍过采样变为 1/8
晶振精度±10ppm~±30ppm温漂叠加后吃掉分频余量成本上升,收益递减恒温场景可用普通晶振
过采样倍率16 倍 / 8 倍8 倍时量化误差翻倍16 倍时高波特率受限高波特率必须用 8 倍
帧长10 位 / 11 位累计偏移按比例放大偏移小但协议开销大校验位可按可靠性需求取舍

九、排查清单

串口出现随机丢字节时,按下面顺序逐项排查,每项都可勾选确认

  • 用第四章的代码算出当前 PCLK 与波特率组合的单端误差,确认绝对值小于 1.5%
  • 查对端芯片手册,确认其波特率误差典型值,两端误差绝对值之和小于 2.5%
  • 确认 BRR 寄存器实际写入值,而不是理论计算值,检查小数部分是否被正确量化
  • 确认 USARTDIV 不小于 1,16 倍过采样下建议不小于 2
  • 用示波器测 0x55 波形 10 位总时间,反推实际波特率与计算值对比
  • 做 10000 字节连续收发误码统计,确认误码率为零
  • 若产品工作温区宽,做高低温循环下的误码统计,确认全温区通过

十、可直接执行的结论

新项目选型时,先用第四章那段批量扫描代码把目标 PCLK 和全部计划使用的波特率跑一遍,任何组合误差超过 ±1.5% 就换晶振频率或改时钟树,不要等到样机阶段用示波器救火。分频误差是可以在图纸阶段算清楚的确定量,把它算对,剩下的不确定性才轮到温漂和对端芯片去承担。


如果这篇对你有用,请扫码关注。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进