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)。

步骤:

  1. 数据:00001010
  2. 补8个0:00001010 00000000
  3. 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计算。