在串口通信中,数据被分割接收是一个常见问题,主要原因是:
1、readyRead 信号触发时机:当串口缓冲区有数据就绪时立即触发,不保证一帧数据完全到达2、网络/硬件延迟:长数据帧可能分批到达接收缓冲区
3、缓冲区大小限制:单次读取可能无法获取完整数据包
解决方案:
方案1:针对固定帧头帧尾,添加数据缓冲区和帧识别机制
python
def __init__(self):
super().__init__()
# 初始化界面(调用生成的setupUi方法,加载.ui定义的界面)
self.setupUi(self)
self.serial_manager = SerialPortManager()
# 添加接收数据缓冲区
self.recv_buffer = bytearray()
# 添加帧开始和结束标志(根据实际协议调整)
self.frame_start_marker = b'\xFF\xAA' # 示例起始标志
self.frame_end_marker = b'\xEE\xEE' # 示例结束标志
def read_serial_data(self):
"""读取串口接收的数据(响应 readyRead 信号)"""
if not self.serial_manager.serial_port.isOpen():
return
# 读取所有可用数据(QByteArray -> bytes)
recv_data = self.serial_manager.serial_port.readAll()
if recv_data.isEmpty():
return
# 转换为 Python 字节类型并添加到缓冲区
new_data = bytes(recv_data.data())
self.recv_buffer.extend(new_data)
# 尝试从缓冲区中提取完整帧
complete_frames = self._extract_complete_frames()
# 处理完整的帧数据
for frame in complete_frames:
recv_hex_spaced = ' '.join(frame.hex()[i:i + 2].upper() for i in range(0, len(frame.hex()), 2))
self._append_recv_text(f"接收:{recv_hex_spaced}")
def _extract_complete_frames(self):
"""从缓冲区中提取完整的数据帧"""
complete_frames = []
while True:
# 查找帧结束标志
end_pos = self.recv_buffer.find(self.frame_end_marker)
if end_pos == -1:
# 没有找到结束标志,等待更多数据
break
# 查找对应帧开始标志
start_pos = self.recv_buffer.rfind(self.frame_start_marker, 0, end_pos + len(self.frame_end_marker))
if start_pos == -1:
# 没有找到对应的开始标志,可能是数据错误
# 移除前面的数据,保留可能的完整帧
self.recv_buffer = self.recv_buffer[end_pos + len(self.frame_end_marker):]
continue
# 提取完整帧(包含开始和结束标志)
complete_frame = self.recv_buffer[start_pos:end_pos + len(self.frame_end_marker)]
complete_frames.append(bytearray(complete_frame))
# 从缓冲区移除已处理的帧
self.recv_buffer = self.recv_buffer[end_pos + len(self.frame_end_marker):]
return complete_frames
方案2:针对固定长度字段,如果协议中有长度字段,可以使用基于长度的帧识别
python
def read_serial_data(self):
"""读取串口接收的数据(响应 readyRead 信号)"""
if not self.serial_manager.serial_port.isOpen():
return
# 读取所有可用数据(QByteArray -> bytes)
recv_data = self.serial_manager.serial_port.readAll()
if recv_data.isEmpty():
return
# 转换为 Python 字节类型并添加到缓冲区
new_data = bytes(recv_data.data())
self.recv_buffer.extend(new_data)
# 尝试解析完整帧
while self._has_complete_frame():
frame = self._get_next_frame()
if frame:
recv_hex_spaced = ' '.join(frame.hex()[i:i + 2].upper() for i in range(0, len(frame.hex()), 2))
self._append_recv_text(f"接收:{recv_hex_spaced}")
def _has_complete_frame(self):
"""检查缓冲区是否有完整帧"""
if len(self.recv_buffer) < 4: # 至少需要4字节(例如:起始符+长度)
return False
# 根据具体协议检查帧完整性
# 这里假设第3-4字节表示数据长度
start_marker = self.recv_buffer[:2]
if start_marker != b'\xFF\xAA': # 根据实际协议调整
return False
if len(self.recv_buffer) >= 4:
data_len = int.from_bytes(self.recv_buffer[2:4], byteorder='big')
expected_frame_size = 4 + data_len + 2 # 开始标志+长度+数据+校验/CRC
return len(self.recv_buffer) >= expected_frame_size
return False
def _get_next_frame(self):
"""从缓冲区获取下一帧并移除"""
if not self._has_complete_frame():
return None
# 解析帧长度
data_len = int.from_bytes(self.recv_buffer[2:4], byteorder='big')
expected_frame_size = 4 + data_len + 2 # 根据实际协议调整
if len(self.recv_buffer) >= expected_frame_size:
frame = self.recv_buffer[:expected_frame_size]
self.recv_buffer = self.recv_buffer[expected_frame_size:]
return bytearray(frame)
return None
方案3:针对不确定协议格式,使用超时机制
python
from PySide6.QtCore import QTimer
def __init__(self):
super().__init__()
# ... 其他初始化代码 ...
# 添加超时定时器用于处理分包
self.frame_timeout_timer = QTimer()
self.frame_timeout_timer.timeout.connect(self._process_partial_frame)
self.frame_timeout_timer.setSingleShot(True)
self.partial_frame_timeout = 100 # 100ms 超时
def read_serial_data(self):
"""读取串口接收的数据(响应 readyRead 信号)"""
if not self.serial_manager.serial_port.isOpen():
return
# 读取所有可用数据
recv_data = self.serial_manager.serial_port.readAll()
if recv_data.isEmpty():
return
new_data = bytes(recv_data.data())
# 如果定时器正在运行,说明正在接收一帧数据
if self.frame_timeout_timer.isActive():
# 继续添加数据到现有缓冲区
self.recv_buffer.extend(new_data)
# 重启定时器
self.frame_timeout_timer.start(self.partial_frame_timeout)
else:
# 新的接收开始
self.recv_buffer = bytearray(new_data)
# 启动定时器
self.frame_timeout_timer.start(self.partial_frame_timeout)
def _process_partial_frame(self):
"""处理接收到的部分帧数据"""
if len(self.recv_buffer) > 0:
recv_hex_spaced = ' '.join(self.recv_buffer.hex()[i:i + 2].upper()
for i in range(0, len(self.recv_buffer.hex()), 2))
self._append_recv_text(f"接收:{recv_hex_spaced}")
# 清空缓冲区
self.recv_buffer.clear()