什么是 Timeout?
timeout(超时)是指在串口操作(如读取数据)时,设置一个最长等待时间,如果在指定的时间内没有收到预期的数据,操作将不再继续等待,而是返回一个结果(通常是 None 或已读取的部分数据)。

它防止了程序因为等待一个可能永远不会到来的数据而“卡死”(无限期阻塞)。
在 Python 中使用 timeout
在 Python 中,最常用的串口库是 pyserial,我们主要以这个库为例进行讲解。
安装 pyserial
如果你还没有安装,请先安装:
pip install pyserial
设置 timeout
timeout 参数可以在创建串口对象时设置,也可以在后续通过修改 timeout 属性来更改。

单位: 秒
值类型: 浮点数(5 表示 500 毫秒)
关键值:
timeout = None(默认值): 阻塞模式,如果没有数据到达,read()方法将无限期地等待下去,直到有数据到达或发生错误,这可能会导致程序长时间卡住。timeout = 0: 非阻塞模式。read()方法会立即返回,如果有数据在输入缓冲区中,就返回读取到的数据;如果没有数据,则立即返回一个空字节对象b''。timeout > 0: 超时模式,这是最常用的模式。read()方法会等待最多timeout秒的时间。- 如果在超时时间内收到数据,则返回数据。
- 如果超时时间结束仍未收到数据,则返回
None。
代码示例
下面我们通过几个具体的例子来感受不同 timeout 值带来的差异。

示例 1: 设置和修改 timeout
import serial
import time
# --- 创建串口时设置 timeout ---
ser = serial.Serial(
port='COM3', # 请替换为你的串口号 (Linux: /dev/ttyUSB0, macOS: /dev/tty.usbserial-XXXX)
baudrate=9600,
timeout=2 # 设置初始超时为 2 秒
)
print(f"初始超时设置: {ser.timeout} 秒")
# --- 运行时修改 timeout ---
ser.timeout = 0.5 # 修改为 0.5 秒超时
print(f"修改后的超时设置: {ser.timeout} 秒")
ser.close()
示例 2: 不同 timeout 值的 read() 行为对比
假设我们有一个串口设备,它每隔 3 秒才发送一次数据 "Hello\n"。
import serial
import time
# 模拟一个串口,请替换为你的实际串口
SERIAL_PORT = 'COM3'
BAUDRATE = 9600
def test_timeout_behavior(port, baudrate, timeout_value, description):
print(f"\n--- 测试: {description} (timeout={timeout_value}s) ---")
try:
ser = serial.Serial(port, baudrate, timeout=timeout_value)
print(f"串口 {ser.name} 已打开")
start_time = time.time()
print(f"开始读取数据 (等待最多 {timeout_value} 秒)...")
data = ser.readline() # readline() 会读取直到遇到换行符或超时
end_time = time.time()
elapsed_time = end_time - start_time
if data:
print(f"成功读取数据: {data.decode('utf-8').strip()}")
else:
print("在超时时间内未收到任何数据,readline() 返回 None")
print(f"实际耗时: {elapsed_time:.2f} 秒")
ser.close()
except serial.SerialException as e:
print(f"无法打开串口 {port}: {e}")
# --- 主程序 ---
if __name__ == '__main__':
# 假设设备每3秒发一次数据
print("假设串口设备每3秒发送一次 'Hello'")
# 1. 测试超时时间小于数据发送间隔
test_timeout_behavior(SERIAL_PORT, BAUDRATE, 1.0, "超时 < 数据间隔 (1s < 3s)")
# 2. 测试超时时间大于数据发送间隔
test_timeout_behavior(SERIAL_PORT, BAUDRATE, 5.0, "超时 > 数据间隔 (5s > 3s)")
# 3. 测试非阻塞模式 (timeout=0)
test_timeout_behavior(SERIAL_PORT, BAUDRATE, 0, "非阻塞模式 (timeout=0)")
预期结果分析:
-
test_timeout_behavior(..., 1.0, ...)readline()开始等待,但设备在 3 秒后才发数据,1 秒超时时间到了,它没有等到数据。- 输出: 在超时时间内未收到任何数据,
readline()返回None,实际耗时约 1.0 秒。
-
test_timeout_behavior(..., 5.0, ...)readline()开始等待,在 3 秒时,设备发来了"Hello\n"。readline()立即读取并返回。- 输出: 成功读取数据,实际耗时约 3.0 秒(而不是 5.0 秒),因为它一收到数据就立刻返回了。
-
test_timeout_behavior(..., 0, ...)readline()立即检查输入缓冲区,由于没有数据,它立刻返回None。- 输出: 在超时时间内未收到任何数据,
readline()返回None,实际耗时接近 0 秒。
常用串口方法与 timeout 的关系
| 方法 | 描述 | timeout 的影响 |
|---|---|---|
ser.read(size) |
读取指定 size 字节数的数据。 |
等待最多 timeout 秒来凑齐 size 字节,如果超时,返回已读取到的部分数据(可能少于 size 字节),如果完全没有数据,返回 None。 |
ser.readline() |
读取一行数据,直到遇到换行符 \n 或超时。 |
等待最多 timeout 秒来读取一行,如果超时,返回已读取到的部分数据(可能不完整),如果完全没有数据,返回 None。 |
ser.readall() |
读取所有缓冲区中的数据。 | 不受 timeout 影响,它会立即返回所有当前在缓冲区中的数据,然后返回。 |
ser.write(data) |
写入数据。 | 通常不受 timeout 影响。write 方法会尝试将数据发送到操作系统的输出缓冲区,然后立即返回,如果需要确保数据完全发送到硬件(等待发送完成),可以使用 ser.flush()。 |
最佳实践和注意事项
-
为你的应用场景选择合适的
timeout- 实时性要求高: 使用较小的
timeout(如1秒),让程序能快速响应其他事件。 - 数据发送不规律但需要完整接收: 使用较大的
timeout,但要确保这个时间在可接受范围内。 - 不确定数据何时到达:
timeout > 0是必须的,可以防止程序永久阻塞。
- 实时性要求高: 使用较小的
-
处理
None返回值 在超时模式下,read()和readline()可能会返回None,你的代码必须能够正确处理这种情况,通常是一个简单的if data:判断。data = ser.readline() if data: # 处理数据 print(f"收到: {data}") else: # 处理超时或无数据的情况 print("超时,未收到数据") -
结合
in_waiting使用ser.in_waiting属性可以告诉你当前输入缓冲区中有多少字节的数据,这可以让你在读取前先检查数据是否可用,从而实现更精细的控制。# 非阻塞检查模式 ser.timeout = 0 if ser.in_waiting > 0: data = ser.read(ser.in_waiting) print(f"读取到 {len(data)} 字节") else: print("缓冲区为空") -
注意换行符 使用
readline()时,确保你的发送方和接收方对“一行”的定义一致(通常是\n),如果发送方不发送换行符,readline()会一直等待直到timeout。
timeout 是串口编程中实现健壮性和非阻塞操作的核心工具。
None: 阻塞,可能卡死程序。0: 非阻塞,立即返回。> 0: 超时模式,在指定时间内等待,是绝大多数情况下的最佳选择。
合理设置 timeout 并妥善处理其返回值,是编写稳定可靠的串口通信程序的关键。
