第 2 章:CRC校验原理与常见算法归纳总结
做网络调试这么多年,我遇到过最头疼的问题就是数据传着传着就变了味。你说线路好好的,信号也没断,但接收端拿到的数据和发送端就是不一样。这时候,CRC就派上用场了。
说白了,CRC就是一种数学魔术。它不关心你传的是什么内容,只关心你传的这一串数据算出来的「指纹」对不对。
CRC到底在干什么?
我第一次接触CRC是在调试一个串口通信项目,那时候拿着逻辑分析仪对着波形看半天,硬是没看懂那多出来的几个字节是什么。后来才明白,原来那是CRC校验码。
它的核心思想很简单:
- 发送方:把原始数据当成一个巨大的二进制数,除以一个固定的多项式,取余数作为校验码,附在数据后面
- 接收方:用同样的多项式去除收到的完整数据(包含校验码),如果余数为0,说明数据没问题
关键点:CRC不是加密,它不隐藏数据内容。它只是给数据加了一个数学上的「防伪标签」。
你想想看,如果传输过程中哪怕只有1个比特翻了,最终算出来的余数也不会是0。这就是CRC的厉害之处。
多项式:CRC的灵魂
多项式就是CRC算法里那个「除数」。不同的多项式决定了CRC的检错能力和计算开销。
| 算法名称 | 多项式(十六进制) | 多项式(二进制) | 校验位长度 |
|---|---|---|---|
| CRC-8 | 0x07 | 0000_0111 | 8 位 |
| CRC-16-IBM | 0x8005 | 1000_0000_0000_0101 | 16 位 |
| CRC-16-CCITT | 0x1021 | 0001_0000_0010_0001 | 16 位 |
| CRC-32 | 0x04C11DB7 | 0000_0100_1100_0001_0001_1101_1011_0111 | 32 位 |
我个人习惯把多项式想象成一个「筛子」。筛子的孔越大(多项式越短),漏掉错误(误判)的概率就越高。以太网用的是CRC-32,这个多项式经过数学验证,可以检测出所有单个比特错误、所有双比特错误、所有奇数个比特错误,以及长度不超过32位的突发错误。
我曾经踩过的坑:有次我在一个嵌入式项目里为了省存储,把CRC-32截断成了CRC-16用,结果误判率直接翻了好几倍。后来老老实实改回去了。记住,多项式是数学选好的,别自己乱改长度。
CRC的计算流程
计算过程其实就是二进制除法。但这里的「加法」是异或操作,不进位也不借位。
// 伪代码:CRC-16-CCITT 计算过程
uint16_t crc16_ccitt(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF; // 初始值
uint16_t poly = 0x1021; // 多项式
for (uint16_t i = 0; i < len; i++) {
crc ^= (data[i] << 8); // 字节对齐
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x8000) { // 检查最高位
crc = (crc << 1) ^ poly; // 左移并与多项式异或
} else {
crc <<= 1; // 直接左移
}
}
}
return crc;
}
这段代码看着简单,但实际跑起来效率不高。在真实硬件或嵌入式系统里,我们一般会用查表法,提前算好所有256种字节的CRC值,运行时直接查表。
小技巧:如果你的MCU资源比较紧张,可以考虑用查表法代替逐位计算。一个256×16位的表只需要512字节,这在现在的主流MCU上基本不算事。
三大主流CRC族系
实际工程中用得最多的就三种:
- CRC-8:用在传感器、小型控制器的内部通信上。开销小,检错能力够用
- CRC-16:工业总线、MODBUS协议、一些存储读写校验。我调试串口设备时最常打交道的
- CRC-32:以太网、ZIP压缩、文件完整性检查。网络工程师的标配
为什么会分成这么多版本?说白了就是历史原因。每个标准化组织都自己选了一个多项式,互不兼容。所以你在写代码时一定要确认用的是哪个变体,不然算出来怎么都对不上。
本章知识体系一览
我的建议:在实际项目里,不要把CRC当成万能保险。它只能检测传输过程中的错误,不能纠正错误。如果遇到连续大量CRC错误,别再纠结多项式了,赶紧去查物理层——大概率是线缆松动或电磁干扰。