1. 代码及过程
inline quint16 CalculateCRC(QByteArray buffer)
{
quint16 crc = 0xffff;
for(int i = 0; i < buffer.length(); i++){
crc = crc ^ (unsigned char)buffer.at(i);
for(int j = 0; j < 8; j++){
quint16 temp = crc & 0x01;
crc = crc >> 1;
if(temp == 1){
crc = crc ^ 0xA001;
}
}
}
return crc;
}
这段代码实现了一个 CRC-16/MODBUS 校验算法(多项式 0xA001),用于计算给定数据缓冲区(QByteArray)的循环冗余校验值(CRC)。以下是逐行逻辑解析:
1. 初始化 CRC 寄存器
quint16 crc = 0xffff;
- 初始化一个 16 位的 CRC 寄存器,初始值为
0xFFFF(这是 MODBUS CRC 的标准初始值)。
2. 遍历输入数据的每个字节
for(int i = 0; i < buffer.length(); i++) {
crc = crc ^ (unsigned char)buffer.at(i);
- 对缓冲区中的每个字节进行按位异或(
XOR)操作:(unsigned char)buffer.at(i)确保当前字节是无符号的(避免符号扩展问题)。crc ^ byte将当前字节与 CRC 寄存器的低 8 位进行异或。
3. 对每个字节的 8 位进行位处理
for(int j = 0; j < 8; j++) {
quint16 temp = crc & 0x01;
crc = crc >> 1;
if(temp == 1) {
crc = crc ^ 0xA001;
}
}
- 位处理逻辑:
- 提取最低位:
temp = crc & 0x01获取 CRC 寄存器的最低位(LSB)。 - 右移 CRC 寄存器:
crc = crc >> 1将 CRC 右移 1 位(相当于除以 2)。 - 多项式异或:如果最低位是
1,则与多项式0xA001(即0x8005的位反射形式)进行异或。- 这是 CRC-16/MODBUS 的核心算法步骤。
- 提取最低位:
4. 返回最终的 CRC 值
return crc;
- 返回计算完成的 16 位 CRC 值(低字节在前,高字节在后,符合 MODBUS 协议要求)。
示例验证
假设输入数据为 {0x01, 0x02}:
- 初始 CRC =
0xFFFF。 - 异或
0x01→ CRC =0xFFFF ^ 0x01 = 0xFFFE。 - 处理 8 位后,CRC 可能变为
0xXXXX(具体值取决于位运算过程)。 - 最终结果需与实际 MODBUS CRC 工具对比验证。
2. 为什么当最低位为 1 时要异或 0xA001?
这个操作是 CRC-16/MODBUS 算法的核心步骤,其背后的原理涉及 模 2 除法(多项式除法) 和 位反射(Bit Reflection)。下面逐步解释:
1. CRC 的本质:模 2 除法
CRC(循环冗余校验)的本质是 用数据除以一个固定的多项式,取余数作为校验值。
- 多项式:在 MODBUS 中,标准多项式是
0x8005(二进制1000 0000 0000 0101)。 - 模 2 除法:计算时不考虑进位,等价于按位异或(XOR)。
2. 为什么右移后要异或 0xA001?
(1) 标准 CRC-16 的运算方式
在标准 CRC-16(多项式 0x8005)中,计算过程是 高位先处理(MSB-first):
- 数据左移,检查最高位(MSB)是否为 1:
- 如果是 1,则 左移后异或
0x8005。 - 如果是 0,仅左移。
- 如果是 1,则 左移后异或
(2) MODBUS 使用低位先处理(LSB-first)
但 MODBUS 协议使用的是 低位先处理(LSB-first),即:
- 数据右移,检查最低位(LSB)是否为 1:
- 如果是 1,则 右移后异或
0xA001。 - 如果是 0,仅右移。
- 如果是 1,则 右移后异或
(3) 0xA001 是 0x8005 的位反射
0x8005的二进制:1000 0000 0000 0101(标准 CRC-16 多项式)。0xA001的二进制:1010 0000 0000 0001(MODBUS CRC 多项式)。
0xA001 实际上是 0x8005 的位反射(Bit-Reversed)形式:
- 把
0x8005的 16 位倒过来:0x8005: 1000 0000 0000 0101 反射后: 1010 0000 0000 0001 → 0xA001 - 由于 MODBUS 先处理 LSB(右移),而标准 CRC 先处理 MSB(左移),所以必须使用反射后的多项式
0xA001来保持计算一致性。
3. 为什么最低位为 1 时要异或?
在 右移(LSB-first) 的 CRC 计算中:
-
检查最低位(LSB):
- 如果最低位是
1,说明当前 CRC 值 不能被多项式整除,需要做调整(即异或0xA001)。 - 如果最低位是
0,说明当前 CRC 值 可以被多项式整除,直接右移即可。
- 如果最低位是
-
异或
0xA001的作用:- 相当于在模 2 除法中 减去(XOR)多项式,使得余数(CRC)符合校验规则。
3. 对比标准 CRC-16 和 MODBUS CRC-16
| 参数 | 标准 CRC-16 (CCITT) | MODBUS CRC-16 |
|---|---|---|
| 多项式 | 0x8005 | 0xA001(0x8005 的反射) |
| 初始值 | 0xFFFF | 0xFFFF |
| 输入反射 | 无(MSB-first) | 有(LSB-first) |
| 输出反射 | 无 | 无 |
| 结果异或 | 0x0000 | 0x0000 |
示例计算
假设数据 0x01 的 CRC 计算过程:
- 初始 CRC =
0xFFFF - 处理
0x01:CRC ^ 0x01 = 0xFFFE- 进入 8 次右移循环:
- 第 1 次:
0xFFFE的最低位是0→ 仅右移 →0x7FFF - 第 2 次:
0x7FFF的最低位是1→ 右移并异或0xA001→0xBFFF - …(继续 6 次)
- 第 1 次:
- 最终 CRC 可能是
0xXXXX(具体值取决于完整计算)。
4. 总结
0xA001是0x8005的位反射,因为 MODBUS 使用 LSB-first 计算。- 最低位为 1 时异或
0xA001,相当于在模 2 除法中调整余数。 - 该算法确保 MODBUS CRC 校验与标准 CRC-16 数学等效,只是计算方向不同。
1397

被折叠的 条评论
为什么被折叠?



