4. pyserial核心API详解:Serial类构造、open/close、read/write、in_waiting、out_waiting、flush、setRTS/DTR
好,咱们今天来啃pyserial这块硬骨头。说实话,很多初学者把串口通信想得太简单了——以为打开端口、发数据、收数据就完事了。我在实际项目中踩过的坑,十有八九都跟API用不对有关。你想想看,一个串口工具如果连缓冲区状态都搞不清楚,那跟瞎子摸象有什么区别?
这一节,我会把Serial类的核心API一个一个拆开来讲。不光是讲怎么用,更重要的是——什么时候用、为什么用、用错了会怎样。
4.1 Serial类构造:你的串口对象是怎么出生的?
创建串口对象,说白了就两种方式:构造时配好参数,或者先创建再慢慢配。我个人习惯用第一种,一步到位,省心。
import serial
# 方式一:构造时指定所有参数
ser = serial.Serial(
port='COM3', # Windows下用COM口,Linux下用/dev/ttyUSB0
baudrate=115200, # 波特率,常用9600、115200
bytesize=8, # 数据位,通常是8
parity='N', # 校验位:N无校验、E偶校验、O奇校验
stopbits=1, # 停止位,通常是1
timeout=1, # 读超时,单位秒
write_timeout=1, # 写超时
xonxoff=False, # 软件流控
rtscts=False, # 硬件流控(RTS/CTS)
dsrdtr=False # 硬件流控(DSR/DTR)
)
# 方式二:先创建,再手动设置
ser = serial.Serial()
ser.port = 'COM3'
ser.baudrate = 115200
ser.timeout = 1
ser.open()
| 参数名 | 默认值 | 说明 | 我的建议 |
|---|---|---|---|
| port | None | 串口设备名 | 必填,别偷懒 |
| baudrate | 9600 | 波特率 | 115200最常用 |
| bytesize | 8 | 数据位 | 几乎都用8 |
| parity | 'N' | 校验位 | 无校验最省事 |
| stopbits | 1 | 停止位 | 1位就够了 |
| timeout | None | 读超时(秒) | 建议设1-3秒 |
| write_timeout | None | 写超时(秒) | 建议设1秒 |
4.2 open()与close():打开和关闭的艺术
open()和close()看起来简单,但用不好会出大问题。我记得有一次在产线调试,程序跑着跑着突然报错,查了半天——原来是上一个线程没关串口,导致新线程打不开。
# 标准用法
try:
ser.open()
print(f"串口 {ser.port} 已打开")
except serial.SerialException as e:
print(f"打开失败: {e}")
# 使用完后一定要关闭
ser.close()
# 推荐:用上下文管理器,自动关闭
with serial.Serial('COM3', 115200, timeout=1) as ser:
# 在这里读写
pass # 退出with块时自动close()
4.3 read()与write():核心读写操作
读写是串口通信的灵魂。但很多人一上来就read(100),以为能读到100个字节——天真了。read()是阻塞的,如果只收到50个字节,它会一直等到超时或者凑满100个。
# 写数据
ser.write(b'Hello, UART!') # 必须传bytes
ser.write('你好'.encode('utf-8')) # 字符串转bytes
# 读数据——三种常用方式
# 方式1:读指定字节数(可能读不够)
data = ser.read(10) # 最多读10个字节
# 方式2:读一行(直到换行符)
line = ser.readline() # 包含\n
# 方式3:读所有可用数据
data = ser.read(ser.in_waiting) # 只读当前缓冲区里的
4.4 in_waiting与out_waiting:缓冲区水位计
这两个属性,说白了就是告诉你缓冲区里有多少数据在排队。in_waiting是接收缓冲区里待读的字节数,out_waiting是发送缓冲区里待发的字节数。
我做过一个项目,需要实时采集传感器数据。如果不用in_waiting,每次固定读10个字节,结果数据对不齐,解析全乱套。后来改成先查in_waiting,再读全部,问题就解决了。
# 高效读取模式
def read_all_available(ser):
"""读取当前所有可用数据"""
if ser.in_waiting > 0:
return ser.read(ser.in_waiting)
return b''
# 检查发送缓冲区是否排空
def wait_for_send_done(ser, timeout=5):
"""等待发送缓冲区排空"""
import time
start = time.time()
while ser.out_waiting > 0:
if time.time() - start > timeout:
return False
time.sleep(0.01)
return True
| 属性 | 类型 | 含义 | 典型值 |
|---|---|---|---|
| in_waiting | int | 接收缓冲区中待读字节数 | 0~缓冲区大小 |
| out_waiting | int | 发送缓冲区中待发字节数 | 0~缓冲区大小 |
4.5 flush():清空缓冲区
flush()这个函数,名字容易让人误解。它不是清空数据,而是等待数据发送完成。说白了,就是阻塞直到out_waiting变成0。
ser.write(b'important command')
ser.flush() # 等数据发完再继续
print("命令已发送完毕")
4.6 setRTS()与setDTR():硬件流控的底层控制
这两个函数用来直接控制RTS和DTR引脚的电平。RTS(Request To Send)和DTR(Data Terminal Ready)是串口硬件流控的信号线。
我记得有一次调试一个老式设备,它需要DTR拉高才能上电。我一开始没管这个,设备死活没反应。后来用setDTR(True)一拉,设备立马工作了。
# 控制RTS引脚
ser.setRTS(True) # RTS拉高
ser.setRTS(False) # RTS拉低
# 控制DTR引脚
ser.setDTR(True) # DTR拉高
ser.setDTR(False) # DTR拉低
# 实际应用:用DTR给设备供电
ser.setDTR(True) # 设备上电
time.sleep(0.5) # 等设备启动
ser.write(b'AT\r') # 发送AT指令
4.7 知识体系总览
下面这张图,把Serial类的核心API串起来了。你一看就明白它们之间的关系。
4.8 实战小贴士:API组合拳
光知道单个API怎么用还不够,得学会组合。下面是我在实际项目中总结的几个常用模式:
- 安全打开模式: 先检查端口是否存在,再open(),配合try/except捕获异常。
- 非阻塞读取模式: 先查in_waiting,有数据再read(),避免阻塞。
- 可靠发送模式: write()后调用flush(),确保数据发完再继续。
- 硬件复位模式: 用setDTR()拉低再拉高,实现设备硬件复位。