3、串口通信基础:RS232/RS485协议、波特率、数据位、停止位、校验位,Python串口扫描与连接测试
做仪器数据采集,串口通信是绕不开的基本功。我刚开始接触多通道同步采集时,以为串口就是插上线就能读数据,结果被各种奇奇怪怪的问题折腾了好几个通宵。说白了,串口通信就像两个人在嘈杂的车间里对话——你得约定好说话的速度、每句话的长度、什么时候结束,还得加点校验防止听错。
3.1 RS232 vs RS485:两种常见的物理层协议
先说说这两个协议的区别。RS232和RS485,本质上都是串口通信的电气标准,但脾气完全不同。
RS232:点对点通信,一根线只能连两个设备。传输距离短,一般15米以内。电压是±12V的逻辑电平,抗干扰能力一般。我在实验室调试一台老式气相色谱仪时,就遇到过232线长了点,数据就开始丢包。嗯,后来老老实实把线剪短到3米,问题就解决了。
RS485:差分信号传输,抗干扰能力强。最让我喜欢的是它支持多节点——一条总线上可以挂32个设备(甚至更多)。传输距离能到1200米。做多通道同步采集时,我几乎都用RS485。你想想看,一个车间里几十台仪器,用485总线串起来,一根双绞线搞定,多省事。
| 特性 | RS232 | RS485 |
|---|---|---|
| 传输方式 | 单端 | 差分 |
| 最大节点数 | 2(点对点) | 32(典型) |
| 最大距离 | 约15米 | 约1200米 |
| 抗干扰 | 一般 | 强 |
| 典型应用 | 近距离调试 | 工业现场、多设备组网 |
3.2 波特率、数据位、停止位、校验位
这四个参数,是串口通信的「四要素」。任何一个对不上,数据就收不到。我见过太多新手在这上面栽跟头。
波特率:说白了就是每秒传输多少个比特。常见的有9600、19200、115200。波特率越高,传输越快,但误码率也会上升。我个人习惯:仪器数据量不大的话,用9600或19200,稳定第一。高速采集时才上115200。
数据位:一帧数据里有效数据的位数。通常是7位或8位。现在绝大多数设备都用8位。7位是早期ASCII码时代的遗物,基本可以忽略。
停止位:表示一帧数据结束的标志位。常见的是1位、1.5位、2位。我几乎只用1位停止位。只有在一些老设备上,才会遇到需要2位停止位的情况。
校验位:用于检测传输错误。有奇校验、偶校验、无校验三种。我建议:除非设备手册明确要求,否则一律用无校验(None)。为什么?因为校验位会占用一个数据位,而且很多设备的校验实现有bug。我曾经被一台进口光谱仪的校验位坑过——手册写的是偶校验,实际跑起来必须设成无校验才能正常通信。
3.3 Python串口扫描:找到你的设备
拿到一台新仪器,第一件事就是找到它连在哪个串口上。Windows下通常是COM3、COM4这样的名字,Linux下是/dev/ttyUSB0、/dev/ttyS0。手动一个个试太慢了,我们用Python自动扫描。
先安装pyserial库:
pip install pyserial
然后写个扫描脚本:
import serial.tools.list_ports
def scan_serial_ports():
"""扫描所有可用串口"""
ports = serial.tools.list_ports.comports()
if not ports:
print("没有发现串口设备")
return []
print(f"发现 {len(ports)} 个串口:")
for port in ports:
print(f" 端口: {port.device}")
print(f" 描述: {port.description}")
print(f" 硬件ID: {port.hwid}")
print("---")
return ports
if __name__ == "__main__":
scan_serial_ports()
运行后,你会看到类似这样的输出:
发现 2 个串口:
端口: COM3
描述: USB Serial Port (COM3)
硬件ID: USB VID:PID=0403:6001 SER=12345678
---
端口: COM5
描述: Bluetooth Serial Port (COM5)
硬件ID: BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}
---
3.4 连接测试:确认通信正常
找到串口后,别急着读数据。先做个简单的连接测试,确认参数设置正确。
import serial
import time
def test_serial_connection(port, baudrate=9600, timeout=2):
"""测试串口连接"""
try:
# 打开串口
ser = serial.Serial(
port=port,
baudrate=baudrate,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=timeout
)
print(f"成功打开串口 {port}")
print(f"参数: {baudrate}波特率, 8数据位, 无校验, 1停止位")
# 发送一个简单的查询命令(具体命令看仪器手册)
# 这里以发送"*IDN?"为例,很多仪器支持这个标准命令
test_cmd = b"*IDN?\r\n"
ser.write(test_cmd)
print(f"发送: {test_cmd}")
# 等待响应
time.sleep(0.5)
response = ser.read_all()
if response:
print(f"收到响应: {response.decode('utf-8', errors='ignore')}")
print("连接测试通过!")
else:
print("没有收到响应,请检查:")
print(" 1. 波特率是否正确")
print(" 2. 仪器是否上电")
print(" 3. 线缆是否接好")
ser.close()
return True
except serial.SerialException as e:
print(f"串口打开失败: {e}")
return False
if __name__ == "__main__":
# 先扫描,再测试
ports = scan_serial_ports()
if ports:
test_serial_connection(ports[0].device)
3.5 实战中的常见问题
最后分享几个我踩过的坑:
- 波特率不匹配:仪器手册写的是9600,但实际可能是19200。我建议先用9600试,收不到再换19200、115200逐个试。
- 流控问题:有些仪器需要硬件流控(RTS/CTS),有些不需要。我一般先关掉流控,不行再开。
- 换行符:有的仪器用\r\n,有的只用\n,还有的用\r。发送命令前,先确认仪器手册里的换行符格式。
- 串口被占用:调试时经常遇到「端口被占用」的错误。检查一下是不是有其他程序(比如串口助手、别的Python脚本)占用了同一个串口。
串口通信看起来简单,但细节决定成败。把基础打牢了,后面做多通道同步采集才能顺风顺水。下一章,我们开始实战——用Python读取第一台仪器的数据。