4、串口通信基础:UART协议帧格式与波特率

串口通信,说白了就是让两个设备之间用一根线“聊天”。在FPGA的世界里,UART(通用异步收发传输器)是最常用的串口协议之一。我刚开始接触FPGA时,第一个实战项目就是通过串口让开发板和电脑“对话”。那时候觉得这东西挺神奇的——一根线就能传数据?后来搞明白了,其实核心就两个东西:帧格式和波特率。

4.1 UART协议帧格式

UART的帧格式,你可以想象成快递包裹。每个包裹都有固定的包装方式,接收方按这个包装拆包,就能拿到里面的数据。一个标准的UART帧包含以下几个部分:

  • 起始位:告诉接收方“我要开始发数据了”。通常是1位,逻辑0。
  • 数据位:真正要传的数据。可以是5到8位,最常见的是8位。
  • 校验位:可选,用于简单的错误检测。有奇校验、偶校验、无校验三种。
  • 停止位:告诉接收方“这包数据发完了”。通常是1位或2位,逻辑1。

我习惯把帧格式画成下面这张图,一目了然:

起始位 1位 (0) 数据位 5~8位 (通常8位) 校验位 可选 (0/1) 停止位 1~2位 (1) 空闲 (1) 一帧数据 (时间方向 →)

你看,空闲时总线保持高电平(逻辑1)。当发送方把电平拉低(逻辑0),接收方就知道“来了来了,数据要来了”。这就是起始位的作用。

4.2 数据位与传输顺序

数据位是帧的核心。这里有个细节我当年踩过坑——UART传输数据时,是先传低位(LSB),再传高位(MSB)。举个例子,你要发送0x55(二进制01010101),实际在线上看到的顺序是:

起始位(0) → 1 → 0 → 1 → 0 → 1 → 0 → 1 → 0 → 校验位 → 停止位(1)

嗯,这里要注意:如果你用逻辑分析仪抓波形,别按习惯从左到右读成高位到低位,否则数据全反了。我有个同事就因为这个bug调了一整天,最后发现是字节序搞反了。

4.3 校验位:奇校验与偶校验

校验位是用来检测传输过程中有没有发生错误的。它的原理很简单:

  • 奇校验:数据位中1的个数 + 校验位 = 奇数
  • 偶校验:数据位中1的个数 + 校验位 = 偶数

举个例子,数据位是0x55(二进制01010101),里面有4个1(偶数)。如果选奇校验,校验位就得是1,让总1的个数变成5(奇数)。如果选偶校验,校验位就是0,保持4个1(偶数)。

我的建议:在大多数FPGA项目中,如果通信环境不太恶劣(比如开发板和电脑之间线不长),我一般直接关掉校验位。省掉这一位,每帧能少传1位,波特率利用率更高。但如果是工业环境或者长线传输,建议开启奇校验,检测能力比偶校验稍好一些。

4.4 停止位

停止位就是给接收方一点时间“消化”刚收到的数据。常见的有1位、1.5位、2位三种。我个人习惯用1位停止位,除非对方芯片手册明确要求用2位。

为什么会有1.5位这种奇怪的存在?其实是因为早期的一些UART芯片,时钟精度不够,需要更长的停止位来保证下一帧起始位能被正确识别。现在FPGA的时钟精度很高,1位停止位完全够用。

4.5 波特率:通信的“节拍器”

波特率,说白了就是每秒能传多少位数据。单位是bps(bits per second)。常见的波特率有:

波特率 (bps) 每位时间 (μs) 常见用途
9600 104.17 调试、低速设备
19200 52.08 一般通信
38400 26.04 较高速通信
115200 8.68 FPGA调试常用
921600 1.09 高速数据传输

波特率怎么算?很简单:每位的时间 = 1 / 波特率。比如115200bps,每位就是1/115200 ≈ 8.68微秒。在FPGA里,你需要用系统时钟去“数”这个时间。假设系统时钟是50MHz(周期20ns),那么每位需要数 8.68μs / 20ns ≈ 434个时钟周期。

核心公式:

波特率时钟分频值 = 系统时钟频率 / 目标波特率

例如:50MHz / 115200 ≈ 434

4.6 波特率误差与容限

这里有个坑,我必须要说——波特率不可能完全精确。因为分频值通常是整数,而计算结果往往是小数。比如50MHz分频到115200,434.0278...取整434,实际波特率是50MHz/434 ≈ 115207,误差只有0.006%,完全没问题。

但如果你用27MHz的晶振分频到115200呢?27MHz/115200 = 234.375,取整234,实际波特率是27MHz/234 ≈ 115385,误差0.16%。还在可接受范围内(一般要求误差小于2%)。

我曾经踩过的坑:有一次用11.0592MHz晶振分频到57600,算出来分频值是192,完美!但后来换了12MHz晶振,分频值208.33,取整208后实际波特率57692,误差0.16%。虽然不大,但在长帧传输(比如一次传几百字节)时,累积误差会导致帧尾数据出错。所以,选晶振时尽量选能被常用波特率整除的频率,比如11.0592MHz、18.432MHz这些。

4.7 异步通信的关键:没有时钟线

UART叫“异步”收发,意思是发送方和接收方没有单独的时钟线。那它们怎么同步呢?靠的是双方约定好相同的波特率,然后接收方在起始位的下降沿“对齐”自己的采样时钟。

具体来说,接收方会用一个比波特率高16倍(或8倍、64倍)的时钟去采样。为什么是16倍?因为这样可以在一个位时间内采样16次,取中间3次做多数表决,能有效避免毛刺干扰。我习惯用16倍过采样,可靠性更高。

你想想看,如果发送方和接收方的波特率有误差,每个位时间的采样点就会逐渐偏移。误差越大,偏移越快。当偏移超过半个位时间时,就会采到错误的电平。这就是为什么波特率误差要控制在2%以内——保证一帧数据(最多10位)不会偏移太多。

4.8 实战中的参数选择

在实际项目中,我一般这样选参数:

  • 数据位:8位,因为一个字节正好8位,方便处理
  • 校验位:无,除非环境恶劣
  • 停止位:1位
  • 波特率:调试用115200,量产用9600或19200(更稳定)

这样一帧就是1 + 8 + 0 + 1 = 10位。在115200波特率下,每秒能传115200/10 = 11520字节,约11.25KB/s。对于大多数调试场景,这个速度足够了。

好了,UART协议的基础就这些。说白了就是“约定好速度,按固定格式打包,一根线来回传”。下一节我们会用Verilog把接收逻辑写出来,到时候你就知道这些理论知识怎么落地了。

可可精品资料库