第4章 UART协议深度解析:帧格式、波特率、奇偶校验、流控制
UART,串口通信的老祖宗了。
说实话,我入行那会儿,第一块板子调的就是UART。那时候连示波器都不会用,就靠串口打印"Hello World"来确认MCU是不是活着。现在想想,UART这东西看着简单,但坑是真不少。
这一章,咱们把UART的底裤扒干净。从帧格式到流控制,每个细节我都会结合自己踩过的坑来讲。
4.1 帧格式:一个字节是怎么飞过去的?
UART通信,说白了就是一根线发,一根线收。没有时钟线,全靠双方约定好节奏。
一个完整的UART帧长什么样?我画个草图给你看:
起始位(1bit) + 数据位(5~9bit) + 校验位(0/1bit) + 停止位(1~2bit)
嗯,就这么简单。但每个部分都有讲究。
4.1.1 起始位
起始位永远是低电平。为什么?因为空闲时总线是高电平。从高跳变到低,接收端就知道:嘿,要发数据了!
我在项目中遇到过一个问题:某款传感器在发送数据前,起始位居然有毛刺。示波器一看,电平跳了好几下才稳定。结果接收端误判了好几次。后来加了硬件滤波才解决。
4.1.2 数据位
常见的是8位数据。但也有用7位的,比如某些老式ASCII终端。9位也有,我见过一些工业协议用它来区分地址和数据帧。
数据位的顺序是LSB在前,MSB在后。也就是说,你先收到的是最低位。这点和I2C、SPI不一样,别搞混了。
4.1.3 校验位
这个咱们后面单独讲,先放一放。
4.1.4 停止位
停止位是高电平,表示这一帧结束了。常见的是1位停止位,也有1.5位和2位的。
你可能会问:为什么要1.5位?我告诉你,这是历史遗留问题。早期有些UART芯片处理速度慢,需要更长的停止位来消化数据。现在基本用1位就够了。
关键点:发送端和接收端的帧格式必须完全一致。数据位、校验位、停止位,一个都不能错。否则收到的全是乱码。
4.2 波特率:节奏感很重要
波特率,就是每秒传输的比特数。9600、115200、921600,这些数字你肯定不陌生。
但你知道吗?波特率不是随便选的。它和系统时钟、分频系数有关。比如一个16MHz的MCU,要产生9600波特率,需要分频到多少?
分频系数 = 系统时钟 / (16 × 目标波特率)
= 16,000,000 / (16 × 9600)
≈ 104.17
看到了吗?104.17,不是整数。这就产生了误差。误差在2%以内通常没问题,但超过5%就可能丢数据了。
我踩过这个坑。有一次用了一个非标准的波特率,比如14400。结果接收端死活收不对。示波器一量,实际波特率偏差了3.8%。换了个标准波特率,问题立刻解决。
我的建议:能用115200就别用9600。速度越快,传输时间越短,出错的概率反而更低。但要注意线长,长距离通信建议降到38400以下。
4.3 奇偶校验:数据对不对?
奇偶校验分三种:无校验、奇校验、偶校验。
| 校验类型 | 原理 | 适用场景 |
|---|---|---|
| 无校验 | 不添加校验位 | 短距离、高可靠性通信 |
| 奇校验 | 数据位+校验位中1的个数为奇数 | 对数据完整性有一定要求 |
| 偶校验 | 数据位+校验位中1的个数为偶数 | 与奇校验类似,看协议要求 |
举个例子:数据是0x55(二进制01010101),有4个1。
- 奇校验:校验位应为1,使总1的个数变成5(奇数)
- 偶校验:校验位应为0,保持4个1(偶数)
奇偶校验能检测奇数个比特的错误。比如一个比特翻了,它能发现。但两个比特同时翻,它就检测不出来了。
说实话,现在很多协议已经不用奇偶校验了。为什么?因为更可靠的CRC校验已经普及。但有些老设备、工业仪表还在用。我调试过一个温控器,必须用偶校验,否则不响应。折腾了半天才发现是校验位没设对。
注意:奇偶校验只能检测错误,不能纠正错误。发现错误后,通常的做法是请求重发。如果你的系统不能重发,那奇偶校验的意义就不大了。
4.4 流控制:数据太多怎么办?
流控制,就是让接收方告诉发送方:慢点,我快撑不住了!
分两种:硬件流控制和软件流控制。
4.4.1 硬件流控制(RTS/CTS)
用两根额外的线:RTS(请求发送)和CTS(清除发送)。
- 接收方准备好接收数据时,拉高RTS
- 发送方看到RTS为高,才开始发送
- 接收方缓冲区快满了,拉低RTS
- 发送方看到RTS为低,停止发送
我在调试一个GPS模块时遇到过这个问题。模块每秒输出大量NMEA语句,MCU处理不过来。不加流控制,数据就丢了。加上RTS/CTS后,一切正常。
4.4.2 软件流控制(XON/XOFF)
不用额外线,靠发送特殊字符来控制。
- XOFF(0x13):暂停发送
- XON(0x11):恢复发送
但有个问题:如果传输的是二进制数据,0x13和0x11可能出现在数据中,就会误触发流控制。所以软件流控制只适合传输文本数据。
我的经验:能用硬件流控制就别用软件。硬件流控制更可靠,而且不影响数据内容。但要注意,很多便宜的USB转串口模块不支持硬件流控制,买的时候要看清。
4.5 实战:用Python调通UART
理论讲完了,咱们来点实际的。用Python的pyserial库,调通一个UART通信。
import serial
import time
# 配置串口
ser = serial.Serial(
port='COM3', # Windows下用COM口,Linux下用/dev/ttyUSB0
baudrate=115200, # 波特率
bytesize=8, # 数据位
parity='N', # 校验位:N无校验,E偶校验,O奇校验
stopbits=1, # 停止位
timeout=1 # 超时时间(秒)
)
# 检查串口是否打开
if ser.is_open:
print(f"串口已打开:{ser.port}")
else:
print("串口打开失败")
exit()
# 发送数据
data_to_send = b'\x01\x02\x03\x04'
ser.write(data_to_send)
print(f"已发送:{data_to_send.hex()}")
# 接收数据
time.sleep(0.1) # 给设备一点反应时间
received = ser.read(10) # 读取最多10个字节
if received:
print(f"收到数据:{received.hex()}")
else:
print("超时,未收到数据")
# 关闭串口
ser.close()
这段代码看着简单,但有几个坑要注意:
- 端口号:Windows下COM口编号可能和实际不符。去设备管理器里确认一下。
- 超时设置:timeout=0表示非阻塞模式,read()立即返回。timeout=None表示阻塞模式,直到读到指定字节数。我一般设1秒,够用了。
- 数据格式:发送和接收的都是bytes类型,不是字符串。别搞混了。
调试小技巧:如果通信不正常,先用串口助手(比如SSCOM、Putty)手动发数据,确认硬件没问题。再用Python脚本。这样能快速定位是硬件问题还是软件问题。
4.6 常见问题与避坑指南
最后,分享几个我踩过的坑,你遇到了可以直接拿来用。
4.6.1 乱码问题
最常见的问题。原因通常是:
- 波特率不匹配:检查两边设置是否一致
- 数据位/校验位/停止位不一致:用示波器看波形,数一数位数
- 电平不匹配:3.3V设备接5V设备,需要电平转换
4.6.2 丢数据问题
数据收不全,或者收着收着就断了。原因可能是:
- 接收缓冲区太小:加大缓冲区,或者用流控制
- 中断优先级太低:检查MCU的中断配置
- 线太长:长距离通信用RS485,别用TTL电平
4.6.3 干扰问题
数据偶尔出错,或者莫名其妙多出几个字节。我遇到过:
- 电源纹波大:给串口加个磁珠或者电容
- 地线没接好:TX和RX共地,这是基本要求
- 线缆屏蔽不好:用屏蔽双绞线,或者缩短线长
曾经有一次:我调试一个工业设备,串口通信时好时坏。折腾了两天,最后发现是USB转串口线质量太差。换了一根带屏蔽的线,问题立刻消失。所以,别在线上省钱,尤其是长距离通信。
好了,UART协议的核心内容就这些。帧格式、波特率、校验、流控制,每个点都值得你花时间吃透。下一章,咱们聊聊I2C协议,那个比UART复杂多了,但用好了也特别强大。