第2章 CRC基本原理:多项式除法、模2运算,CRC校验的数学本质

很多工程师刚接触CRC时,感觉像在看天书。一堆多项式、模2运算,跟平时算加减法完全不一样。我刚开始做I2C通信时也是这感觉——明明只是校验几个字节,干嘛搞得这么复杂?后来在一条量产产线上被坑过一次,才老老实实把底层的数学啃下来。

这一章,咱们就把CRC的数学本质拆开揉碎。你不需要成为数学家,但搞懂这几个核心概念,以后做校验设计就不会再“凭感觉选多项式”了。

2.1 模2运算:没有进位的加法

先看最基础的操作。模2运算说白了就是二进制下的异或(XOR)。加法不进位,减法不借位。

  • 0 + 0 = 0
  • 0 + 1 = 1
  • 1 + 0 = 1
  • 1 + 1 = 0(没有进位!)

减法跟加法完全一样:1 - 1 = 0,1 - 0 = 1,0 - 1 = 1(不借位)。你发现没有?加法和减法在模2里是同一回事——异或操作。

核心原则:CRC的所有计算,底层只有异或运算。没有进位、没有借位。

举个例子,计算 1101 ⊕ 1011:
1101
⊕ 1011
------
0110

你看,第2位 1⊕0=1,第3位 0⊕1=1,没有进位。这就是模2加法的本质。

2.2 多项式怎么来的?

有了模2运算,就可以聊多项式了。CRC里的多项式,其实只是二进制数的一种表示方式。

比如常见的CRC-8多项式 x⁸ + x² + x + 1,对应的二进制是 1 0000 0111(从高次到低次写系数,x⁸对应第8位,x²对应第2位,x对应第1位,常数1对应第0位)。

我个人习惯把多项式写成这样的二进制串:1 0000 0111。注意最高位一定是1,因为它代表了多项式的阶数。

多项式名称 代数形式 二进制(含最高位)
CRC-8 x⁸ + x² + x + 1 1 0000 0111
CRC-16-CCITT x¹⁶ + x¹² + x⁵ + 1 1 0001 0000 0010 0001
CRC-32 x³² + x²⁶ + x²³ + x²² + x¹⁶ + x¹² + x¹¹ + x¹⁰ + x⁸ + x⁷ + x⁵ + x⁴ + x² + x + 1 1 0000 0100 1100 0001 0001 1101 1011 0111

我的经验:在嵌入式里写CRC代码时,多项式通常省略最高位的1,因为最高位在除法计算中会被“消掉”。比如CRC-8写成0x07(即0000 0111),但底层计算时仍然按9位(含最高位1)处理。这一点很多新手容易踩坑。

2.3 多项式除法:手算CRC的过程

好了,现在数据也有了(比如要发送的字节流),多项式也有了。怎么算CRC?

步骤很简单:把数据当作被除数,多项式当作除数,做模2除法。得到的余数就是CRC值。

我们来手算一个例子。假设数据是 1101001,多项式是 x³ + x² + 1(二进制 1101)。

  1. 数据后面补3个0(因为多项式是3阶,余数最多3位)→ 1101001000
  2. 用多项式 1101 从左到右逐位异或:
        1010101 (商,我们不关心)
        _______
1101 ) 1101001000
       1101
       ----
        0000  (第一轮异或得0,直接跳过)
         0000
         ----
          1001 (下移一位)
          1101
          ----
           1000
           1101
           ----
            1010
            1101
            ----
             1110
             1101
             ----
              011 → 余数 011 (即CRC值)

所以 CRC 是 011。发送时把原始数据后面带上这个余数:1101001 011,接收方用同样的多项式再做一次除法,如果余数为0则认为数据正确。

我曾经踩过的坑:很多初学者会在第一步忘记在数据后面补0(补的位数等于多项式阶数)。不补0的话,除法根本没意义。另外,注意这里说的是“模2除法”,每一步都是异或,跟整数除法完全两码事。

2.4 数学本质:CRC是二元域上的多项式环

到这你可能会问:为什么偏偏用多项式除法?为什么余数能用来校验?

我尽量用大白话解释它的数学本质。

CRC 的底层是伽罗瓦域 GF(2)——其实就是两个元素 {0,1} 上的域。在这个域里,加法就是异或,乘法就是与门。所有二进制数都可以看成这个域上的多项式系数。

原始数据 D(x) 写成多项式,生成多项式 G(x) 是我们选好的除数。那么:

D(x) · xⁿ ÷ G(x) = Q(x) + R(x) / G(x)

其中 n 是 G(x) 的阶数。发送的数据是 D(x)·xⁿ + R(x)。接收方收到后计算 [D(x)·xⁿ + R(x)] / G(x),如果传输正确,这个多项式能被 G(x) 整除(余数为0)。

说白了,CRC 的本质就是把一个长多项式转换成它在某个生成多项式下的同余类。余数是这个同余类的唯一标识。只要余数匹配,数据很大概率是完整的。

一句话总结:CRC的数学本质 = 在二元域GF(2)上做多项式带余除法,用余数代表数据包的特征。

2.5 知识体系总览

这里我用一张SVG图把本章的核心脉络画出来。你看一遍就能把多项式、模2运算、除法、CRC联系到一块了。

CRC校验的数学本质 → 模2除法 模2运算(异或) 多项式与二进制转换 多项式除法 余数 = CRC校验值 GF(2)上的多项式同余类 发送端 + 接收端整除验证 关键理解:所有运算只有异或,没有进位/借位 → 硬件实现极其简单

你看这个图,从左到右:先有模2运算(异或),再把二进制数据写成多项式形式,然后用多项式除法求余数。余数就是CRC。而这一切的数学基础是GF(2)域。最后在实际通信中,发送端加上余数,接收端再做一次除法验证。

2.6 一些实用建议

我整理了几个要点,你写CRC代码时可以作为检查清单:

  • 确定多项式阶数:决定补多少位0,也决定CRC值的位数。比如8阶多项式,CRC就是8位。
  • 保持最高位为1:多项式二进制表示最高位必须是1,否则阶数不对。
  • 计算过程不要偷懒:手算时每一步异或都要对齐,错位会导致结果完全错误。
  • 实际代码会优化:手算用多项式除法理解原理,但实际嵌入式代码常用查表法或直接移位异或,效率更高。这些我们后面章节会讲。

特别提醒:有些在线CRC计算工具的输出跟你手算的结果可能不同。为什么?因为不同标准对初始值、结果异或值、输入输出反转等处理不一样。这属于CRC模型的差异(比如CRC-8/CRC-8/ITU等)。别慌,咱们第4章专门讲这些参数。

好了,这一章的核心就这些。你只要记住:CRC = 模2除法求余数,数学上是GF(2)多项式环。后面几章我们会在这个基础上加实际参数、看硬件实现、再结合I2C实战。慢慢来,你很快就会觉得CRC其实就是个“带反馈的移位寄存器”而已。