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