zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",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
    ",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

  • 相关阅读:
    【redis】--安全
    【redis】-- 数据备份和恢复
    2018.2.8 cf
    寒假零碎的东西 不定时更新补充.......
    hdu 1018
    2018寒假acm训练计划
    UVAlive 7466
    母函数
    简单数学题(水的不能在水的题了)
    随便写写的搜索
  • 原文地址:https://www.cnblogs.com/stupidpeng/p/13266346.html
Copyright © 2011-2022 走看看