CRC校验

CRC介绍

详细介绍请看这位大佬的文章:

https://zhuanlan.zhihu.com/p/396165368https://zhuanlan.zhihu.com/p/396165368

CRC 循环冗余校验(Cyclic Redundancy Check)。

在要发送的数据后面添加若干位数据,以保证数据的完整性。

具体实现是:

        发送方:根据发送数据,计算出CRC校验码,添加到数据后面一起发送出去。

        接收方:对接收到的整体数据(数据+CRC),以同样的方式再次计算CRC,计算结果应当是0,表示数据是完整的。否则,说明数据出错。

CRC的计算方法是用除法求余数。二进制除法,没有进位。

        被除数是 发送的数据;

        除数是 一个多项式(发送方与接收方共同约定好,例如CRC-8:x8+x2+x+1,

                                                                                               CRC-16:x16+x15+x2+1);

        求得的商不需要;

        求得的余数就是CRC校验码。

        例如:多项式 CRC-8:x8+x2+x+1,求 0xC1 的校验码。

        

CRC在线工具

CRC(循环冗余校验)在线计算http://www.ip33.com/crc.html

如图,可以看出上面的计算是正确的。

把要发送的数据,放到最上面的数据框里,可以放多个数据,并非只能一个字节。

参数模型,可以用给定的模型,也可以选则自定义,选择自定义的话,下面的  宽度,多项式,初始值,结果异或值,输入数据反转,输出数据反转  就需要自己设置了。

宽度,就是计算结果的宽度,例如CRC-8,宽度就是8。

多项式POLY,去除掉最高位后的值,例如CRC-8:x8+x2+x+1,1 0000 0111,去掉最高位的1之后就是0x07。

初始值:数据先与初始值计算一次,之后再正式与多项式计算(开始计算CRC)。

结果异或值:计算出的CRC校验码,再与结果异或值进行异或计算。

输入数据反转和输出数据反转,是数据按字节左右反转,例如0x49,反转为 0x92。

如果全选择,计算的顺序是,输入数据反转-->与初始值计算-->开始计算CRC-->输出数据反转-->与结果异或值异或。

CRC程序

CRC程序思路

继续观察CRC的计算过程:(以CRC-8:x8+x2+x+1 为例)

由于我们只关心余数,所以整个过程其实是在做减法计算,循环减去0x107,我们要这个减法的最终结果。

被减数:一开始是要发送的数据,之后是每一次运算的结果。

减数:由于商只能上0或1,所以减数为0x000或0x107。

而减去0,数据不变,所以可以省略,那么减数只有0x107

计算过程:

当被减数最高位为1时,就减去0x107。

最高位为0时不做计算,在后面补0,最高位再次为1时,再次进行计算。

直到补完8个0,就得到了最终的结果。

转换为程序思路:

上面的减法计算过程是模二运算,没有进位

这个过程跟异或是一样的,相同为0,不同为1。

后面补0的操作,可以用左移来完成,需要补8个0,就刚好左移8位。

还有一个问题,就是多项式x8+x2+x+1(1 0000 0111)是 9bit,不是一个字节,

在程序中处理时希望多项式是 0x07 (0000 0111),不包含最高位,这样会更方便一些。

(实际上多项式的值就是不包含最高位的,比如上面的在线工具里的(POLY))。

因为每次都是被减数最高位为1时才会计算,多项式的最高位也为1,相减后最高位必定为0,所以考虑9位与考虑8位情况是一样的。

只是考虑8位时,要先把被减数左移一位,再与0x07异或。

总结一下:

        被减数最高位为0时:不参与运算,左移一位(直到最高位变为1)。

        被减数最高位为1时:先左移一位,再跟多项式(0x07)异或。

        左移8位之后,得到的就是CRC。

程序(单字节计算):

#include "stdio.h"

#define POLYGEN 0x07


unsigned char CalcCrc(unsigned char data);


int main(void)
{
	
	printf("CRC = 0x%x \n",CalcCrc(0xC1));
	
	return 0;
}

