CRC校驗的概念及具體實現


概念

CRC(Cyclic redundancy check),循環冗余校驗
CRC校驗是用於檢測一幀數據發送是否正確,只有確認對錯的作用,並沒有糾錯的能力。
還有一點就是CRC校驗通過了,並不代表這個數據肯定就是正確的,只能說盡可能減少出錯的概率,當然
CRC錯了那么這個數據肯定是不正確的
而這個概率是跟CRC的位數相關,也跟選擇的多項式有關,大致可以理解為CRC8,就是1/(28),CRC16則是1/(216)以此類推。

對於檢驗一幀數據是否正確有很多算法,CRC只是其中的一種,SUM的形式也可以的,只是算法不同對於校驗結果的效果也是不一樣的,最好的效果是,每一位的變化都可以引起最終checksum的值發生較大的改變。引入除法計算是一種很好的方法,每一位發生改變對於最后的余數都會引起較大的變化。

多項式(Polynomical)

多項式即CRC除法的除數,而且多項式是總於高於CRCN中N的一位,這樣可以保證余數的位數與N相同。同時多項式也有好壞之分,區別就是在於出錯的概率,至於哪種多項式好一些,這個一般來說是數學家的事情,我們工程上拿過來用就好,而且一般的協議中也已經規定了這個CRC的多項式。
其實多項式只是一種表現方式,當然也可以直接用16進制表示
以CRC-CCITT為例

\[\displaystyle x^{16}+x^{12}+x^{5}+1 \]

也可以表示為0x1021

計算例子

引用別人文檔中的例子來說明CRC機制,如下是一個CRC4計算的例子

            1100001010 = Quotient (nobody cares about the quotient)
       _______________
10011 ) 11010110110000 = Augmented message (1101011011 + 0000)
=Poly   10011,,.,,....
        -----,,.,,....
         10011,.,,....
         10011,.,,....
         -----,.,,....
          00001.,,....
          00000.,,....
          -----.,,....
           00010,,....
           00000,,....
           -----,,....
            00101,....
            00000,....
            -----,....
             01011....
             00000....
             -----....
              10110...
              10011...
              -----...
               01010..
               00000..
               -----..
                10100.
                10011.
                -----.
                 01110
                 00000
                 -----
                  1110 = Remainder = THE CHECKSUM!!!!

先將要計算的后方填充相應位數的0(CRC4,4位),再對POLY進行求余操作,這個余數就是我們要的checksum
這個操作就是一個除法操作,只是在減的時候用XOR來代替減法,這樣就不要考慮進位借位的問題,而且XOR來代替減法也不會使CRC的效果變差,因為每一位的改變還是會引起checksum較大的變化。

計算方法

了解了CRC的原理,接下來就對CRC校驗進行計算,接下來的討論都以CRC16為模板。

一、直接計算法

這個方法就是根據CRC的定義,進行按位操作,先將數據移位,若高位是1的話,就將當前的CRC與poly進行XOR操作來更新當的CRC值,直至所有的數據被更新后,再在數據的結尾添加相應的0位來得到最終的CRC結果,對於CRC16來說這個零位就是2個字節。總體來說,這種實現方式是根據定義來的,比較好理解,不過運行速度有待提高。
示例代碼如下:

unsigned int poly = 0x11021;
unsigned char testData[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa};
unsigned short crcUpdate(unsigned short crc, unsigned char data)
{
    int i;
    unsigned short result;
    unsigned int tmp;
    tmp = crc;
    for(i = 0; i < 8; i++)
    {
        tmp <<= 1;
        if(data & 0x80)
        {
            tmp += 1;
        }
        data <<= 1;
        
        if(tmp & 0x10000)
        {
            tmp ^= poly;
        }
    }
    result = tmp &0xffff;
    
    return result;
}

unsigned short crcCheck(unsigned char *pData, unsigned char size)
{
    unsigned short result = 0;
    int i;
    
    for(i = 0; i < size; i++)
    {
        result = crcUpdate(result, *(pData+i));
    }
    //補零
    for(i = 0; i < 2; i++)
    {
        result = crcUpdate(result, 0);
    }
    return result;
}

void demo(void)
{
    printf("Result %04x\n",crcCheck(testData,10)); //print 0xd877
}

二、表驅動法(Table-Driven Implementation)

在直接計算的情況下,為了提高運行的速度別人又提出了用表驅動的方法(根據當前的值來查找相應的CRC的結果,再代入公式進行計算最終的結果)。換句話,直接計算是通過一次移一位的操作來進行,而表驅動法剛是采用一次移多位的形式來進行CRC計算,來提高運行速度。
原理XOR也是滿足交換律如下

