crc校驗算法


crc校驗的描述:
1.CRC校驗原理
CRC校驗的原理的難易暫不評價,畢竟會者不難,難者不會么。
CRC校驗的根本思想是在要發送的幀之后附加一個數(CRC校驗值),生成一個新幀,然后發送給接收端。
當然,這個附加的數並不是隨意的,他要使新生成的幀能在發送端和接收端選定的某個特定的數整除。
當然,這個特定的數和整除的方法也不是隨意的,特定的數是經過多次論證選定的一些列數,這在之
后會述說,整除的方法是模2除法。

模2除法(crc原理最核心的思想)
模2除法與算術除法類似,都是除法,不同的是,除的時候既不向上借位,也不比較除數和被除數的大
小,只要以相同的位數進行相除即可。模2加法:1+1=0,0+1=1, 0+0=0,無進位也無借位;模2減法:
1-1=0, 0-1=1,0-0=0,也無進位,無借位,相當於二進制中的異或運算。即比較后,相同為0,相異
為1。如100101除以1110,結果的商為11,余數為1.
 
關於模2除法中除數的選擇,這個你可以自己隨意選擇,開玩笑的,隨意選擇的除數,會導致幀校驗的
正確率下降,這是不確定的,要看你選擇的除數。而我們一般的除數的選擇是直接去參照一些專家講過
多次試驗下來的一些除數,這些除法能極大的保證幀校驗的正確率。一般而言crc8校驗的錯誤率為1/256,
crc16校驗的錯誤率為1/65536,crc32校驗的錯誤率為1/(65536*65536).

那這里就有一個問題,我們傳送的是一串字節數據,而不是一個數據,怎么將一串數字變成一個數據呢?
這也很簡單,比如說2個字節B1,B2,那么對應的數就是(B1<<8)+B2;如果是3個字節B1,B2,B3,那么對
應的數就是((B1<<16)+(B2<<8)+B3),比如數字是0x01,0x02,0x03,那么對應的數字就是0x10203;依次類
推。如果字節數很多,那么對應的數就非常非常大,不過幸好CRC只需要得到余數,而不需要得到商。
從上面介紹的原理我們可以大致知道CRC校驗的准確率,在CRC8中出現了誤碼但沒發現的概率是1/256,
CRC16的概率是1/65536,而CRC32的概率則是1/2^32,那已經是非常小了,所以一般在數據不多的情況下
用CRC16校驗就可以了,而在整個文件的校驗中一般用CRC32校驗。
這里還有個問題,如果被除數比除數小,那么余數就是被除數本身,比如說只要傳一個字節,那么它的
CRC就是它自己,為避免這種情況,在做除法之前先將它移位,使它大於除數,那么移多少位呢?這就與
所選的固定除數有關了,左移位數比除數的位數少1,下面是常用標准中的除數:
  CRC8:多項式是X8+X5+X4+1,對應的數字是0x131,左移8位
  CRC12:多項式是X12+X11+X3+X2+1,對應的數字是0x180D,左移12位
  CCITT CRC16:多項式是X16+X12+X5+1,對應的數字是0x11021,左移16位
  ANSI CRC16:多項式是X16+X15+X2+1,對應的數字是0x18005,左移16位
  CRC32:多項式是X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1,對應數字是0x104C11DB7,
左移32因此,在得到字節串對應的數字后,再將數字左移M位(比如ANSI-CRC16是左移16位),就得到了被
除數。好了,現在被除數和除數都有了,那么就要開始做除法求CRC校驗碼了。CRC除法的計算過程與我們筆
算除法類似,首先是被除數與除數高位對齊后,被除數減去除數,得到了差,除數再與差的最高位對齊,進
行減法,然后再對齊再減,直到差比除數小,這個差就是余數。不過和普通減法有差別的是,CRC的加(減)法
是不進(借)位的,比如10減01,它的結果是11,而不是借位減法得到的01,因此,實際上CRC的加法和減
法所得的結果是一樣的,比如10加01的結果是11,10減01的結果也是11,這其實就是異或操作。說了這么多
也不一定能說清楚,我們還是看一段CRC除法求余程序吧:


用crc16計算0x88的校驗碼:依照crc16的規則,先向左移動16位,得到0x880000,依照相除的方法,數據不動,移動除數

unsigned short crc16_div()
{
     unsigned long data = 0x880000;
     unsigned long citt16 = 0x11021;
 
     unsigned long cmp_value = 0x10000;
 
     citt16 << = 7; //(為了讓data最高位能直接異或citt16的最高位)
     cmp_value << = 7;
     while(data > cmp_value)
     {
          if(data & cmp_value)
          {
               data ^= citt16;
          }
          citt16 >>= 1;
          cmp_value >>= 1;
     }
 
     return (data & 0xffff);
}            

 好了,現在我們已經會計算0x88的CRC校驗碼了,它只是對0x880000做除法運算求余數而已,不過這只是求單字節的CRC校驗碼,
那如果有十多個字節怎么辦?我們的計算機也存不下那么大的數呀,看來我們還要對程序進行些改進,使它能對大數求除法了。

unsigned short crc16_div_2()
{
     unsigned short data = 0x88;
     unsigned short citt16 = 0x1021;
 
     int i;
     data <<= 8;
     for(i=0;i<8;i++)    
 /* 為什么要小於8呢?因為按照crc16的校驗,照理說需要左移16位,上面已經移了8位,所以還可以再移8位 */
     {
          if(data & 0x8000)
          {
               data <<=1;
               data ^= citt16;
          }
          else
               data <<= 1;
     }
 
     return (data & 0xffff);
}      

 對上述代碼進行優化:

unsigned short crc16_ccitt(unsigned char data, unsigned short crc)
{
     unsigned short citt16 = 0x1021;
     int i;
 
     crc ^= (data << 8);      //我們之前默認的是crc中沒有一個初值
     for(i=0;i<8;i++)
     {
          if(crc & 0x8000)
          {
               crc <<= 1;
               crc ^= citt16;
          }
          else
          {
               crc <<=1;
          }
     }
}    

最后要說的是CRC的正序和反轉問題,比如前面ccitt-crc16的正序是0x1021,如果是反轉就是0x8408
(就是將0x1021倒過來低位變高位)為什么要反轉?這是因為數據傳輸可能是先傳低位再傳高位(比
如串口就是低位在前高位在后)。反轉的CRC算法與正序類似,只是需要注意移位的方向相反。

unsigned short crc16_ccitt_r(unsigned char data, unsigned short crc)
{
        unsigned short ccitt16 = 0x8408;
        int i;
        crc ^= data;
        for (i=0; i<8; i++)
        {
             if (crc & 1)
             {
                   crc
>>= 1;                    crc ^= ccitt16;              }              else              {                    crc >>= 1;              }         }         return crc;

 


參考:https://www.cnblogs.com/sparkbj/articles/6027670.html

https://blog.csdn.net/Ele_Dd/article/details/78193279


免責聲明!

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



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