能看懂的CRC

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章