你说的 CRC 是哪个 CRC:一次上下位机联调翻车实录
前段时间组里上下位机联调。协议文档看起来挺完整:帧头、命令字、数据、帧尾带 CRC16 校验。两边各自实现、各自自测,全都通过;一联调,各种 bug 排着队来。查了好几天,最后发现元凶平平无奇:两边都实现了 CRC16,但从头到尾没人问过一句——是哪个 CRC16。 先说结论:CRC 不是"一个算法",是一个算法族。“加个 CRC 校验"这句话的规格含量,跟"加点辣"差不多——都是辣,川湘黔滇各不一样。 翻车姿势 CRC 变体没对齐时的症状,通常是这三种: 全帧校验失败。发什么错什么,怀疑链从串口时序、大小端、接地一路排查到换线换板子,通信工程师的经典受难路线。其实链路好得很,两边算的压根不是同一个数。 为联调把校验旁路。有人图快,把"校验失败"改成"打条日志继续跑”,业务先跑通了,恢复和补测永远排在下个迭代。校验从此形同虚设,比没有还危险——它给人安全感。 偶发"漏检"玄学。压力测试几百万帧,偶尔一两帧校验通过但数据是错的。于是开始研究 CRC 漏检率、查电磁干扰、上磁环。其实是两套算法在 65536 个值里随缘撞上了——不是漏检,是压根没检。 CRC 是一个算法族 CRC 除了"用哪个多项式做除法"这个自由度,除法前后还有一串参数。业界通用的描述模型叫 Rocksoft 模型,出自 Ross Williams 1993 年那篇 A Painless Guide to CRC Error Detection Algorithms,一共五个参数: width:位宽 poly:多项式 init:寄存器初值 refin / refout:输入、输出是否按位反转 xorout:结果异或值 “CRC16” 只锁定了第一个参数,剩下四个全开放。同一个多项式能配出多少种不同结果?看下表(check 是官方测试向量:9 字节 ASCII 字符串 "123456789" 的校验值): 名字 poly init refin/refout xorout check CRC-16/XMODEM 0x1021 0x0000 否/否 0x0000 0x31C3 CRC-16/CCITT-FALSE 0x1021 0xFFFF 否/否 0x0000 0x29B1 CRC-16/KERMIT 0x1021 0x0000 是/是 0x0000 0x2189 CRC-16/ARC 0x8005 0x0000 是/是 0x0000 0xBB3D CRC-16/MODBUS 0x8005 0xFFFF 是/是 0x0000 0x4B37 CRC-32/ISO-HDLC 0x04C11DB7 0xFFFFFFFF 是/是 0xFFFFFFFF 0xCBF43926 前三个共享多项式 0x1021,check 是三个毫不相干的数;中间两个只差一个 init(0x0000 与 0xFFFF),结果就分道扬镳。网上抄的 crc16.c,十份里能抄出五种参数组合,而且多半不写注释。 ...