
本文深入探讨了使用python的pyserial库进行串口通信时,数据接收不成功的常见问题,特别是`ser.in_waiting`始终为零的困境。文章解释了设备回显机制与终端工具本地回显的差异,并提供了通过发送触发响应的命令和利用`readline()`方法高效读取串口数据的解决方案。同时,详细介绍了pyserial的各项参数配置,并提供了实用的代码示例和调试技巧,旨在帮助开发者顺利实现python与串口设备的稳定通信。
引言:pySerial与串口通信基础
pySerial是Python中一个功能强大的库,用于在各种操作系统上通过串口(Serial Port)进行通信。它允许Python程序与各种硬件设备(如传感器、微控制器、测试设备等)进行数据交换。在工业控制、物联网设备通信、嵌入式系统调试等领域,pySerial都扮演着重要的角色。
通常,进行串口通信需要以下几个基本步骤:
- 导入pySerial库:import serial
- 配置串口参数:包括端口号、波特率、数据位、停止位、奇偶校验、流控制和超时设置。
- 打开串口:ser.open()
- 发送数据:ser.write(data)
- 接收数据:ser.read(num_bytes) 或 ser.readline()
- 关闭串口:ser.close()
然而,在实际操作中,开发者可能会遇到一些意想不到的问题,尤其是在数据接收环节。
剖析ser.in_waiting为零的困境
许多开发者在使用pySerial发送命令后,期望立即通过ser.in_waiting检查是否有数据可读,但却发现其值始终为零。即使在其他终端工具(如Termite)中,相同的命令能够得到响应,但在pySerial中却无法读取到数据。这通常是由于对串口通信机制的误解以及设备和终端工具行为差异造成的。
立即学习“Python免费学习笔记(深入)”;
设备回显(Echo)机制的误解
核心问题在于,大多数串口设备在接收到命令后,并不会默认将收到的数据“回显”(echo)回来。设备通常只会在处理完命令后,根据命令的定义,发送特定的响应数据。例如,发送一个查询型号的命令'K',设备可能会返回"0309",但这并非对'K'的回显,而是对'K'命令的响应。
当ser.in_waiting返回0时,很可能意味着:
- 设备根本没有发送任何数据。
- 设备发送了数据,但程序在数据到达之前就检查了in_waiting,或者数据在检查后才到达。
终端工具的“本地回显”效应
为什么在Termite等终端工具中看起来一切正常呢?这通常是因为这些终端工具内置了“本地回显”(Local Echo)功能。当你在终端中输入字符时,终端会立即将这些字符显示在屏幕上,让你感觉设备“回显”了你发送的数据。实际上,这些字符可能根本没有经过串口设备,或者即使发送给了设备,设备也没有回传。当设备真正发送响应时,终端才会显示这些响应。这种本地回显机制很容易让开发者误以为设备会回显所有接收到的数据。
高效的数据接收策略
要解决ser.in_waiting为零的问题,并确保正确接收数据,需要采取以下策略:
1. 发送触发响应的命令
首先,要确保你发送的命令是设备文档中明确规定会触发响应的命令。不要仅仅期望设备回显你发送的内容。仔细查阅设备的用户手册或通信协议说明,了解哪些命令会产生何种格式的响应。
例如,如果设备在接收到'K'后会返回型号,那么发送b'K'后,你应该期待接收到型号字符串,而不是'K'本身。
2. 利用readline()读取行数据
对于许多基于文本或行终止符(如换行符\n、回车符\r)的协议,ser.readline()是一个非常有效的接收数据方法。它会一直读取数据,直到遇到行终止符或达到设定的超时时间。
import serial
import time
# 串口配置
ser = serial.Serial()
ser.baudrate = 9600
ser.port = 'COM4' # 根据实际情况修改端口号
ser.bytesize = serial.EIGHTBITS
ser.stopbits = serial.STOPBITS_ONE
ser.xonxoff = False
ser.dsrdtr = False
ser.rtscts = True # 根据设备要求设置流控制
ser.parity = serial.PARITY_NONE
ser.timeout = 1 # 设置超时时间,单位秒。对于readline,建议设置为非零值,避免无限等待。
try:
ser.open()
print(f"串口 {ser.port} 已打开。")
# 发送触发响应的命令
command = b'K\r\n' # 假设设备需要回车换行作为命令结束符
print(f"发送命令: {command.decode('ascii').strip()}")
ser.write(command)
time.sleep(0.1) # 给予设备处理和响应的时间
# 读取并打印所有行直到超时
print("开始接收数据...")
while True:
line = ser.readline() # 读取一行数据
if not line:
# 如果在超时时间内没有接收到数据,readline会返回空字节串
print("未接收到更多数据或达到超时。")
break
try:
# 尝试使用UTF-8解码,如果失败则尝试其他编码或打印十六进制
decoded_line = line.decode('utf-8').strip()
print(f"接收到: {decoded_line}")
except UnicodeDecodeError:
print(f"解码失败,原始数据(十六进制): {line.hex()}")
except Exception as e:
print(f"处理数据时发生错误: {e}")
except serial.SerialException as e:
print(f"串口错误: {e}")
except Exception as e:
print(f"发生未知错误: {e}")
finally:
if ser.is_open:
ser.close()
print(f"串口 {ser.port} 已关闭。")
代码说明:
- ser.timeout = 1: 这是一个关键设置。当timeout设置为正数时,readline()会在指定时间内等待数据。如果在这段时间内没有收到数据,它将返回一个空字节串b''。如果timeout设置为0,readline()将是非阻塞的,立即返回可用的数据(如果不足一行,则返回部分;如果没有数据,则返回b'')。设置为None则会无限等待。对于大多数应用,设置一个合理的超时时间是最佳实践。
- time.sleep(0.1): 在发送命令后,给设备留出处理和发送响应的时间非常重要。
- line.decode('utf-8').strip(): 接收到的数据是字节串,需要根据设备的编码格式进行解码(通常是UTF-8或ASCII)。strip()用于去除首尾的空白字符(包括行终止符)。
3. 处理字符编码问题
串口设备发送的数据编码可能多种多样,最常见的是ASCII或UTF-8。如果直接使用decode('utf-8')遇到UnicodeDecodeError,说明数据可能不是UTF-8编码,或者包含非标准字符。此时可以尝试:
- line.decode('ascii').strip()
- line.decode('latin-1').strip()
- 如果仍然无法解码,打印数据的十六进制表示 (line.hex()),这有助于调试和理解原始数据格式。
pySerial串口参数配置详解
正确的串口参数配置是成功通信的基础。以下是主要参数的详细说明:
- ser.port: 串口名称,例如Windows上的'COM1', 'COM4',Linux上的'/dev/ttyUSB0', '/dev/ttyS0'。
- ser.baudrate: 波特率,通信速率,如9600, 115200。必须与设备设置一致。
- ser.bytesize: 数据位,每帧数据的位数,通常是serial.EIGHTBITS (8位)。
- ser.stopbits: 停止位,用于标识数据帧的结束,通常是serial.STOPBITS_ONE (1位)。
- ser.parity: 奇偶校验,用于错误检测,通常是serial.PARITY_NONE (无校验)。其他选项包括serial.PARITY_ODD (奇校验) 和 serial.PARITY_EVEN (偶校验)。
- ser.xonxoff: 软件流控制(XON/XOFF),布尔值。True启用,False禁用。
- ser.rtscts: 硬件流控制(RTS/CTS),布尔值。True启用,False禁用。
- ser.dsrdtr: 硬件流控制(DSR/DTR),布尔值。True启用,False禁用。
流控制的重要性: 流控制(Flow Control)用于防止数据溢出,确保发送方不会发送过快导致接收方来不及处理。如果设备使用硬件流控制(RTS/CTS或DSR/DTR),而pySerial中未正确配置,可能会导致数据丢失或通信阻塞。对于某些设备,即使在终端工具中rtscts设置为True或False都能工作,但在pySerial中仍需根据设备实际需求进行精确匹配。
注意事项与调试技巧
- 参考设备文档:始终以设备制造商提供的通信协议文档为准,了解正确的波特率、数据格式、命令集和响应格式。
- 端口占用检查:确保串口没有被其他程序占用。在Windows上,可以使用设备管理器查看端口状态;在Linux上,可以使用lsof /dev/tty*。
- 逐步调试:从最简单的配置开始,逐步添加复杂功能。先确保能发送数据,再确保能接收数据。
- 使用串口监视工具:在开发过程中,使用专业的串口监视工具(如Bus Hound、Serial Port Monitor、Wireshark for Serial)可以捕获和分析串口上的原始数据流,这对于诊断问题非常有帮助。
- 尝试不同的编码:如果解码失败,尝试'ascii', 'latin-1', 'gbk'等常见编码。
- 物理连接检查:确保串口线缆连接牢固,没有损坏。USB转串口适配器驱动程序安装正确。
- 超时设置:合理设置ser.timeout参数,避免程序无限等待或过早结束读取。
总结
通过pySerial进行Python串口通信时,理解设备的回显机制、正确使用readline()方法以及精确配置串口参数是成功的关键。当ser.in_waiting始终为零时,不要急于怀疑连接问题,而应首先检查是否发送了能够触发设备响应的命令,并使用适当的方法(如readline()配合超时)来接收数据。结合设备文档和串口调试工具,可以高效地解决串口通信中的各种问题,实现Python程序与硬件设备的稳定可靠交互。










