常见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的区别。你说冤不冤?

CRC多项式选择决策流程 开始选择CRC 是通用数据交换? CRC-32 嵌入式低资源? CRC-16 无线/蓝牙通信? CRC-CCITT 查手册再定 决策依据 带宽/资源/兼容性

4.7 最后的提醒

选多项式这事儿,说难不难,说简单也不简单。

我最想强调的一点:参数匹配比多项式本身更重要。Init、RefIn、RefOut、XorOut这四个参数,但凡有一个对不上,CRC结果就一定错。

遇到CRC对不上的时候,别急着怀疑多项式。先确认双方的参数配置是不是完全一致——这个坑我替你踩过了。