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多项式一览
名称 宽度 多项式(十六进制) 典型应用
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运算框架图 CRC编码/校验流程 数据输入 移位寄存器 异或网络 生成多项式 CRC余数 数据 + CRC码 说明:数据逐位移入寄存器, 当所有位移完后,寄存器中 保存的内容即为CRC校验码。 发送时拼在数据末尾。

图中流程很清楚:数据进来,进移位寄存器,同时拉上多项式做异或,最后得到余数就是CRC。在QSPI通信中,每发一字节几乎都在重复这个操作。硬件单周期就能算完——所以我常说,能用硬件的千万别用软件轮。

避坑提醒:我曾经在电路上忘记给CRC模块做复位,导致第一次算出来的CRC永远不对。后来做任何项目一定在初始化阶段清零CRC寄存器,并且确认数据输入顺序(高位还是低位优先)与对方一致。QSPI通常MSB先发,但有些Flash的SPI模式用LSB,需要仔细看手册。

小结

这一章我们把CRC的老底揭了一遍。定义、用途、历史、基本原理,还有那个经典的多项式除法图。你想想看,从1961年Peterson的到现在,CRC几乎伴随了整个数字通信史。时至今日,QSPI接口的每个字节背后都有CRC在默默守护。