常见CRC多项式:CRC-16/32/CCITT标准多项式、初始化值
好了,多项式来了。
说实话,刚开始学CRC的时候,我最烦的就是这一串串十六进制数。什么0x8005、0x04C11DB7,看着就头疼。后来在项目里吃了几次亏,才明白这些数字背后藏着不少门道。
今天咱们就把这些常用多项式掰开揉碎了讲。不搞死记硬背,只讲实际怎么用,坑在哪儿。
4.1 为什么要有标准多项式?
你想想看,如果每个人搞CRC都用自己随便编的多项式,那数据交换的时候谁认谁?所以行业里约定了几套标准多项式,大家都在用。
我见过不少开发新手选多项式很随意,结果发现跟对方设备的校验结果对不上。嗯,这就是没跟标准走。
- 通信双方必须使用完全相同的多项式
- 这一点谁也别自作主张
4.2 三大主流CRC标准
实际工作中,90%的场景就这三种:
| 标准名称 | 宽度 | 多项式(十六进制) | 初始化值 | 典型应用 |
|---|---|---|---|---|
| CRC-16 | 16位 | 0x8005 | 0x0000 | Modbus RTU、USB |
| CRC-32 | 32位 | 0x04C11DB7 | 0xFFFFFFFF | 以太网、ZIP、PNG |
| CRC-CCITT | 16位 | 0x1021 | 0xFFFF | XMODEM、蓝牙 |
这三个基本够覆盖八成以上的场景了。我个人习惯,只要没有特殊要求,首选CRC-32,32位的检错能力确实强。
4.3 初始化值到底干嘛的?
这个问题我问过很多人,答案五花八门。
说白了,初始化值就是给CRC计算器一个起点。你想想看,如果数据前面全是0,有些多项式算出来的CRC结果也是0,这就尴尬了——万一真实数据也是0,你怎么判断有没有出错?
Init=0xFFFF和Init=0x0000的区别,其实就是为了避免全0数据的CRC结果也全是0。我在做蓝牙协议栈的时候,就因为初始化值设错了,跟手机死活配对不上。
4.4 常用参数速查表
这里我给一张比较全的参数表,都是实际项目里验证过的:
| 别名 | Poly | Init | RefIn | RefOut | XorOut |
|---|---|---|---|---|---|
| CRC-16/Modbus | 0x8005 | 0xFFFF | True | True | 0x0000 |
| CRC-16/USB | 0x8005 | 0xFFFF | True | True | 0xFFFF |
| CRC-32/Ethernet | 0x04C11DB7 | 0xFFFFFFFF | True | True | 0xFFFFFFFF |
| CRC-16/CCITT | 0x1021 | 0xFFFF | False | False | 0x0000 |
RefIn和RefOut很多人会忽略。RefIn表示输入数据是否需要按位翻转,RefOut表示输出CRC是否需要翻转。这两个参数跟多项式一样重要!我曾经在做一个工业控制项目时,就因为RefIn设反了,调试了整整两天。
4.5 代码实现对比
光讲理论没用,咱们直接上代码看看真实区别。
C语言实现CRC-16算例:
// CRC-16/Modbus 实现
uint16_t crc16_modbus(uint8_t *data, uint16_t len) {
uint16_t crc = 0xFFFF; // Init
for (uint16_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001; // 0x8005 反转
} else {
crc >>= 1;
}
}
}
return crc ^ 0x0000; // XorOut
}
对应Python版,方便你快速验证:
def crc16_modbus(data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc ^ 0x0000
你看,代码量其实不大。但注意这个0xA001——它是0x8005的反射多项式。为什么会有反射?因为很多硬件在移位时是从LSB开始的,所以多项式也跟着反射了。
4.6 我的选择建议
折腾了这么多年,我总结了三条经验:
- 通用数据交换:无脑选CRC-32/Ethernet,通用性最强
- 嵌入式低资源:CRC-16/Modbus就行,16位够用还省内存
- 无线通信:CRC-CCITT(XMODEM)用的最多,蓝牙、ZigBee都在用
曾经我有个项目,前后端用了不同的CRC库,结果两边参数就差一个XorOut。数据传过来永远对不上。后来花了三个小时一行一行对代码,才发现0x0000和0xFFFF的区别。你说冤不冤?
4.7 最后的提醒
选多项式这事儿,说难不难,说简单也不简单。
我最想强调的一点:参数匹配比多项式本身更重要。Init、RefIn、RefOut、XorOut这四个参数,但凡有一个对不上,CRC结果就一定错。
遇到CRC对不上的时候,别急着怀疑多项式。先确认双方的参数配置是不是完全一致——这个坑我替你踩过了。