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 数学等效,只是计算方向不同。