杰瑞科技汇

Python串口timeout如何设置与处理?

什么是 Timeout?

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

Python串口timeout如何设置与处理?-图1
(图片来源网络,侵删)

它防止了程序因为等待一个可能永远不会到来的数据而“卡死”(无限期阻塞)。


在 Python 中使用 timeout

在 Python 中,最常用的串口库是 pyserial,我们主要以这个库为例进行讲解。

安装 pyserial

如果你还没有安装,请先安装:

pip install pyserial

设置 timeout

timeout 参数可以在创建串口对象时设置,也可以在后续通过修改 timeout 属性来更改。

Python串口timeout如何设置与处理?-图2
(图片来源网络,侵删)

单位:

值类型: 浮点数(5 表示 500 毫秒)

关键值:

  • timeout = None (默认值): 阻塞模式,如果没有数据到达,read() 方法将无限期地等待下去,直到有数据到达或发生错误,这可能会导致程序长时间卡住。
  • timeout = 0: 非阻塞模式read() 方法会立即返回,如果有数据在输入缓冲区中,就返回读取到的数据;如果没有数据,则立即返回一个空字节对象 b''
  • timeout > 0: 超时模式,这是最常用的模式。read() 方法会等待最多 timeout 秒的时间。
    • 如果在超时时间内收到数据,则返回数据。
    • 如果超时时间结束仍未收到数据,则返回 None

代码示例

下面我们通过几个具体的例子来感受不同 timeout 值带来的差异。

Python串口timeout如何设置与处理?-图3
(图片来源网络,侵删)

示例 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)")

预期结果分析:

  1. test_timeout_behavior(..., 1.0, ...)

    • readline() 开始等待,但设备在 3 秒后才发数据,1 秒超时时间到了,它没有等到数据。
    • 输出: 在超时时间内未收到任何数据,readline() 返回 None,实际耗时约 1.0 秒。
  2. test_timeout_behavior(..., 5.0, ...)

    • readline() 开始等待,在 3 秒时,设备发来了 "Hello\n"readline() 立即读取并返回。
    • 输出: 成功读取数据,实际耗时约 3.0 秒(而不是 5.0 秒),因为它一收到数据就立刻返回了。
  3. 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()

最佳实践和注意事项

  1. 为你的应用场景选择合适的 timeout

    • 实时性要求高: 使用较小的 timeout(如 1 秒),让程序能快速响应其他事件。
    • 数据发送不规律但需要完整接收: 使用较大的 timeout,但要确保这个时间在可接受范围内。
    • 不确定数据何时到达: timeout > 0 是必须的,可以防止程序永久阻塞。
  2. 处理 None 返回值 在超时模式下,read()readline() 可能会返回 None,你的代码必须能够正确处理这种情况,通常是一个简单的 if data: 判断。

    data = ser.readline()
    if data:
        # 处理数据
        print(f"收到: {data}")
    else:
        # 处理超时或无数据的情况
        print("超时,未收到数据")
  3. 结合 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("缓冲区为空")
  4. 注意换行符 使用 readline() 时,确保你的发送方和接收方对“一行”的定义一致(通常是 \n),如果发送方不发送换行符,readline() 会一直等待直到 timeout

timeout 是串口编程中实现健壮性和非阻塞操作的核心工具。

  • None: 阻塞,可能卡死程序。
  • 0: 非阻塞,立即返回。
  • > 0: 超时模式,在指定时间内等待,是绝大多数情况下的最佳选择。

合理设置 timeout 并妥善处理其返回值,是编写稳定可靠的串口通信程序的关键。

分享:
扫描分享到社交APP
上一篇
下一篇