UART 波特率自适应:起始位测量、分数分频与同步采样实现
更新时间:2026-09-19。前置:UART 波特率、过采样与 FIFO 溢出。本文解决「不知道对端波特率也能正确通信」的检测机制。
本文要回答的问题
- 自动波特率检测量的是哪一段时间,为什么常用回车符 0x0D?
- 怎么从测得的位周期算出分频系数(含小数分频)?
- 允许多大误差,怎么防止噪声/毛刺误触发?
- 检测成功后采样点怎么对齐,为什么要二次验证?
一、基本思路
普通 UART 靠固定分频寄存器得到 16× 过采样时钟。自动波特率则反过来:在一个已知参考高频时钟下,测量输入 RX 引脚上单个 bit 的持续时间(边沿到边沿),再反推波特率。
bash
波特率 b → 1 bit 时间 Tbit = 1/b
参考时钟 fref → 1 bit 内参考周期数 N = fref / b
分频系数 divisor = fref / (osr × b) // osr 通常 16二、两种测量法
| 方法 | 测量边沿 | 特点 |
|---|---|---|
| 起始位脉宽法(2 边沿) | 起始位下降沿 → 第一个数据位边沿 | 简单,受首字节内容约束 |
| 特征字符周期法(多边沿) | 用 0x0D(CR) 或 0x55('U'),测两个特定沿 | 更鲁棒,工业默认约定发回车/0x55 |
为什么用 0x0D = 0000_1101(LSB 先发:起始0、数据 1,0,1,1,0,0,0,0):起始位后紧跟一个高电平位,形成「下降沿→上升沿」恰好 1 bit 的干净窗口,且回车是终端最常发送的键。0x55 = 0101_0101 则产生连续跳变,可测多位取平均。
bash
0x0D 帧(idle=1):
idle 1 | S=0 | D0=1 | D1=0 ...
└下降沿 └上升沿
◄── 正好 1 个 bit 时间 ──► 用计数器测这段三、检测状态机
verilog
// 参考:abd_measure —— 自动波特率测量
module abd_measure #(
parameter FREF_HZ = 100_000_000
)(
input wire clk, rst_n,
input wire rx, // 已做两级同步
output reg abd_done,
output reg [31:0] bit_ticks // 一个 bit 内的参考时钟数
);
reg [31:0] cnt;
reg armed;
wire rx_fall = armed & ~rx & rx_d; // 同步后下降沿
reg rx_d;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
armed <= 1'b1; abd_done <= 1'b0; cnt <= 0; bit_ticks <= 0; rx_d <= 1'b1;
end else begin
rx_d <= rx;
if (armed) begin
if (rx_fall) begin // 起始位下降沿:开始计数
armed <= 1'b0; cnt <= 0;
end
end else begin
cnt <= cnt + 1'b1;
if (rx && !rx_d) begin // 下一个上升沿(D0=1):停止计数
bit_ticks <= cnt; // = 1 bit 的参考周期数
abd_done <= 1'b1;
end
end
end
end
endmodule得到 bit_ticks 后:
verilog
// divisor = fref / (osr * baud),其中 baud = fref / bit_ticks
// 化简:divisor(含16x) ≈ bit_ticks / osr
wire [31:0] divisor_int = bit_ticks >> 4; // osr=16 整数部分
wire [15:0] divisor_frac = (bit_ticks[3:0] * 625)>>6;// 低4位映射到小数(按UART IP定义)分数分频(fractional divider)用整数商 + 小数累加抖动(Bresenham 式)把实际过采样时钟误差压到 ±1 个参考周期内。
四、误差容限与稳健性
- 允许误差:常用异步 8N1 总余量约 ±4.5%(采样在第 8/16 点,两侧各留半位,再考虑对端误差),工程一般要求本机误差 < 2%。
- 毛刺防护:检测前 RX 必须先同步(两级 FF,见 CDC 两级同步),并要求总线空闲为高才允许 arm。
- 限幅/合理性判断:算出的 baud 必须落在支持表(如 1200~921600)或与标准档误差 ❤️%,否则丢弃重来。
- 二次验证:用算出的分频去接收同一字节,回读是否等于 0x0D/0x55,或连续两帧一致才锁定,防止碰巧的边沿。
五、常见的坑
- RX 未同步直接采:亚稳态导致边沿抖动、计数忽大忽小,先过两级 FF。
- 非 0x0D/0x55 首字节:第一个字节不产生期望的「下降→上升 1 bit」窗口,需约定主机先发探测字符。
- 只测一位不验证:噪声毛刺被当成起始沿,锁定到荒谬波特率;务必限幅 + 回读校验。
- 忽略分数分频:只用整数分频,高波特率下误差超 ±4.5% 采样错位。
- 参考时钟未就绪就测:PLL 未 lock 时 fref 不准,检测结果整体偏移。
- 半位起步误差累积:测量起点不在沿正中会引入系统偏差,多边沿平均可减小。
- 检测中仍按旧波特收数:abd_done 前必须丢弃 RX 数据/保持接收复位。
- 断线恒低:常被当成超长起始位,需超时判定为 break 而非锁定极低波特率。
- 数据格式假设错误:默认 8N1,若对端是 7E1/校验位,0x0D 边沿位置改变导致检测窗口错。
- 锁定后波特率漂移:温漂/精度差的对端会逐渐偏出,可加帧错误(FE)计数触发重新检测。
相关与延伸
总结
自动波特率的本质是「用稳定高频参考时钟测 RX 上一个 bit 的边沿到边沿时间,再反推 fref/(osr×baud) 分频系数」:让对端先发 0x0D 或 0x55 以获得干净/多跳变窗口,RX 先两级同步、测值限幅到标准档并回读同一字节二次验证,用分数分频把误差压到 ±1 参考周期;整机误差须留足 8N1 的 ±4.5% 余量——未同步、不验证、漏小数分频和把断线当起始位是四大坑。