QT中使用QSerialPort数据接收分包和粘包的一种解决方法

背景

有时候在与下位机通信时会选择串口,所以就需要使用到QT中的QSerialPort类。在接收下位机返回的指令时,经常会出现数据包分包和粘包的现象。

数据包格式

一般在与下位机通信时的协议都是具有一定的格式,也就是利用这个格式来解决数据分包和粘包的问题。

  • 一般数据格式如下
帧头功能码数据长度数据内容检验位
1 byte1 byte2 byten byte1 byte
  • 解释
    (1)帧头:定位到一帧数据的起始位置。可以定特殊一点的,不会经常出现的,如:0xfa、0xfb
    (2)功能:这条协议表示什么功能。如:0x01表示控制灯光、0x02表示查询状态
    (3)数据长度:数据内容的大小。
    (4)数据内容:真正使用到的数据。如:0x01表示开灯成功,0x02表示关灯成功,这样UI就可以根据这个进行变化。
    (5)检验位:检验这一帧数据是否正确。如:异或校验、和校验。
    注意:可能有的协议还会有索引位、帧尾之类的
解决方法
  • 基本流程

(1)将每次接收到的数据保存到一个buffer中
(2)定位到帧头索引,若索引不为0,则说明前面这一部分的数据无用,可以舍弃
(3)获取数据长度字节,得到数据内容的字节大小
(4)提取到一条完整的指令,如上面的格式则指令长度为 n + 5
(5)检查校验位是否正确
(6)从buffer中移除该指令

  • 代码实现
char getXorCheck(const QByteArray &data)
{
    char crc = 0x00;
    for (const auto &ch : data) {
        crc ^= ch;
    }

    return crc;
}

void slot_receiveSerialPortData()
{
    QByteArray data = m_serialPort->readAll();
    // 串口通信数据会分包或粘包,所以需要将收到的数据缓存起来,再从中解析出相应的指令
    m_buffer.append(data);

    // 先找到帧头的位置
    int headerIndex = m_buffer.indexOf(char(0xfa));
    if (-1 == headerIndex) {
        m_buffer.clear();  // 找不到帧头,则将所有缓存数据清空
    } else {
        if (headerIndex > 0) {
            // 如果帧头不在第一个,则说明前面的数据是没用的,直接舍弃
            m_buffer.remove(0, headerIndex);  
        }

        // 数据长度位的索引为2,3  所以size需要>=4
        if (m_buffer.size() >= 4) {  // 有包含数据长度位
            // 取出完整的一条指令
            int dataLen = QString(m_buffer.mid(2, 2).toHex()).toInt(nullptr, 16);  // 数据内容长度
            int cmdLen = dataLen + 5;  // 完整指令数据长度
            if (m_buffer.size() >= cmdLen) {
                QByteArray cmd = m_buffer.left(cmdLen);  // 指令数据

            	// 异或校验不包含帧头
                // 检查校验位
                char pbCrc = getXorCheck(cmd.mid(1, dataLen + 3));
                char pcCrc = cmd.at(cmdLen - 1);
                if (pbCrc == pcCrc) {
                    // 解析指令
                	// 按功能单元区分
                	char funcNum = cmd.at(1);
                	if (char(0x01) == funcNum) {
                    
                	} 
                } else {
                    // 失败 错误处理
                }
            
                m_buffer.remove(0, cmdLen);  // 将取出的指令数据从缓存中移除
            }
        }
    }
}
  • 2
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本方法。编译原理不仅是计算机科学理论的重要组成部分,也是实现高效、可靠的计算机程序设计的关键。本文将对编译原理的基本概念、发展历程、主要内容和实际应用进行详细介绍编译原理是计算机专业的一门核心课程,旨在介绍编译程序构造的一般原理和基本

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值