循環冗余校驗
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);
}
計算結果:
計算資源結果: