CRC 基础概念:什么是 CRC、应用场景、与奇偶校验的区别

做通信协议这么多年,我最早接触 CRC 是在一个串口项目里——当时用奇偶校验老是丢包,换了 CRC 之后问题就解决了。说白了,CRC(循环冗余校验)就是一种检错码,它能把你要发送的数据当成一个巨大的二进制数,然后用一个约定的多项式去做“除法”,余数就是校验码,附加在数据后面一起发出去。接收方收到后做同样的除法,如果余数不为 0,那就说明数据被改动了。

你可能会问,这和普通校验有什么本质区别吗?嗯,关键在于它的检错能力——CRC 对突发错误的检测率非常高,这也是为什么它在工业、网络、存储领域几乎是标配。

CRC 的核心思想:模 2 除法

简单讲,CRC 不使用我们熟悉的代数字除法,而是用“模 2 除法”,也就是按位异或运算。你不用背复杂的数学公式,只要记住三步:

  1. 在原始数据后面补 N 个 0(N 是校验码的位数)
  2. 用生成多项式对这个加长后的数据做模 2 除法
  3. 得到的余数(N 位)就是 CRC 校验码

比如常用的 CRC-16 多项式是 x¹⁶ + x¹⁵ + x² + 1,二进制表示为 11000000000000101。我习惯把这个多项式记作 0x8005(去掉最高位的 1 后)。

避坑提醒:我曾经在一个项目里顺手用了 CRC-8 多项式 0x07,结果测试时发现连续 8 个 bit 的突发错误竟然没检出来。后来查资料才明白,不同多项式的检错能力差别很大——不是所有多项式都靠谱。

应用场景:CRC 无处不在

协议/标准使用的 CRC典型场景
Ethernet (IEEE 802.3)CRC-32局域网数据传输
USBCRC-5 / CRC-16USB packet 校验
WiFi (802.11)CRC-32无线帧校验
ZigBeeCRC-16低功耗无线传感器
SD 卡 / NAND FlashCRC-7 / CRC-16存储介质错误检测
ModbusCRC-16 (Modbus)工业总线通信

你可以看到,CRC 几乎贯穿了我们每天用的网络、USB 和存储设备。我个人习惯在协议设计初期就把 CRC 选型定下来,不然后期换多项式会牵扯很大的改动。

与奇偶校验的区别:不是一个量级

奇偶校验(Parity Check)其实是最简单的检错方式——它只统计 1 的个数是奇数还是偶数,加 1 位校验位。但它的缺陷很明显:如果错误发生在偶数个 bit 上,奇偶性不变,你就完全看不到问题。CRC 就不一样了,它的校验码位数更多(常见 8、16、32 位),并且通过多项式生成,对突发错误非常敏感。

对比项奇偶校验CRC
校验位长度1 bit8/16/32 bit 以上
检错能力只能检测奇数个 bit 错误可检测所有 1/2/3 位错误 & 大部分突发错误
突发错误检测无法检测偶数个 bit 错误能检测长度 ≤ N 的连续突发错误(N=校验码位数)
实现复杂度极低(一个异或门)中等(需要多项式除法电路或查表)
典型误检概率50%(偶数 bit 错误场景)1/2^N 量级

你想想看,如果是一个工业控制报文,数据里同时错两个 bit,奇偶校验直接“放过”了,但控制器可能因此执行错误动作。我遇到过消防系统因为这种问题报警误触发,最后全换了 CRC-16。所以别图省那 1 个 bit 的带宽,可靠性永远是第一位的。

特别提醒:不要以为 CRC 能保证 100% 不漏检。实际上,CRC 只是概率上极低——比如 CRC-32 的漏检率大约为 1/4.3e9。对于关键控制指令,我们通常还会搭配序列号、超时重传和 FEC 一起使用。
调试技巧:如果你在做底层软件调试,可以先在 PC 上用 Python 的 zlib.crc32() 或 binascii.crc_hqx() 算一遍,和你的硬件 CRC 模块输出对比。我曾经一个 bug 就是查表法多项式顺序写反了,多亏两边的结果不一样才抓到。
CRC 发送与接收校验流程 发送端 原始数据 生成多项式 模2除法计算余数 数据 + CRC 打包 发送到接收端 接收端 接收数据包 用相同多项式重算 CRC 比较余数 == 0 ? 数据正确 / 请求重传 数据 多项式

上图展示的是经典的 CRC 完整生命周期:发送端用数据加多项式算出 N 位余数,拼在数据尾;接收端拿到完整包后,用同一多项式重新计算,如果余数为零,基本可以相信数据没损坏。否则,就像我们课程标题说的——“重传机制”就该上场了。

这一章把 CRC 的底子打牢了,后面讲重传和 FEC 调优时,你才会知道什么时候用 CRC 检错,什么时候该用 FEC 纠错,以及两者怎么配合才能把带宽浪费降到最低。嗯,这就是基础的力量。