第4章 CRC计算的手工推导:用手算方式理解CRC-8
很多工程师学CRC,上来就写代码,结果多项式、初始值、异或顺序…一通乱调,最后跑不通也不知道问题在哪。我当年也是这样,踩了不少坑。
后来我发现,用手算一遍比看十遍文档都管用。为什么?因为手算能让你看清每一位是怎么被“搅”进去的。这节课,我们就拿CRC-8来动手推一遍。
4.1 准备材料
先定好我们今天用的参数,后面全按这个来算:
| 参数 | 值 | 说明 |
|---|---|---|
| 多项式 | 0x07 | x⁸ + x² + x + 1,去掉最高位x⁸ |
| 初始值 | 0x00 | 寄存器全0 |
| 输入数据 | 0x4D | 二进制 0100 1101 |
| 输出异或值 | 0x00 | 不翻转 |
| 数据反序 | 否 | 高位在先 |
注意:CRC-8的多项式是8位,但最高位x⁸在计算时只用于判断是否对齐,不写在寄存器里。所以存的是0x07,而不是0x107。
我刚开始手算时老纠结“多项式为啥要丢掉最高位”,后来想明白了——8位寄存器只能存8位,那第9位在哪儿?它是个隐形的“哨兵”,只在异或时用。
4.2 手算步骤:一步步来
我们按CRC最经典的“模2除法”(其实就是不带进位的异或)来算。数据从左到右喂入,每移一位,看看最高位是不是1,是就异或多项式。
- 初始化寄存器:初始值0x00,寄存器8位全0。
- 数据逐位移入:把0x4D的8个bit从高位到低位依次移入寄存器的最低位(LSB),同时寄存器左移一位,最高位移出。
- 判断并异或:如果移出的那一位是1(即寄存器原来的最高位),就用多项式0x07异或整个寄存器。
- 重复8次:所有数据位处理完后,寄存器里的值就是CRC-8。
4.3 用手写一写
咱们拿笔在纸上跟着推一遍(在这里我用表格模拟)。
初始寄存器:0000 0000(0x00)
数据是0x4D,二进制:0100 1101,从左到右分别是bit7=0, bit6=1, bit5=0, bit4=0, bit3=1, bit2=1, bit1=0, bit0=1。
| 步骤 | 新进来的bit | 左移前的寄存器 | 左移后(低位补新bit) | 移出的MSB | 是否异或? | 异或后寄存器 |
|---|---|---|---|---|---|---|
| 1 | 0 | 0000 0000 | 0000 0000 | 0 | 否 | 0000 0000 |
| 2 | 1 | 0000 0000 | 0000 0001 | 0 | 否 | 0000 0001 |
| 3 | 0 | 0000 0001 | 0000 0010 | 0 | 否 | 0000 0010 |
| 4 | 0 | 0000 0010 | 0000 0100 | 0 | 否 | 0000 0100 |
| 5 | 1 | 0000 0100 | 0000 1001 | 0 | 否 | 0000 1001 |
| 6 | 1 | 0000 1001 | 0001 0011 | 0 | 否 | 0001 0011 |
| 7 | 0 | 0001 0011 | 0010 0110 | 0 | 否 | 0010 0110 |
| 8 | 1 | 0010 0110 | 0100 1101 | 0 | 否 | 0100 1101 |
等等……所有步骤里移出的MSB都是0?这意味着我们一次异或都没做。最终的寄存器是0100 1101,也就是数据本身。这不对啊,CRC通常不会是原数据。问题出在哪儿?
4.4 正确做法:先补零
我们重来。数据0x4D,后面补8个0,变成:0100 1101 0000 0000。总共16位。我们只移入前面8位数据bit,然后继续移入8个0。移入0的过程中,数据高位逐渐移出,最终寄存器剩下的就是余数,也就是CRC。
继续上面的表格,从第9步开始:
| 步骤 | 新进来的bit | 左移前的寄存器 | 左移后 | 移出的MSB | 是否异或? | 异或后寄存器 |
|---|---|---|---|---|---|---|
| 9 | 0 | 0100 1101 | 1001 1010 | 0 | 否 | 1001 1010 |
| 10 | 0 | 1001 1010 | 0011 0100 | 1 | 是 | 0011 0100 ⨁ 0000 0111 = 0011 0011 |
| 11 | 0 | 0011 0011 | 0110 0110 | 0 | 否 | 0110 0110 |
| 12 | 0 | 0110 0110 | 1100 1100 | 0 | 否 | 1100 1100 |
| 13 | 0 | 1100 1100 | 1001 1000 | 1 | 是 | 1001 1000 ⨁ 0000 0111 = 1001 1111 |
| 14 | 0 | 1001 1111 | 0011 1110 | 1 | 是 | 0011 1110 ⨁ 0000 0111 = 0011 1001 |
| 15 | 0 | 0011 1001 | 0111 0010 | 0 | 否 | 0111 0010 |
| 16 | 0 | 0111 0010 | 1110 0100 | 0 | 否 | 1110 0100 |
最终寄存器是1110 0100,即0xE4。这就是数据0x4D加上多项式0x07算出来的CRC-8校验值。
4.5 为什么补零?一张图看懂整个流程
把手算的完整流程总结成下图,核心就三步:移位、判断、异或。
看到虚线箭头了吗?处理完所有位之后,如果还有位(比如补的零),就循环回去继续移。直到所有补的零都移入完毕,最终寄存器就是CRC。
4.6 验证:换个数据
我们来验证一下这个手算方法。如果数据是0x00,补8个0后全部是0,移来移去寄存器一直是0,所以CRC=0x00。这合理,0的CRC就是0。
如果数据是0x01(0000 0001),补零后:0000 0001 0000 0000。手算一下,注意第一次移出MSB=0,一直异或到后面吗?我快速算了一下,结果是0x07。你可以自己试试。
4.7 常见手算错误汇总
- 忘记补零:这是初学者的头号坑。CRC-8必须补8个0,否则余数不对。
- 异或方向反了:用的是异或,不是加法。0⨁1=1,1⨁1=0,进位不放。
- 多项式写错:0x07对应x⁸+x²+x+1,但有人写0x17或0x87,那是CRC-8的其他变种。
- 移位方向反了:我们这里是MSB先移入(高位在先),有些协议用LSB先移入。手算前务必确认。
- 只移了数据位没移补零位:补零不是一次补完,而是逐位移入。
好了,讲到这儿,CRC-8的手算过程你已经完全掌握了。从数据补零、逐位移位、判断异或,到最终拿到校验码——其实核心就是一个“除法”游戏。拿笔算一算,比任何教程都管用。