一、简介
循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
CRC校验计算速度快,检错能力强,易于用编码器等硬件电路实现。从检错的正确率与速度、成本等方面,都比奇偶校验等校验方式具有优势。因而,CRC 成为计算机信息通信领域最为普遍的校验方式。常见应用有以太网/USB通信,压缩解压,视频编码,图像存储,磁盘读写等。
设想 我们要设计一种校验,例如主机要传一个 十进制的数字 100 给从机。它可以再传输完100之后,再给从机传输一个2,表示传输的是一个偶数。这样如果发生了错误,从机发现拿到的数字是个奇数但是主机明明告诉它是偶数(最后是个2),就说明发生了错误。
但是这种办法显然很不靠谱。我们可以换一种方式。
- 从机和主机约定好一个数字(除数),比如都是 7
- 主机向从机发送数据 100 。然后在末尾附加上 100/7 的余数 2
- 如果传输没问题,从机接收到了两个数字 100 和 2。然后从机用 100-2 = 98,再用98 除以约定好的除数7,发现刚好整除,从机就知道了传输确实是没问题的,数字100可以放心用。
- 如果传输发生了问题,那么显然极大概率就不能整除了(可能会有极其凑巧的情况)
CRC 校验也是基于 除法 和 余数 的概念,但是它利用的是多项式除法(模二除法 或者二进制除法)
例如 数据 101010(0x42)
,可以转化为多项式:
x
5
+
x
3
+
x
x^5+x^3+x
x5+x3+x。(注意最低位0次方,任何数零次方都等于1)
假设主机和从机约定都用数据除以多项式 x + 1 x+1 x+1,可以得到如下式子:
x 3 + x 2 + x x + 1 = x 3 + x 2 + x + 1 − 1 x + 1 = ( x 2 + 1 ) ( x + 1 ) x + 1 − 1 x + 1 = ( x 2 + 1 ) − 1 x + 1 \frac{x^3+x^2+x}{x+1} = \frac{x^3+x^2+x+1-1}{x+1} = \frac{(x^2+1)(x+1)}{x+1} - \frac{1}{x+1} = (x^2+1) - \frac{1}{x+1} x+1x3+x2+x=x+1x3+x2+x+1−1=x+1(x2+1)(x+1)−x+11=(x2+1)−x+11
我们在两边再乘一个 x + 1 x+1 x+1
( x 3 + x 2 + x ) = ( x 2 + 1 ) ( x + 1 ) − 1 (x^3+x^2+x) = (x^2+1)(x+1) - 1 (x3+x2+x)=(x2+1)(x+1)−1
这里不难看出:得到一个商×除数+余数的式子,其中的 ( x 2 + 1 ) (x^2+1) (x2+1)就是商, − 1 -1 −1就是余数。我们还可以对左边式子再提取一次 x x x,可得
( x 2 + x + 1 ) x = ( x 2 + 1 ) ( x + 1 ) − 1 (x^2+x+1)x = (x^2+1)(x+1) - 1 (x2+x+1)x=(x2+1)(x+1)−1
我们把余数放到原始数据那边:
( x 2 + x + 1 ) x + 1 = ( x 2 + 1 ) ( x + 1 ) (x^2+x+1)x + 1 = (x^2+1)(x+1) (x2+x+1)x+1=(x2+1)(x+1)
不难发现,等号左侧就是先将 原始数据 对应的二进制数左移1位(右侧补0),然后把余数补到右测补0的位置。这样做之后,这个整个值就恰好是 商 和 除数 的乘积了!!也就是说它恰好可以用 除数 ( x + 1 ) (x+1) (x+1) 整除
写成二进制的形式就是 1011 << 1 ⊕ 1 \oplus 1 ⊕1 = 0101 ⊗ 0011 \otimes 0011 ⊗0011
1011 << 1 ⊕ \oplus ⊕ 1 = 1011 1 就是主机要发送的数据。从机检查接收到的数据 模二除以 ( x + 1 ) (x+1) (x+1) (0011)是否可以整除即可。
通过以上的式子的整理可以得出通用公式即是:
M ( x ) × x n = Q ( x ) × K ( x ) − R ( x ) M(x) \times x^n = Q(x) \times K(x) - R(x) M(x)×xn=Q(x)×K(x)−R(x)
M ( x ) M(x) M(x) 就代表的是原始数据, x n x^n xn 代表的是数据末补 n n n个0, K ( x ) K(x) K(x)代表的是 K e y Key Key也就是生成项多项式也就是除数, R ( x ) R(x) R(x)代表的余数。接收者接受到 M ( x ) ∗ x n + R ( x ) M(x)*x^n+R(x) M(x)∗xn+R(x)之后检查能否被 K ( x ) K(x) K(x)整除,可以即为正确数据。
要注意的一点 x n x^n xn的位数(右侧补零的个数)和 R ( x ) R(x) R(x)一致,且一定比 K ( x ) K(x) K(x)的位数少1。
二、CRC参数模型
一个完整的CRC参数模型应该包含以下信息:WIDTH,POLY,INIT,REFIN,REFOUT,XOROUT。
NAME
:参数模型名称WIDTH
:生成的CRC校验码的位宽,如CRC-8,生成的CRC校验码为8位POLY
:多项式省略最高位的1
,并且转换为十六进制。例如 多项式为 x8 + x2 + x + 1,二进制为1 0000 0111,省略最高位1并转换为十六进制之后为0x07。INIT
:CRC初始值,位宽为WIDTH
。REFIN
:true或false。在进行计算之前,原始数据是否翻转,如原始数据:0x34 = 0011 0100,如果REFIN为true,进行翻转之后为0010 1100 = 0x2cREFOUT
:true或false,运算完成之后,得到的CRC值是否进行翻转,如计算得到的CRC值:0x97 = 1001 0111,如果REFOUT为true,进行翻转之后为11101001 = 0xE9。XOROUT
:计算结果与此参数进行异或运算后得到最终的CRC值,位宽等于WIDTH
。
通常,如果只给了一个多项式,其他参数没有说明,默认使用 INIT
=0x00,REFIN
=false,REFOUT
=false,XOROUT
=0x00
这里需要注意的一点是 POLY
忽略了最高位,因为最高位肯定是 1
所以就没必要记录了。
CRC-8 的多项式写出来其实是有 9 位,但是我们忽略了肯定为 1
的最高位,所以 POLY
是 8 位的。
CRC算法名称 | 多项式公式 | 宽度 | 多项式(hex) | 初始值(hex) | 结果异或值(hex) | 输入反转 | 输出反转 |
---|---|---|---|---|---|---|---|
CRC-4/ITU | x 4 + x + 1 x^4 + x + 1 x4+x+1 | 4 | 03 | 00 | 00 | true | true |
CRC-5/EPC | x 5 + x 3 + 1 x^5 + x^3 + 1 x5+x3+1 | 5 | 09 | 09 | 00 | false | false |
CRC-5/ITU | x 5 + x 4 + x 2 + 1 x^5 + x^4 + x2 + 1 x5+x4+x2+1 | 5 | 15 | 00 | 00 | true | true |
CRC-5/USB | x 5 + x 2 + 1 x^5 + x^2 + 1 x5+x2+1 | 5 | 05 | 1F | 1F | true | true |
CRC-6/ITU | x 6 + x + 1 x^6 + x + 1 x6+x+1 | 6 | 03 | 00 | 00 | true | true |
CRC-7/MMC | x 7 + x 3 + 1 x^7 + x^3 + 1 x7+x3+1 | 7 | 09 | 00 | 00 | false | false |
CRC-8 | x 8 + x 2 + x + 1 x^8 + x^2 + x + 1 x8+x2+x+1 | 8 | 07 | 00 | 00 | false | false |
CRC-8/ITU | x 8 + x 2 + x + 1 x^8 + x^2 + x + 1 x8+x2+x+1 | 8 | 07 | 00 | 55 | false | false |
CRC-8/ROHC | x 8 + x 2 + x + 1 x^8 + x^2 + x + 1 x8+x2+x+1 | 8 | 07 | FF | 00 | true | true |
CRC-8/MAXIM | x 8 + x 5 + x 4 + 1 x^8 + x^5 + x^4 + 1 x8+x5+x4+1 | 8 | 31 | 00 | 00 | true | true |
CRC-16/IBM | x 16 + x 15 + x 2 + 1 x^{16} + x^{15} + x^2 + 1 x16+x15+x2+1 | 16 | 8005 | 0000 | 0000 | true | true |
CRC-16/MAXIM | x 16 + x 15 + x 2 + 1 x^{16} + x^{15} + x^2 + 1 x16+x15+x2+1 | 16 | 8005 | 0000 | FFFF | true | true |
CRC-16/USB | x 16 + x 15 + x 2 + 1 x^{16} + x^{15} + x^2 + 1 x16+x15+x2+1 | 16 | 8005 | FFFF | FFFF | true | true |
CRC-16/MODBUS | x 16 + x 15 + x 2 + 1 x^{16} + x^{15} + x^2 + 1 x16+x15+x2+1 | 16 | 8005 | FFFF | 0000 | true | true |
CRC-16/CCITT | x 16 + x 12 + x 5 + 1 x^{16} + x^{12} + x^5 + 1 x16+x12+x5+1 | 16 | 1021 | 0000 | 0000 | true | true |
CRC-16/CCITT-FALSE | x 16 + x 12 + x 5 + 1 x^{16} + x^{12} + x^5 + 1 x16+x12+x5+1 | 16 | 1021 | FFFF | 0000 | false | false |
CRC-16/X25 | x 16 + x 12 + x 5 + 1 x^{16} + x^{12} + x^5 + 1 x16+x12+x5+1 | 16 | 1021 | FFFF | FFFF | true | true |
CRC-16/XMODEM | x 16 + x 12 + x 5 + 1 x^{16} + x^{12} + x^5 + 1 x16+x12+x5+1 | 16 | 1021 | 0000 | 0000 | false | false |
CRC-16/DNP | x 16 + x 13 + x 12 + x 11 + x 10 + x 8 + x 6 + x 5 + x 2 + 1 x^{16} + x^{13} + x^{12} + x^{11} + x^{10} + x^8 + x^6 + x^5 + x^2 + 1 x16+x13+x12+x11+x10+x8+x6+x5+x2+1 | 16 | 3D65 | 0000 | FFFF | true | true |
CRC-32 | x 32 + x 26 + x 23 + x 22 + x 16 + x 12 + x 11 + x 10 + x 8 + x 7 + x 5 + x 4 + x 2 + x + 1 x^{32} + x^{26} + x^{23} + x^{22} + x^{16} + x^{12} + x^{11} + x^{10} + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 | 32 | 04C11DB7 | FFFFFFFF | FFFFFFFF | true | true |
CRC-32/MPEG-2 | x 32 + x 26 + x 23 + x 22 + x 16 + x 12 + x 11 + x 10 + x 8 + x 7 + x 5 + x 4 + x 2 + x + 1 x^{32} + x^{26} + x^{23} + x^{22} + x^{16} + x^{12} + x^{11} + x^{10} + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1 | 32 | 04C11DB7 | FFFFFFFF | 00000000 | false | false |
三、CRC校验码计算
设需要计算CRC校验码的数据为 data = 0x34 = 0011 0100
,下面以 CRC-8/MAXIN参数模型 为例, 其CRC参数为:
POLY = 0x31 = 0011 0001 补上最高位之后,多项式 = 1 0011 0001
INIT = 0
REFIN = TRUE
XOROUT = 0
REFOUT = 0
求解步骤如下
-
INIT = 0
,原始数据高8位和初始值进行异或运算,这里与0异或仍为原值,data
保持不变。 -
REFIN
为TRUE
,所以需要先对data
进行翻转:0011 0100 --> 0010 1100
-
data
左移8位,即后面补8个0:10 1100 0000 0000
-
把处理之后的
data
和1 0011 0001
进行模2除法,求得余数:data = 10 1100 0000 0000
多项式 = 1 0011 0001
- 作“模2除法”,取得余数为:
CRC = 1111 1011
-
CRC
与XOROUT
进行异或,1111 1011 xor 0000 0000 = 1111 1011
-
因为
REFOUT
为TRUE
,对结果进行翻转得到最终的CRC-8值:1111 1011 -> 1101 1111 = 0xDF
-
原始数据
拼接CRC:0011 0100 1101 1111 = 34DF,相当于原始数据左移8位+余数。
注意:第4步进行模2除法时,如果余数不足 8 位 的,高位用 0
补齐。除数是9位的多项式,而不是省略了最高位的 POLY
四、引入——单字节数据求CRC
前面 CRC参数模型
中提到的 POLY
是省略了最高位的多项式。为什么要这么做呢?而且在原理里面我们确实是用的完整的多项式进行的除法运算的,为什么 参数里面反而要规定一个省略了最高位的 POLY
呢?
我们观察一下模2除法的过程,每一次用多项式和上一次的结果异或运算时,最高位肯定都是 1
和 1
进行异或(绿色框起来的)。
以 CRC-4 为例,多项式虽然有 5 位,但是其实我们每次做异或时,只关注后四位就可以了。
所以我们根本没必要关注和记录最高位。这样可以节约不必要的运算,并且少用一位存储空间(如果内存非常紧张的话)
CRC-4代码实现
以 CRC-4 为例,计算一个 1byte (8bit)
数据的 CRC-4 校验码:
我们在程序中,由于最小的数据类型就是8位的 uint8_t
,所以只能用 uint8_t
类型暂存每一步的 crc (有些教程里面会叫做寄存器)
CRC-4 只需要使用4位即可,所以我们使用 uint8_t
的高4位,不关心低4位。
#include<stdio.h>
#include<stdint.h>
void main()
{
uint8_t crc = 0; //初始值,由于 INIT = 0
uint8_t data = 0xb3; // 原始数据
uint8_t poly = 0x09 << 4; // 把 4位的 poly 挪到 uint8_t 的高4位去
crc = crc^data;
// 一共左移8次就可以了
for (size_t i = 0; i < 8; i++)
{
// 取出来上一次结果的最高位
if (crc&0x80)
//上一次结果的最高位如果是 1 。之前提到过,这时没有必要对最高位的1进行异或运算,直接把 crc 左移一位,把最高位的 1 就扔掉,新的一位补充了进来。然后和poly进行异或运算
crc = (crc << 1) ^ poly;
else
//如果上一次结果的最高位如果是 0 ,那就得再左移一位。对应上面除法运算中 商0 的那几次运算
crc = (crc << 1);
}
printf("crc is : 0x%x", crc>>4); //最后,注意 crc 存在了 uint8_t 的高4位。打印时注意右移到低四位上打印出来
}
上面的程序比较简单,因为我们的数据是个 1byte (8bit)
长度的数据,直接在一个 uint8_t
类型 中就可以存储和进行移位运算。但是,实际情况经常是要对一串byte型的字符串求 CRC 校验码,即便是C语言最长的数据类型可能都存不下这么长的一串数,这时如何处理移位操作和除法呢?
五、计算任意长数据的CRC
计算原理,以CRC-16 为例
1. 基本算法(人工笔算):
以CRC16-CCITT为例进行说明,CRC校验码为16位,生成多项式17位。假如数据流为4字节:BYTE[3]、BYTE[2]、BYTE[1]、BYTE[0];
数据流左移16位(右侧补16个0),再除以生成多项式 0x11021
,做不借位的除法运算(模二除法),所得的余数就是CRC校验码。
发送时的数据流为6个字节,分别位:BYTE[3]、BYTE[2]、BYTE[1]、BYTE[0]、CRC[1]、CRC[0];
2. 计算机算法1(比特型算法):
这个方法其实就是模拟的人工笔算的除法。
1)将扩大后的数据流(6字节)高16位(BYTE[3]、BYTE[2])放入一个长度为16的寄存器;
2)如果寄存器的首位为1,将寄存器左移1位(寄存器的最低位从下一个字节BYTE[1]移入一位),再与生成多项式的简记式异或;如果寄存器的首位为,仅将寄存器左移1位(寄存器的最低位从下一个字节BYTE[1]移入一位);
3)重复第2步,直到数据流(6字节)全部移入寄存器;
4)寄存器中的值则为CRC校验码CRC[1]、CRC[0]。
该方法理解起来简单,但是涉及到太多位运算,效率不高。
3. 计算机算法2(字节型算法):
假如数据流为n
字节:
B
n
,
B
n
−
1
,
B
n
−
2
⋯
B
0
B_n,B_{n-1},B_{n-2} \cdots B_{0}
Bn,Bn−1,Bn−2⋯B0
计算CRC的过程本质上是多项式除法,所以我们直接从多项式除法的层面去推导CRC-16的生成过程
把按字节排列的数据流表示成数学多项式, B n × x 8 n + B n × x 8 ( n − 1 ) ⋯ B n × x 0 B_n \times x^{8n} +B_n \times x^{8(n-1)} \cdots B_n \times x^0 Bn×x8n+Bn×x8(n−1)⋯Bn×x0
CRC-16的POLY是16bit,多项式为17阶,设生成多项式为 G 17 G17 G17 例如 G 17 = x 16 + x 12 + x 5 + 1 G17 = x^{16} + x^{12} + x^5 + 1 G17=x16+x12+x5+1。
求 CRC-16的多项式 的过程就是先对 B n × x 8 n + B n × x 8 ( n − 1 ) ⋯ B n × x 0 B_n \times x^{8n} +B_n \times x^{8(n-1)} \cdots B_n \times x^0 Bn×x8n+Bn×x8(n−1)⋯Bn×x0 左移16位,然后做多项式除法,求得的余数就是CRC校验多项式。即
( B n × x 8 n + B n − 1 × x 8 ( n − 1 ) + ⋯ + B 0 × x 0 ) × x 16 ÷ G 17 (B_n \times x^{8n} +B_{n-1} \times x^{8(n-1)} + \cdots + B_0 \times x^0) \times x^{16} \div G17 (Bn×x8n+Bn−1×x8(n−1)+⋯+B0×x0)×x16÷G17
OK,现在来明确一下我们的需求:
- 第一步,我们可以很容易得求得第一个
byte
B n B_n Bn的CRC值,也就是先左移16位再除以多项式。 B n × x 16 ÷ G 17 = Z [ n ] + Y [ n ] G 17 B_n \times x^{16} \div G17 = Z[n] + \frac{Y[n]}{G17} Bn×x16÷G17=Z[n]+G17Y[n]。其中, Z [ n ] Z[n] Z[n]是商, Y [ n ] Y[n] Y[n] 是余数,也就是 B n B_n Bn 的CRC值。 - 现在,我们拿到第二个
byte
和第一个byte
拼接为了 [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 的16个字符。如何利用已经求出来的 B n B_n Bn 的CRC值计算现在的 16bit [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 的RCR值呢?他们之间有什么关系呢?
我们继续推导这个公式
( B n × x 8 n + B n − 1 × x 8 ( n − 1 ) + ⋯ + B 0 × x 0 ) × x 16 G = ( Z [ n ] + Y [ n ] G ) × x 8 n + B n − 1 × x 8 ( n − 1 ) × x 16 G + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G = Z [ n ] × x 8 n + Y [ n ] × x 8 + B n − 1 × x 16 G × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G \begin{aligned} &\frac{(B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} + \cdots + B_0 \times x^0) \times x^{16}}{G}\\ &= (Z[n] + \frac{Y[n] }{G}) \times x^{8n} + \frac{B_{n-1} \times x^{8(n-1)} \times x^{16}}{G} + \frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G}\\ &= Z[n]\times x^{8n} + \frac{Y[n] \times x^{8} + B_{n-1} \times x^{16} }{G} \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G}\\ \end{aligned} G(Bn×x8n+Bn−1×x8(n−1)+⋯+B0×x0)×x16=(Z[n]+GY[n])×x8n+GBn−1×x8(n−1)×x16+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16=Z[n]×x8n+GY[n]×x8+Bn−1×x16×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16
在第二步的时候,直接计算法就已经推导出来了。 [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 左移16位除以多项式的余数,也就是下面的式子的余数缩小 x 8 ( n − 1 ) x^{8(n-1)} x8(n−1) 倍(因为实际求得是 B n × x 8 n + B n − 1 × x 8 ( n − 1 ) B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} Bn×x8n+Bn−1×x8(n−1) 即 [ B n , B n − 1 , 0000 , 0000 , ⋯ , 0000 ] [B_n,B_{n-1},0000, 0000, \cdots ,0000] [Bn,Bn−1,0000,0000,⋯,0000] 后面会进一步讲解):
( B n × x 8 n + B n − 1 × x 8 ( n − 1 ) ) × x 16 G = ( Z [ n ] + Y [ n ] G ) × x 8 n + B n − 1 × x 8 ( n − 1 ) × x 16 G = Z [ n ] × x 8 n + Y [ n ] × x 8 + B n − 1 × x 16 G × x 8 ( n − 1 ) \begin{aligned} &\frac{(B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)}) \times x^{16}}{G}\\ &= (Z[n] + \frac{Y[n] }{G}) \times x^{8n} + \frac{B_{n-1} \times x^{8(n-1)} \times x^{16}}{G} \\ &= Z[n]\times x^{8n} + \frac{Y[n] \times x^{8} + B_{n-1} \times x^{16} }{G} \times x^{8(n-1)} \\ \end{aligned} G(Bn×x8n+Bn−1×x8(n−1))×x16=(Z[n]+GY[n])×x8n+GBn−1×x8(n−1)×x16=Z[n]×x8n+GY[n]×x8+Bn−1×x16×x8(n−1)
这里的余数还没求完,继续把 Y [ n ] × x 8 + B n − 1 × x 16 G \frac{Y[n] \times x^{8} + B_{n-1} \times x^{16}}{G} GY[n]×x8+Bn−1×x16 算完就是最终的结果了。设 Y [ n ] × x 8 + B n − 1 × x 16 G = P [ n − 1 ] + Q [ n − 1 ] G \frac{Y[n] \times x^{8} + B_{n-1} \times x^{16}}{G} = P[n-1] + \frac{Q[n-1]}{G} GY[n]×x8+Bn−1×x16=P[n−1]+GQ[n−1] 则:
( B n × x 8 n + B n − 1 × x 8 ( n − 1 ) ) × x 16 G = ( Z [ n ] + Y [ n ] G ) × x 8 n + B n − 1 × x 8 ( n − 1 ) × x 16 G = Z [ n ] × x 8 n + ( P [ n − 1 ] + Q [ n − 1 ] G ) × x 8 ( n − 1 ) = ( Z [ n − 1 ] + Y [ n − 1 ] G ) × x 8 ( n − 1 ) \begin{aligned} &\frac{(B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)}) \times x^{16}}{G}\\ &= (Z[n] + \frac{Y[n] }{G}) \times x^{8n} + \frac{B_{n-1} \times x^{8(n-1)} \times x^{16}}{G} \\ &= Z[n]\times x^{8n} + (P[n-1] + \frac{Q[n-1]}{G}) \times x^{8(n-1)} \\ &= (Z[n-1] + \frac{Y[n-1]}{G}) \times x^{8(n-1)} \\ \end{aligned} G(Bn×x8n+Bn−1×x8(n−1))×x16=(Z[n]+GY[n])×x8n+GBn−1×x8(n−1)×x16=Z[n]×x8n+(P[n−1]+GQ[n−1])×x8(n−1)=(Z[n−1]+GY[n−1])×x8(n−1)
其中 :
Z [ n − 1 ] = Z [ n ] × x 8 + P [ n − 1 ] Z[n-1] = Z[n]\times x^{8} + P[n-1] Z[n−1]=Z[n]×x8+P[n−1]
Y [ n − 1 ] = Q [ n − 1 ] Y[n-1] = Q[n-1] Y[n−1]=Q[n−1]
这样依此类推,有了 [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 的 CRC值 Y [ n − 1 ] Y[n-1] Y[n−1],我们就可以递推求出 [ B n , B n − 1 , B n − 2 ] [B_n,B_{n-1},B_{n-2}] [Bn,Bn−1,Bn−2] 的CRC值,直到最后一个字节结束。
注意 我们并不关心 商 是多少,在代码中也不会去关心,我们只需要维护和递推余数即可。
每次求新的CRC值就是做这样的一个除法求余数 :
Y [ n ] × x 8 + B n − 1 × x 16 G \frac{Y[n] \times x^{8} + B_{n-1} \times x^{16} }{G} GY[n]×x8+Bn−1×x16
变一下形式:
( Y [ n ] + B n − 1 × x 8 ) × x 8 G \frac{ (Y[n] + B_{n-1} \times x^{8}) \times x^{8} }{G} G(Y[n]+Bn−1×x8)×x8
也就是说,我们要将上一次的CRC值加上当前的数据左移8位,相加的结果整体左移8位右侧补8个0。然后除以 G 求余数。
这个过程不就是For循环里面的做的吗?只不过这里被除数和除数都是 16 bit,但是同样只需要左移8次即可。实在难以理解就可以手推一下。For循环里面就是算余数的,一共移几位就是求原始数据后面补几位之后的余数。
CRC-16 计算
代码:
/******************************************************************************
* Name: CRC-16/XMODEM x16+x12+x5+1
* Poly: 0x1021
* Init: 0x0000
* Refin: False
* Refout: False
* Xorout: 0x0000
* Alias: CRC-16/ZMODEM,CRC-16/ACORN
*****************************************************************************/
uint16_t crc16_xmodem(uint8_t *data, uint16_t length)
{
uint8_t i;
uint16_t crc = 0; // Initial value
while(length--)
{
crc ^= (uint16_t)(*data++) << 8; // crc ^= (uint16_t)(*data)<<8; data++;
for (i = 0; i < 8; ++i)
{
if ( crc & 0x8000 )
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}
}
return crc;
}
crc16的计算使用了 uint16_t 的数据类型存放每一个数据的 crc-16 值。但是注意,我们依然是每次只取出 1 byte
的数据进行计算。
CRC-8计算
同理,我们可以计算 CRC-8。只要把上面公式里面的 × x 16 \times x^{16} ×x16 改成 × x 8 \times x^{8} ×x8 即可,因为 CRC-8 是左移8位再求余数嘛:
最终for循环里面算的就是:
( Y [ n ] + B n − 1 ) × x 8 G \frac{ (Y[n] + B_{n-1}) \times x^{8} }{G} G(Y[n]+Bn−1)×x8
下面是代码:
uint8_t crc8(uint8_t *data, uint16_t length)
{
uint8_t i;
uint8_t crc = 0; // Initial value
while(length--)
{
crc ^= *data++; // crc ^= *data; data++;
for ( i = 0; i < 8; i++ )
{
if ( crc & 0x80 )
crc = (crc << 1) ^ 0x07;
else
crc <<= 1;
}
}
return crc;
}
CRC-4计算
同理,我们可以计算 CRC-4。只要把上面公式里 改成 × x 4 \times x^{4} ×x4 即可,因为 CRC-4 是左移4位再求余数嘛:
最终for循环里面算的就是:
( Y [ n ] × x 4 + B n − 1 ) × x 4 G \frac{ (Y[n] \times x^{4} + B_{n-1}) \times x^{4} }{G} G(Y[n]×x4+Bn−1)×x4
#include<stdio.h>
#include<stdint.h>
uint8_t crc4(uint8_t *buffer, uint32_t len)
{
// 同样 CRC-4 的POLY只有4位,但是C语言中没有恰好占4位的数据类型,所以用 uint8_t 的高四位
uint8_t crc = 0; // 由于 INIT = 0 所以crc初始值为0
uint8_t data = 0;
uint8_t poly = 0x09 << 4; // 左移4位,因为CRC使用uint8_t的高4位
while (len--)
{
// 依次取出每一个byte(8位)的数据
data = *(buffer++); //注意++的使用,这一句其实相当于 先进行 data = *buffer; 后 buffer++;
crc = data^crc;
for (size_t i = 0; i < 8; i++)
{
if (crc&0x80)
crc = (crc << 1) ^ poly;
else
crc = (crc << 1);
// 由于我们把算完的CRC-4放到了高4位,低4位一直是0,这就相当于自动左移4位了
}
}
return crc;
}
void main()
{
uint8_t buf[] = "1234";
printf("crc is : 0x%x", crc4(buf, 4)>>4);
}
六、查表法
原理
我们继续推导上面的 CRC-16
的公式:
( B n × x 8 n + B n − 1 × x 8 ( n − 1 ) + ⋯ + B 0 × x 0 ) × x 16 G = ( Z [ n ] + Y [ n ] G ) × x 8 n + B n − 1 × x 8 ( n − 1 ) × x 16 G + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G = Z [ n ] × x 8 n + Y [ n ] × x 8 + B n − 1 × x 16 G × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G = Z [ n ] × x 8 n + ( Y [ n ] H 8 × x 8 + Y [ n ] L 8 ) × x 8 + B n − 1 × x 16 G × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G = Z [ n ] × x 8 n + Y [ n ] L 8 × x 8 + ( Y [ n ] H 8 + B n − 1 ) × x 16 G × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G \begin{aligned} &\frac{(B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} + \cdots + B_0 \times x^0) \times x^{16}}{G}\\ &= (Z[n] + \frac{Y[n] }{G}) \times x^{8n} + \frac{B_{n-1} \times x^{8(n-1)} \times x^{16}}{G} + \frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G}\\ &= Z[n]\times x^{8n} + \frac{Y[n] \times x^{8} + B_{n-1} \times x^{16} }{G} \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G}\\ &= Z[n]\times x^{8n} + \frac{(Y[n]_{H8} \times x^{8} +Y[n]_{L8}) \times x^{8} + B_{n-1} \times x^{16} }{G} \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G}\\ &= Z[n]\times x^{8n} + \frac{Y[n]_{L8} \times x^{8} + (Y[n]_{H8} + B_{n-1}) \times x^{16}}{G} \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G} \end{aligned} G(Bn×x8n+Bn−1×x8(n−1)+⋯+B0×x0)×x16=(Z[n]+GY[n])×x8n+GBn−1×x8(n−1)×x16+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16=Z[n]×x8n+GY[n]×x8+Bn−1×x16×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16=Z[n]×x8n+G(Y[n]H8×x8+Y[n]L8)×x8+Bn−1×x16×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16=Z[n]×x8n+GY[n]L8×x8+(Y[n]H8+Bn−1)×x16×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16
我们仔细观察这一项: Y [ n ] L 8 × x 8 + ( Y [ n ] H 8 + B n − 1 ) × x 16 G \frac{Y[n]_{L8} \times x^{8} + (Y[n]_{H8} + B_{n-1}) \times x^{16}}{G} GY[n]L8×x8+(Y[n]H8+Bn−1)×x16
它可以分为两部分:
Y [ n ] L 8 × x 8 + ( Y [ n ] H 8 + B n − 1 ) × x 16 G = Y [ n ] L 8 × x 8 G + ( Y [ n ] H 8 + B n − 1 ) × x 16 G \begin{aligned} &\frac{Y[n]_{L8} \times x^{8} + (Y[n]_{H8} + B_{n-1}) \times x^{16}}{G} \\ &= \frac{Y[n]_{L8} \times x^{8}}{G} + \frac{ (Y[n]_{H8} + B_{n-1}) \times x^{16}}{G} \end{aligned} GY[n]L8×x8+(Y[n]H8+Bn−1)×x16=GY[n]L8×x8+G(Y[n]H8+Bn−1)×x16
第二部分又是一个求CRC-16
的形式(“右侧补16个0然后除以G”这样的形式),但是注意它不是对
B
n
−
1
B_{n-1}
Bn−1 求,而是对
(
Y
[
n
]
H
8
+
B
n
−
1
)
(Y[n]_{H8} + B_{n-1})
(Y[n]H8+Bn−1) 求 CRC-16
。
(
Y
[
n
]
H
8
+
B
n
−
1
)
(Y[n]_{H8} + B_{n-1})
(Y[n]H8+Bn−1)是一个八位的数据,对它求CRC很容易,设:
( Y [ n ] H 8 + B n − 1 ) × x 16 G = Z [ n − 1 ] + Y [ n − 1 ] G \frac{ (Y[n]_{H8} + B_{n-1}) \times x^{16}}{G} = Z[n-1] + \frac{Y[n-1]}{G} G(Y[n]H8+Bn−1)×x16=Z[n−1]+GY[n−1]
Z
[
n
−
1
]
Z[n-1]
Z[n−1] 是商,
Y
[
n
−
1
]
Y[n-1]
Y[n−1] 是余数,也就是
(
Y
[
n
]
H
8
+
B
n
−
1
)
(Y[n]_{H8} + B_{n-1})
(Y[n]H8+Bn−1)的 CRC16
校验码。
把这个东西代回去:
( B n × x 8 n + B n − 1 × x 8 ( n − 1 ) + ⋯ + B 0 × x 0 ) × x 16 G = Z [ n ] × x 8 n + Y [ n ] L 8 × x 8 + ( Y [ n ] H 8 + B n − 1 ) × x 16 G × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G = Z [ n ] × x 8 n + ( Y [ n ] L 8 × x 8 G + Z [ n − 1 ] + Y [ n − 1 ] G ) × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G = ( Z [ n ] × x 8 + Z [ n − 1 ] ) × x 8 ( n − 1 ) + Y [ n ] L 8 × x 8 + Y [ n − 1 ] G × x 8 ( n − 1 ) + ( B n − 2 × x 8 ( n − 2 ) + ⋯ + B n × x 0 ) × x 16 G \begin{aligned} &\frac{(B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} + \cdots + B_0 \times x^0) \times x^{16}}{G}\\ &= Z[n]\times x^{8n} + \frac{Y[n]_{L8} \times x^{8} + (Y[n]_{H8} + B_{n-1}) \times x^{16}}{G} \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G}\\ &= Z[n]\times x^{8n} + (\frac{Y[n]_{L8} \times x^{8}}{G} + Z[n-1] + \frac{Y[n-1]}{G}) \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G} \\ &= (Z[n]\times x^{8} + Z[n-1]) \times x^{8(n-1)} + \frac{Y[n]_{L8} \times x^{8} + Y[n-1]}{G} \times x^{8(n-1)} +\frac{( B_{n-2} \times x^{8(n-2)} + \cdots + B_n \times x^0) \times x^{16}}{G} \\ \end{aligned} G(Bn×x8n+Bn−1×x8(n−1)+⋯+B0×x0)×x16=Z[n]×x8n+GY[n]L8×x8+(Y[n]H8+Bn−1)×x16×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16=Z[n]×x8n+(GY[n]L8×x8+Z[n−1]+GY[n−1])×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16=(Z[n]×x8+Z[n−1])×x8(n−1)+GY[n]L8×x8+Y[n−1]×x8(n−1)+G(Bn−2×x8(n−2)+⋯+Bn×x0)×x16
显然, [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 左移16位并除以多项式G之后, Z [ n ] × x 8 + Z [ n − 1 ] Z[n]\times x^{8} + Z[n-1] Z[n]×x8+Z[n−1] 就是商,余数就是 Y [ n ] L 8 × x 8 + Y [ n − 1 ] Y[n]_{L8} \times x^{8} + Y[n-1] Y[n]L8×x8+Y[n−1]
注:
公式推导里面,在商和余数后面还跟着个 × x 8 ( n − 1 ) \times x^{8(n-1)} ×x8(n−1) ,因为实际上求CRC是对 B n × x 8 n + B n − 1 × x 8 ( n − 1 ) B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} Bn×x8n+Bn−1×x8(n−1) 也就是 [ B n , B n − 1 , 00000000 , ⋯ , 0000 ] [B_n,B_{n-1}, 0000 0000, \cdots ,0000] [Bn,Bn−1,00000000,⋯,0000] 求的,其实是放大了 x 8 ( n − 1 ) x^{8(n-1)} x8(n−1) 倍的,所以商和余数也要放大这么多倍,虽然现在的余数不符合小于除数的规则,但是无所谓,因为我们不在乎 B n × x 8 n + B n − 1 × x 8 ( n − 1 ) B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} Bn×x8n+Bn−1×x8(n−1) 的余数是多少,我们只在乎 B n × x 8 + B n − 1 B_n \times x^{8} + B_{n-1} Bn×x8+Bn−1 也就是 [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 的余数。
举个十进制的例子,我们知道了 1100 / 10 = 100 ⋯ 100 1100/10 =100 \cdots 100 1100/10=100⋯100 ,虽然这个余数根本不符合小学老师教的规则,但是我们很容易就可以写出 11 / 10 = 1 ⋯ 1 11/10 =1 \cdots 1 11/10=1⋯1,这才是我们在乎的,它只要对上面的商和余数同时除以100即可。
在这里也是同样的道理:
- 我们知道了 B n × x 8 n + B n − 1 × x 8 ( n − 1 ) B_n \times x^{8n} + B_{n-1} \times x^{8(n-1)} Bn×x8n+Bn−1×x8(n−1) (或者写成 [ B n , B n − 1 , 0000 , 0000 , ⋯ , 0000 ] [B_n,B_{n-1}, 0000,0000, \cdots,0000] [Bn,Bn−1,0000,0000,⋯,0000]) 它左移16位除以多项式的“余数”(暂且称之为余数,其实是不符合要求的)就等于 ( Y [ n ] L 8 × x 8 + Y [ n − 1 ] ) × x 8 ( n − 1 ) (Y[n]_{L8} \times x^{8} + Y[n-1]) \times x^{8(n-1)} (Y[n]L8×x8+Y[n−1])×x8(n−1)
- 除以
x
8
(
n
−
1
)
x^{8(n-1)}
x8(n−1),可以很容易的知道
B
n
×
x
8
+
B
n
−
1
B_n \times x^{8} + B_{n-1}
Bn×x8+Bn−1 (也就是
[
B
n
,
B
n
−
1
]
[B_n,B_{n-1}]
[Bn,Bn−1]) 左移16位除以多项式的余数是
Y
[
n
]
L
8
×
x
8
+
Y
[
n
−
1
]
Y[n]_{L8} \times x^{8} + Y[n-1]
Y[n]L8×x8+Y[n−1],这也就是其
CRC16
的值
结论:
这样就推导出, [ B n , B n − 1 ] [B_n,B_{n-1}] [Bn,Bn−1] 的CRC16校验码是由两部分异或的结果:
-
- Y [ n ] L 8 × x 8 Y[n]_{L8} \times x^{8} Y[n]L8×x8 上一次求出的CRC的低8位左移8位
-
-
Y
[
n
−
1
]
Y[n-1]
Y[n−1] 对 上一次求出的CRC的高8位和本字节异或的结果 (即:
(
Y
[
n
]
H
8
+
B
n
−
1
)
(Y[n]_{H8} + B_{n-1})
(Y[n]H8+Bn−1)) 求得的
CRC-16
码
-
Y
[
n
−
1
]
Y[n-1]
Y[n−1] 对 上一次求出的CRC的高8位和本字节异或的结果 (即:
(
Y
[
n
]
H
8
+
B
n
−
1
)
(Y[n]_{H8} + B_{n-1})
(Y[n]H8+Bn−1)) 求得的
我们不难发现,每次求 CRC-16
都是对一个 8 位的数据求,所以一共有 256种可能,可以做成一张表,或者说一个大数组。数组的索引就是输入的 8 位的数据,数组的值就是对它求得的 CRC16
CRC-16
字节型算法查表法过程如下:
1)初始化 16bit CRC寄存器,初始化为全"0"(0x0000)。(注意:CRC寄存器组初始化全为1时,最后CRC应取反。)
2)CRC寄存器组向左移8位,并保存到CRC寄存器组。
3)原CRC寄存器组高8位(右移8位)与数据字节进行异或运算,得出一个8bit的指向值表的索引。
4)索引所指的表值与CRC寄存器值做异或运算。
5)数据指针加1,如果数据没有全部处理完,则重复步骤。
6)得出CRC。
CRC-16查表法
看一个CRC-16/T10-DIF的例子:
#include <stdio.h>
#include <stdint.h>
// 预先计算的CRC-16/T10-DIF查找表 (多项式0x8BB7)
static const uint16_t CRC16_T10DIF_TABLE[256] = {
0x0000, 0x8BB7, 0x9CD9, 0x171E, 0xB205, 0x39B2, 0x2EDC, 0xA56B,
0xEFBD, 0x640A, 0x7364, 0xF8D3, 0x5DC8, 0xD67F, 0xC111, 0x4AA6,
0xC3E7, 0x4850, 0x5F3E, 0xD489, 0x71B2, 0xFA05, 0xED6B, 0x66DC,
0x2C0A, 0xA7BD, 0xB0D3, 0x3B64, 0x9E7F, 0x15C8, 0x02A6, 0x8911,
0x3C0B, 0xB7BC, 0xA0D2, 0x2B65, 0x8E7E, 0x05C9, 0x12A7, 0x9910,
0xD3C6, 0x5871, 0x4F1F, 0xC4A8, 0x61B3, 0xEA04, 0xFD6A, 0x76DD,
0xFF9C, 0x742B, 0x6345, 0xE8F2, 0x4DE9, 0xC65E, 0xD130, 0x5A87,
0x1061, 0x9BD6, 0x8CB8, 0x070F, 0xA214, 0x29A3, 0x3ECD, 0xB57A,
0x7816, 0xF3A1, 0xE4CF, 0x6F78, 0xCA63, 0x41D4, 0x56BA, 0xDD0D,
0x97DB, 0x1C6C, 0x0B02, 0x80B5, 0x258E, 0xAE39, 0xB957, 0x32E0,
0xBBA1, 0x3016, 0x2778, 0xACC7, 0x09DC, 0x826B, 0x9505, 0x1EB2,
0x5464, 0xDFD3, 0xC8BD, 0x430A, 0xE611, 0x6DA6, 0x7AC8, 0xF17F,
0x4405, 0xCFB2, 0xD8DC, 0x536B, 0xF670, 0x7DC7, 0x6AA9, 0xE11E,
0xABC8, 0x207F, 0x3711, 0xBCA6, 0x19BD, 0x920A, 0x8564, 0x0ED3,
0x8792, 0x0C25, 0x1B4B, 0x90FC, 0x35E7, 0xBE50, 0xA93E, 0x2289,
0x686F, 0xE3D8, 0xF4B6, 0x7F01, 0xDA1A, 0x51AD, 0x46C3, 0xCD74,
0xF02C, 0x7B9B, 0x6CF5, 0xE742, 0x4259, 0xC9EE, 0xDEA0, 0x5517,
0x1FC1, 0x9476, 0x8318, 0x08AF, 0xADB4, 0x2603, 0x316D, 0xBAD0,
0x3391, 0xB826, 0xAF48, 0x24FF, 0x81E4, 0x0A53, 0x1D3D, 0x968A,
0xDC5C, 0x57EB, 0x40A5, 0xCB12, 0x6E09, 0xE5BE, 0xF2D0, 0x7967,
0xCC7D, 0x47CA, 0x50A4, 0xDB13, 0x7E08, 0xF5BF, 0xE2D1, 0x6966,
0x23B0, 0xA807, 0xBF69, 0x34DE, 0x91C5, 0x1A72, 0x0D1C, 0x86AB,
0x0FEA, 0x845D, 0x9333, 0x1884, 0xBDBF, 0x3608, 0x2166, 0xAAD1,
0xE007, 0x6BB0, 0x7CDE, 0xF769, 0x5272, 0xD9C5, 0xCEA9, 0x451E,
0x8832, 0x0385, 0x14EB, 0x9F5C, 0x3A47, 0xB1F0, 0xA69E, 0x2D29,
0x67FF, 0xEC48, 0xFB26, 0x7091, 0xD58A, 0x5E3D, 0x4953, 0xC2E4,
0x4BA5, 0xC012, 0xD77C, 0x5CCB, 0xF9D0, 0x7267, 0x6509, 0xEEBE,
0xA468, 0x2FDF, 0x38B1, 0xB306, 0x161D, 0x9DAA, 0x8AC4, 0x0173,
0xB469, 0x3FDE, 0x28B0, 0xA307, 0x061C, 0x8DAB, 0x9AC5, 0x1172,
0x5BA4, 0xD013, 0xC77D, 0x4CCA, 0xE9D1, 0x6266, 0x7508, 0xFEBF,
0x77FE, 0xFC49, 0xEB27, 0x6090, 0xC58B, 0x4E3C, 0x5952, 0xD2E5,
0x9823, 0x1394, 0x04FA, 0x8F4D, 0x2A56, 0xA1E1, 0xB68F, 0x3D38
};
// 计算CRC-16/T10-DIF的函数
uint16_t calculate_crc16_t10dif(const uint8_t *data, size_t length) {
uint16_t crc = 0x0000; // 初始化CRC
for (size_t i = 0; i < length; ++i) {
crc = (crc << 8) ^ CRC16_T10DIF_TABLE[((crc >> 8) ^ data[i]) & 0xFF];
}
return crc;
}
int main() {
// 示例数据
uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
size_t length = sizeof(data) / sizeof(data[0]);
// 计算CRC-16/T10-DIF
uint16_t crc = calculate_crc16_t10dif(data, length);
// 打印结果
printf("CRC-16/T10-DIF: 0x%04X\n", crc);
return 0;
}
核心代码是这一句:
crc = (crc << 8) ^ CRC16_T10DIF_TABLE[((crc >> 8) ^ data[i]) & 0xFF];`
(crc >> 8) ^ data[i]
就是表索引(需要查表计算CRC的数据)
CRC16_T10DIF_TABLE[((crc >> 8) ^ data[i])
就是(crc >> 8) ^ data[i]
的CRC值
& 0xFF
的目的在于确保结果在0到255之间(虽然在这个情况下已经是0-255之间的值,但这是一个保险操作),因为查找表CRC16_T10DIF_TABLE有256个条目。
CRC-8 查表法
对于CRC-8来说,我们可以不用上面推导的公式那么麻烦。
仔细观察 CRC-8 直接法 for
循环里面做的事情,不难发现它的输入和输出结果其实只有256种可能。因为输入是个 8bit 的数据。
对于CRC-8来说, for
循环是在算 1 byte
数据的 CRC 值。1 byte
的范围是 0~255
。也就是说,这个for
循环的输入和输出结果只有256种可能,256个byte
的内存的消耗完全可以接受。
我们完全可以做一个数组把这256种可能存起来,每个数据的索引就是输入,值就是输出。
以计算CRC-8为例 POLY = 0x07
对 0x00 求 CRC-8 结果是 0x00
对 0x01 求 CRC-8 结果是 0x07
对 0x02 求 CRC-8 结果是 0x0E
....
写成一个大数组:
0x00 0x07 0x0e 0x09 0x1c 0x1b 0x12 0x15
0x38 0x3f 0x36 0x31 0x24 0x23 0x2a 0x2d
0x70 0x77 0x7e 0x79 0x6c 0x6b 0x62 0x65
0x48 0x4f 0x46 0x41 0x54 0x53 0x5a 0x5d
...........省略
0xde 0xd9 0xd0 0xd7 0xc2 0xc5 0xcc 0xcb
0xe6 0xe1 0xe8 0xef 0xfa 0xfd 0xf4 0xf3
可以在这个网站直接查询和生成CRC表 :
https://crccalc.com/?crc=123456789&method=crc8&datatype=0&outtype=0
在有了CRC表之后,for
循环中的事情其实就可以用查表代替了,对于任意一个输入值(十进制)
N
(
N
∈
[
0
,
255
]
)
N (N \in [0,255])
N(N∈[0,255]) ,直接找表中索引为
N
N
N 的位置的值就是对应的CRC值。
#include <stdio.h>
#include <stdint.h>
// 预先计算的CRC-8查找表 (多项式0x07)
static const uint8_t CRC8_TABLE[256] = {
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
};
// 计算CRC-8的函数
uint8_t calculate_crc8(const uint8_t *data, size_t length) {
uint8_t crc = 0x00; // 初始化CRC
for (size_t i = 0; i < length; ++i) {
crc = CRC8_TABLE[crc ^ data[i]]; // 查表计算新的CRC值
}
return crc;
}
int main() {
// 示例数据
uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
size_t length = sizeof(data) / sizeof(data[0]);
// 计算CRC-8
uint8_t crc = calculate_crc8(data, length);
// 打印结果
printf("CRC-8: 0x%02X\n", crc);
return 0;
}
参考
模二运算在线计算网址
https://www.23bei.com/tool/744.html
CRC码和CRC表在线生成网址
https://crccalc.com/?crc=123456789&method=crc8&datatype=0&outtype=0
大佬写的CRC函数库-C语言版本
https://github.com/whik/crc-lib-c