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 知识结构总览

说了这么多,我画了一张图帮你理清思路:

pyserial核心API知识结构 Serial类 构造方式 构造时直接打开 先构造后手动open() 端口枚举 list_ports.comports() 按device/description/hwid过滤 参数配置 波特率/数据位 停止位/校验位 构造时配置/运行时修改 超时设置 timeout=None 阻塞模式 timeout=1.0 固定超时 timeout=0 非阻塞模式 read() / readline() in_waiting / flush() 核心原则:两端参数一致 + 合理超时 = 稳定通信 参数对不上,数据就是乱码;没有超时,程序可能卡死

2.6 实战小贴士

最后,分享几个我这些年积累的经验:

  • 端口枚举别硬编码:产品代码里永远不要写死COM口,用枚举+过滤的方式自动识别。
  • 参数校验不能省:每次打开串口前,检查一下参数是否在有效范围内。pyserial不会帮你校验所有参数。
  • 超时时间要留余量:理论计算值乘以1.5到2倍,给系统波动留空间。
  • 异常处理要到位:串口操作一定要包在try-except里,serial.SerialException和serial.TimeoutException是家常便饭。
血的教训:我曾经在一个24小时运行的采集系统里,因为没处理串口断开重连的情况,导致系统跑了3天后死机。从那以后,我的每个串口操作都加了重连机制和看门狗。嗯,有些坑踩过一次就够了。