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除法。

img

模2除法具有下列三个性质:

1、当最后余数的位数小于除数位数时,除法停止。

2、当被除数的位数小于除数位数时,则商数为0,被除数就是余数。

3、只要被除数或部分余数的位数与除数一样多,且最高位为1,不管其他位是什么数,皆可商1。

算法原理

对数据对于原数据D进行计算后得到一个校验码F,得到一个新的数据T,接收方接收到数据后在进行校验即可。

img

特别的,循环冗余校验提供一个预先设定的(n-k+1)比特整数P,并且要求添加的(n-k)比特F满足:

\[T mod P == 0 \]

其中 \(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值为五位数,所以添加四个零。
  • 对数据进行模二运算

img

得到校验码0100,发送数据为101100110100。

模二除法进行判断是否为0,如果为0,表示数据正确。

image-20210220145611689

证明过程

发送方在发送数据前需要确定填充的(n-k)比特F。一下所有的mod是指模二运算的余数。

CRC码 T 需要满足(1)式,即 \((2^{n-k}D+F)/P\) 结果为某一整数

对此表达式进行恒等变换,可得:

\[(2^{n-k}D + F)/P = 2^{n-k}D / P + F / P \]

继续对等式中\(2^{n-k}D / P\)进行恒等变换将其整数部分 Q 分离,即 \(Q=(2^{n-k}D - R)/P\),有

\[2^{n-k}D / P = Q + R / P \]

将(3)式带入(2)式 得到:

\[(2^{n-k}D + F) / P = Q + R / P+ F / P \]

由于采用无进位的二进制加法(等价于XOR操作),由于是模二运算,所以当我们令 F = R 时,有R+F=0,所以\(R/P+F/P=0\):

\[(2^{n-k}D + F) / P = Q + R / P+ F / P = Q \]

当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为:

\[F = R = 2^{n-k}D modP \]

多项式公式

对于除数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为才可能进行计算。

image-20210222165112337

对于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的校验码。

  1. 输入反转

    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.

  2. 和初始值异或

    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

  3. 得到余数

    \(x^{16} + x^{15} + x^2 + 1\) 得到poly值为 1 1000 0000 0000 0101

    image-20210223102302577

    得到余数为 1001 1101 0001 0111

  4. 输出反转

    1001 1101 0001 0111 反转得到 1110 1000 1011 1001

  5. 与结果异或值异或

    异或值为0000,所以不变,所以CRC码为E8B9

  6. 结果与网上一致

    image-20210223102222033

上述过程可以看作:

(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计算过程

image-20210224101539560

CRC16/MODBUS码表生成

码表是在除法使用,如果每16位进行计算的话,会生成256*256的码表,所以每次进行8位的计算,那么就只会生成256位的码表。

由于输入数据有反转,所以我们在计算码表的时候需要使用1 1000 0000 0000 0101翻转进行模二运算,这样相当于正常情况的镜像操作。

由于REFIN=true,REFOUT=true,使用16位寄存器方法计算码表:

image-20210224102454482

或者使用原始的计算方法,但是在最终获取后需要翻转:

image-20210224102542414

CRC16码表生成代码,使用寄存器法

图例如下:

image-20210224102454482

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使用码表计算校验码

图例如下:

image-20210224153345369

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);
}

计算结果:

image-20210224153850499

计算资源结果:

http://www.ip33.com/crc.html

image-20210224153754463

posted @ 2021-02-24 16:06  leevi-ding  阅读(4551)  评论(2编辑  收藏  举报