CRC校验算法
循环冗余校验
CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
参数
每种CRC校验算法有不同的参数值,需要具体对待。
-
NAME:参数模型名称。
-
WIDTH:宽度,即CRC比特数。 通常有8bit,16bit,24bit,32bit等。
-
POLY:生成项的简写,以16进制表示。例如:CRC-32即是0x04C11DB7,忽略了最高位的"1",即完整的生成项是0x104C11DB7。
-
INIT:这是算法开始时寄存器(crc)的初始化预置值,十六进制表示。 Init 的位数和Poly的位数相同,它的值为全0或者全F,当全为0时,在算法开始前对数据(这个数据是根据RefIn的值得到的)后面补上CRC位数个0后就可以进行后续计算了。当全为1时,表示在算法开始前对数据的前CRC位数(高位)先和对应位数个1进行异或(即:前CRC位数的值按位取反),再在后面补上CRC位数个0,才进行后续计算。
例:INIT为FFFF,原数据为1101 1010 0100 1010 1001 0001 ,那么先异或FFFF ^ 1101 1010 0100 1010 1001 0001得到0010 0101 1011 0101 1001 0001 后面添加16个零进行计算0010 0101 1011 0101 1001 0001 0000 0000 0000 0000。
INIT为0000,直接在数据后添加16个零计算。
-
REFIN:待测数据的每个字节是否按位反转,True或False。注意是按字节反转,例如:0001 1001 0010 1010得到的值是1001 1000 0101 0100。
-
REFOUT:在计算后之后,异或输出之前,整个数据是否按位反转,True或False。例如:0001 1001 0010 1010得到的值是0101 0100 1001 1000。
-
RefIn和Refout:它们要么全为False,要么全为True。
-
XOROUT:计算结果与此参数异或后得到最终的CRC值,即计算后得到的值与XOROUT异或得到最终的值。例如计算后得到的值为0100 1000,XOROUT为1111 1111,那么最终的CRC码为1011 0111。
模二运算
模2除法与算术除法类似,但每一位除的结果不影响其它位,即不向上一位借位,所以实际上就是异或。在循环冗余校验码(CRC)的计算中有应用到模2除法。
模2除法具有下列三个性质:
1、当最后余数的位数小于除数位数时,除法停止。
2、当被除数的位数小于除数位数时,则商数为0,被除数就是余数。
3、只要被除数或部分余数的位数与除数一样多,且最高位为1,不管其他位是什么数,皆可商1。
算法原理
对数据对于原数据D进行计算后得到一个校验码F,得到一个新的数据T,接收方接收到数据后在进行校验即可。
特别的,循环冗余校验提供一个预先设定的(n-k+1)比特整数P,并且要求添加的(n-k)比特F满足:
其中 \(T =2^{n-k}D + F\)
操作步骤:
- 发送方和接收方在通信前,约定好预设整数P。
- 发送方在发送前根据数据D确定满足(1)式的F,生成CRC码 T,T 即为数据位D与校验位F的拼接,发送T。
- 接收方收到CRC码 T,进行 result = T mod P 运算,当且仅当result = 0时接收方认为没有差错。
校验码求值
原始数据为10110011,序列P值为11001,求CRC校验码值
- 需要在原数据添加少一个的位数,P值为五位数,所以添加四个零。
- 对数据进行模二运算
得到校验码0100,发送数据为101100110100。
模二除法进行判断是否为0,如果为0,表示数据正确。
证明过程
发送方在发送数据前需要确定填充的(n-k)比特F。一下所有的mod是指模二运算的余数。
CRC码 T 需要满足(1)式,即 \((2^{n-k}D+F)/P\) 结果为某一整数
对此表达式进行恒等变换,可得:
继续对等式中\(2^{n-k}D / P\)进行恒等变换,将其整数部分 Q 分离,即 \(Q=(2^{n-k}D - R)/P\),有
将(3)式带入(2)式 得到:
由于采用无进位的二进制加法(等价于XOR操作),由于是模二运算,所以当我们令 F = R 时,有R+F=0,所以\(R/P+F/P=0\):
当Q为整数时,\(T =(2^{n-k}D + F)\)满足\(T mod P == 0\)。
故我们只要找到 F = R 使得(3)式中 Q 恒为整数即可。
由 \(Q=(2^{n-k}D - R)/P\),可知z
(1)当 \(2^{n-k}DmodP ≠ 0\) 时
\(R=2^{n-k}D mod P\) 可使等式恒成立。
(2)当 \(2^{n-k}D modP == 0\) 时
$F = R = n * P(n ∈ Z) $可使等式恒成立。
\(R=2^{n-k}D modP\) 即为 n = 0 时情况。
综上,令\(R=2^{n-k}D modP\) 时 可使等式 \(Q=(2^{n-k}D - R)/P\) 中Q恒为整数。
因此我们需要添加的帧检验序列F为:
多项式公式
对于除数P可以使用多项式表示,其中0不表示,1表示为\(X^y\)。
例如:\(G(x) = X^5+X4+1\) 可以表示为11001的除数。转换成十六进制并去除最高位则是多项式公示的表示。
x8 + x5 + x4 + 1对应的P值为100110001对应的十六进制131,去除最高位为31.
CRC算法名称 | 多项式公式 | 宽度 | 多项式 | 初始值 | 结果异或值 | 输入反转 | 输出反转 |
---|---|---|---|---|---|---|---|
CRC-4/ITU | x4 + x + 1 | 4 | 03 | 00 | 00 | true | true |
CRC-5/EPC | x5 + x3 + 1 | 5 | 09 | 09 | 00 | false | false |
CRC-5/ITU | x5 + x4 + x2 + 1 | 5 | 15 | 00 | 00 | true | true |
CRC-5/USB | x5 + x2 + 1 | 5 | 05 | 1F | 1F | true | true |
CRC-6/ITU | x6 + x + 1 | 6 | 03 | 00 | 00 | true | true |
CRC-7/MMC | x7 + x3 + 1 | 7 | 09 | 00 | 00 | false | false |
CRC-8 | x8 + x2 + x + 1 | 8 | 07 | 00 | 00 | false | false |
CRC-8/ITU | x8 + x2 + x + 1 | 8 | 07 | 00 | 55 | false | false |
CRC-8/ROHC | x8 + x2 + x + 1 | 8 | 07 | FF | 00 | true | true |
CRC-8/MAXIM | x8 + x5 + x4 + 1 | 8 | 31 | 00 | 00 | true | true |
CRC-16/IBM | x16 + x15 + x2 + 1 | 16 | 8005 | 0000 | 0000 | true | true |
CRC-16/MAXIM | x16 + x15 + x2 + 1 | 16 | 8005 | 0000 | FFFF | true | true |
CRC-16/USB | x16 + x15 + x2 + 1 | 16 | 8005 | FFFF | FFFF | true | true |
CRC-16/MODBUS | x16 + x15 + x2 + 1 | 16 | 8005 | FFFF | 0000 | true | true |
CRC-16/CCITT | x16 + x12 + x5 + 1 | 16 | 1021 | 0000 | 0000 | true | true |
CRC-16/CCITT-FALSE | x16 + x12 + x5 + 1 | 16 | 1021 | FFFF | 0000 | false | false |
CRC-16/X25 | x16 + x12 + x5 + 1 | 16 | 1021 | FFFF | FFFF | true | true |
CRC-16/XMODEM | x16 + x12 + x5 + 1 | 16 | 1021 | 0000 | 0000 | false | false |
CRC-16/DNP | x16 + x13 + x12 + x11 + x10 + x8 + x6 + x5 + x2 + 1 | 16 | 3D65 | 0000 | FFFF | true | true |
CRC-32 | 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 | x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1 | 32 | 04C11DB7 | FFFFFFFF | 00000000 | false | false |
查表法
按位计算,每一位都需要计算,同时还需要判断首位是否为0,这样对于我们而言计算量还是挺大的,所以提供一种查表法,可以一次运算出多个位的模二运算的结果。
表中所列的索引是指前n位的数据大小。
已知\((aXORb)XORc = aXOR(bXORc)\)
由公式可得,任意多个数据之间的异或计算和计算顺序无关。
对于任意定义的poly(除数)F,假设F有n位,那么我们取需要计算校验值的前2(n-1)位,那么前2(n-1)位计算过程相当于异或运算,当高位为1为才可能进行计算。
对于XXXX XXXX可以使用XXXX 0000来进行计算出相对应的余数Z,然后再用余数Z和后四位XXXX来和Z进行异或运算,这样或得到的数就是前n-1位的余数。
例如:一个poly为1 0011(\(X^4+X^1+1\)),那么求其数据表。
由于前n-1位和poly对表有关系。所以对于原始数据前4位的是0000B的时候,那么余数位0000B。这就是表中的第一个元素。
0001B的时候,0001 0000B/10011,那么得到结果为 0011B
0010B的时候,0010 0000B/10011,那么得到结果为 0110B
0011B的时候,0011 0000B/10011,那么得到结果为 0101B。
0100B的时候,0100 0000B/10011,那么得到结果为 1100B。
0101B的时候,0100 0000B/10011,那么得到结果为 1111B。
0110B的时候,0110 0000B/10011,那么得到结果为 1010B。
0111B的时候,0111 0000B/10011,那么得到结果为 1001B。
1000B的时候,1000 0000B/10011,那么得到结果为 1011B。
。。。
索引 | 原数据前4位 | 值 |
---|---|---|
0 | 0000B | 0000B |
1 | 0001B | 0011B |
2 | 0010B | 0110B |
3 | 0011B | 0101B |
4 | 0100B | 1100B |
5 | 0101B | 1111B |
6 | 0110B | 1010B |
7 | 0111B | 1001B |
8 | 1000B | 1011B |
9 | 1001B | 1000B |
10 | 1010B | 1101B |
11 | 1011B | 1110B |
12 | 1100B | 0111B |
13 | 1101B | 0100B |
14 | 1110B | 0001B |
15 | 1111B | 0010B |
如何根据表来计算CRC校验码呢?
例如:有一个原始数据0011 1110,poly=10011,求校验码
已知10011的数据表,原始数据位0011 1110,左移四位,那么就是0011 1110 0000,计算前四位0011 由表可得位0101B,和1110B异或计算得1011B,由表可得1110B,1110^0000=1110,所以校验码位1110.
CRC16
CRC16有多种算法,以CRC-16/MODBUS为例:公式项为:\(x^{16} + x^{15} + x^2 + 1\),初始值为FFFF,结果异或值为0000,输入反转和输出反转为true。假设原数据为AE 03 D3 F1 2D,求其CRC16的校验码。
-
输入反转
AE 03 D3 F1 2D => 1010 1110 0000 0011 1101 0011 1111 0001 0010 1101。
1010 1110 0000 0011 1101 0011 1111 0001 0010 1101 按字节反转得:
0111 0101 1100 0000 1100 1011 1000 1111 1011 0100.
-
和初始值异或
0111 0101 1100 0000 1100 1011 1000 1111 1011 0100 ^ 1111 1111 1111 1111 =
1000 1010 0011 1111 1100 1011 1000 1111 1011 0100 后位添加16个零
1000 1010 0011 1111 1100 1011 1000 1111 1011 0100 0000 0000 0000 0000
-
得到余数
\(x^{16} + x^{15} + x^2 + 1\) 得到poly值为 1 1000 0000 0000 0101
得到余数为 1001 1101 0001 0111
-
输出反转
1001 1101 0001 0111 反转得到 1110 1000 1011 1001
-
与结果异或值异或
异或值为0000,所以不变,所以CRC码为E8B9
-
结果与网上一致
上述过程可以看作:
(1)预置1个16位的寄存器为十六进制FFFF(即全为1),称此寄存器为CRC寄存器;
(2)把第一个8位二进制数据(既通讯信息帧的第一个字节)与16位的CRC寄存器的低
8位相异或,把结果放于CRC寄存器,高八位数据不变;
(3)把CRC寄存器的内容右移一位(朝低位)用0填补最高位,并检查右移后的移出位;
(4)如果移出位为0:重复第3步(再次右移一位);如果移出位为1,CRC寄存器与多
项式A001(1010 0000 0000 0001)进行异或;
(5)重复步骤3和4,直到右移8次,这样整个8位数据全部进行了处理;
(6)重复步骤2到步骤5,进行通讯信息帧下一个字节的处理;
(7)将该通讯信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低
字节进行交换;
(8)最后得到的CRC寄存器内容即为:CRC码。
下图为CRC16计算过程
CRC16/MODBUS码表生成
码表是在除法使用,如果每16位进行计算的话,会生成256*256的码表,所以每次进行8位的计算,那么就只会生成256位的码表。
由于输入数据有反转,所以我们在计算码表的时候需要使用1 1000 0000 0000 0101翻转进行模二运算,这样相当于正常情况的镜像操作。
由于REFIN=true,REFOUT=true,使用16位寄存器方法计算码表:
或者使用原始的计算方法,但是在最终获取后需要翻转:
CRC16码表生成代码,使用寄存器法
图例如下:
private static void calcCRC16(){
// poly翻转,1010 0000 0000 0001
int refPoly = 0xA001;
String[] crc16 = new String[256];
// 计算每个码表的元素
for(int i = 0 ; i < 256 ; i++){
// 寄存器数据
int temp = i | 0;
// 每个元素最多计算8次
for (int j = 0; j < 8; j++) {
boolean isOne = Integer.toBinaryString(temp).endsWith("1");
temp = temp >> 1;
if(isOne)
temp = temp ^ refPoly;
}
String hexStr = Integer.toHexString(temp);
int length = hexStr.length();
switch(length){
case 1:
hexStr = "000"+hexStr;
break;
case 2:
hexStr = "00"+hexStr;
break;
case 3:
hexStr = "0"+hexStr;
break;
default:
break;
}
crc16[i] = hexStr.toUpperCase();
}
System.out.println(Arrays.toString(crc16));
}
输出结果:
[0000, C0C1, C181, 0140, C301, 03C0, 0280, C241, C601, 06C0, 0780, C741, 0500, C5C1, C481, 0440, CC01, 0CC0, 0D80, CD41, 0F00, CFC1, CE81, 0E40, 0A00, CAC1, CB81, 0B40, C901, 09C0, 0880, C841, D801, 18C0, 1980, D941, 1B00, DBC1, DA81, 1A40, 1E00, DEC1, DF81, 1F40, DD01, 1DC0, 1C80, DC41, 1400, D4C1, D581, 1540, D701, 17C0, 1680, D641, D201, 12C0, 1380, D341, 1100, D1C1, D081, 1040, F001, 30C0, 3180, F141, 3300, F3C1, F281, 3240, 3600, F6C1, F781, 3740, F501, 35C0, 3480, F441, 3C00, FCC1, FD81, 3D40, FF01, 3FC0, 3E80, FE41, FA01, 3AC0, 3B80, FB41, 3900, F9C1, F881, 3840, 2800, E8C1, E981, 2940, EB01, 2BC0, 2A80, EA41, EE01, 2EC0, 2F80, EF41, 2D00, EDC1, EC81, 2C40, E401, 24C0, 2580, E541, 2700, E7C1, E681, 2640, 2200, E2C1, E381, 2340, E101, 21C0, 2080, E041, A001, 60C0, 6180, A141, 6300, A3C1, A281, 6240, 6600, A6C1, A781, 6740, A501, 65C0, 6480, A441, 6C00, ACC1, AD81, 6D40, AF01, 6FC0, 6E80, AE41, AA01, 6AC0, 6B80, AB41, 6900, A9C1, A881, 6840, 7800, B8C1, B981, 7940, BB01, 7BC0, 7A80, BA41, BE01, 7EC0, 7F80, BF41, 7D00, BDC1, BC81, 7C40, B401, 74C0, 7580, B541, 7700, B7C1, B681, 7640, 7200, B2C1, B381, 7340, B101, 71C0, 7080, B041, 5000, 90C1, 9181, 5140, 9301, 53C0, 5280, 9241, 9601, 56C0, 5780, 9741, 5500, 95C1, 9481, 5440, 9C01, 5CC0, 5D80, 9D41, 5F00, 9FC1, 9E81, 5E40, 5A00, 9AC1, 9B81, 5B40, 9901, 59C0, 5880, 9841, 8801, 48C0, 4980, 8941, 4B00, 8BC1, 8A81, 4A40, 4E00, 8EC1, 8F81, 4F40, 8D01, 4DC0, 4C80, 8C41, 4400, 84C1, 8581, 4540, 8701, 47C0, 4680, 8641, 8201, 42C0, 4380, 8341, 4100, 81C1, 8081, 4040]
CRC16使用码表计算校验码
图例如下:
java代码示例:
private static void testCRC16(){
int[] crc16 = {0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040};
// 原数据
String str = "05 05";
String[] data = str.split(" ");
int[] dataHex = new int[data.length];
for (int i = 0; i < data.length; i++) {
dataHex[i] = Integer.parseInt(data[i],16);
}
// 寄存器初始数据
int temp = 0xffff;
for (int i = 0; i < dataHex.length; i++) {
// 低八位异或
temp = dataHex[i] ^ temp;
// 根据低八位获取角标,直接八位运算
int index = temp & 0x00ff;
// 将高八位和码表中的值进行异或
temp = ((temp & 0xff00) >> 8) ^ crc16[index];
}
// 输出crc校验码
String crc = Integer.toHexString(temp);
switch(crc.length()){
case 1:
crc = "000"+crc.toUpperCase();
break;
case 2:
crc = "00"+crc.toUpperCase();
break;
case 3:
crc = "0"+crc.toUpperCase();
break;
default:
crc = crc.toUpperCase();
break;
}
System.out.println(crc);
}
计算结果:
计算资源结果: