in_waiting 是串口通信中一个非常核心和常用的属性,尤其是在处理异步数据时。

什么是 in_waiting?
in_waiting 是一个只读属性,它返回当前串口输入缓冲区中等待读取的字节数。
它就像一个“计数器”,告诉你串口硬件缓冲区里已经堆积了多少个数据字节,正等着你的程序去读取。
为什么需要 in_waiting?
串口通信是异步的,这意味着发送方和接收方没有共享的时钟,数据是一个字节一个字节地、以不确定的时间间隔到达的。
当你调用 ser.read() 时,它会:

- 立即返回缓冲区中已有的所有数据。
- 如果缓冲区是空的,它会阻塞(等待)直到有数据到达,或者直到你指定的超时时间结束。
这种“阻塞”行为在很多场景下是不理想的,
- 你只想读取一帧完整的数据:比如一个以
0xAA开头,以0xBB结尾的数据包。read()阻塞了,你可能读到一半数据就被卡住了,永远等不到结束符。 - 你需要同时处理其他任务:比如在一个 GUI 程序中,你不能让主线程因为等待串口数据而卡死。
in_waiting 的出现就是为了解决这个问题,它允许你在读取数据之前,先检查一下缓冲区里有多少数据,从而做出更智能的决策。
如何使用 in_waiting?(核心用法)
最常见的用法是配合 read() 或 readline(),来一次性读取缓冲区中所有的数据。
基本语法
import serial # 假设 ser 已经是一个打开的 serial.Serial 对象 # num_bytes = ser.in_waiting # data = ser.read(num_bytes)
示例代码
下面是一个完整的、可运行的示例,你需要准备一个 USB 转串口模块(如 CH340/FT232),并连接一个设备(比如另一个 Arduino)不断发送数据,或者使用一个串口调试助手来模拟发送数据。

import serial
import time
# --- 配置串口 ---
# 请根据你的实际情况修改以下参数
SERIAL_PORT = 'COM3' # Windows: 'COMx', Linux: '/dev/ttyUSBx', Mac: '/dev/cu.usbserial-x'
BAUD_RATE = 9600
TIMEOUT = 1 # 设置超时,防止无限等待
try:
# 打开串口
ser = serial.Serial(
port=SERIAL_PORT,
baudrate=BAUD_RATE,
timeout=TIMEOUT
)
print(f"串口 {ser.name} 打开成功")
# 主循环
while True:
# 1. 检查串口缓冲区中是否有数据
if ser.in_waiting > 0:
print(f"检测到 {ser.in_waiting} 个字节等待读取...")
# 2. 读取缓冲区中所有的数据
# in_waiting 会告诉你该读多少字节,避免阻塞
received_data = ser.read(ser.in_waiting)
# 3. 处理数据
# received_data 是 bytes 类型,通常需要解码成字符串
try:
decoded_data = received_data.decode('utf-8').strip()
print(f"接收到数据: '{decoded_data}'")
# 在这里可以添加你的数据处理逻辑
# 查找特定的数据帧格式
except UnicodeDecodeError:
print(f"接收到无法解码的数据 (原始字节): {received_data}")
# 4. 做其他事情...
# 即使串口没有数据,程序也不会卡在这里,可以继续执行其他任务
print("执行其他任务...")
time.sleep(1) # 模拟程序其他部分的耗时操作
except serial.SerialException as e:
print(f"无法打开串口 {SERIAL_PORT}: {e}")
except KeyboardInterrupt:
print("程序被用户中断")
finally:
if 'ser' in locals() and ser.is_open:
ser.close()
print("串口已关闭")
in_waiting vs. readline()
很多初学者会混淆 in_waiting 和 ser.readline()。
-
ser.readline():- 它会持续读取,直到遇到一个换行符
\n(或者超时)。 - 如果发送方没有发送换行符,
readline()会一直阻塞,直到超时。 - 它读取的是一行逻辑数据,而不是所有物理字节。
- 它会持续读取,直到遇到一个换行符
-
ser.read(ser.in_waiting):- 它会一次性读取缓冲区中所有的字节,无论这些字节是否构成一个完整的逻辑行。
- 它读取的是所有等待的物理数据。
- 如果你的协议是基于换行符的(比如很多调试信息),
readline()更方便。 - 如果你的协议是自定义的、二进制的,或者你需要确保不丢失任何数据包,
in_waiting+read()是更可靠的选择。
注意事项和常见问题
-
数据丢失风险:
in_waiting只告诉你缓冲区里有多少数据,如果你的程序处理速度比数据到达速度慢,新的数据可能会覆盖旧的数据(如果缓冲区满了),导致数据丢失,增大串口缓冲区大小或加快数据处理速度可以缓解这个问题。 -
timeout的重要性:在打开串口时设置一个合理的timeout是一个好习惯,这可以防止你的程序在意外情况下(如设备断开)无限期地阻塞。 -
read(size)的size参数:ser.read(ser.in_waiting)是一个很常见的模式,但要注意,in_waiting的值在你调用read()的瞬间和read()执行期间可能会改变(如果新数据到达),对于大多数应用场景,这种影响可以忽略不计。 -
跨平台性:
in_waiting是pyserial库的标准属性,在 Windows, Linux, macOS 上的行为是一致的。
实际应用场景举例
假设你要接收一个以 0xAA 开头,0x55 结尾的数据包,长度不定。
# 在主循环中
while True:
if ser.in_waiting > 0:
# 先读取一个字节,看看是不是包头
first_byte = ser.read(1)
if first_byte == b'\xAA':
print("检测到包头 0xAA")
# 继续等待,直到收到至少一个字节的结束符
while ser.in_waiting < 1:
time.sleep(0.01) # 短暂休眠,避免空转CPU
# 现在缓冲区里至少有1个字节,我们把它读完
# 假设数据包在结束符之前到达
data = ser.read(ser.in_waiting)
if data.endswith(b'\x55'):
print(f"收到完整数据包: {data}")
else:
print("收到数据包,但格式不正确(缺少结束符)") 