2. pyserial库核心API详解:Serial类构造、端口枚举、参数配置与超时设置
做嵌入式开发这么多年,我几乎每天都在跟串口打交道。pyserial这个库,说白了就是Python世界里操作串口的标配。今天咱们就把它的核心API掰开揉碎,好好聊一聊。
2.1 Serial类构造——打开串口的第一步
Serial类是pyserial的灵魂。你想想看,所有串口操作都得从它开始。我习惯这么用:
import serial
# 最常用的构造方式
ser = serial.Serial(
port='COM3', # Windows下用COM口,Linux下用/dev/ttyS0或/dev/ttyUSB0
baudrate=115200, # 波特率
bytesize=8, # 数据位
parity='N', # 校验位:N无校验,E偶校验,O奇校验
stopbits=1, # 停止位
timeout=1 # 超时时间,单位秒
)
这里有个细节我得提醒你——构造Serial对象时,串口会立刻被打开。如果你只想创建对象但不打开端口,可以这样:
ser = serial.Serial()
ser.port = 'COM3'
ser.baudrate = 9600
# 后续手动调用 ser.open()
我的习惯:调试阶段我一般用第一种方式,一步到位。但做产品代码时,我倾向于先构造再手动open,这样能更好地处理异常情况。
2.2 端口枚举——找到你的设备
在实际项目中,你不可能每次都去设备管理器里查COM口号。pyserial提供了端口枚举功能,这个我太爱了。
import serial.tools.list_ports
# 获取所有可用串口
ports = serial.tools.list_ports.comports()
for port in ports:
print(f"端口: {port.device}")
print(f"描述: {port.description}")
print(f"硬件ID: {port.hwid}")
print("-" * 40)
输出大概长这样:
端口: COM3
描述: USB Serial Port (COM3)
硬件ID: USB\VID_0403&PID_6001\12345678
----------------------------------------
端口: COM5
描述: Bluetooth Serial Port (COM5)
硬件ID: BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}\LOCALMFG&0002
----------------------------------------
我曾经在一个自动化测试项目里,需要同时管理8个串口设备。用枚举功能配合硬件ID过滤,几行代码就搞定了设备识别。比人工一个个对COM口省心多了。
注意:在Linux系统下,枚举结果可能不包含硬件ID信息。这时候我一般通过设备路径(/dev/ttyUSB*)和产品描述来区分。
2.3 参数配置——波特率、数据位、停止位、校验位
串口通信的参数配置,说白了就是让两端设备说同一种语言。参数对不上,数据就是一堆乱码。我吃过这个亏,所以现在特别重视。
| 参数 | 可选值 | 说明 |
|---|---|---|
| 波特率 | 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 | 每秒传输的符号数,两端必须一致 |
| 数据位 | 5, 6, 7, 8 | 每个数据帧中实际数据的位数,常用8位 |
| 停止位 | 1, 1.5, 2 | 帧结束标志位,常用1位 |
| 校验位 | 'N', 'E', 'O', 'M', 'S' | N无校验,E偶校验,O奇校验,M标记,S空格 |
参数配置有两种方式,我分别说说:
方式一:构造时配置
ser = serial.Serial(
port='COM3',
baudrate=115200,
bytesize=serial.EIGHTBITS, # 也可以用8
parity=serial.PARITY_NONE, # 也可以用'N'
stopbits=serial.STOPBITS_ONE # 也可以用1
)
方式二:运行时修改
ser = serial.Serial('COM3', 9600)
# 运行中修改参数
ser.baudrate = 115200
ser.bytesize = 8
ser.parity = 'N'
ser.stopbits = 1
# 修改后需要重新配置硬件
ser.flushInput()
ser.flushOutput()
避坑指南:我曾经在一个工业现场调试时,发现设备偶尔丢数据。排查了半天,最后发现是校验位设置错了——设备手册写的是"Even",我设成了"Odd"。就这一个字母,折腾了我一整天。所以我的建议是:拿到设备后先用示波器或者串口助手确认参数,再写代码。
2.4 超时设置——别让你的程序卡死
超时设置是串口编程里最容易忽略但又最关键的部分。没有超时,你的程序可能因为一个字节没收到就永远卡在那里。
pyserial提供了三种超时模式:
| 超时类型 | 设置方式 | 行为说明 |
|---|---|---|
| 无超时 | timeout=None | 阻塞等待,直到收到指定字节数 |
| 固定超时 | timeout=1.0 | 等待最多1秒,超时返回已收到的数据 |
| 零超时 | timeout=0 | 非阻塞模式,立即返回,不管有没有数据 |
我一般这样用:
# 读取固定长度,带超时
data = ser.read(10) # 最多等timeout秒,返回最多10字节
# 读取直到遇到换行符,带超时
line = ser.readline() # 最多等timeout秒,返回一行数据
# 读取所有可用数据,不等待
available = ser.in_waiting
data = ser.read(available) if available > 0 else b''
我的经验:在工业通信中,我通常把timeout设为1-3秒。太短容易误判为超时,太长会影响系统响应。对于Modbus这类协议,timeout一般设为协议规定的响应超时时间,通常是500ms到3s。
还有一个容易被忽略的函数——ser.setTimeout(),可以在运行时动态修改超时:
ser.timeout = 2.5 # 直接赋值即可
# 或者用setTimeout方法
ser.setTimeout(2.5)
2.5 知识结构总览
说了这么多,我画了一张图帮你理清思路:
2.6 实战小贴士
最后,分享几个我这些年积累的经验:
- 端口枚举别硬编码:产品代码里永远不要写死COM口,用枚举+过滤的方式自动识别。
- 参数校验不能省:每次打开串口前,检查一下参数是否在有效范围内。pyserial不会帮你校验所有参数。
- 超时时间要留余量:理论计算值乘以1.5到2倍,给系统波动留空间。
- 异常处理要到位:串口操作一定要包在try-except里,serial.SerialException和serial.TimeoutException是家常便饭。
血的教训:我曾经在一个24小时运行的采集系统里,因为没处理串口断开重连的情况,导致系统跑了3天后死机。从那以后,我的每个串口操作都加了重连机制和看门狗。嗯,有些坑踩过一次就够了。