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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM