第一级流水线:波特率时钟生成与过采样技术

各位同学,今天我们正式开始串口接收模块的流水线改造。第一级流水线要解决什么问题呢?说白了,就是怎么在正确的时刻,准确地采集到串口线上的数据。

串口通信不像SPI或I2C那样有独立的时钟线。它只有一根数据线,发送端按约定的速率往线上丢数据,接收端得自己想办法跟上这个节奏。这个节奏,就是波特率。

波特率时钟的本质

波特率(Baud Rate)定义了每秒传输的符号数。对于UART来说,一个符号就是一个bit。所以115200波特率,就是每秒传115200个bit。每个bit持续的时间是:

1 / 115200 ≈ 8.68 微秒

接收端需要在这个时间窗口内,找到每个bit的中间位置进行采样。为什么是中间?因为边沿容易受噪声干扰,中间最稳定。我在项目中遇到过,有些同学直接拿波特率时钟去采样,结果在边沿附近采到毛刺,数据全乱了。

过采样技术:为什么要采16次?

你可能会问:既然每个bit持续8.68微秒,我直接生成一个8.68微秒的时钟去采样不就行了?

嗯,理论上可以。但实际中,发送端和接收端的时钟不可能完全同步。晶振有误差,温度会漂移。如果只采一次,一旦采样点偏离了bit中心,就可能采到错误的数据。

过采样技术就是为了解决这个问题。我个人的习惯是用16倍过采样,也就是在每个bit时间内采样16次。这样做的优势很明显:

  • 抗抖动:16次采样中,多数值可以投票决定真实电平
  • 定位准确:可以精确找到起始位的下降沿,误差在1/16 bit以内
  • 时钟容错:允许收发双方的时钟有±4%左右的偏差

核心公式:

采样时钟频率 = 波特率 × 过采样倍数

例如:115200 × 16 = 1.8432 MHz

计数器设计:从系统时钟到采样时钟

假设我们的系统时钟是50MHz,要生成1.8432MHz的采样时钟,怎么做?用计数器分频。

先算分频系数:

50,000,000 / 1,843,200 ≈ 27.13

取整为27。但27.13不是整数,意味着每个采样周期会有累积误差。怎么办?

我早期做这个的时候,直接用了27分频,结果跑了几万个bit之后,采样点漂移了将近半个bit。后来我改用了一种叫「累加器分频」的方法,误差被均匀分散,效果好了很多。

累加器分频法(推荐)

这种方法不用取整除法,而是用一个累加器不断累加一个步进值。当累加器溢出时,就产生一个采样时钟脉冲。

// 参数定义
parameter SYS_CLK = 50_000_000;  // 系统时钟 50MHz
parameter BAUD_RATE = 115200;     // 目标波特率
parameter OVERSAMPLE = 16;        // 过采样倍数

// 计算步进值(定点数,高16位为整数部分)
localparam STEP = (BAUD_RATE * OVERSAMPLE * 2**16) / SYS_CLK;
// STEP = (115200 * 16 * 65536) / 50_000_000 ≈ 2416

reg [15:0] accum;
wire sample_clk;

always @(posedge sys_clk) begin
    accum <= accum + STEP;
end

assign sample_clk = accum[15];  // 取最高位作为采样时钟

这段代码的精妙之处在于:累加器每16个系统时钟周期左右溢出一次,但溢出时刻不是固定的,而是根据STEP值均匀分布。这样产生的采样时钟,长期来看频率非常准确。

我的经验:累加器位宽建议用16位或24位。位宽越大,频率精度越高,但消耗的寄存器也越多。对于115200波特率,16位足够了。

采样点的选择:为什么是第8个采样点?

有了16倍过采样的时钟,每个bit时间内我们会得到16个采样值。那么,取哪一个作为最终的数据呢?

答案是第8个采样点,也就是正中间的那个。为什么?

  • 第0~7个采样点:靠近bit的前半段,可能受前一个bit的边沿影响
  • 第8个采样点:正好在bit的中间,最稳定
  • 第9~15个采样点:靠近bit的后半段,可能受下一个bit的边沿影响

我曾经在一个项目中,因为采样点选得太靠前,导致在高速率(921600波特率)下频繁出错。后来改成第8个采样点,问题就解决了。

起始位检测:同步的关键

串口接收的起点是检测到起始位(高电平变低电平)。在空闲状态下,RX线为高电平。当检测到下降沿时,启动采样计数器。

但这里有个坑:毛刺也可能产生下降沿。我建议的做法是:

  1. 连续采样到3个低电平,才确认是真正的起始位
  2. 确认起始位后,从第8个采样点开始采集数据位
  3. 每16个采样时钟采集一个数据位

避坑指南:我曾经在一个工业控制项目中,因为没做毛刺滤波,结果电机启动时的电磁干扰让串口误触发了几百次起始位检测。从那以后,我所有的串口模块都加了3次连续采样确认的逻辑。

本章知识体系

下面这张图展示了第一级流水线的核心逻辑:

第一级流水线:波特率时钟生成与过采样 系统时钟 50MHz 累加器分频 STEP = 2416 16位累加器 取最高位输出 采样时钟 1.8432MHz 过采样计数器 0~15循环计数 采样点 = 第8个 输入 ↓ 分频计算 ↓ 采样时钟 ↓ 数据采集

参数配置参考表

系统时钟 目标波特率 过采样倍数 采样时钟频率 累加器步进值(16位)
50 MHz 9600 16 153.6 kHz 201
50 MHz 115200 16 1.8432 MHz 2416
50 MHz 921600 8 7.3728 MHz 9665
100 MHz 115200 16 1.8432 MHz 1208

小技巧:如果系统时钟不是50MHz的整数倍,别慌。累加器分频法天然支持非整数分频,你只需要重新算一下STEP值就行。我一般用Python或者Excel算好,直接写到参数里。

好了,第一级流水线的核心内容就这些。总结一下:我们用累加器分频法生成了精确的采样时钟,用16倍过采样保证了采样点的稳定性,用第8个采样点获取了最可靠的数据。下一级流水线,我们会用这些采样点来恢复出完整的串口数据帧。

可可精品资料库