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

背景

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

数据包格式

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

  • 一般数据格式如下
帧头 功能码 数据长度 数据内容 检验位
1 byte 1 byte 2 byte n byte 1 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);  // 将取出的指令数据从缓存中移除
            }
        }
    }
}

你可能感兴趣的:(QT,·,随记,数据粘包,数据分包,串口通信,QSerialPort)