第4章 CRC计算的手工推导:用手算方式理解CRC-8

很多工程师学CRC,上来就写代码,结果多项式、初始值、异或顺序…一通乱调,最后跑不通也不知道问题在哪。我当年也是这样,踩了不少坑。

后来我发现,用手算一遍比看十遍文档都管用。为什么?因为手算能让你看清每一位是怎么被“搅”进去的。这节课,我们就拿CRC-8来动手推一遍。

4.1 准备材料

先定好我们今天用的参数,后面全按这个来算:

参数值说明
多项式0x07x⁸ + x² + x + 1,去掉最高位x⁸
初始值0x00寄存器全0
输入数据0x4D二进制 0100 1101
输出异或值0x00不翻转
数据反序否高位在先

注意:CRC-8的多项式是8位,但最高位x⁸在计算时只用于判断是否对齐,不写在寄存器里。所以存的是0x07,而不是0x107。

我刚开始手算时老纠结“多项式为啥要丢掉最高位”,后来想明白了——8位寄存器只能存8位,那第9位在哪儿?它是个隐形的“哨兵”,只在异或时用。

4.2 手算步骤:一步步来

我们按CRC最经典的“模2除法”(其实就是不带进位的异或)来算。数据从左到右喂入,每移一位,看看最高位是不是1,是就异或多项式。

  1. 初始化寄存器:初始值0x00,寄存器8位全0。
  2. 数据逐位移入:把0x4D的8个bit从高位到低位依次移入寄存器的最低位(LSB),同时寄存器左移一位,最高位移出。
  3. 判断并异或:如果移出的那一位是1(即寄存器原来的最高位),就用多项式0x07异或整个寄存器。
  4. 重复8次:所有数据位处理完后,寄存器里的值就是CRC-8。
⚠️ 关键点:CRC的异或时机不是“移入一位后就异或”,而是先左移,再根据移出的最高位决定是否异或。顺序颠倒了,结果全错。

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是否异或?异或后寄存器
100000 00000000 00000否0000 0000
210000 00000000 00010否0000 0001
300000 00010000 00100否0000 0010
400000 00100000 01000否0000 0100
510000 01000000 10010否0000 1001
610000 10010001 00110否0001 0011
700001 00110010 01100否0010 0110
810010 01100100 11010否0100 1101

等等……所有步骤里移出的MSB都是0?这意味着我们一次异或都没做。最终的寄存器是0100 1101,也就是数据本身。这不对啊,CRC通常不会是原数据。问题出在哪儿?

💡 经验教训:我第一回手算就是这个结果,百思不得其解。后来发现——CRC不是只处理数据本身,还要在数据后面补8个0再算!因为CRC-8产生8位校验码,相当于把数据左移8位再做除法。忘了补零,等于白算。

4.4 正确做法:先补零

我们重来。数据0x4D,后面补8个0,变成:0100 1101 0000 0000。总共16位。我们只移入前面8位数据bit,然后继续移入8个0。移入0的过程中,数据高位逐渐移出,最终寄存器剩下的就是余数,也就是CRC。

继续上面的表格,从第9步开始:

步骤新进来的bit左移前的寄存器左移后移出的MSB是否异或?异或后寄存器
900100 11011001 10100否1001 1010
1001001 10100011 01001是0011 0100 ⨁ 0000 0111 = 0011 0011
1100011 00110110 01100否0110 0110
1200110 01101100 11000否1100 1100
1301100 11001001 10001是1001 1000 ⨁ 0000 0111 = 1001 1111
1401001 11110011 11101是0011 1110 ⨁ 0000 0111 = 0011 1001
1500011 10010111 00100否0111 0010
1600111 00101110 01000否1110 0100

最终寄存器是1110 0100,即0xE4。这就是数据0x4D加上多项式0x07算出来的CRC-8校验值。

🚨 注意:手算时每一步的异或都是按位异或,不是数学加法。1100 ⨁ 0111 = 1011,进位丢弃。这个一定要记清楚,我见过有人用加法算CRC,结果永远不对。

4.5 为什么补零?一张图看懂整个流程

把手算的完整流程总结成下图,核心就三步:移位、判断、异或。

CRC-8 计算流程(手算视角) 1. 数据补8个0 2. 初始化寄存器=0x00 3. 取1位数据,左移寄存器 移出的MSB是否为1? 4a. 是与寄存器异或多项式 4b. 否,不做操作 所有位处理完? 补零后的总位数 = 数据位数 + 8

看到虚线箭头了吗?处理完所有位之后,如果还有位(比如补的零),就循环回去继续移。直到所有补的零都移入完毕,最终寄存器就是CRC。

4.6 验证:换个数据

我们来验证一下这个手算方法。如果数据是0x00,补8个0后全部是0,移来移去寄存器一直是0,所以CRC=0x00。这合理,0的CRC就是0。

如果数据是0x01(0000 0001),补零后:0000 0001 0000 0000。手算一下,注意第一次移出MSB=0,一直异或到后面吗?我快速算了一下,结果是0x07。你可以自己试试。

💡 实践技巧:手算熟了之后,你可以用Excel或简单的Python脚本来验证。我当年做I2C从机固件时,就是先手算一次,再用代码跑一遍,两边对上才敢下板子。有一次手算和代码差了1个bit,查了半天发现是多了一位补零——嗯,从那以后我养成了“先画表格再写代码”的习惯。

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的手算过程你已经完全掌握了。从数据补零、逐位移位、判断异或,到最终拿到校验码——其实核心就是一个“除法”游戏。拿笔算一算,比任何教程都管用。