杰瑞科技汇

Python串口readline如何正确读取数据?

readline() 是串口通信中一个非常常用的函数,它的作用是从串口缓冲区中读取一行数据,直到遇到换行符(\n)为止。


准备工作:安装 pyserial

你需要确保已经安装了 pyserial 库,如果尚未安装,可以通过 pip 进行安装:

pip install pyserial

readline() 的基本用法

readline() 的核心逻辑是:不断读取数据,直到遇到一个换行符 \n,然后将之前读取的所有数据作为一个字符串返回。

下面是一个最简单的示例代码:

import serial
# --- 配置串口参数 ---
# 'COM3' 是 Windows 下的串口名
# '/dev/ttyUSB0' 是 Linux 下的串口名
# '/dev/tty.serialname' 是 macOS 下的串口名
SERIAL_PORT = 'COM3' 
BAUD_RATE = 9600  # 波特率,必须与设备端设置一致
try:
    # 1. 打开串口连接
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
    print(f"成功打开串口 {ser.name}")
    # 2. 写入数据(可选,用于触发设备发送数据)
    # ser.write(b'SEND_DATA\n') # 注意:通常需要发送换行符
    # 3. 读取一行数据
    # readline() 会阻塞,直到读取到换行符或超时
    line = ser.readline()
    # 4. 处理读取到的数据
    # readline() 返回的是 bytes 类型,需要解码为字符串
    if line:
        decoded_line = line.decode('utf-8').strip() # .strip() 用于去除两端的空白字符和换行符
        print(f"接收到数据: {decoded_line}")
except serial.SerialException as e:
    print(f"无法打开串口 {SERIAL_PORT}: {e}")
except Exception as e:
    print(f"发生错误: {e}")
finally:
    # 5. 关闭串口连接
    if 'ser' in locals() and ser.is_open:
        ser.close()
        print("串口已关闭")

关键参数详解:timeout (超时)

timeout 是使用 readline()最重要的参数,它定义了 readline() 函数等待数据的最大时间。

  • timeout = None (默认值): 阻塞模式

    • readline() 会一直等待,直到读取到换行符。
    • 如果设备从不发送换行符,程序将永远卡在这里,无法执行后续代码。
    • 适用于确定数据一定会以换行符结尾的场景。
  • timeout = 0: 非阻塞模式

    • readline() 会立即返回。
    • 如果缓冲区中没有数据或没有完整的行(没有换行符),它会返回一个空的 bytes 对象 (b'')。
    • 适用于需要不停轮询串口,而不希望程序被卡住的场景。
  • timeout = x (x > 0, 1): 超时模式

    • readline() 会等待最多 x 秒。
    • 如果在 x 秒内读取到了换行符,则立即返回数据。
    • x 秒后仍未读取到换行符,则返回已读取到的所有数据(即使没有换行符),或者如果什么都没读到,则返回空的 bytes 对象。
    • 这是最常用、最推荐的模式,因为它既能让程序等待数据,又能防止程序因设备故障或无数据而永久卡死。

readline() 的常见陷阱与问题

陷阱 1:换行符不匹配

这是最常见的问题。readline() 依赖换行符 \n 来识别一行的结束。

  • 问题: 你的设备发送的数据格式是 Hello, World!没有末尾的 \n

  • 结果: ser.readline() 会一直等待,直到超时,如果设置了 timeout=1,它将在 1 秒后返回 b'Hello, World!'(没有换行符)。timeout=None,它会永久阻塞。

  • 解决方案:

    1. 修改设备端: 让设备在发送数据后自动加上换行符 \n,这是最佳方案
    2. 修改 Python 端: 使用 ser.read_until(expected=terminator),它可以自定义结束符。ser.read_until(b'\n')ser.readline() 在默认情况下行为一致。
    3. 不依赖换行符: 如果确实无法添加换行符,可以使用 ser.read(size) 来读取固定长度的数据,但这不够灵活。

陷阱 2:数据不完整(缓冲区问题)

串口通信是异步的,数据可能分多次到达。

  • 场景: 设备发送 12345\n,但数据分两包到达:12345\n
  • 结果: readline() 会先读取到 12,发现没有 \n,于是继续等待。345\n 到达,readline() 将它们拼接成 12345\n 并返回,这个过程对用户是透明的,通常没有问题。

陷阱 3:数据丢失

  • 场景: readline() 正在等待数据时,缓冲区里已经有了一些旧数据(OLD_DATA\n),此时新数据 NEW_DATA\n 到达,但 readline() 立即读取了缓冲区里的 OLD_DATA\n
  • 结果: 程序处理了旧数据,而新数据可能被下一个 readline() 读取,或者被后续的数据覆盖,这通常需要通过协议设计(如数据帧头)来解决。

更稳健的读取方式:read_until()

pyserial 提供了 read_until() 方法,它比 readline() 更灵活,可以指定自定义的结束符。

import serial
ser = serial.Serial('COM3', 9600, timeout=1)
# 假设你的设备使用回车符 '\r' 作为行尾
line = ser.read_until(b'\r') 
print(f"接收到数据: {line.decode('utf-8').strip()}")
ser.close()

read_until() 同样受 timeout 参数影响,并且可以指定一个 max_length 参数来防止内存溢出攻击(如果对方发送一个超长的“行”)。


最佳实践总结

  1. 始终设置 timeout: 强烈建议设置一个合理的超时时间(如 timeout=1),避免程序无响应。
  2. 统一换行符: 与设备开发者确认,确保数据包的结尾有明确的、一致的结束符(最好是 \n)。
  3. 处理返回值: readline() 返回的是 bytes 类型,记得使用 .decode('utf-8') 或其他编码(如 'ascii')将其转换为字符串。
  4. 清理数据: 使用 .strip() 去除字符串两端可能存在的 \n, \r 或空格。
  5. 使用 try...finallywith 语句: 确保串口在使用完毕后一定会被关闭,防止资源泄漏。

使用 with 语句是更 Pythonic 的方式,它能自动处理资源的关闭:

import serial
SERIAL_PORT = 'COM3'
BAUD_RATE = 9600
try:
    with serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) as ser:
        print(f"成功打开串口 {ser.name}")
        while True: # 进入一个循环来持续读取数据
            line = ser.readline()
            if line:
                print(f"接收到数据: {line.decode('utf-8').strip()}")
            # 可以在这里添加其他逻辑,比如检查某个条件来退出循环
except serial.SerialException as e:
    print(f"无法打开串口 {SERIAL_PORT}: {e}")
分享:
扫描分享到社交APP
上一篇
下一篇