第4章 Python pyserial库入门:安装、枚举、开关与读写

做嵌入式通信,串口是绕不开的硬骨头。我见过不少工程师,硬件调通了,上位机却卡在串口通信上。其实Python的pyserial库就是专门干这个的——它把底层的串口操作封装得干干净净,你只需要几行代码就能收发数据。

这一章,咱们就把它彻底搞明白。

4.1 pyserial安装:别踩Python版本的坑

安装很简单,但有个细节我吃过亏。先看标准做法:

pip install pyserial

如果你用的是Python 3,记得用pip3:

pip3 install pyserial
我曾经踩过的坑: 有次在树莓派上装pyserial,系统里同时有Python 2.7和3.8,我用pip install装到了Python 2的环境里,结果代码跑起来各种报错。后来养成习惯,装包前先确认Python版本:
python --version
pip --version
或者直接用虚拟环境,一劳永逸。

验证安装是否成功,进Python交互环境试一下:

import serial
print(serial.__version__)

不报错,就说明装好了。

4.2 串口枚举:找到你的设备

插上USB转串口模块后,怎么知道它对应哪个端口?Windows上是COM3、COM4这种,Linux上是/dev/ttyUSB0或/dev/ttyAMA0。手动找太麻烦,pyserial提供了枚举函数:

import serial.tools.list_ports

ports = serial.tools.list_ports.comports()
for port in ports:
    print(f"端口: {port.device}, 描述: {port.description}")

输出大概长这样:

端口 描述
COM3 USB Serial Port (COM3)
/dev/ttyUSB0 CP2102 USB to UART Bridge Controller

我个人习惯在程序启动时先做一次枚举,把可用端口列出来让用户选。这样既避免了硬编码,也方便调试。

小技巧: 如果枚举结果为空,先检查驱动。Windows上看看设备管理器里有没有感叹号,Linux上ls /dev/tty* 看看设备节点在不在。

4.3 打开与关闭串口:参数别搞错

打开串口需要指定端口名和波特率,其他参数通常用默认值就行。但有几个参数我建议你显式设置:

import serial

ser = serial.Serial(
    port='COM3',        # 端口名
    baudrate=115200,    # 波特率
    bytesize=8,         # 数据位
    parity='N',         # 校验位:N无校验,E偶校验,O奇校验
    stopbits=1,         # 停止位
    timeout=1           # 超时时间,单位秒
)

关闭串口更简单:

ser.close()

嗯,这里要注意:程序退出前一定要关串口,否则下次打开时会报“端口被占用”。我习惯用with语句自动管理:

with serial.Serial('COM3', 115200, timeout=1) as ser:
    # 在这里读写
    pass
# 退出with块后自动关闭
我曾经遇到的坑: 有次调试RS485设备,忘记关串口就拔了USB线,再插上时端口号变了,程序直接崩溃。后来我加了异常处理,捕获serial.SerialException,并在finally块里确保关闭。

4.4 读写基础操作:发什么,收什么

写数据用write,读数据用read。注意write需要传字节数据,不是字符串:

# 发送数据
ser.write(b'AT\r\n')           # 发送字节串
ser.write('AT\r\n'.encode())   # 字符串转字节

# 读取数据
data = ser.read(10)            # 读10个字节
line = ser.readline()          # 读一行,直到换行符
all_data = ser.read_all()      # 读所有可用数据

实际项目中,我常用的是readline和read的组合。比如跟Modbus设备通信,先发指令,再等固定长度的回复:

ser.write(b'\x01\x03\x00\x00\x00\x01\x84\x0A')
response = ser.read(8)  # Modbus RTU回复固定8字节
print(response.hex())
核心原则: 发什么格式,收什么格式。上位机和下位机必须约定好帧格式、校验方式、超时时间。否则就是鸡同鸭讲。

4.5 超时设置:别让程序卡死

超时是串口通信里最容易忽略的细节。pyserial支持三种超时模式:

模式 设置方式 行为
无超时 timeout=None read会一直阻塞,直到读到指定字节数
固定超时 timeout=1.0 最多等1秒,读不到就返回已读数据
非阻塞 timeout=0 立即返回,不管有没有数据

我建议新手用固定超时,比如1秒。这样既不会卡死,也不会漏数据。看个实际例子:

ser.timeout = 2  # 设置超时2秒
data = ser.read(100)
if len(data) == 0:
    print("超时了,没收到数据")
else:
    print(f"收到 {len(data)} 字节: {data}")
避坑指南: 我曾经在读取GPS模块时把timeout设成了None,结果模块没输出数据,程序就永远卡在read那里。后来改成timeout=3,配合循环重试,问题解决。

4.6 知识体系总览

下面这张图把pyserial的核心操作串起来了,你一看就明白:

pyserial 核心操作流程 pip install pyserial list_ports.comports() serial.Serial(port, baudrate) write() / read() timeout 参数设置 close() 或 with 语句 说明 安装后先枚举,确认端口存在 打开时设置好波特率和超时 读写完成后务必关闭串口

说白了,pyserial就是这六个步骤:安装→枚举→打开→读写→超时控制→关闭。每一步都有坑,但只要你按这个流程走,基本不会出大问题。

4.7 实战小练习:串口回环测试

学完理论,动手试试。把USB转串口的TX和RX短接,运行下面代码:

import serial

ser = serial.Serial('COM3', 115200, timeout=1)
ser.write(b'Hello pyserial!')
response = ser.read(100)
print(f"收到: {response}")
ser.close()

如果短接正确,你会看到“收到: b'Hello pyserial!'”。这就是最简单的回环测试,用来验证串口通路是否正常。我在现场调试时经常用这招,先排除硬件问题,再查协议。

记住: 串口通信没有“可能通”,只有“通”或“不通”。用回环测试一测便知。