通用型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)运行效果
图3.1 测试1
图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即可。
最后,我的成功是站在巨人的肩膀上的,我姑且这么说吧,模型参考了一位前辈的,但是前辈的核心思想有问题,其核心是自己总结的