CRC校验码生成过程详解:多项式除法、移位寄存器实现
讲CRC,说到底就是讲怎么把一堆数据比特,跟一个约定的“多项式”做除法,然后拿余数当校验码。
我刚开始接触这个的时候,也觉得很绕。多项式除法?二进制除法?移位寄存器又是干嘛的?
后来在项目中调一个串口通信,数据老出错,逼着我彻底搞懂了它。今天我就把这个过程掰开了跟你讲,保证听完你也能手算出来。
1. 多项式表达:CRC的数学底座
我们平时说的CRC-16、CRC-32,其实背后都有一个多项式。这个多项式用二进制表示,就是除法的除数。
举个例子:CRC-8的多项式是 x⁸ + x² + x + 1。
怎么转成二进制?
- 看指数:最高是8次方,代表二进制长度9位(指数+1)。
- 有哪几项,哪几位就置1。
x⁸→ bit8 = 1;x²→ bit2 = 1;x¹→ bit1 = 1;x⁰→ bit0 = 1。- 所以多项式二进制是:1 0000 0111(十六进制0x107)。
这里有个坑,我提醒你一下:最高位的1在计算时会被隐去。为什么?因为移位寄存器里,我们只看低n位(n是多项式最高次数)。这个后面会讲。
2. 多项式除法:手算CRC
说白了,就是二进制下的模2除法。跟普通除法区别就一点:减法用异或(XOR),不进位也不借位。
我拿一个简单例子演示:
- 数据:
0b1101(4比特) - 多项式:
0b1011(4位,对应x³+x+1) - CRC-3,所以寄存器初始全0,数据后面补3个0 →
1101 000
手算步骤:
- 被除数 =
1101000,除数 =1011。 - 看最高位,被除数第1位是1,商1。做XOR:
1101 XOR 1011 = 0110。 - 结果高位为0,左移一位,补下一个比特0,变成
1100。 - 最高位又是1,商1,XOR:
1100 XOR 1011 = 0111。 - 左移一位,补下一位0,变成
1110。 - 最高位1,XOR:
1110 XOR 1011 = 0101。 - 左移,补最后一位0,变成
1010。 - 最高位1,XOR:
1010 XOR 1011 = 0001。 - 已经移完所有数据位+补位,最终余数
001就是CRC校验码。
你想想看,这个过程中我们一直在做异或和移位,完全可以用硬件实现。
3. 移位寄存器实现:从数学到硬件
多项式除法的手算,说白了就是一遍遍“移位 → 看最高位 → 决定要不要异或”。这个逻辑用移位寄存器加几个异或门就能实现。
结构长这样:
- 一个n位的移位寄存器(n = 多项式次数),初始全0。
- 每个寄存器位后面,根据多项式对应位置,拉出一根线,经过异或门再反馈。
- 数据比特从高位或低位串行移入,每移入一位,寄存器做一次移位+条件异或。
我画了张图,帮你理解这个结构:
看到没?每个寄存器D0、D1、D2之间串接,D2的输出(也就是最高位)决定是否启动XOR反馈。反馈的抽头位置,就是多项式为1的位置。
4. 软件实现:用代码模拟移位寄存器
搞嵌入式的时候,不一定都用硬件CRC单元。有时候为了省成本,或者处理器太老,我们就用软件模拟。
核心逻辑就三条:
- 把寄存器初始化为0(或全1,取决于CRC算法变种)。
- 每个数据比特,从高位到低位(或反之)移入寄存器。
- 每次移位后,如果移出的比特是1,就跟多项式做异或。
我贴一段C语言代码,模拟CRC-8(多项式0x07,不含高位):
uint8_t crc8_software(uint8_t *data, uint8_t len) {
uint8_t crc = 0x00; // 寄存器初始值
uint8_t poly = 0x07; // 多项式 x⁸ + x² + x + 1 的低8位
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i]; // 逐字节异或
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80) { // 检查最高位
crc = (crc << 1) ^ poly;
} else {
crc = crc << 1;
}
}
}
return crc;
}
代码很简单,就两个循环。外层按字节处理,内层按比特处理。如果你处理的数据量不大,效率也能接受。
5. 常见的CRC算法变种
同样是CRC-16,不同的厂商标准,结果可能不一样。原因就在于那几张“表”的差异。
| 参数 | CRC-16/CCITT | CRC-16/MODBUS | CRC-32 |
|---|---|---|---|
| 多项式(含高位) | 0x11021 | 0x18005 | 0x104C11DB7 |
| 初始值 | 0xFFFF | 0xFFFF | 0xFFFFFFFF |
| 结果异或值 | 0x0000 | 0x0000 | 0xFFFFFFFF |
| 输入反转 | 否 | 是(LSB First) | 是 |
| 输出反转 | 否 | 是 | 是 |
这些参数看起来很繁琐,但理解了移位寄存器原理,就知道它们其实都是在调整“初始状态”和“最终处理”。
举个例子:初始值全1,相当于在数据前面先补了一串1,避免前导0让CRC全0。结果再异或一次,是让全0数据也能产生非零的CRC,用来检测全0帧。
6. 避坑指南
避坑2:移位方向。有些标准是LSB-first,数据从低位移入。这时你的移位操作要反过来,检查最低位而不是最高位。
好了,今天把CRC的生成原理、手算过程、硬件结构、软件实现都过了一遍。核心就一句话:CRC就是用数据除以一个预设的多项式,把余数当校验码。无论是用移位寄存器还是用代码,本质都一样。
能把这个过程想清楚,后面理解CRC检错能力和误码排查,就轻松多了。