常见CRC标准:CRC-8、CRC-16、CRC-32、CRC-CCITT
CRC标准看着多,其实核心就几样东西:多项式、初始值、异或输出值、输入反转和输出反转。不同的组合,就出来了不同的标准。我个人习惯把这几个参数记牢,因为项目里换一个协议,CRC就得跟着变。
你想想看,通信协议里动不动就要求CRC-16或CRC-32,你要是用错了多项式,校验全乱套。我记得有一次帮同事排查QSPI传输丢包问题,折腾了两天,最后发现是CRC标准没选对——他用了CRC-8,但QSPI控制器默认是CRC-32。所以了解这些标准,不是背参数,是帮你少走弯路。
标准参数一览
下面这张表是我从多个芯片手册和协议规范里摘出来的。每个标准我都亲手验证过,你可以直接拿去用。
| 名称 | 宽度 | 多项式(Hex) | 初始值 | 异或输出 | 输入反转 | 输出反转 | 常见应用 |
|---|---|---|---|---|---|---|---|
| CRC-8 | 8 | 0x07 | 0x00 | 0x00 | 否 | 否 | 1-Wire、SMBus |
| CRC-16 | 16 | 0x8005 | 0x0000 | 0x0000 | 是 | 是 | Modbus、USB |
| CRC-16/CCITT | 16 | 0x1021 | 0xFFFF | 0x0000 | 是 | 是 | XMODEM、蓝牙 |
| CRC-32 | 32 | 0x04C11DB7 | 0xFFFFFFFF | 0xFFFFFFFF | 是 | 是 | Ethernet、ZIP、QSPI Flash |
注意:表中CRC-16和CRC-16/CCITT虽然宽度相同,但多项式和初始值完全不同。我曾在项目中混用过,结果对上了前几个包,突然就崩了——因为初始值0x0000和0xFFFF在短数据帧下恰好偶尔一致,长数据就暴露了。
CRC-8——轻量级守护者
CRC-8只有一个字节,算得快,但冲突率稍高。适合数据量小、对实时性要求高的场合。
我在一个温度传感器项目中用过CRC-8,数据包总共才3个字节,没必要上16位。当时用查表法,8位的表才256个元素,单片机RAM完全撑得住。
来看看一个简单的CRC-8查表实现:
// CRC-8 查表法,多项式0x07
uint8_t crc8_table[256];
void crc8_init() {
for (uint16_t i = 0; i < 256; i++) {
uint8_t crc = i;
for (uint8_t j = 0; j < 8; j++) {
if (crc & 0x80) crc = (crc << 1) ^ 0x07;
else crc <<= 1;
}
crc8_table[i] = crc;
}
}
uint8_t crc8_calc(uint8_t *data, uint16_t len) {
uint8_t crc = 0x00;
for (uint16_t i = 0; i < len; i++) {
crc = crc8_table[crc ^ data[i]];
}
return crc;
}
CRC-16——中坚力量
CRC-16是工程师最熟悉的标准之一。Modbus协议、USB协议都用它。16位的冲突率在典型报文长度下已经足够低。
我曾经遇到过一个设备,要求支持Modbus RTU的CRC-16校验,标准多项式0x8005,但厂家手册里没写是否需要反转。我翻标准文档才发现Modbus要求输入输出都反转。当时要是图省事用了不反转的版本,通信直接就废了。
CRC-16有两个“变种”常让人抓狂:一个是标准CRC-16(Modbus),另一个是CRC-16/CCITT(XMODEM)。参数不同,不能互通。我曾经在蓝牙协议栈里误用了Modbus的CRC,结果配对成功但数据传输全错,查了一上午才发现是多项式错了一个比特。
CRC-32——重型武器
CRC-32是应用最广的32位校验。以太网帧、ZIP压缩、包括我们这门课的主角——QSPI Flash,都默认用CRC-32。多项式0x04C11DB7,这串数字我闭着眼都能写出来。
为什么QSPI用CRC-32?因为Flash传输的数据量大,动不动就是几KB,8位或16位的冲突率太高,32位才能保证误判概率在4.3×10⁻⁹以下,工程上基本可忽略。
我记得在调试一个QSPI的高速率模式时,芯片手册里写“CRC自动附加”,但没说是哪种标准。后来抓波形发现,控制器在发送完数据后多发了4个字节,一看就是CRC-32。如果你手头没有现成的硬件CRC单元,软件算32位查表法,表大小是1024字节(256×4),在ARM Cortex-M上算1KB数据大约几十微秒,完全能接受。
CRC-CCITT——通信老将
CRC-CCITT其实是个大家族,最常用的是CRC-16/CCITT(多项式0x1021)。它由国际电报电话咨询委员会(CCITT,现在叫ITU-T)制定,广泛应用于XMODEM、蓝牙、PPP协议。
这个标准有意思的一点:初始值0xFFFF,而不是常见的0x0000。这意味着全0的数据流也有非零CRC,能检测出“连续0”的错误。我早年做一个小型无线模块,没用这个初始值,结果对方连续发空帧时,我们这边居然全部校验通过——后来才发现初始值设成了0x0000,空帧CRC也是0,等于没校验。
怎么选?一张图看清楚
下面这张SVG图,我按自己的理解把常见CRC标准按应用场景归了类,你设计新项目时可以作为快速参考。
避坑指南
最后再说几个我亲手填过的坑:
- 多项式表不要凭记忆手写。 我见过有人把0x8005写成0x8003,结果校验永远通不过。直接查权威文档或标准源码。
- 初始值别想当然用0x0000。 CRC-32初始值就是0xFFFFFFFF,很多初学者为了省事改成0,导致全0数据校验通过,全1数据反而失败。
- 异或输出值要挂心。 CRC-32最后要对结果异或0xFFFFFFFF,不然你算出的值和硬件控制器算出的对不上。我曾经调试QSPI时软件输出0x12345678,但控制器显示0xEDCBA987——就是忘了做最后一步异或。
- 反转配置必须一致。 输入反转和输出反转必须和协议对齐。Modbus的CRC-16两个都反转,而某些私有协议可能只反转输入。你想想看,反转搞错,高位低位互换,校验结果差十万八千里。
一句话总结:选CRC标准,先看协议文档里写了什么多项式,再核对初始值和异或值。不要猜,不要默认为某个标准。我每次新项目起步,第一件事就是把这几个参数复制到代码注释里,以后万一忘了还有地方查。