2、串口通信原理:UART帧结构、起始位、数据位、校验位、停止位详解

串口通信,说白了就是让两个设备通过一根线,一个比特一个比特地聊天。你可能会问:「为什么不用并行通信?一次传8位多快啊!」嗯,我在项目里吃过并行通信的亏——线多了容易受干扰,距离一长就丢数据。所以UART这种异步串行方式,反而成了嵌入式世界里最常用的通信手段。

今天咱们就把它扒开看看。UART的帧结构,其实就五个部分:起始位、数据位、校验位、停止位。别小看这几位,我当年调试一个GPS模块,死活收不到正确数据,最后发现是停止位配置错了。这种坑,你早晚也会遇到。

2.1 UART帧结构总览

先看一张图,把整体框架搭起来。UART传输一个字节,就像寄一封信——有信封、有内容、有校验。

UART 一帧数据结构 空闲位 (高电平) 起始位 0 数据位 (5~8位) LSB → MSB 校验位 可选 停止位 1/1.5/2位 空闲/下一帧 时间轴 → 一帧完整数据 = 起始位 + 数据位 + 校验位(可选) + 停止位

从图上你能看到,UART在空闲时保持高电平。一旦拉低,就表示「我要开始传数据了」。这个拉低的动作,就是起始位。

2.2 起始位:通信的「握手信号」

起始位只有一个比特,固定为逻辑0(低电平)。它的作用很简单:告诉接收方「注意,数据来了」。

我记得有一次调试一个老旧设备,对方UART的起始位宽度不太标准,导致我这边总是对不齐。后来用示波器一看,原来是时钟偏差太大。所以啊,起始位是双方时钟同步的起点。接收方检测到下降沿后,就开始按约定的波特率采样后续位。

关键点:起始位的下降沿是同步信号。接收方以此为准,启动内部的波特率计数器。

2.3 数据位:真正要传的内容

数据位是帧的核心,长度可以是5、6、7或8位。最常见的是8位,因为一个字节正好8位。为什么会有5位?嗯,早期电传打字机用的就是5位,现在基本见不到了。

数据位的传输顺序是LSB(最低有效位)在前,MSB(最高有效位)在后。举个例子,你要发送0x55(二进制01010101),先发最低位1,再发0,再发1……最后发最高位0。

我习惯在代码里这样配置数据位:

// 以STM32为例,配置8位数据位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;

// 如果要用7位数据位(比如某些Modbus协议)
USART_InitStructure.USART_WordLength = USART_WordLength_7b;

我的经验:除非有特殊协议要求,否则一律用8位数据位。7位数据位在传输ASCII码时够用,但传二进制数据就麻烦了。你想想看,一个字节拆成7位+1位,多别扭。

2.4 校验位:数据完整性的「小保镖」

校验位是可选的,用于检测传输过程中是否发生了错误。常见的有三种:

  • 无校验(None):不发送校验位,帧长度最短。
  • 奇校验(Odd):数据位中1的个数 + 校验位 = 奇数。
  • 偶校验(Even):数据位中1的个数 + 校验位 = 偶数。

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

我曾经在一个项目中,用奇校验传传感器数据。结果发现偶尔有误码,但校验位没报错。后来查出来是干扰太强,连续两位都翻转了——奇校验只能检测奇数个错误,偶数个错误它发现不了。所以啊,校验位不是万能的,它只能防小错。

注意:校验位只能检测错误,不能纠正错误。如果发现校验错误,通常的做法是丢弃这一帧,请求重发。我在工业现场见过有人用校验位做纠错,那是理解错了。

2.5 停止位:帧的「结束符」

停止位固定为逻辑1(高电平),长度可以是1位、1.5位或2位。它的作用有两个:

  1. 告诉接收方「这一帧结束了」。
  2. 给接收方一点时间处理刚收到的数据。

为什么会有1.5位这种奇怪的配置?嗯,这是历史遗留问题。早期有些UART芯片的时钟精度不够,1.5位停止位能提供更宽松的容错。现在基本只用1位停止位,除非你对接的老设备要求2位。

我建议你默认用1位停止位。只有在通信距离远、干扰大的环境下,才考虑用2位停止位——多一个比特的时间,能让接收方更稳定地采样。

// 配置1位停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;

// 配置2位停止位(用于特殊场景)
USART_InitStructure.USART_StopBits = USART_StopBits_2;

2.6 波特率:通信的「节拍器」

波特率决定了每秒传输多少个比特。常见的值有9600、19200、115200等。注意,发送方和接收方的波特率必须一致,否则数据会错位。

波特率怎么算?很简单:1 / 波特率 = 每个比特的持续时间。比如115200bps,每个比特约8.68微秒。接收方就在这个时间窗口内采样。

波特率 (bps) 每比特时间 传输1字节耗时 (8N1) 常见应用
9600 104.17 μs 约1.04 ms GPS模块、工业仪表
19200 52.08 μs 约0.52 ms 传感器、PLC
115200 8.68 μs 约86.8 μs 调试口、WiFi模块
921600 1.09 μs 约10.9 μs 高速数据传输

我个人习惯调试时用115200,速度快,响应及时。但产品量产时,如果线缆长或者环境差,我会降到9600或19200。你想想看,速度越快,每个比特的时间越短,对干扰越敏感。

2.7 完整帧的传输过程

咱们把上面这些拼起来,看一个完整的例子。假设你要发送0x55(01010101),配置为8N1(8位数据、无校验、1位停止位):

  1. 空闲:TX线保持高电平。
  2. 起始位:拉低电平,持续1个比特时间。
  3. 数据位:依次发送LSB到MSB:1、0、1、0、1、0、1、0。
  4. 停止位:拉高电平,持续1个比特时间。
  5. 空闲/下一帧:保持高电平,等待下一帧。

用示波器看,你会看到这样的波形:一个低电平脉冲,跟着一串高低变化的脉冲,最后回到高电平。我当年第一次用示波器抓UART波形时,觉得这东西太直观了——比I2C和SPI好理解多了。

避坑指南:我曾经在配置UART时,把数据位和校验位搞混了。配置成7E1(7位数据、偶校验、1位停止位),结果发送0x55时,接收方总是收到0x2A。后来发现,7位数据模式下,最高位被当作校验位处理了。所以,配置前一定要确认协议文档

2.8 常见配置速查

为了方便你查阅,我把常见的UART配置整理成了一张表。你以后配串口时,直接对着看就行。

配置名称 数据位 校验位 停止位 帧总长度 典型用途
8N1 8 1 10位 最常用,通用串口
8E1 8 偶校验 1 11位 Modbus RTU
8O1 8 奇校验 1 11位 某些工业协议
7E1 7 偶校验 1 10位 ASCII传输
8N2 8 2 11位 长距离/低质量线路

看到8N1的帧总长度是10位了吗?起始位1位 + 数据位8位 + 停止位1位 = 10位。所以传输一个字节的实际时间是 10 / 波特率。比如115200下,传一个字节约86.8微秒,每秒能传约11520个字节。这个数字你心里要有数,算吞吐量时用得上。

好了,UART帧结构就讲到这里。说白了,它就是一套约定——什么时候开始、怎么传数据、怎么校验、什么时候结束。你只要把两边的配置调成一样,通信就能跑起来。如果跑不起来,先查波特率,再查帧格式,八成是这两个地方没对齐。


专注资料整理