\[(A\ xor\ B) \ xor\ C = A\ xor\ (B\ xor\ C) \]

根據這個交換律,我們可以先將POLY進行移位XOR,再將結果同最初的值來進行XOR,來得來相應的移位。
下面以CRC8來說明

                //以1位為單位為進行XOR
                                   1 0 1 1 1 0 0 0 
                  ________________________________
1 0 0 0 1 1 1 0 0/ 1 0 1 1 0 1 0 0 0 0 0 0 0 0 0 0
                   1 0 0 0 1 1 1 0 0
                     0 0 0 0 0 0 0 0 0
                       1 0 0 0 1 1 1 0 0
                         1 0 0 0 1 1 1 0 0
                           1 0 0 0 1 1 1 0 0
                             0 0 0 0 0 0 0 0 0
                               0 0 0 0 0 0 0 0 0
                                 0 0 0 0 0 0 0 0 0
                   -------------------------------
                                   1 0 1 0 0 0 0 0
                                   
                  //先將POLY的多次移位進行XOR                 
                   1 0 0 0 1 1 1 0 0
                     0 0 0 0 0 0 0 0 0
                       1 0 0 0 1 1 1 0 0
                         1 0 0 0 1 1 1 0 0
                           1 0 0 0 1 1 1 0 0
                             0 0 0 0 0 0 0 0 0
                               0 0 0 0 0 0 0 0 0
                                 0 0 0 0 0 0 0 0 0
                    -------------------------------
                  (1 0 1 1 0 1 0 0)1 0 1 0 0 0 0 0

相當於是這樣次一次性移位8位,8位的值為(10110100)其對於應是這樣的操作,后8位直接XOR(10100000)
這個其實跟直接除xor是一樣的,只是這樣一次移位得多,可以加快計算結果。

表的生成
表的長度

是由一次移動的位數決定,如一次性移4bit,那么表的長度就是24(16),如果一次性移8bit(1byte),那么表的長度就是28(256),一般都是256為長度。

表的一個單元

這個就是由CRCN,這個N來決定,CRC8為8bit,CRC16為16bit

表的生成代碼
unsigned short crc16Table[256] = {0};
unsigned short poly = 0x8005;
void crcTableCreate(void)
{
    int i = 0;
    for(i = 0; i < 256; i++)
    {
        unsigned short crc;
        crc = i << 8;
        for(int j = 0; j < 8; j++)
        {
            if(crc & 0x8000)
            {
                crc = (crc << 1) ^ poly;
            }
            else
            {
                crc <<= 1;
            }
        }
        crc16Table[i] = crc;
    }
}

//crc16Table
0000,8005,800f,000a,801b,001e,0014,8011,8033,0036,003c,8039,0028,802d,8027,0022,
8063,0066,006c,8069,0078,807d,8077,0072,0050,8055,805f,005a,804b,004e,0044,8041,
80c3,00c6,00cc,80c9,00d8,80dd,80d7,00d2,00f0,80f5,80ff,00fa,80eb,00ee,00e4,80e1,
00a0,80a5,80af,00aa,80bb,00be,00b4,80b1,8093,0096,009c,8099,0088,808d,8087,0082,
8183,0186,018c,8189,0198,819d,8197,0192,01b0,81b5,81bf,01ba,81ab,01ae,01a4,81a1,
01e0,81e5,81ef,01ea,81fb,01fe,01f4,81f1,81d3,01d6,01dc,81d9,01c8,81cd,81c7,01c2,
0140,8145,814f,014a,815b,015e,0154,8151,8173,0176,017c,8179,0168,816d,8167,0162,
8123,0126,012c,8129,0138,813d,8137,0132,0110,8115,811f,011a,810b,010e,0104,8101,
8303,0306,030c,8309,0318,831d,8317,0312,0330,8335,833f,033a,832b,032e,0324,8321,
0360,8365,836f,036a,837b,037e,0374,8371,8353,0356,035c,8359,0348,834d,8347,0342,
03c0,83c5,83cf,03ca,83db,03de,03d4,83d1,83f3,03f6,03fc,83f9,03e8,83ed,83e7,03e2,
83a3,03a6,03ac,83a9,03b8,83bd,83b7,03b2,0390,8395,839f,039a,838b,038e,0384,8381,
0280,8285,828f,028a,829b,029e,0294,8291,82b3,02b6,02bc,82b9,02a8,82ad,82a7,02a2,
82e3,02e6,02ec,82e9,02f8,82fd,82f7,02f2,02d0,82d5,82df,02da,82cb,02ce,02c4,82c1,
8243,0246,024c,8249,0258,825d,8257,0252,0270,8275,827f,027a,826b,026e,0264,8261,
0220,8225,822f,022a,823b,023e,0234,8231,8213,0216,021c,8219,0208,820d,8207,0202,

