第2章:Python串口库安装与配置
好,咱们正式开始动手了。这一章要解决一个最基础的问题——怎么让Python能和串口设备说上话。说白了,就是装个库、配个参数、扫个端口。别小看这几步,我在现场调试时,十次有八次的问题都出在配置上。
2.1 pySerial库安装
pySerial是Python操作串口的标配,没有之一。我个人习惯用pip安装,简单粗暴:
pip install pyserial
如果你用的是Python 3,可能会遇到pip3的问题:
pip3 install pyserial
嗯,这里要注意。有些工控机是离线环境,没法联网。我曾经在一条汽车产线上遇到过,现场网络被严格管控。那怎么办?提前下载好whl文件,用U盘拷进去:
pip install pyserial-3.5-py2.py3-none-any.whl
验证是否装成功,打开Python交互环境,敲一行:
import serial
print(serial.__version__)
不报错,就说明成了。如果报错说找不到模块,多半是pip装到了别的Python环境里。你想想看,一台工控机里可能装了Python 2.7、Python 3.6、Anaconda好几个版本,很容易搞混。
python -m pip install pyserial 可以确保装到当前激活的Python环境里。
2.2 串口参数配置
串口通信不是插上线就能聊的。你得告诉双方:波特率多少?数据位几位?停止位几位?有没有校验?这些参数必须完全一致,否则收到的全是乱码。
我见过最典型的坑:一个工程师调了半天,发现波特率设成了9600,但设备实际是19200。嗯,这种低级错误我也犯过。
来看一个标准的配置代码:
import serial
ser = serial.Serial(
port='COM3', # Windows下用COM口,Linux下用/dev/ttyUSB0
baudrate=115200, # 波特率,常见的有9600、19200、38400、115200
bytesize=8, # 数据位,通常是8位
parity='N', # 校验位:N无校验、E偶校验、O奇校验
stopbits=1, # 停止位:1位或2位
timeout=1 # 超时时间,单位秒。不设的话会一直等
)
参数说明,我整理成了一张表,方便你对照:
| 参数名 | 说明 | 常用值 |
|---|---|---|
| port | 串口号 | COM1~COM256, /dev/ttyUSB0 |
| baudrate | 波特率 | 9600, 19200, 38400, 115200 |
| bytesize | 数据位 | 5, 6, 7, 8 |
| parity | 校验位 | 'N', 'E', 'O', 'M', 'S' |
| stopbits | 停止位 | 1, 1.5, 2 |
| timeout | 超时(秒) | None(阻塞), 0(非阻塞), 正数 |
2.3 端口扫描与枚举
很多时候你根本不知道设备插在了哪个COM口上。尤其是USB转串口的设备,每次插拔端口号都可能变。怎么办?写个扫描程序。
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:PID=1A86:7523 SER=5 LOCATION=1-2
----------------------------------------
端口: COM1
描述: 通信端口 (COM1)
硬件ID: ACPI PNP0501
----------------------------------------
我个人习惯是根据硬件ID来识别设备。比如某个PLC的USB转串口芯片是CH340,VID/PID是固定的,我就可以写个自动匹配的逻辑:
def find_serial_port(vid_pid="1A86:7523"):
ports = serial.tools.list_ports.comports()
for port in ports:
if vid_pid in port.hwid:
return port.device
return None
这样不管插在哪个USB口上,程序都能自动找到它。我在做自动化测试台架时就是这么干的,省去了手动配置的麻烦。
2.4 常见异常处理
串口通信最容易出异常。你想想看,硬件连接、驱动、权限、端口占用,任何一个环节出问题都会报错。我总结了几个最常见的:
2.4.1 serial.serialutil.SerialException
这个异常最常见。原因通常是端口不存在、被占用或者权限不够。
try:
ser = serial.Serial('COM10', 115200)
except serial.SerialException as e:
print(f"打开串口失败: {e}")
# 常见错误信息:
# "could not open port COM10: FileNotFoundError" -> 端口不存在
# "could not open port COM3: PermissionError" -> 权限不足或被占用
2.4.2 serial.serialutil.Timeout
读写超时。设了timeout之后,如果设备在规定时间内没回应,就会抛出这个异常。
try:
data = ser.read(100) # 读100个字节
except serial.serialutil.Timeout:
print("读取超时,设备可能未响应")
2.4.3 乱码问题
这个不算异常,但比异常更让人头疼。乱码的原因99%是波特率或数据格式不匹配。我曾经调试一个称重仪表,波特率设对了,但数据位设成了7位,结果读出来的重量值全是错的。
2.4.4 完整的异常处理模板
我写串口程序时,习惯用一个统一的异常处理结构:
import serial
import serial.tools.list_ports
def safe_serial_open(port, baudrate=115200, timeout=1):
try:
ser = serial.Serial(port, baudrate, timeout=timeout)
print(f"成功打开 {port},波特率 {baudrate}")
return ser
except serial.SerialException as e:
print(f"打开失败: {e}")
# 尝试自动扫描可用端口
ports = serial.tools.list_ports.comports()
print("当前可用端口:")
for p in ports:
print(f" {p.device} - {p.description}")
return None
# 使用示例
ser = safe_serial_open('COM3')
if ser:
try:
data = ser.read(10)
print(f"收到数据: {data.hex()}")
except serial.serialutil.Timeout:
print("读取超时")
finally:
ser.close()
这个模板我用了好几年,基本覆盖了90%的异常场景。你拿去改改就能用。