第三章 CRC常用标准:从CRC-8到CRC-64

做通信协议的,手上没几张CRC表都不好意思跟人打招呼。我这些年接触过的项目,从几块钱的传感器到上万的基站设备,CRC几乎无处不在。说白了,CRC就是个多项式除法,但不同场景下用的多项式、宽度、初始值、输出异或都不一样,选错了,误码率可能高一个数量级。

嗯,这章咱们就掰扯清楚常用的几个CRC标准:CRC-8、CRC-16、CRC-32、CRC-64。什么时候用哪个?特性差在哪?调优时怎么选?

3.1 核心参数:别看它们都叫CRC

一个完整的CRC标准,绝不止“多项式”这一个参数。我调试时吃过亏——曾经拿着一个CRC-16/IBM的代码直接用在Modbus项目上,结果两边怎么都对不上。为什么?因为初值、出值异或、数据字节序都不一样。

你得关注的参数有这些:

  • 宽度 (Width):校验和位数,8/16/32/64。
  • 多项式 (Poly):二进制除法用的除数,一般是简写。比如CRC-32的标准多项式是0x04C11DB7。
  • 初始值 (Init):计算开始前寄存器的值。常见0x0000或0xFFFF。
  • 结果异或值 (XorOut):计算完后和这个值异或再输出。
  • 输入/输出反转 (RefIn/RefOut):数据字节是否按位反转。这个细节最容易被忽视。
参数 CRC-8/SMBus CRC-16/Modbus CRC-32/Ethernet CRC-64/ECMA-182
Width 8 16 32 64
Poly 0x07 0x8005 0x04C11DB7 0x42F0E1EBA9EA3693
Init 0x00 0xFFFF 0xFFFFFFFF 0x0000000000000000
XorOut 0x00 0x0000 0xFFFFFFFF 0x0000000000000000
RefIn False True True False
RefOut False True True False
调优小贴士:如果一个CRC实现和对方标准对不上,90%是RefIn/RefOut搞反了。我建议先在纸上算一遍小数据(比如0x01),和标准结果比对,一步到位。

3.2 各标准适用场景分析

CRC-8:低成本设备的轻量级保镖

CRC-8我常用在单字节传感器、I²C数据包、1-Wire总线这类低速短包场景。一个字节的额外开销,计算极快,能在单片机上用查找表方式跑。

举个例子:SMBus规范就用CRC-8保护命令字节。当年我在智能电池项目中,电池状态每100ms上报一次,用CRC-8就够了,误码率从万分之一降到百万分之一以下。

如果数据包超过64字节,CRC-8的汉明距离会急剧下降。你想想看,2^8=256种校验和,200字节长包碰撞概率不低。这时候就得往上升级。

CRC-16:通信协议的中流砥柱

我个人用得最多的就是CRC-16。Modbus RTU、USB数据包、BBP协议、PPP……遍地都是。16位校验,2字节开销,1024字节内数据包误检概率相当低。

我喜欢CRC-16/Modbus变体的一个原因:它初始化全1,输入输出都反转,这样使得全0数据流也能产生非零校验码,避免“空包假通过”的坑。曾经我在调试一条1200米RS485总线时,收到一堆0x00填充,幸好CRC没绕过,抓到了线路虚接问题——嗯,这就是反转设计的精妙之处。

注意:很多工程师默认“CRC-16”就是0x8005多项式,但别忘了还有CRC-16/CCITT(0x1021)、CRC-16/DNP(0x3D65)等变种。混搭使用会直接导致握手失败。我建议在协议文档开头就写明具体标准全称。

CRC-32:数据完整性底线

网络协议、文件存储、磁盘校验……CRC-32几乎是行业事实标准。以太网帧的FCS字段、Gzip压缩文件、PNG图片头校验,都靠它。

CRC-32的32位校验,理论上1023字节内汉明距离最高可达4。什么意思?能检测最多4比特错误。对于一般网络传输来说,足够用了。我见过有人把CRC-32用在几兆字节的大块数据上,其实过了几KB后检测能力会退化,这时该换CRC-64或哈希算法。

多项式0x04C11DB7是个本原多项式,具备最大长度周期。我记得有一次要在FPGA上硬加速CRC-32,查表法+流水线设计,一个时钟周期处理4字节,做到了10Gbps线速。嗯,当时挺有成就感的。

CRC-64:大数据的深度护航

CRC-64,一听就知道是给超大文件、高速存储链路准备的。比如ECMA-182标准用在磁带备份、ZFS文件系统里。64位校验和,碰撞概率接近微乎其微——2^64分之一。

但代价也很明显:4字节变成8字节,查表表大小从256×4变为256×8,对内存敏感的场景不友好。我做过一个远程存储网关,数据块1MB一次传输,CRC-64相比CRC-32多了0.8%的带宽开销,但能检测到最多5比特错误,客户验收时格外放心。

3.3 如何根据场景选择?一张图说明

下面这张图总结了四种CRC标准的选择逻辑,算是我多年调试经验的浓缩。你用来做方案评审时的决策参考很方便。

CRC标准选择决策流程 数据包长度? ≤ 64 字节 64 ~ 1024 字节 > 1024 字节 CRC-8 传感器 / I²C / 1-Wire CRC-16 Modbus / USB / RS485 看资源 CRC-32 网络 / 以太网 / PNG CRC-64 大文件 / 存储 / 磁带 提示:低端MCU内存有限,256字节查表CRC-64可能超标。 若需极高可靠性且资源充足,优先选CRC-64。

3.4 实战:CRC查表法实现(以CRC-32为例)

查表法是最常见的CRC加速手段。说白了就是提前计算好256个多项式余数,查表代替逐位除法。我一个典型实现供你参考:

// CRC-32 查表法实现(Ethernet标准)
static uint32_t crc32_table[256];

void crc32_init_table(void) {
    for (uint32_t i = 0; i < 256; i++) {
        uint32_t crc = i;
        for (int j = 0; j < 8; j++) {
            if (crc & 1)
                crc = (crc >> 1) ^ 0xEDB88320; // 反射多项式
            else
                crc >>= 1;
        }
        crc32_table[i] = crc;
    }
}

uint32_t crc32(const uint8_t *data, size_t len) {
    uint32_t crc = 0xFFFFFFFF;
    for (size_t i = 0; i < len; i++) {
        uint8_t index = (crc ^ data[i]) & 0xFF;
        crc = (crc >> 8) ^ crc32_table[index];
    }
    return crc ^ 0xFFFFFFFF;
}
注意:反射多项式0xEDB88320和标准多项式0x04C11DB7是互为反射关系,千万别搞混。如果直接用移位版本,需先反转数据位,性能反而更低。

3.5 选型避坑锦囊

  • 不要只看多项式:Init和XorOut调错,校验结果全乱套。我建议任何项目都写一个自测用例:输入0x3132(ASCII"12"),输出已知标准结果。
  • 查表法的表要放在SRAM里:有些廉价MCU的Flash读取有等待周期,查表速度反而不如位运算。我做过对比:STM32F103上,Flash查表比RAM查表慢约30%。
  • CRC-64查表内存开销大:256×8=2048字节,对于小RAM设备是个负担。可考虑用半字节查表(16×8=128字节)折中。
  • 数据反转的硬件陷阱:SPI外设发送MSB优先,但CRC计算要求LSB优先时,你需要在软件里主动反转。我之前被坑过——数据线没问题,但CRC永远不对,折腾了半天才发现是字节序反了。