unsigned char CalcCrc(unsigned char data)
{
	unsigned char crc = 0;
	unsigned char i = 0;
	
	crc ^= data;
	for(i=0;i<8;i++)
	{
		if(crc&0x80)
		{
			crc = (crc<<1) ^ POLYGEN;
		}
		else
		{
			crc = crc<<1;
		}
		
	}

	return crc;	
}
 

运算结果:

可以看到,计算结果仍然是 0x49,当然这个函数只是计算一个字节,我们需要计算一组数据的CRC。

程序(多字节计算):

多字节计算,只需要对上面的函数稍微调整就可以。

例如一个数组,

        buf[0]=0xC1;

        buf[1]=0xF5;

        buf[2]=0x55;

计算这三个数据的CRC校验码

需要先计算buf[0]的CRC,比如结果为CRC0.

让CRC0异或buf[1],再计算出CRC1,

让CRC1异或buf[2],再计算出CRC2.

CRC2就是最终整个数组的CRC。

按照前面的理解,要计算这三个数据的CRC,应该把这三个数据写在一起,依次排开。然后后面补8个0,计算出最后的CRC。

至于为什么可以这样一个一个字节的计算,我没有搞清楚,但计算的过程确实是这样的。

程序

#include "stdio.h"

#define POLYGEN 0x07


unsigned char CalcCrc(unsigned char *data,unsigned char len);



unsigned char buf[3] = {0xC1,0xF5,0x55};


int main(void)
{
	
	printf("CRC = 0x%x \n",CalcCrc(buf,3));
	
	return 0;
}

unsigned char CalcCrc(unsigned char *data,unsigned char len)
{
	unsigned char crc = 0;
	unsigned char i = 0;
	
	while(len--)
	{
		crc ^= *data;
		for(i=0;i<8;i++)
		{
			if(crc&0x80)
			{
				crc = (crc<<1) ^ POLYGEN;
			}
			else
			{
				crc = crc<<1;
			}
			
		}
		data++;
	}
	return crc;	
}
 

运行结果

通过在线工具验证一下,结果正确。

CRC表

由于CRC计算,需要大量的循环移位操作,占用CPU较多。

而刚好一组数据的CRC计算可以拆分成一个字节一个字节的CRC计算,那么就可以先把 0x00~0xFF 这所有的情况都先计算出来,存放起来,之后直接查表就可以了。这样能节约大量时间。

注意,一个多项式一个CRC表,不同的多项式CRC表是不一样的。

例如上面那个例子,

        buf[0]=0xC1;

        buf[1]=0xF5;

        buf[2]=0x55;

计算这三个数据的CRC校验码。

原本的计算方法:

需要先计算buf[0]的CRC,比如结果为CRC0.

让CRC0异或buf[1],再计算出CRC1,

让CRC1异或buf[2],再计算出CRC2.

CRC2就是最终整个数组的CRC。

最多时需要移位异或 8*3+2 = 26次

有了CRC表之后的计算方法:

查表,找到buf[0]的CRC,为CRC0,

让CRC0异或buf[1]得到data1,查表找到data1的CRC,为CRC1,

让CRC1异或buf[2]得到data2,查表找到data2的CRC,为CRC2,

CRC2就是最终整个数组的CRC。

需要异或两次,查表3次

生成CRC表的程序:

#include "stdio.h"
 
 
 
#define POLYGEN 0x07
 
void set_crc8tab_value(unsigned char *tab);
 
 
 
unsigned char crc8tab[256] = {0};
 
 
int main(void)
{
	unsigned int i = 0;
	
	set_crc8tab_value(crc8tab);
	
	for(i=0;i<256;)
	{
		printf("0x%02x,",crc8tab[i]);
		i++;
		if(i %16 == 0)
			printf("\n");
	}
 
 
	
	return 0;
}
 
 
 
