2、串口通信原理:UART帧结构、起始位、数据位、校验位、停止位详解
串口通信,说白了就是让两个设备通过一根线,一个比特一个比特地聊天。你可能会问:「为什么不用并行通信?一次传8位多快啊!」嗯,我在项目里吃过并行通信的亏——线多了容易受干扰,距离一长就丢数据。所以UART这种异步串行方式,反而成了嵌入式世界里最常用的通信手段。
今天咱们就把它扒开看看。UART的帧结构,其实就五个部分:起始位、数据位、校验位、停止位。别小看这几位,我当年调试一个GPS模块,死活收不到正确数据,最后发现是停止位配置错了。这种坑,你早晚也会遇到。
2.1 UART帧结构总览
先看一张图,把整体框架搭起来。UART传输一个字节,就像寄一封信——有信封、有内容、有校验。
从图上你能看到,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.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位停止位):
- 空闲:TX线保持高电平。
- 起始位:拉低电平,持续1个比特时间。
- 数据位:依次发送LSB到MSB:1、0、1、0、1、0、1、0。
- 停止位:拉高电平,持续1个比特时间。
- 空闲/下一帧:保持高电平,等待下一帧。
用示波器看,你会看到这样的波形:一个低电平脉冲,跟着一串高低变化的脉冲,最后回到高电平。我当年第一次用示波器抓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帧结构就讲到这里。说白了,它就是一套约定——什么时候开始、怎么传数据、怎么校验、什么时候结束。你只要把两边的配置调成一样,通信就能跑起来。如果跑不起来,先查波特率,再查帧格式,八成是这两个地方没对齐。