CRC,Cyclic Redundancy Check,中文稱爲“循環冗餘校驗”。
它的應用很廣,一般常見的說法都是用於通信。其實,在壓縮、解壓文件的時候,也普遍用到了它。
另外,單片機系統在掉電時,一般都要把當前有用的狀態信息,保存在 EEPROM 中,爲了保證信息的正確,也可以用 CRC 來檢驗。
1.CRC 的作用
還是用通信來說明 CRC 的作用。
由於線路上的干擾,通信時可能會有錯碼,那麼當接收方收到了信息,怎麼來確認這些信息就是正確的呢?
爲了確認通信的正確性,發送方還要在信息之後,再發送一組“校驗碼”,這些校驗碼,是用前面的信息數據算出來的。
接收方收到信息數據和校驗碼之後,再按照同樣的算法來計算,如果結果符合規則,就認爲這次數據傳輸是正確的。
2.CRC 的計算規則
設信息數據 M(x)有 k 位,後面的 CRC 校驗碼爲 r 位,那麼總共發送的位數則爲 n = k + r 位,因此,這種編碼又叫(n, k)碼。
利用 k 位信息碼,求取 r 位 CRC 碼時,需要先確定一個“生成多項式 G(x)”。
G(x) 也是一個二進制數,要求最高位和最低位都是 1 纔行。
在不同的標準裏面,G(x)的數值是不同的。常見的 G(x) 如下:
CRC-CCITT: G(x) = X16 + X12 + X5 + 1 = 1 0001 0000 0010 0001 = 1 1021H
CRC-16: G(x) = X16 + X15 + X2 + 1 = 1 1000 0000 0000 0101 = 1 8005H
CRC-32: G(x) = X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1
= 1 0000 0100 1100 0001 0001 1101 1011 0111 = 04C1 1DB7H
選定了 G(x) 之後,就可以用信息數據 M(x) 除以 G(x),得出的餘數 R(x) 就是 r 位的 CRC 碼。
注意,這裏使用除法是“模2除”,其實就是“異或”算法。
把 M(x) 和 r 位的 CRC 碼合併在一起(即(n, k)碼),再除以 G(x),得出的餘數就是 0。
如果這組(n, k)碼中含有錯誤,再除以 G(x),得出的餘數就不是 0。
根據餘數的狀態,就可以判定傳送的數據信息是否正確。
3.CRC 的算法舉例
例:已知信息碼爲 M(x) = 1100,生成多項式 G(x) = x3 + x + 1,求 CRC 碼,及(n, k)碼。
解:
根據 G(x) = x3 + x + 1,可知除數爲 1011;餘數(CRC 碼)應爲 3 位二進制數。
那麼,先在 M(x) 後面填寫上 3 個 0,即爲:1100 000,再用它“模2除” 1011。
現在填寫的這些 0,將來是要用 CRC 碼來填充,組成的(n, k)碼的。這個步驟也可以寫成:
CRC 碼 = M(x) * x3 / G(x)
乘以 x3,就代表把 M(x) 左移 3 位,後面填上 3 個 0。 /,代表“模2除”。
計算的豎式如下圖的左式:
在此得出的餘數 R(x) = 010 即爲 CRC 碼。
那麼,(n, k)碼 = M(x) * x3 + R(x) = 1100 000 + 010 = 1100 010。
4.CRC 的驗證
還是利用上面的例題來說明驗證的方法。
當收到的代碼是:1100 010,按照同樣的方法求餘數,豎式如上圖中的右式。
餘數爲 0,就說明數據傳輸是正確的。
5.用 CRC 的檢錯
當有一位數是錯誤的,模2除的餘數,就不是 0 了,可見下面的豎式:
可以看出,發生錯誤的位置不同,餘數也不同。
使用不同的信息碼 M(x) 來實驗,能夠看出,錯誤位置和餘數的關係是固定的,不會因爲信息碼的不同而變化。
如果知道了錯誤的位置,是不是就可以糾錯呢?不可,因爲難以確定錯誤是不是僅僅有一位,錯了更多的位,餘數並不會對此有所表現。
因此,有人說利用 CRC 的方法可以糾錯,這是不對的,CRC 只能用於檢錯,不能用於糾錯。
CRC 檢錯的能力和生成多項式的位數有關,位數越多,檢錯的概率越高。
6.實用的 CRC 計算步驟
前面介紹的 CRC 算法,僅僅用於說明基本概念,實用時的計算,規模要大的多。
最常用的形式是:數據是 8 位數,CRC 校驗碼是 16 位數。
下面就詳細描述一下從一個字節數據求出 16 位數 CRC 碼的過程,從而得出編程的思路。
設 8 位的信息數據是 0x4A,G(x) 選用 CRC-CCITT,即 1 0001 0000 0010 0001 = 1 1021H,求 16 位的 CRC 豎式如下:
從豎式的計算過程中可以看出以下的特點:
(1)異或運算進行了多次,是從數據的高位到低位,依次判斷進行的;
(2)信息數據中,有一個 1,就進行一次異或 11021H、是 0 就移位,判斷下一位;
信息數據中,只有 3 個 1,但是有一次的異或,在信息數據的範圍中,異或出來一個 1,
對於這個 1,也要進行一次異或 11021H 的運算,所以共進行了 4 次異或;
(3)異或後保留下來的餘數,實際上僅僅是用 1021H 異或的結果;
(4)16 個 0,是從左邊到右邊,逐個參加運算。
7.實用的 CRC 彙編程序
看懂了上述的步驟,編寫程序就是輕而易舉的了。下面是用 51 單片機的彙編語言編寫的 CRC 程序。
ORG 0000H
MOV R2, P2 ;從P2輸入數據,假設是4A
CALL CRC ;求取CRC
MOV P0, R2 ;輸出CRC的高8位 E9
MOV P1, R3 ;輸出CRC的低8位 8E
NOP
SJMP $
;------------------------------------
CRC:
MOV R3, #0 ;CRC低8位
MOV R4, #8
C1: CLR C
MOV A, R3 ;R2R3左移一位
RLC A
MOV R3, A
MOV A, R2
RLC A
MOV R2, A
JNC C_NEXT ;移出位爲0就轉移
XRL A, #10H ;爲1就異或1021H
MOV R2, A
MOV A, R3
XRL A, #21H
MOV R3, A
C_NEXT:
DJNZ R4, C1
RET
;------------------------------------
END
呵呵,上述程序,僅僅用了 30 字節,執行時間平均爲 114 個機器週期。
8.求取 CRC 的 C 程序
用 C 語言編程就可以規模大一些,藉助計算機的屏幕,可以顯示更多的內容。
下面就是顯示數據00~FF 的全部 CRC 代碼的程序。
//===============================================
#include<stdio.h>
#include<stdlib.h>
void main(void)
{
unsigned char i;
unsigned int j, crc;
//--------------------------------------下面循環求出CRC
for(j = 0; j < 256; j++) { //8位數據00~FF
crc = 0; //每個數據的16位的CRC
for(i = 0x80; i != 0; i >>= 1) { //對8位數據從高位到低位進行判斷
if((crc & 0x8000) != 0) {
crc <<= 1; crc ^= 0x1021;
}
else crc = crc << 1;
if((j & i) != 0) crc ^= 0x1021;
}
//--------------------------------------計算完畢,下面進行顯示
if (j % 4 == 0) printf("\n");
if (j % 32 == 0) system("pause");
printf("0x%02X: 0x%04x, ", j, crc & 0xFFFF);
}
}
//===============================================
上述程序執行後,顯示的結果如下:
0x00: 0x0000, 0x01: 0x1021, 0x02: 0x2042, 0x03: 0x3063,
0x04: 0x4084, 0x05: 0x50a5, 0x06: 0x60c6, 0x07: 0x70e7,
0x08: 0x8108, 0x09: 0x9129, 0x0A: 0xa14a, 0x0B: 0xb16b,
0x0C: 0xc18c, 0x0D: 0xd1ad, 0x0E: 0xe1ce, 0x0F: 0xf1ef,
0x10: 0x1231, 0x11: 0x0210, 0x12: 0x3273, 0x13: 0x2252,
0x14: 0x52b5, 0x15: 0x4294, 0x16: 0x72f7, 0x17: 0x62d6,
0x18: 0x9339, 0x19: 0x8318, 0x1A: 0xb37b, 0x1B: 0xa35a,
0x1C: 0xd3bd, 0x1D: 0xc39c, 0x1E: 0xf3ff, 0x1F: 0xe3de,
0x20: 0x2462, 0x21: 0x3443, 0x22: 0x0420, 0x23: 0x1401,
0x24: 0x64e6, 0x25: 0x74c7, 0x26: 0x44a4, 0x27: 0x5485,
0x28: 0xa56a, 0x29: 0xb54b, 0x2A: 0x8528, 0x2B: 0x9509,
0x2C: 0xe5ee, 0x2D: 0xf5cf, 0x2E: 0xc5ac, 0x2F: 0xd58d,
0x30: 0x3653, 0x31: 0x2672, 0x32: 0x1611, 0x33: 0x0630,
0x34: 0x76d7, 0x35: 0x66f6, 0x36: 0x5695, 0x37: 0x46b4,
0x38: 0xb75b, 0x39: 0xa77a, 0x3A: 0x9719, 0x3B: 0x8738,
0x3C: 0xf7df, 0x3D: 0xe7fe, 0x3E: 0xd79d, 0x3F: 0xc7bc,
0x40: 0x48c4, 0x41: 0x58e5, 0x42: 0x6886, 0x43: 0x78a7,
0x44: 0x0840, 0x45: 0x1861, 0x46: 0x2802, 0x47: 0x3823,
0x48: 0xc9cc, 0x49: 0xd9ed, 0x4A: 0xe98e, 0x4B: 0xf9af,
0x4C: 0x8948, 0x4D: 0x9969, 0x4E: 0xa90a, 0x4F: 0xb92b,
0x50: 0x5af5, 0x51: 0x4ad4, 0x52: 0x7ab7, 0x53: 0x6a96,
0x54: 0x1a71, 0x55: 0x0a50, 0x56: 0x3a33, 0x57: 0x2a12,
0x58: 0xdbfd, 0x59: 0xcbdc, 0x5A: 0xfbbf, 0x5B: 0xeb9e,
0x5C: 0x9b79, 0x5D: 0x8b58, 0x5E: 0xbb3b, 0x5F: 0xab1a,
0x60: 0x6ca6, 0x61: 0x7c87, 0x62: 0x4ce4, 0x63: 0x5cc5,
0x64: 0x2c22, 0x65: 0x3c03, 0x66: 0x0c60, 0x67: 0x1c41,
0x68: 0xedae, 0x69: 0xfd8f, 0x6A: 0xcdec, 0x6B: 0xddcd,
0x6C: 0xad2a, 0x6D: 0xbd0b, 0x6E: 0x8d68, 0x6F: 0x9d49,
0x70: 0x7e97, 0x71: 0x6eb6, 0x72: 0x5ed5, 0x73: 0x4ef4,
0x74: 0x3e13, 0x75: 0x2e32, 0x76: 0x1e51, 0x77: 0x0e70,
0x78: 0xff9f, 0x79: 0xefbe, 0x7A: 0xdfdd, 0x7B: 0xcffc,
0x7C: 0xbf1b, 0x7D: 0xaf3a, 0x7E: 0x9f59, 0x7F: 0x8f78,
0x80: 0x9188, 0x81: 0x81a9, 0x82: 0xb1ca, 0x83: 0xa1eb,
0x84: 0xd10c, 0x85: 0xc12d, 0x86: 0xf14e, 0x87: 0xe16f,
0x88: 0x1080, 0x89: 0x00a1, 0x8A: 0x30c2, 0x8B: 0x20e3,
0x8C: 0x5004, 0x8D: 0x4025, 0x8E: 0x7046, 0x8F: 0x6067,
0x90: 0x83b9, 0x91: 0x9398, 0x92: 0xa3fb, 0x93: 0xb3da,
0x94: 0xc33d, 0x95: 0xd31c, 0x96: 0xe37f, 0x97: 0xf35e,
0x98: 0x02b1, 0x99: 0x1290, 0x9A: 0x22f3, 0x9B: 0x32d2,
0x9C: 0x4235, 0x9D: 0x5214, 0x9E: 0x6277, 0x9F: 0x7256,
0xA0: 0xb5ea, 0xA1: 0xa5cb, 0xA2: 0x95a8, 0xA3: 0x8589,
0xA4: 0xf56e, 0xA5: 0xe54f, 0xA6: 0xd52c, 0xA7: 0xc50d,
0xA8: 0x34e2, 0xA9: 0x24c3, 0xAA: 0x14a0, 0xAB: 0x0481,
0xAC: 0x7466, 0xAD: 0x6447, 0xAE: 0x5424, 0xAF: 0x4405,
0xB0: 0xa7db, 0xB1: 0xb7fa, 0xB2: 0x8799, 0xB3: 0x97b8,
0xB4: 0xe75f, 0xB5: 0xf77e, 0xB6: 0xc71d, 0xB7: 0xd73c,
0xB8: 0x26d3, 0xB9: 0x36f2, 0xBA: 0x0691, 0xBB: 0x16b0,
0xBC: 0x6657, 0xBD: 0x7676, 0xBE: 0x4615, 0xBF: 0x5634,
0xC0: 0xd94c, 0xC1: 0xc96d, 0xC2: 0xf90e, 0xC3: 0xe92f,
0xC4: 0x99c8, 0xC5: 0x89e9, 0xC6: 0xb98a, 0xC7: 0xa9ab,
0xC8: 0x5844, 0xC9: 0x4865, 0xCA: 0x7806, 0xCB: 0x6827,
0xCC: 0x18c0, 0xCD: 0x08e1, 0xCE: 0x3882, 0xCF: 0x28a3,
0xD0: 0xcb7d, 0xD1: 0xdb5c, 0xD2: 0xeb3f, 0xD3: 0xfb1e,
0xD4: 0x8bf9, 0xD5: 0x9bd8, 0xD6: 0xabbb, 0xD7: 0xbb9a,
0xD8: 0x4a75, 0xD9: 0x5a54, 0xDA: 0x6a37, 0xDB: 0x7a16,
0xDC: 0x0af1, 0xDD: 0x1ad0, 0xDE: 0x2ab3, 0xDF: 0x3a92,
0xE0: 0xfd2e, 0xE1: 0xed0f, 0xE2: 0xdd6c, 0xE3: 0xcd4d,
0xE4: 0xbdaa, 0xE5: 0xad8b, 0xE6: 0x9de8, 0xE7: 0x8dc9,
0xE8: 0x7c26, 0xE9: 0x6c07, 0xEA: 0x5c64, 0xEB: 0x4c45,
0xEC: 0x3ca2, 0xED: 0x2c83, 0xEE: 0x1ce0, 0xEF: 0x0cc1,
0xF0: 0xef1f, 0xF1: 0xff3e, 0xF2: 0xcf5d, 0xF3: 0xdf7c,
0xF4: 0xaf9b, 0xF5: 0xbfba, 0xF6: 0x8fd9, 0xF7: 0x9ff8,
0xF8: 0x6e17, 0xF9: 0x7e36, 0xFA: 0x4e55, 0xFB: 0x5e74,
0xFC: 0x2e93, 0xFD: 0x3eb2, 0xFE: 0x0ed1, 0xFF: 0x1ef0,
Press any key to continue
//===============================================
9.求取數組的 CRC
實際的數據傳輸,往往是要把幾十個字節的信息數據,求出一個 16 位的 CRC 碼,再把它附在信息的後面進行傳送。
網上可以找到這樣的 C 程序,做而論道加上了一些註釋,如下所示:
//===============================================
unsigned char test[16] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
unsigned char len = 16;
//----------------------------
unsigned int crc16l(unsigned char *ptr, unsigned char len)
{ //ptr 爲數據指針,len 爲數據長度
unsigned char i;
unsigned int crc = 0;
while(len--) { //按照數據的個數進行循環
for(i = 0x80; i != 0; i >>= 1) { //由高位到低位
if((crc & 0x8000) != 0) { crc <<= 1; crc ^= 0x1021; }
else crc <<= 1;
if((*ptr & i) != 0 ) crc ^= 0x1021;//針對數據進行處理
}
ptr++; //下一個數據
}
return(crc);
}
//----------------------------
void main(void)
{
printf("0x%04x", crc16l(test, len) & 0xFFFF);
}
//===============================================
程序運行後,將顯示出來:0x1248,這就是數組中 16 個字節數據的 CRC 碼。
10.在 51 單片機中的 CRC 應用
上述的 C 程序,也可以移植到單片機系統中,但是 C 程序的速度和佔用空間就不敢恭維了。
針對同樣一個字節(即0x4F)進行處理的時候,C 程序使用的時間是 263 T,大約是彙編程序的 2.5 倍 !
當然,也可以採用查表的方法,速度就可以提高几十倍,但是表格佔用的空間,也是很可惜的。
所以,做而論道關於這方面的應用程序,都是用匯編語言編寫的。
這種程序,雖然執行的效率較高,但是篇幅卻較長。恐怕多數人都沒有耐心看,所以做而論道的程序,也就不在此處公佈了。
http://hi.baidu.com/do_sermon/item/6eb87a5425d25baeacc85783
http://hi.baidu.com/do_sermon/item/9aecc41ac7bd6ef387ad4e83