3、CRC计算原理:模2除法、多项式除法、余数计算过程、手工计算示例
好,咱们进入正题。CRC校验,说白了就是给数据算一个“指纹”。这个指纹怎么来的?核心就是除法。但不是咱们小学学的除法,是模2除法。
我刚开始接触CRC时,也被这个“模2”搞得有点晕。后来发现,你把它想成“二进制下的不带进位的加法”,就简单多了。说白了,就是异或运算。
3.1 模2除法:没有借位的减法
咱们先看一个最简单的例子。二进制里,1+1=0,要进位对吧?模2运算里,1+1=0,而且不进位。减法也一样,1-1=0,不借位。
这其实就是异或(XOR)运算。你记住一个口诀:相同为0,不同为1。
举个例子:
1010
+ 0101
------
1111 (模2加法,没有进位)
再看减法:
1010
- 0101
------
1111 (模2减法,没有借位)
发现没?加法和减法结果一样。这就是模2运算的特点。我在项目中调试一个CAN总线通信时,就遇到过因为把模2加法和普通加法搞混,导致CRC算出来一直不对。嗯,那次排查了整整一个下午。
3.2 多项式除法:把数据看成多项式
CRC里,我们把要发送的数据,看成是一个多项式的系数。比如数据 1011,就对应多项式:
1 * x³ + 0 * x² + 1 * x¹ + 1 * x⁰
也就是 x³ + x + 1。
为什么这么搞?因为多项式除法在数学上有很好的性质,能保证检错能力。说白了,就是让除法运算变得“可预测”。
我习惯把多项式除法想象成“用一把尺子去量数据”。这把尺子就是生成多项式(Generator Polynomial),比如常见的CRC-16的生成多项式是 x¹⁶ + x¹² + x⁵ + 1。
3.3 余数计算过程:一步一步来
好,现在咱们动手算一个。假设数据是 1101,生成多项式是 1011(对应 x³ + x + 1)。
第一步:数据后面补0
补多少个0?看生成多项式的最高次幂。这里是3次,所以补3个0。数据变成 1101 000。
第二步:开始做模2除法
用生成多项式 1011 去“除” 1101000。注意,这里的“除”就是模2除法。
1011 (商,我们不关心)
_______
1011 | 1101000
1011
----
0110 (余数,继续)
0000 (对齐,不够除,商0)
----
1100
1011
----
1110
1011
----
101 (最终余数,3位)
最终余数是 101。这个余数,就是CRC校验码。
你想想看,这个过程是不是很像咱们小学学的长除法?只不过每一步的减法变成了异或。
关键点:余数的位数,一定比生成多项式少一位。这里生成多项式是4位(1011),余数就是3位(101)。
3.4 手工计算示例:完整走一遍
咱们再算一个实际点的例子。假设数据是 0x0A(二进制 00001010),生成多项式用CRC-8标准多项式 0x07(二进制 00000111,对应 x⁸ + x² + x + 1)。
步骤:
- 数据:
00001010 - 补8个0:
00001010 00000000 - 用
00000111做模2除法
咱们一步步算:
10100010 (商,不关心)
___________
111 | 0000101000000000
00000111
--------
00011000
00000111
--------
01111000
00000111
--------
01111000
00000111
--------
01111000
00000111
--------
00001110
00000111
--------
0001000 (最终余数,8位)
最终余数是 00001000,也就是 0x08。所以CRC-8校验码就是 0x08。
我的小技巧:手工算CRC时,我习惯在纸上画竖线对齐。每做一次异或,就把结果写在下面,然后从被除数里拉下一位。这样不容易乱。我曾经在给一个Bootloader写CRC校验时,就是靠手算验证了第一版代码的正确性。
3.5 避坑指南
我曾经踩过的坑:
- 补0位数搞错:生成多项式是几阶,就补几个0。比如CRC-16是16阶,补16个0。我见过有人补成15个,结果算出来全错。
- 初始值问题:很多CRC算法不是从0开始的,而是有一个初始值(比如0xFFFF)。这个初始值会先和数据进行异或,然后再做除法。我刚开始做时忽略了这一点,和标准CRC库对不上。
- 输出异或:算完余数后,有些算法还会对余数再做一次异或(比如异或0xFFFF)。这个叫“输出异或”,也是常见的坑。
嗯,手工计算虽然繁琐,但能帮你深刻理解CRC的本质。等你理解了,再用代码实现,就水到渠成了。下一节咱们就聊聊怎么用C语言实现CRC计算。