第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复杂多了,但用好了也特别强大。