第三章:手工计算——手算一个4位CRC校验码的完整过程
说实话,很多工程师学CRC,上来就啃数学公式,结果越看越懵。
我个人习惯是——先动手算一遍。哪怕是最简单的4位CRC,你亲手走一遍流程,比看十遍理论都管用。
这一章,我就带你手算一个完整的4位CRC校验码。你跟着我一步步来,保证能搞明白。
3.1 准备材料:我们需要什么?
先明确一下我们要用的参数:
- 原始数据:二进制
1101 0011(8位) - 生成多项式:
10011(5位,对应CRC-4) - CRC宽度:4位
- 初始值:
0000 - 结果异或值:
0000
这里要提醒一句:生成多项式的位数 = CRC宽度 + 1。4位CRC,多项式就是5位。这个关系千万别搞反了,我在项目里见过有人拿4位多项式去算4位CRC,结果怎么都对不上。
3.2 第一步:数据补零
为什么要补零?
因为CRC校验码要放在数据末尾,相当于给数据腾出4个空位。补零的个数 = CRC宽度。
原始数据:1101 0011
补4个零后:1101 0011 0000
嗯,这一步很简单,但容易忘。我刚开始学的时候,就老忘记补零,算出来的结果自然不对。
3.3 第二步:模2除法(核心步骤)
模2除法说白了就是不带进位的二进制除法。你只需要记住一个规则:异或运算。
我们拿补零后的数据 110100110000 除以多项式 10011。
来,我写给你看:
1101011010 ← 商(我们不关心)
---------
10011 ) 110100110000
10011 ← 第一次异或
-----
10010
10011 ← 第二次异或
-----
00011 ← 注意:这里不够5位,直接拉下一位
00000 ← 不够除,商0
-----
00111
00000 ← 不够除,商0
-----
01110
00000 ← 不够除,商0
-----
11100
10011 ← 第三次异或
-----
11110
10011 ← 第四次异或
-----
11010
10011
-----
10010
10011
-----
00010
00000
-----
00100
00000
-----
01000
00000
-----
10000
10011
-----
0011 ← 余数,这就是CRC校验码!
最终余数:0011
这就是我们手工算出来的4位CRC校验码。
3.4 第三步:结果处理
我们算出来的余数是 0011,但有些协议会要求对结果进行异或处理。本例中结果异或值为 0000,所以最终校验码就是 0011。
发送方发送的数据就是:原始数据 + CRC校验码 = 1101 0011 0011
3.5 验证:接收方怎么做?
接收方拿到 110100110011,用同样的多项式 10011 再做一次模2除法。
如果余数为0,说明数据没问题。如果不为0,说明数据被篡改了。
我曾经在一个Modbus项目中遇到过这种情况:明明CRC算对了,但接收方总报错。后来发现是字节序搞反了——高位字节和低位字节的顺序不对。这种坑,你早晚也会遇到。
3.6 手算过程中的常见坑
避坑指南(我踩过的雷)
- 补零个数搞错:4位CRC补4个零,不是5个。多项式位数是5,但补零看的是CRC宽度。
- 异或时对齐位置:每次异或,多项式最高位必须对齐当前被除数的最高位。我刚开始手算时经常对歪,结果全错。
- 余数位数:最终余数不足4位时,前面补0。比如余数是
11,要写成0011。 - 初始值不为0的情况:有些协议初始值是
1111,那就要先把数据跟初始值异或,再做除法。这个我们后面章节会讲。
3.7 手算的意义
你可能会问:现在都有现成的库函数,为什么还要手算?
我的回答是:手算能帮你理解CRC的本质。当你遇到CRC校验不通过、需要调试协议栈时,手算能力就是你的救命稻草。
我记得有一次调试一个老旧的PLC设备,它的CRC实现跟标准不一样。我硬是拿笔在纸上算了一遍,才找到问题出在多项式顺序上。如果我不会手算,那天可能就卡死了。
3.8 小结
这一章我们手算了一个4位CRC校验码。核心就三步:
- 数据补零(补CRC宽度个零)
- 模2除法(用异或运算)
- 取余数作为校验码
下一章,我们会把这个手算过程用Python代码实现。到时候你会发现,代码其实就是把手工步骤翻译成计算机语言。
小提示:如果你手算时总出错,可以先用Python的 bin() 函数打印每一步的中间结果,对照着检查。我就是这么干的。
好了,这一章就到这里。拿起笔,找个数据自己算一遍,比看十遍都管用。