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()
⚠ 我曾经犯过的错: 在Linux下用/dev/ttyUSB0时,忘记检查权限。结果程序跑起来,open()直接抛PermissionError。后来我养成了一个习惯——每次构造前先确认当前用户有串口权限,或者用sudo运行。
参数名 默认值 说明 我的建议
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()
💡 我的习惯: 能用with就用with。如果必须手动管理,我会在finally块里加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)  # 只读当前缓冲区里的
🔑 关键点: write()返回实际写入的字节数。如果返回值小于你传入的长度,说明写缓冲区满了。这时候别硬写,等一等再试。

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("命令已发送完毕")
⚠ 注意: flush()只等发送完成,不会清空接收缓冲区。如果你想清空接收缓冲区,得自己读出来扔掉。

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指令
💡 经验之谈: 很多USB转串口模块,DTR和RTS默认是低电平。如果你发现设备不工作,先试试把DTR拉高。我遇到过好几次这种情况。

4.7 知识体系总览

下面这张图,把Serial类的核心API串起来了。你一看就明白它们之间的关系。

pyserial Serial类核心API关系图 pyserial Serial类核心API关系图 Serial 对象 构造 + open() close() read() / readline() write() in_waiting out_waiting flush() setRTS() setDTR() 生命周期管理 打开→使用→关闭 数据读写 核心收发操作 缓冲区状态 实时水位监测

4.8 实战小贴士:API组合拳

光知道单个API怎么用还不够,得学会组合。下面是我在实际项目中总结的几个常用模式:

  1. 安全打开模式: 先检查端口是否存在,再open(),配合try/except捕获异常。
  2. 非阻塞读取模式: 先查in_waiting,有数据再read(),避免阻塞。
  3. 可靠发送模式: write()后调用flush(),确保数据发完再继续。
  4. 硬件复位模式: 用setDTR()拉低再拉高,实现设备硬件复位。
🎯 核心思想: 串口通信不是简单的读写,而是对缓冲区、超时、硬件信号的综合管理。把每个API的职责搞清楚,你的程序才能稳定可靠。