通用型CRC校验算法

1、CRC校验简单原理
CRC校验方法是在通讯领域应用极广的一类数据校验方法,常用的包括CRC8、CRC16、CRC32(数字为生成多项式Gx-1),在嵌入式领域应用较多(DS18B20温度传感器正负温度精度校验(CRC查表法)),其校验手段极为有效,但是其本生并不具有纠错能力。假设有目前有效数据Kx(信息码)有K位,生成多项式为Gx,经过有限次取模运算(等同于XOR,不借位的模2运算),求得冗余码(FCS序列)有N位,则最终传输数据为Tx=Kx+N,而接收方在收到数据后用Tx%Gx(有限次XOR)是否为0判断数据传输的正确性。
具体CRC校验原理,还可以参看其他博客或者百度了解学习。
2、算法
经过上述的简单说明,应该知道可以引入Kx、Gx、Tx、Rx,采用最高位对其(Gx补偿)直接计算法,对数据比特串较短、时间要求不高的可以采用,使用必须要满足以下要求:
*

① Gx补偿位数满足:Gx*2^(sizeof(Kx)-sizeof(Gx))。
② 运算次数满足:sizeof(Kx)-sizeof(Gx)。
③ CRC进行XOR运算满足:CRC & (2^(sizeof(Kx)-1)。
④ Rx还原满足:CRC/(2^(sizeof(Kx)-sizeof(Gx)+1)。



其中sizeof表示取得元素在二进制下位长。
3、应用效果
(1)测试数据:
①Kx=110011,Gx=11001,Rx=1001
②Kx=101001,Gx=1101,Rx=001

/****
vision:v1.0.0
Author:LHC
Gx:生成码(生成多项式) 
KX:信息码(要发送的数据部分)
TX:真实发送的数据(Kx+Rx)
Rx:CRC循环冗余检验码(FCS序列)
目前Bug:单crc序列的首位或者多位为00时,将不会显示,需要按添加位数人工补0
Data_Test:Kx=110011,Gx=11001,Rx=1001;Kx=101001,Gx=1101,Rx=001
Ways:直接计算法(位数直接与Kx对其),在简单应用领域满足要求,不适用极限时间要求
****/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

typedef unsigned int uint_32;
#define BDH_SYSTEM 2 //用于表示输出数据进制
uint_32 gx,kx,tx,rx; 
char gx_c[32],kx_c[32],rx_c[32],tx_c[32]; //最大数据串长度
char flag='y';

uint_32 Binnary_Change_Decimal(char p[]) //把二进制数据串转换为10进制
{
    int length =0,i=0;
    uint_32 sum=0;
    length =strlen(p);
    for(i=0;i<length;i++) //此处length不包含‘/0’
    {
        if(p[i]=='1') //带权法算出10进制值
        sum =sum+(uint_32)pow(2,length-1-i); //pow函数用于计算y^x,x、y及返回值都是double型
    }
    //printf("%d\n",sizeof(sum));
    return sum;
}

void Input_Binary() //输入为2进制比特串
{
    printf("please Kx with (binary) system:\n");
    gets(kx_c);
    kx =Binnary_Change_Decimal(kx_c);
    tx =kx; //首次把TX值赋值为KX,方便后面扩展

    printf("please Gx with (binary) system:\n");
    gets(gx_c);
    gx =Binnary_Change_Decimal(gx_c);
}

/*void Input_Decimal() //输入为10进制模式
{
    printf("please Kx with (decimal) system:\n");
    scannf("%d",&kx);
    printf("please Gx with (decimal) system:\n");
    scannf("%d",&gx);
}*/

uint_32 CRC_Value(uint_32 *Kx,uint_32 Gx) //通用CRC核心函数
{
    uint_32 crc=0; //暂存每次XOR运算后的数据
    char kx_c_t[32]; //用于求出10进制转2进制后的数据而设定的临时变量
    int length_1 =0,length_2=0,i;
    char Gx_Binary[32];

    itoa(Gx,Gx_Binary,BDH_SYSTEM); //将int型数据转换成数据串,2表示二进制
    length_1=strlen(Gx_Binary);
    *Kx =(*Kx)*(uint_32)pow(2,length_1-1); //还原出被除数 
    itoa(*Kx,kx_c_t,BDH_SYSTEM);
    length_2 =strlen(kx_c_t);
    crc =*Kx; //还原后的除数为首次运算的crc
    Gx =Gx*(uint_32)pow(2,(length_2-length_1)); //凑出GX的长度和KX一样长
    for(i=0;i<(length_2-length_1)+1;i++) //运算次数9-4+1、10-5+1
    {
        //if(crc&0x100) //只要满足被除数KX的最高位与除数相与后不为0即可参加XOR运算,512、256
        if(crc&((uint_32)pow(2,length_2-1))) //2^0=1,次数起始为0
        {
            crc ^=Gx;
            printf("CRC1:%d\n",crc); //crc运算步骤
            crc <<=1;
        }
        else //最高位不为1移位
        {
            crc <<=1;
            printf("CRC2:%d\n",crc);
        }
    }
    //printf("pow:%d\n",(uint_32)pow(2,length_1));
    return crc/(uint_32)pow(2,(length_2-length_1)+1); //去除运算结束后多余的RX位数
    //此处必须注意运算结束后CRC都会被<<1,所以还原的CRC应在原基础上>>1
}

void main()
{
    while(flag=='y')
    {
        fflush(stdin); //清空缓冲区
        Input_Binary();
        rx =CRC_Value(&kx,gx);
        
        itoa(kx,kx_c,BDH_SYSTEM); //把真实的KX算出
        itoa(rx,rx_c,BDH_SYSTEM);
        itoa(tx,tx_c,BDH_SYSTEM);
        strcat(tx_c,rx_c); //此处Tx为字符串的简单拼接

        printf("\noutput (Kx):%s\n",kx_c); //输出为2进制
        printf("output (GX):%s\n",gx_c);
        printf("output (Tx):%s\n",tx_c);
        printf("output (RX):%s\n",rx_c);
        
        tx =Binnary_Change_Decimal(tx_c);
        if(tx%gx==0) //检测数据是否出错
            printf("\nRX=%d,data is true!\n",(tx%gx));
        else
            printf("data have errors! please cheak!\n");
            printf("Have a want to continue?(y/n)\n");
            scanf("%s",&flag);
    }  
}

(2)运行效果

crc检验python crc校验方法_进制

图3.1	测试1

crc检验python crc校验方法_进制_02

图3.2	测试2

4、说明
首先,为什么我要大费周章的开发这样一个程序呢?原因其实很简单,网络上关于CRC校验的原代码的确是很多,原理更是不计其数,但是有三点需要注意,其一,CRC校验是有很多标准(CRC—16/IBM、CRC-8等)的,而这些标准的区别就在于采用的生成多项式不同,比如说CRC-8的Gx为:X8+X2+X+1(100000101,注意最高位和最低位为1),这就会造成你的直接引用却无法得到预期的结果,在者CRC校验本身并不难,而难的是如何用计算机实现,因为你要考虑很多因素,最多的就是数据边界问题(char类型数据在Keil 5中容纳数据为255),这个自己体会了;其二,当你去不断参考别人的经验代码的时候,你会发现这样一句话“CRC为嵌入式开发人员的法宝之一,但仅有少数人能掌握其核心算法!”,真的有这么难吗,前辈的答案显然是正确的,当你浏览很多个码龄超过4年以上的前辈的代码后你会发现,难于理解,因为数学思维极强,最后结果就会是直接不想看甚至放弃了,但是我编写的则不同,简单,易于理解,起源于谢希仁计网,通用型极高;其三,纯属个人爱好,还有就是特别讨厌直接引用别人代码,而不知所云(可能是个人强迫症),以及编写过后对收获成果的一种成就感促使我这么做。
还有一点需要说明的是,从图3.2可以看出,最后的Tx是错误的,原因是C98编译器只统计数据的有效位,原Rx=001,有效数据为1,所以00被丢弃了,造成Kx%Gx出错,这个是我故意留于检测用,改正仅需加入2个0即可。
最后,我的成功是站在巨人的肩膀上的,我姑且这么说吧,模型参考了一位前辈的,但是前辈的核心思想有问题,其核心是自己总结的