1. CRC算法基础
各位同学好,我是做嵌入式十几年的老工程师。今天咱们聊的是CRC——循环冗余校验。这玩意儿在QSPI通信里至关重要,但很多人只把它当"黑盒子"用。我个人习惯从根上弄明白,所以第一章先打好基础。
什么是CRC?
CRC的全称是Cyclic Redundancy Check,中文叫循环冗余校验。说白了,它是一种通过多项式除法算出的校验码。发数据的时候,把数据看作一个巨大的二进制数,用约定的多项式去"除"它,余数就是CRC码。收数据的时候,用同样的多项式再除一次,看余数是不是0。是0,数据基本靠谱;不是0,那肯定有问题。
嗯,这里要注意:"除"是模2除法,跟普通除法不同——不进位、不借位,本质上就是异或运算。所以硬件实现特别简单,几个异或门和移位寄存器就搞定了。我记得十几年前做某款电力采集终端时,MCU主频才几十兆,用软件模拟CRC跑得汗流浃背,后来换成硬件CRC模块,速度直接翻十倍。
CRC的用途
CRC最大的用处是检测数据传输中的错误。单比特跳变、双比特跳变,甚至一长串连续错误,CRC都能抓出来——当然要看多项式选得好不好。具体哪些场景?
- 通信协议:I²C、SPI、QSPI、UART、CAN、Ethernet……几乎你能想到的串行总线都有CRC保护。我在调试QSPI Flash时,每次读写都靠CRC确认数据没被干扰。
- 存储介质:NAND Flash、SD卡、硬盘扇区内部都嵌CRC。有一次做固态硬盘中间件,发现某芯片的ECC纠错后还要过一遍CRC才算完。
- 文件校验:像ZIP、RAR、网盘下载的MD5/SHA太慢,很多场合直接用CRC32做快速完整性检查。
关键认识:CRC是检错码,不是纠错码。它能发现错误,但不能定位哪里错、也不能纠正。不过在很多嵌入式场景里,检错后重传就足够了。
发展历史
CRC的历史其实挺有意思。1961年,W. Wesley Peterson在《Cyclic Codes for Error Detection》里首次提出了循环码思想。当时计算机刚起步,通信错误率又高,急需一种低成本校验方法。Peterson用数学证明了多项式除法在检错上的优越性。
后来IBM在1975年发布了CRC-16和CRC-32标准,广泛应用于同步数据链路控制(SDLC)和以太网。我最早接触CRC是在大学做单片机课设,那时用的就是CRC-16-IBM,查表法,代码只有几十行。
到了90年代,ITU-T统一了CRC-8、CRC-16-CCITT等国际标准。现在你打开任意一个微控制器的数据手册,大概率都能找到硬件CRC外设。我在去年的项目中,用STM32的硬件CRC引擎算32位CRC,每字节只花1个时钟周期,简直爽到飞起。
| 名称 | 宽度 | 多项式(十六进制) | 典型应用 |
|---|---|---|---|
| CRC-8 | 8 | 0x07 | 1-Wire、SMBus |
| CRC-16-IBM | 16 | 0x8005 | Modbus、USB |
| CRC-16-CCITT | 16 | 0x1021 | XMODEM、蓝牙 |
| CRC-32 | 32 | 0x04C11DB7 | Ethernet、ZIP、PNG |
| CRC-64-ECMA | 64 | 0x42F0E1EBA9EA3693 | 大型文件校验 |
你可能会问:为什么不同标准用的多项式不一样?说白了,多项式决定了检测能力。好的多项式能抓住所有奇数个错误、所有长度小于多项式阶数的突发错误。我职业生涯里吃过一次亏——某通信项目随便抄了个网上找的多项式,结果现场数据经常误判,最后换了ITU-T推荐的多项式才解决问题。从那以后我选多项式只看标准文档。
CRC的基本运算原理
简单讲,发送方把数据看作二进制多项式M(x),用生成多项式G(x)去除,得到余数R(x),然后把M(x)的后面补上|R(x)|位0再拼上R(x)一起发出去。接收方收到完整数据后用G(x)去除,余数应为0。
举个例子:数据是0b1101,生成多项式0b1011(对应x³+x+1)。
模2除法过程(手算示意):
1101 000 ← 数据左移3位(补多项式阶数个0)
÷ 1011
——————————
1000
1011
——————————
011 ← 余数就是CRC码
实际工程中没人手动算,都是用查表法或者硬件寄存器。
小技巧:我在QSPI项目中常常预计算一个256字节的CRC表,然后每次查4个字节的索引更新,速度比逐位计算快几十倍。后面章节我会详细讲查表法的实现。
CRC在数字电路中的实现
数字电路里用一个线性反馈移位寄存器(LFSR)就搞定了。多项式中的每一项对应一位寄存器的反馈抽头。数据逐位移入,移位过程就是多项式除法。我画了一张框架图,帮你快速理解整体流程。
图中流程很清楚:数据进来,进移位寄存器,同时拉上多项式做异或,最后得到余数就是CRC。在QSPI通信中,每发一字节几乎都在重复这个操作。硬件单周期就能算完——所以我常说,能用硬件的千万别用软件轮。
避坑提醒:我曾经在电路上忘记给CRC模块做复位,导致第一次算出来的CRC永远不对。后来做任何项目一定在初始化阶段清零CRC寄存器,并且确认数据输入顺序(高位还是低位优先)与对方一致。QSPI通常MSB先发,但有些Flash的SPI模式用LSB,需要仔细看手册。
小结
这一章我们把CRC的老底揭了一遍。定义、用途、历史、基本原理,还有那个经典的多项式除法图。你想想看,从1961年Peterson的到现在,CRC几乎伴随了整个数字通信史。时至今日,QSPI接口的每个字节背后都有CRC在默默守护。