1. UART基础与物理层:RS232/RS485/TTL电平标准、DB9接口定义、波特率误差计算、硬件流控(RTS/CTS)实战
做嵌入式开发这么多年,我调试过最多的接口就是UART。说白了,串口就是嵌入式工程师的「眼睛」——没有它,你根本不知道芯片在干什么。但很多人一上来就调Python脚本,结果硬件连不上,还以为是代码写错了。其实问题往往出在物理层。
这一章,咱们先把UART的「底裤」扒清楚。电平标准、接口定义、波特率误差、硬件流控,这些搞不明白,后面写再多代码也是白搭。
1.1 三种电平标准:TTL、RS232、RS485
UART本身只定义了数据帧格式(起始位、数据位、校验位、停止位),但没规定用多少伏代表0和1。于是就有了不同的物理层标准。我见过不少新手把TTL电平直接怼到RS232接口上,结果烧了芯片——嗯,这里要注意。
| 标准 | 逻辑1(高电平) | 逻辑0(低电平) | 典型电压摆幅 | 最大距离 |
|---|---|---|---|---|
| TTL | 2.0V ~ 5.0V | 0V ~ 0.8V | 0~5V | <1m |
| RS232 | -3V ~ -15V | +3V ~ +15V | ±12V | ~15m |
| RS485 | A-B > +200mV | A-B < -200mV | 差分±5V | ~1200m |
TTL电平:最常见,单片机直接输出。3.3V或5V系统,空闲时高电平,起始位拉低。适合板级通信,线长别超过30cm。
RS232:用正负电压表示逻辑,抗干扰能力强。DB9接口就是它的标准载体。我习惯在调试工控设备时用RS232,因为能走十几米。
RS485:差分信号,半双工,支持多节点。适合工业现场总线。注意它只有A/B两根线,没有时钟,靠电压差判断逻辑。
1.2 DB9接口定义与接线
DB9是RS232最常用的连接器。很多人只知道用2、3、5三根线,但硬件流控需要用到7、8脚。我画个图帮你理清楚。
实际项目中,我习惯这样接线:
- 三线制(最常用):2-RXD、3-TXD、5-GND。交叉连接,即设备的TXD接对方的RXD。
- 五线制(带流控):增加7-RTS、8-CTS。同样交叉:RTS接对方CTS。
- 全串口(九线制):很少用,除非是古老Modem设备。
1.3 波特率误差计算
波特率是UART通信的「心跳」。发送方和接收方必须用同一个节奏采样。但晶振有误差,分频器有舍入,所以实际波特率和标称值总有偏差。
误差公式很简单:
误差 = |(实际波特率 - 标称波特率) / 标称波特率| × 100%
举个例子。我用STM32F103,系统时钟72MHz,要产生115200bps:
USARTDIV = 72,000,000 / (16 × 115200) = 39.0625
整数部分 = 39
小数部分 = 0.0625 × 16 = 1
实际波特率 = 72,000,000 / (16 × (39 + 1/16)) = 72,000,000 / 625 = 115200
误差 = 0% // 完美匹配
但换一个时钟频率就没这么幸运了。比如用8MHz晶振产生115200:
USARTDIV = 8,000,000 / (16 × 115200) = 4.3403
整数部分 = 4
小数部分 = 0.3403 × 16 = 5.44 → 取整为5
实际波特率 = 8,000,000 / (16 × (4 + 5/16)) = 8,000,000 / 69 = 115,942
误差 = |115942 - 115200| / 115200 × 100% = 0.64%
不同波特率下的常见误差表现:
| 误差范围 | 通信表现 | 建议 |
|---|---|---|
| < 1% | 完全正常 | 放心使用 |
| 1% ~ 2% | 偶尔丢字节 | 短帧可用,长帧建议调整 |
| 2% ~ 3% | 频繁乱码 | 必须换晶振或改波特率 |
| > 3% | 基本不通 | 重新计算分频系数 |
1.4 硬件流控 RTS/CTS 实战
很多人觉得流控没用——「我115200从来没丢过数据啊」。那是你没遇到高速率或长距离。我做过一个项目,用921600波特率通过10米线缆传输,不加流控,每帧必丢。
RTS/CTS 工作原理:
- RTS(Request To Send):发送方告诉接收方「我要发数据了,你准备好没?」
- CTS(Clear To Send):接收方回复「准备好了,发吧」或「忙,等一下」。
说白了就是一对握手信号。接收方缓冲区快满时,拉低CTS,发送方看到CTS为低就暂停发送。等缓冲区空了再拉高CTS,继续发。
硬件流控接线:
设备A 设备B
TXD ————————→ RXD
RXD ←———————— TXD
RTS ————————→ CTS
CTS ←———————— RTS
GND ————————→ GND
在Python中启用硬件流控很简单:
import serial
ser = serial.Serial(
port='COM3',
baudrate=921600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
rtscts=True, # 启用硬件流控
timeout=1
)
# 发送数据
ser.write(b'Hello, UART with flow control!')
# 接收数据
data = ser.read(100)
print(data)
ser.close()
我个人的调试流程是这样的:
- 先用三线制调通基本通信,确认波特率、数据格式没问题。
- 如果发现长帧丢数据,或者高速率下不稳定,就加上RTS/CTS。
- 用示波器抓RTS和CTS波形,确认握手时序正确。
- 最后在Python代码里设置 rtscts=True,重新测试。