基于Python的Qt开发之Pyside6 串口接收数据被分割的解决方案

在串口通信中,数据被分割接收是一个常见问题,主要原因是:

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()
相关推荐
没有bug.的程序员2 小时前
Java 并发容器深度剖析:ConcurrentHashMap 源码解析与性能优化
java·开发语言·性能优化·并发·源码解析·并发容器
万行2 小时前
机器人系统ROS2
人工智能·python·机器学习·机器人·计算机组成原理
量子炒饭大师2 小时前
【C++入门】零域终端的虚空指针协议——【nullptr】还在为编译器给NULL匹配为int而头疼?nullptr给予你全新的字面量!
开发语言·c++·nullptr
edisao2 小时前
一。星舰到底改变了什么?
大数据·开发语言·人工智能·科技·php
阿豪只会阿巴2 小时前
【多喝热水系列】从零开始的ROS2之旅——Day10 话题的订阅与发布1:Python
开发语言·c++·python·ubuntu·ros2
羊小猪~~2 小时前
【QT】--文件操作
前端·数据库·c++·后端·qt·qt6.3
Frank Castle3 小时前
【C语言】详解C语言字节打包:运算符优先级、按位或与字节序那些坑
c语言·开发语言
kk哥88993 小时前
分享一些学习JavaSE的经验和技巧
java·开发语言
橙露3 小时前
时间序列分析实战:用 Python 实现股票价格预测与风险评估
人工智能·python·机器学习