串口通信原理:UART帧结构、波特率、奇偶校验、流控制详解

串口通信,说白了就是让两个设备通过一根线,一个比特一个比特地聊天。UART(Universal Asynchronous Receiver/Transmitter)是其中最经典的协议。我刚开始做嵌入式时,总觉得串口很简单,不就是收发数据嘛。直到有一次调试一个GPS模块,死活收不到正确数据,折腾了两天才发现是波特率配错了。嗯,从那以后我再也不敢小看这些基础参数了。

UART帧结构:数据是怎么打包的?

UART是异步通信,没有时钟线。收发双方得靠约定好的帧格式来同步。一个完整的UART帧长这样:

起始位 数据位(5~9位) 奇偶校验位(可选) 停止位(1/1.5/2位)
1位,低电平 LSB在前,MSB在后 偶校验/奇校验/无 高电平,表示帧结束

我习惯把帧结构想象成火车:起始位是火车头,告诉接收方「注意,数据要来了」;数据位是车厢,装着真正的信息;奇偶校验位是检票员,检查车厢有没有少人;停止位是车尾,告诉接收方「这趟车结束了」。

关键点:起始位必须是低电平,停止位必须是高电平。这是UART协议最基础的约定,也是接收方判断帧开始和结束的唯一依据。

举个例子,发送一个字节 0x55(二进制 01010101),采用8位数据、无校验、1位停止位:

空闲状态:高电平
起始位:0(低电平)
数据位(LSB先):1 0 1 0 1 0 1 0
停止位:1(高电平)

你想想看,0x55 的比特流是交替的01,在示波器上看起来就像方波一样规整。我经常用这个字节来测试串口链路——如果示波器上看到规整的方波,说明物理层没问题。

波特率:通信的节拍器

波特率就是每秒传输的符号数。对于UART来说,一个符号就是一个比特,所以波特率等于比特率。常见的波特率有:

波特率(bps) 位时间(μs) 常见用途
9600 104.17 调试终端、老旧设备
115200 8.68 最常用,GPS/蓝牙模块
921600 1.09 高速数据传输

收发双方的波特率必须一致,误差不能超过±2%。我记得有一次项目,用STM32和ESP8266通信,STM32内部PLL配置有偏差,实际波特率是115400,而ESP8266是标准的115200。表面上看数据能收到,但偶尔会丢字节。用示波器一量,位时间差了0.1μs,累积到一帧末尾就出错了。

避坑指南:我曾经遇到过晶振频率不是整数倍的情况,比如用8MHz晶振产生115200波特率,误差高达3.5%。这种情况下,要么换晶振,要么用支持分数波特率生成的MCU。千万别硬扛,数据出错是迟早的事。

奇偶校验:简单的错误检测

奇偶校验分两种:

  • 偶校验(Even Parity):数据位中1的个数加上校验位,总共有偶数个1。
  • 奇校验(Odd Parity):数据位中1的个数加上校验位,总共有奇数个1。

举个例子,数据 0x55(二进制 01010101),有4个1:

  • 偶校验:校验位为0,因为4已经是偶数
  • 奇校验:校验位为1,因为4是偶数,需要加1变成奇数

奇偶校验只能检测奇数个比特错误。如果两个比特同时翻转,校验位不变,错误就漏掉了。说白了,它就是个轻量级的检查,适合噪声不严重的场景。

我的习惯:在调试阶段,我通常不开奇偶校验。因为一旦开了,帧结构就变了,调试工具也得跟着配。等通信稳定了,再根据实际环境决定要不要加。工业现场干扰大,我会开偶校验;实验室调试,直接关掉省事。

流控制:防止数据溢出

流控制解决的是「发送方太快,接收方来不及处理」的问题。有两种方式:

类型 信号线 工作原理 适用场景
硬件流控(RTS/CTS) RTS、CTS 接收方拉低CTS,发送方停止发送 高速通信、大数据量
软件流控(XON/XOFF) 无额外线 接收方发送0x13(XOFF)暂停,0x11(XON)恢复 只有TX/RX两线

硬件流控更可靠,但需要额外两根线。软件流控的问题是,如果传输的是二进制数据,0x13和0x11可能出现在数据中,造成误触发。我一般只在文本终端场景用软件流控,比如串口命令行。

实战建议:如果你用DMA+环形缓冲区处理串口接收,流控制其实可以不用。只要缓冲区够大,CPU处理够快,数据不会丢。我做过一个项目,115200波特率,缓冲区设成1KB,DMA半满中断,从来没丢过数据。流控制反而增加了硬件复杂度。

UART通信完整流程

下面这张图展示了UART从发送到接收的完整流程:

UART通信完整流程 发送端(TX) 接收端(RX) 1. 配置波特率/帧格式 1. 配置相同参数 2. 拉低TX(起始位) 检测到下降沿 3. 逐位发送数据(LSB先) 按位时钟采样 4. 拉高TX(停止位) 检测到高电平,帧结束 双方时钟独立,靠约定波特率同步

流程其实很简单:发送端先拉低TX线(起始位),然后按LSB-first的顺序逐位发送数据,最后拉高TX线(停止位)。接收端检测到下降沿后,按约定的波特率采样数据位,最后检查停止位是否为高电平。

调试小技巧:我习惯在发送端先发一个固定的字节(比如0x55),然后用示波器看TX脚的波形。如果波形规整、位时间准确,说明发送端没问题。接着看接收端的RX脚,如果波形一致,说明物理层通了。剩下的就是软件配置问题了。

UART看似简单,但每个参数都牵一发动全身。波特率错了,数据全乱;帧格式不匹配,接收方解析失败;流控制没配好,大数据量时丢数据。我见过太多工程师在这些基础问题上翻车,包括我自己。嗯,把这些搞透了,串口通信这块就算入门了。