unsigned short crcUpdate2(unsigned short crcIn, unsigned char data)
{
    unsigned short result = 0;
    result = (crcIn << 8 | data) ^ crc16Table[(crcIn >> 8) & 0xff];
    return result;
}

unsigned short crcCheck2(unsigned char *pData, unsigned char size)
{
    unsigned short crcResult = 0;//Initial Value
    for(int i = 0; i < size; i++)
    {
        crcResult = crcUpdate2(crcResult, *(pData+i));
    }
    // add zero to the tail
    for(int i = 0; i < 2; i++)
    {
        crcResult = crcUpdate2(crcResult, 0);
    }
    return crcResult;
}

void demo(void)
{
    crcTableCreate();
    printf("Result %04x\n",crcCheck2(testData,10)); //print 0x2a62
}

三、直驅表法(Slightly Mangled Table-Driven Implementation)

直驅表法這種翻譯說法,我也不知道是否合理,照我個理解來說,這個應該叫做一種表驅動法的變種
這個變種作用是在於在進行CRC計算之后不需要進行填充相應位數的0。
驅動表還是一樣的,只是計算的公式不一樣

unsigned short crcUpdate3(unsigned short crcIn, unsigned char data)
{
    unsigned short result = 0;
    result = (crcIn << 8) ^ crc16Table[(crcIn >> 8) ^ data];
    return result;
}

unsigned short crcCheck3(unsigned char *pData, unsigned char size)
{
    unsigned short crcResult = 0;//Initial Value
    for(int i = 0; i < size; i++)
    {
        crcResult = crcUpdate3(crcResult, *(pData+i));
    }
    return crcResult;
}

void demo(void)
{
    crcTableCreate();
    printf("Result %04x\n",crcCheck3(testData,10)); //print 0x2a62
}

這個做法是這樣的,至於這個公式是如何推導出來的,看的資料也沒有解釋清楚的,本人暫時也還不太理解,不過實際工程上基本都用的是這個方法。
而實際表驅動法與直驅表法,其實就是兩種算法,雖然兩者可以用的是相同的表,只有在驅動表法的初始值為0與填充值為0才與驅動表法初始值為0的情況下結果是一樣的,其他情況下值應該都是不一樣的。
對於CRC的真正用途來說,算法沒有具體的意義,只是有在數據發生改變的時候,CHECKSUM就可以發生較大的改變,且重復的概率比較小,那么這種算法就是一種比較好的算法。工程上用這種算法,就可以省掉補零的操作。

CRC的其他述語

上面說的是CRC的基本概念以及實現的方式,而在實際的用途中這個CRC還是會一些細微的參數。
如下是一些CRC的表述方式

   Name   : "CRC-16/CITT"
   Width  : 16
   Poly   : 1021
   Init   : FFFF
   RefIn  : False
   RefOut : False
   XorOut : 0000
   Check  : ?

   Name   : "XMODEM"
   Width  : 16
   Poly   : 8408
   Init   : 0000
   RefIn  : True
   RefOut : True
   XorOut : 0000
   Check  : ?

   Name   : "ARC"
   Width  : 16
   Poly   : 8005
   Init   : 0000
   RefIn  : True
   RefOut : True
   XorOut : 0000
   Check  : ?
標識 含義
Name 名字標識
Width CRC長度
Poly 多項式
Init 寄存器初始值,針對直驅表法的寄存器初始值
RefIn 輸入是否是低位在前
RefOut 輸出是否低位在前
XorOut 輸出前與這個值進行XOR再輸出
Check 表示的一個參考輸出,以STRING(123456789)為例

這里的參數無論哪一個修改了,最終的值都會發生變化,相當於就生成一個新的CRC校驗方式


網上寫CRC的文章很多,記錄自己的理解前也參考了很多的文章

主要參考如下幾篇文章

我學習 CRC32、CRC16、CRC 原理和算法的總結(與 WINRAR 結果一致)

【腦凍結】CRC我就拿下了

A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS


免責聲明!

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



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