由於本人的腦子比較笨,根本看不懂文獻關於CRC的講解,被博士女友罵了說智商低 不配看論文 不能像博士那樣能死磕論文。於是自己琢磨加上網上大神的文章一步一步弄出了CRC的原理 下面和大家一起分享。
參考網站:http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html
首先講模2除法
【說明】“模2除法”與“算術除法”類似,但它既不向上位借位,也不比較除數和被除數的相同位數值的大小,只要以相同位數進行相除即可。模2加法運算為:1+1=0,0+1=1,0+0=0,無進位,也無借位;模2減法運算為:1-1=0,0-1=1,1-0=1,0-0=0,也無進位,無借位。相當於二進制中的邏輯異或運算。也就是比較后,兩者對應位相同則結果為“0”,不同則結果為“1”。如100101除以1110,結果得到商為11,余數為1,如圖5-9左圖所示。如11×11=101,如圖5-9右圖所示。
這個是CRC校驗的基礎。我們的CRC校驗就是以這個為基礎的來進行計算的。
2. 多項式 我們可以把一個二進制用一個多項式來進行表示:
例如 0x25 = 00100101就可以變成0*x7 + 0*x6 + 1*x5 + 0*x4 + 0*x3 + 1*x2 + 0*x1 + 1*x0
就可以變成 x5 + x2 + 1
3. CRC的校驗過程:
在發送端要進行CRC的加密過程:
1. 二進制的信息首先根據CRC的多項式的長度在后面加入相應的0 (比如CRC8 就是加上8個0 CRC16就是加上16個0) 模2除以 上面給定的多項式(一會兒給出多項式) 然后求出最后的余數 把余數append到這個信息的最后 加密完成
在接收端:
1. 接收到的數據首先模2的除以CRC多項式 如果求出的CRC為0 則說明驗證成功。
我們首先看下面的例子:下面的例子給出了一個信息和CRC-8之間在發送端加密的過程:
閱讀上面的英文我們發現CRC的多項式是由9個二進制組成的,我們后面會講第一個1是可以忽略的。
由此我們就得到了CRC的值是0x0F 然后把這個值放到信息的最后 當接收端收到數據之后進行模2的處理 如果得到0 就說明驗證成功。
底下是接收端的計算過程請看圖:
4.CRC的移位寄存器的概念:
為了模仿這個模2的過程 我們使用CRC寄存器的概念 這個寄存器是把傳遞的信息放入,對於CRC8 來說里面可以一次性放8位 對於CRC16 來說可以放置16位 對於CRC32 來說可以放置32位信息(二進制信息),我們在上圖看到了其實信息是一位一位的從高位到低位和多項式進行比較的。先比較的是bit7 bit6 bit5 bit4 bit3 bit2 bit1 然后比較bit6 bit5 bit4 bit3 bit2 bit1 bit-1(表示后面新進入的數據)
5.完成CRC-8的程序。
我們下面開始完成CRC8的程序,CRC8 一共是9位 但是其實最高位是可以去掉的,因為他一定是1 而且每次他都是和信息的是1 的最高位對齊(下面的圖會告訴你是怎么對齊的)所以每次的異或總是0
我們假設信息是0xC2 多項式是0x1D 那么程序可以寫成如下的形式:
public static byte Compute_CRC8_Simple_OneByte_ShiftReg(byte byteVal) { const byte generator = 0x1D; byte crc = 0; /* init crc register with 0 */ /* append 8 zero bits to the input byte */ byte[] inputstream = new byte[] { byteVal, 0x00 }; /*the 0 presenting in the array is to append at the end of the valid information*/ /* handle each bit of input stream by iterating over each bit of each input byte */ foreach (byte b in inputstream) { for (int i = 7; i >= 0; i--) { /* check if MSB is set */ if ((crc & 0x80) != 0) { /* MSB set, shift it out of the register */ crc = (byte)(crc << 1); //請注意一上來的時候CRC等於0 所以第一次循環的之后CRC還是等於0
/* shift in next bit of input stream: * If it's 1, set LSB of crc to 1. * If it's 0, set LSB of crc to 0. */ crc = ((byte)(b & (1 << i)) != 0) ? (byte)(crc | 0x01) : (byte)(crc & 0xFE); /* Perform the 'division' by XORing the crc register with the generator polynomial */ crc = (byte)(crc ^ generator); //第一次循環的時候CRC 就是b的值 然后異或上poly的值 } else { /* MSB not set, shift it out and shift in next bit of input stream. Same as above, just no division */ crc = (byte)(crc << 1); crc = ((byte)(b & (1 << i)) != 0) ? (byte)(crc | 0x01) : (byte)(crc & 0xFE); } } } return crc; }
6 CRC8的改進:
我們發現一上來就把CRC的值給成信息的值 那么就不用一上來就移位了而且由於移位的時候信息后面會自動不零 所以我們也不用手工在后面補零了。於是程序變成了
public static byte Compute_CRC8_Simple_OneByte(byte byteVal) { const byte generator = 0x1D; byte crc = byteVal; /* init crc directly with input byte instead of 0, avoid useless 8 bitshifts until input byte is in crc register */ for (int i = 0; i < 8; i++) { if ((crc & 0x80) != 0) { /* most significant bit set, shift crc register and perform XOR operation, taking not-saved 9th set bit into account */ crc = (byte)((crc << 1) ^ generator); } else { /* most significant bit not set, go to next bit */ crc <<= 1; } } return crc; }
處理過程如下:
其中有一個問題就是為什么這里要先移位然后再進行異或?
回答:因為CRC8 是九位的(雖然在程序里面是8位的,我們其實是省去了最靠前的那一位)但是如圖所示我們每一次MSB都是會異或得0 所以其實是我們同時把信息的最高位和多項式的多高位一起去掉了。所以每一次信息都是先移位在異或。
7. 多個字節的CRC校驗:
上面我們都是校驗了一個字節 那么多個字節怎么做?
1. 請看下圖,描述了多個字節的校驗:
我們會發現其實他的過程是首先和第一個字節異或 然后和第二個字節異或。
下面是代碼:
我們已經學會了CRC的基本原理,可是上面的復雜度太高 我們需要優化代碼:
我們需要建立一個表來進行查詢。原理 因為每一個byte都是和poly異或 然后再以此異或到一起 所以我們先求出(0~255)和poly異或的結果 然后再讓程序去查表。
程序如下:
public static void CalulateTable_CRC8() { const byte generator = 0x1D; crctable = new byte[256]; /* iterate over all byte values 0 - 255 */ for (int divident = 0; divident < 256; divident++) { byte currByte = (byte)divident; /* calculate the CRC-8 value for current byte */ for (byte bit = 0; bit < 8; bit++) { if ((currByte & 0x80) != 0) { currByte <<= 1; currByte ^= generator; } else { currByte <<= 1; } } /* store CRC value in lookup table */ crctable[divident] = currByte; } }
public static byte Compute_CRC8(byte[] bytes) { byte crc = 0; foreach (byte b in bytes) { /* XOR-in next input byte */ byte data = (byte)(b ^ crc); /* get current CRC value = remainder */ crc = (byte)(crctable[data]); } return crc; }
CRC16:
我們學會了CRC8的校驗 如何進行CRC16呢?其實就是把每個字節的信息向左位移8位 然后再進行計算:
代碼如下:
public static ushort Compute_CRC16_Simple(byte[] bytes) { const ushort generator = 0x1021; /* divisor is 16bit */ ushort crc = 0; /* CRC value is 16bit */ foreach (byte b in bytes) { crc ^= (ushort(b << 8); /* move byte into MSB of 16bit CRC */ for (int i = 0; i < 8; i++) { if ((crc & 0x8000) != 0) /* test for MSB = bit 15 */ { crc = (ushort((crc << 1) ^ generator); } else { crc <<= 1; } } } return crc; }
CRC16 制表的程序:
public static void CalculateTable_CRC16() { const ushort generator = 0x1021; crctable16 = new ushort[256]; for (int divident = 0; divident < 256; divident++) /* iterate over all possible input byte values 0 - 255 */ { ushort curByte = (ushort(divident << 8); /* move divident byte into MSB of 16Bit CRC */ for (byte bit = 0; bit < 8; bit++) { if ((curByte & 0x8000) != 0) { curByte <<= 1; curByte ^= generator; } else { curByte <<= 1; } } crctable16[divident] = curByte; } }
CRC-8 Shift Register Example: Input data = 0xC2 = b11000010 (with 8 zero bits appended: b1100001000000000), Polynomial = b100011101
1. CRC-8 register initialized with 0. --- --- --- --- --- --- --- --- | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | <-- b1100001000000000 --- --- --- --- --- --- --- --- 2. Left-Shift register by one position. MSB is 0, so nothing do happen, shift in next byte of input stream. --- --- --- --- --- --- --- --- | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | <-- b100001000000000 --- --- --- --- --- --- --- --- 3. Repeat those steps. All steps are left out until there is a 1 in the MSB (nothing interesting happens), then the state looks like: --- --- --- --- --- --- --- --- | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | <-- b00000000 --- --- --- --- --- --- --- --- 4. Left-Shift register. MSB 1 pops out: --- --- --- --- --- --- --- --- 1 <- | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | <-- b0000000 --- --- --- --- --- --- --- --- So XOR the CRC register (with popped out MSB) b110000100 with polynomial b100011101 = b010011001 = 0x99. The MSB is discarded, so the new CRC register value is 010011001: --- --- --- --- --- --- --- --- | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | <-- b0000000 --- --- --- --- --- --- --- --- 5. Left-Shift register. MSB 1 pops out: b100110010 ^ b100011101 = b000101111 = 0x2F: --- --- --- --- --- --- --- --- | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | <-- b000000 --- --- --- --- --- --- --- --- 6. Left-shift register until a 1 is in the MSB position: --- --- --- --- --- --- --- --- | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | <-- b0000 --- --- --- --- --- --- --- --- 7. Left-Shift register. MSB 1 pops out: b101111000 ^ b100011101 = b001100101 = 0x65: --- --- --- --- --- --- --- --- | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | <-- b000 --- --- --- --- --- --- --- --- 8. Left-shift register until a 1 is in the MSB position: --- --- --- --- --- --- --- --- | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | <-- b00 --- --- --- --- --- --- --- --- 9. Left-Shift register. MSB 1 pops out: b110010100 ^ b100011101 = b010001001 = 0x89: --- --- --- --- --- --- --- --- | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | <-- b0 --- --- --- --- --- --- --- --- 10. Left-Shift register. MSB 1 pops out: b10001001 ^ b100011101 = b000001111 = 0x0F: --- --- --- --- --- --- --- --- | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | <-- <empty> --- --- --- --- --- --- --- --- All input bits are processed, the algorithm stops. The shift register contains now the CRC value which is 0x0F.