QByteArray处理异常致类HDLC协议CRC校验失败问题排查
类HDLC协议解码CRC校验失败问题分析与解决
问题背景
基于开源项目Arduhdlc实现类HDLC协议解码,使用Qt的QByteArray处理串口接收数据,流程为:移除帧边界字节→处理转义序列→CRC校验。编写test1至test5共5个测试用例后,发现test2、test3、test4的CRC序列与预期不符(如test2中本该传入\xf6\x41,实际传入\xff\xf6),导致CRC校验失败。
相关代码
#include <QCoreApplication> #include <QDebug> #include "hdlc_qt.h" #define FRAME_BOUNDARY_OCTET 0x7E #define CONTROL_ESCAPE_OCTET 0x7D #define INVERT_OCTET 0x20 QByteArray receive_frame_buffer; quint16 frame_position; quint16 frame_checksum; bool escape_character; bool hdlcFrameCRC_check(int frame_index); void charReceiver(QByteArray dataArray) { quint8 data = 0; qDebug() << "Received:\t"<<dataArray.toHex(); for(QByteArray::iterator it = dataArray.begin(); it != dataArray.end(); it++) { data = (*it); /* Start flag or end flag */ if(data == FRAME_BOUNDARY_OCTET) { qDebug("FRAME_BOUNDARY_OCTET: %x",data); qDebug() << "frame_position:"<<frame_position; if(escape_character == true) { escape_character = false; } /* Do CRC check if frame is valid */ else if( (frame_position >= 2) && (hdlcFrameCRC_check(frame_position)) ) { /* Call user defined function to handle HDLC frame */ qDebug() << "ValidFrame"<<receive_frame_buffer.toHex(); } qDebug() << "frame_buffer:\t"<<receive_frame_buffer.toHex(); /* Reset all for next frame */ frame_position = 0; frame_checksum = 0; receive_frame_buffer.clear(); continue; } if(escape_character) { escape_character = false; data ^= INVERT_OCTET; } else if(data == CONTROL_ESCAPE_OCTET) { escape_character = true; continue; } receive_frame_buffer.append(data); frame_position++; /* If we don't ever receive valid frame, * buffer will keep growing bigger and bigger. * Hard coded max size limit and then reset */ if(frame_position >= 2048) { receive_frame_buffer.clear(); frame_position = 0; frame_checksum = 0; } } } bool hdlcFrameCRC_check(int frame_index) { /* frame = ...[CRC-LO] [CRC-HI] */ quint16 crc_received = 0; crc_received = receive_frame_buffer[frame_index-1]; // msb crc_received = crc_received << 8; crc_received |= receive_frame_buffer[frame_index-2]; // lsb quint16 crc_calculated = qChecksum((const char*)receive_frame_buffer.constData(), frame_index-2); //crc_calculated = crc_calculated^0xFFFF; qDebug("CRC received:%x",crc_received); qDebug("CRC calculated:%x",crc_calculated); if(crc_received == crc_calculated) { return true; } else { return false; } } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QByteArray test1 = QByteArray("\x7E\x08\x1A\x00\x00\x07\x2B\x08\x00\x08\x16\x00\x00\x08\x8E\x07\xF7\x22\x20\x7E",20); QByteArray test2 = QByteArray("\x7e\x08\x22\x00\x00\x07\x28\x08\x08\x08\x10\x00\x05\x08\x95\x07\xff\xf6\x41\x7e",20); QByteArray test3 = QByteArray("\x7e\x08\x22\x00\x00\x07\x21\x08\x09\x08\x10\x00\x07\x08\x8f\x08\x00\xe7\x89\x7e",20); QByteArray test4 = QByteArray("\x7e\x08\x0e\x00\x00\x07\x2f\x08\x07\x08\x1b\x00\x00\x08\x93\x07\xf8\x9a\xb1\x7e",20); QByteArray test5 = QByteArray("\x7e\x08\x27\x00\x00\x07\x27\x08\x0a\x08\x10\x00\x05\x08\x93\x07\xfd\x18\x53\x7e",20); QVector<QByteArray> test; test.push_back(test1); test.push_back(test2); test.push_back(test3); test.push_back(test4); test.push_back(test5); for(int i = 0; i < test.size();++i) { qDebug()<<"-------------------------------------------------------"; qDebug()<<"Test #" << i+1<< ":\t "<< test[i].toHex(); charReceiver(test[i]); } return a.exec(); }
问题原因
- CRC算法不匹配:Qt的
qChecksum采用IBM CRC-16(初始值0x0000,多项式0x8005),而Arduhdlc使用的是CRC-16/IBM反转算法(初始值0xFFFF,多项式0xA001),两者计算逻辑差异导致校验结果不匹配。你注释掉了crc_calculated = crc_calculated^0xFFFF;,这一步是将qChecksum结果转换为Arduhdlc兼容格式的关键。 - CRC字节顺序可能错误:代码注释标注帧格式为
...[CRC-LO] [CRC-HI],但当前逻辑将最后一个字节作为MSB、倒数第二个作为LSB,若Arduhdlc实际发送的是CRC-HI在前、CRC-LO在后,会导致顺序颠倒,校验失败。
解决办法
1. 修正CRC计算逻辑
方案一:恢复异或操作
取消crc_calculated = crc_calculated^0xFFFF;的注释,或替换为更直观的取反操作:
quint16 crc_calculated = qChecksum((const char*)receive_frame_buffer.constData(), frame_index-2); crc_calculated = ~crc_calculated; // 等价于^0xFFFF,匹配Arduhdlc的CRC输出格式
方案二:实现Arduhdlc原生CRC函数
直接使用与Arduhdlc一致的CRC计算逻辑,避免依赖Qt的qChecksum:
quint16 crc16(const uint8_t *data, uint16_t length) { quint16 crc = 0xFFFF; for (uint16_t i = 0; i < length; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x0001) { crc = (crc >> 1) ^ 0xA001; } else { crc >>= 1; } } } return crc; }
然后在hdlcFrameCRC_check中替换为:
quint16 crc_calculated = crc16((const uint8_t*)receive_frame_buffer.constData(), frame_index-2);
2. 修正CRC字节顺序
根据Arduhdlc的实际帧格式调整字节顺序:
- 若Arduhdlc发送的是CRC低字节在前、高字节在后,当前逻辑无需修改;
- 若发送的是高字节在前、低字节在后,调整CRC接收逻辑:
crc_received = receive_frame_buffer[frame_index-2]; // MSB crc_received = crc_received << 8; crc_received |= receive_frame_buffer[frame_index-1]; // LSB
3. 验证测试用例的转义正确性
确保测试用例中的HDLC帧符合转义规则:帧边界0x7E和转义符0x7D必须被转义为0x7D+原字节^0x20(如0x7E转义为0x7D 0x5E),避免未转义的特殊字节被误判为帧边界或转义符,导致数据错误。
内容的提问来源于stack exchange,提问作者Marco81
相关产品推荐
相关产品推荐