void set_crc8tab_value(unsigned char *tab)
{
	unsigned char crc = 0;
	unsigned int index = 0;
	unsigned char i = 0;
	
	for(index=0;index<256;index++)
	{
		crc = index;
		for(i=0;i<8;i++)
		{
			if(crc&0x80)
			{
				crc = (crc<<1) ^ POLYGEN;
			}
			else
			{
				crc = crc<<1;
			}
			
		}
		tab[index] = crc;			
	}	
}
 

运行结果

查表计算CRC程序(多字节)

#include "stdio.h"



#define POLYGEN 0x07

unsigned char CalcCrc(unsigned char *data,unsigned char len); 


unsigned char crc8tab[256] = {

0x00,0x07,0x0e,0x09,0x1c,0x1b,0x12,0x15,0x38,0x3f,0x36,0x31,0x24,0x23,0x2a,0x2d,
0x70,0x77,0x7e,0x79,0x6c,0x6b,0x62,0x65,0x48,0x4f,0x46,0x41,0x54,0x53,0x5a,0x5d,
0xe0,0xe7,0xee,0xe9,0xfc,0xfb,0xf2,0xf5,0xd8,0xdf,0xd6,0xd1,0xc4,0xc3,0xca,0xcd,
0x90,0x97,0x9e,0x99,0x8c,0x8b,0x82,0x85,0xa8,0xaf,0xa6,0xa1,0xb4,0xb3,0xba,0xbd,
0xc7,0xc0,0xc9,0xce,0xdb,0xdc,0xd5,0xd2,0xff,0xf8,0xf1,0xf6,0xe3,0xe4,0xed,0xea,
0xb7,0xb0,0xb9,0xbe,0xab,0xac,0xa5,0xa2,0x8f,0x88,0x81,0x86,0x93,0x94,0x9d,0x9a,
0x27,0x20,0x29,0x2e,0x3b,0x3c,0x35,0x32,0x1f,0x18,0x11,0x16,0x03,0x04,0x0d,0x0a,
0x57,0x50,0x59,0x5e,0x4b,0x4c,0x45,0x42,0x6f,0x68,0x61,0x66,0x73,0x74,0x7d,0x7a,
0x89,0x8e,0x87,0x80,0x95,0x92,0x9b,0x9c,0xb1,0xb6,0xbf,0xb8,0xad,0xaa,0xa3,0xa4,
0xf9,0xfe,0xf7,0xf0,0xe5,0xe2,0xeb,0xec,0xc1,0xc6,0xcf,0xc8,0xdd,0xda,0xd3,0xd4,
0x69,0x6e,0x67,0x60,0x75,0x72,0x7b,0x7c,0x51,0x56,0x5f,0x58,0x4d,0x4a,0x43,0x44,
0x19,0x1e,0x17,0x10,0x05,0x02,0x0b,0x0c,0x21,0x26,0x2f,0x28,0x3d,0x3a,0x33,0x34,
0x4e,0x49,0x40,0x47,0x52,0x55,0x5c,0x5b,0x76,0x71,0x78,0x7f,0x6a,0x6d,0x64,0x63,
0x3e,0x39,0x30,0x37,0x22,0x25,0x2c,0x2b,0x06,0x01,0x08,0x0f,0x1a,0x1d,0x14,0x13,
0xae,0xa9,0xa0,0xa7,0xb2,0xb5,0xbc,0xbb,0x96,0x91,0x98,0x9f,0x8a,0x8d,0x84,0x83,
0xde,0xd9,0xd0,0xd7,0xc2,0xc5,0xcc,0xcb,0xe6,0xe1,0xe8,0xef,0xfa,0xfd,0xf4,0xf3,

};
unsigned char buf[3] = {0xC1,0xF5,0x55};

int main(void)
{
	//计算数组buf的CRC
	 printf("0x%02x\n",CalcCrc(buf,3));
	
	return 0;
}



unsigned char CalcCrc(unsigned char *data,unsigned char len)
{
	unsigned char crc = 0;
	
	while(len--)
	{
		crc = crc8tab[crc^(*data++)];
	}
	return crc;	
}






 

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值