CRC 基础概念:什么是 CRC、应用场景、与奇偶校验的区别
做通信协议这么多年,我最早接触 CRC 是在一个串口项目里——当时用奇偶校验老是丢包,换了 CRC 之后问题就解决了。说白了,CRC(循环冗余校验)就是一种检错码,它能把你要发送的数据当成一个巨大的二进制数,然后用一个约定的多项式去做“除法”,余数就是校验码,附加在数据后面一起发出去。接收方收到后做同样的除法,如果余数不为 0,那就说明数据被改动了。
你可能会问,这和普通校验有什么本质区别吗?嗯,关键在于它的检错能力——CRC 对突发错误的检测率非常高,这也是为什么它在工业、网络、存储领域几乎是标配。
CRC 的核心思想:模 2 除法
简单讲,CRC 不使用我们熟悉的代数字除法,而是用“模 2 除法”,也就是按位异或运算。你不用背复杂的数学公式,只要记住三步:
- 在原始数据后面补 N 个 0(N 是校验码的位数)
- 用生成多项式对这个加长后的数据做模 2 除法
- 得到的余数(N 位)就是 CRC 校验码
比如常用的 CRC-16 多项式是 x¹⁶ + x¹⁵ + x² + 1,二进制表示为 11000000000000101。我习惯把这个多项式记作 0x8005(去掉最高位的 1 后)。
0x07,结果测试时发现连续 8 个 bit 的突发错误竟然没检出来。后来查资料才明白,不同多项式的检错能力差别很大——不是所有多项式都靠谱。
应用场景:CRC 无处不在
| 协议/标准 | 使用的 CRC | 典型场景 |
|---|---|---|
| Ethernet (IEEE 802.3) | CRC-32 | 局域网数据传输 |
| USB | CRC-5 / CRC-16 | USB packet 校验 |
| WiFi (802.11) | CRC-32 | 无线帧校验 |
| ZigBee | CRC-16 | 低功耗无线传感器 |
| SD 卡 / NAND Flash | CRC-7 / CRC-16 | 存储介质错误检测 |
| Modbus | CRC-16 (Modbus) | 工业总线通信 |
你可以看到,CRC 几乎贯穿了我们每天用的网络、USB 和存储设备。我个人习惯在协议设计初期就把 CRC 选型定下来,不然后期换多项式会牵扯很大的改动。
与奇偶校验的区别:不是一个量级
奇偶校验(Parity Check)其实是最简单的检错方式——它只统计 1 的个数是奇数还是偶数,加 1 位校验位。但它的缺陷很明显:如果错误发生在偶数个 bit 上,奇偶性不变,你就完全看不到问题。CRC 就不一样了,它的校验码位数更多(常见 8、16、32 位),并且通过多项式生成,对突发错误非常敏感。
| 对比项 | 奇偶校验 | CRC |
|---|---|---|
| 校验位长度 | 1 bit | 8/16/32 bit 以上 |
| 检错能力 | 只能检测奇数个 bit 错误 | 可检测所有 1/2/3 位错误 & 大部分突发错误 |
| 突发错误检测 | 无法检测偶数个 bit 错误 | 能检测长度 ≤ N 的连续突发错误(N=校验码位数) |
| 实现复杂度 | 极低(一个异或门) | 中等(需要多项式除法电路或查表) |
| 典型误检概率 | 50%(偶数 bit 错误场景) | 1/2^N 量级 |
你想想看,如果是一个工业控制报文,数据里同时错两个 bit,奇偶校验直接“放过”了,但控制器可能因此执行错误动作。我遇到过消防系统因为这种问题报警误触发,最后全换了 CRC-16。所以别图省那 1 个 bit 的带宽,可靠性永远是第一位的。
zlib.crc32() 或 binascii.crc_hqx() 算一遍,和你的硬件 CRC 模块输出对比。我曾经一个 bug 就是查表法多项式顺序写反了,多亏两边的结果不一样才抓到。
上图展示的是经典的 CRC 完整生命周期:发送端用数据加多项式算出 N 位余数,拼在数据尾;接收端拿到完整包后,用同一多项式重新计算,如果余数为零,基本可以相信数据没损坏。否则,就像我们课程标题说的——“重传机制”就该上场了。
这一章把 CRC 的底子打牢了,后面讲重传和 FEC 调优时,你才会知道什么时候用 CRC 检错,什么时候该用 FEC 纠错,以及两者怎么配合才能把带宽浪费降到最低。嗯,这就是基础的力量。