CRC校验

CRC简介

CRC的全称为Cyclic Redundancy Check,中文名称为循环冗余校验。它是一类重要的线性分组码,编码和解码方法简单,检错和纠错能力强,在通信领域广泛地用于实现差错控制。

CRC原理简介

发送方和接收方知道生成多项式(1011),将原始数据(1010)左移R位(3位就是校验码存储的位置)得到1010 000,将1010 000模2除生成多项式(1011)得到的余数011(校验码),所以最终发送给接收方的数据就是1010 011共7位数据接收方收到数据后,将接收到的7位数据模2除生成多项式(1011)得到3位余数,如果是000代表数据传输正常,如果是非0代表的就是7位中的某一位出错了,将余数继续补0做模2除,当得到余数和表示最高位出错的余数一样为止,记录做模2除的次数为n,这时将接收到的数据循环左移n位,这时候出错为就在最高位了,将最高位取反,然后循环左移N-n位,这样数据就还原为正确的数据了(其实就是对余数做1次模2除,边对收到数据循环左移1位,当得到余数101时候就表示出错位移到了最高位,直接对最高位取反就能纠错了)。

“循环”的来源

例如:生成多项式:1011,原文:1010,计算得到的CRC=011,假如第1位出现错误,那么余数为001,继续对余数做模2除(对00补全4位=0010),得到余数010如果补齐后的余数最高位为0,那就不用做模2除运算,直接用余数最低3位作为余数),继续对010补全(0100)后做模2除,得到余数100,继续模2除,依次得到余数:011、110、111、101,当余数是101时,表明收到数据(1010+CRC一共7位)出错位置在第7位,再做模2除之后,余数为000,完成了一次循环,这就是循环的由来。

CRC算法简介

CRC的计算方法从计算单位看有3种:基本算法(人工笔算)、计算机计算1(比特型算法)和计算机计算2(字节型算法),而字节型算法又分为3种:查表、计算、查表+计算。一般来说,查表法最快,但是需要较大的空间存放表格;计算法最慢,但是代码最简洁、占用空间最小;而在既要求速度,空间又比较紧张时常用查表+计算法。下面就每种算法做相应的介绍(实际例子都是CRC16校验的CRC-CCITT方式)。

1.基本算法

基本算法实际上就是根据CRC的基本原理进行的计算,例如以CRC16中的CRC-CCITT为例,发送数据Data=byte[3]byte[2]byte[1]byte[0],如果采用CRC-CCITT算法,生成多项式 G(x)= 0x11021,直接将发送数据左移16bit组成实际发送数据SendData=Data(4byte)+CRC(16bit),用SendData对生成多项式G(x)做不借位除法(相当于做异或运算),然后所得到的余数(16bit)就是实际的CRC16校验值,组成实际发送数据SendData。

2.比特型算法

bit行算法利用了1与1异或为0的原理,在基本算法的基础之上做了改进:生成多项式G(x)=0x11021使用简记式:0x1021,针对实际的简记式参与CRC的实际计算。因为在对SendData做CRC计算的时候,不借位除G(x),如果SendData的最高位是1的话除得的结果的最高位必定是0,没有意义,肯定需要左移1bit将0除去。因此在进行CRC计算的时候直接将最G(x)的最高有效位1省去生成简记式,在计算CRC的时候:

(1)用16bit寄存器crc_reg存放SendData的高16bit(12byte)

(2)先判断crc_reg的最高有效位是否为1,为1则crc_reg左移1bit,右边空出来的1bit用SendData的

    MSB(最高有效位)填充,之后用crc_reg对简记式进行异或运算;

(3)为0的话,就直接将crc_reg左移1bit, 空出的1bit用SendData的MSB填充。

(4)循环(2)-(3)直到SendData有效数据全部填充到crc_reg,crc_reg的值就是CRC。

3.字节型算法("+"表示异或,"×256^n"表示左移N个byte,Z(n)和Y(n)分别表示与G(17)异或的商和余数并且长度是16bit)

对于实际发送数据Data=byte[n]byte[n-1]...byte[1]byte[0],用数学表达式表示:Data = byte[n]×256^n+byte[n-1]×256^(n-1)+...+byte[1]×256+byte[0],生成多项式为G(17),因此可以得到对应的计算CRC的数学表达式:CRC=(byte[n]×256^n+byte[n-1]×256^(n-1)+...+byte[1]×256+byte[0])×256^2/G(17),根据异或运算的交换律及结合律推导:

(1)CRC=byte[n]×256^n×256^2/G(17)+byte[n-1]×256^(n-1)×256^2/G(17)+...+byte[1]×256×256^2/G(17)+byte[0]×256^2/G(17)//结合律

(2)CRC=(Z(n)+Y(N)/G(17))×256^n+byte[n-1]×256^(n-1)×256^2/G(17)+...+byte[1]×256×256^2/G(17)+byte[0]×256^2/G(17)//先对byte[n]左移2byte,然后与G(17)做不借位除,Z(n)就是得到的商,Y(n)就是余数(Y(n)/G(17)=余数),而商在CRC计算中是没用的,我们只用到余数

(3)CRC=Z(n)×256^n+{Y(n)×256/G(17)+byte[n-1]×256^2/G(17)}×256^(n-1)+...+byte[1]×256×256^2/G(17)+byte[0]×256^2/G(17)//结合律

(4)CRC=Z(n)×256^n+{(YH8(n)×256+YL8)×256/G(17)+byte[n-1]×256^2/G(17)}×256^(n-1)+...+byte[1]×256×256^2/G(17)+byte[0]×256^2/G(17)

(5)CRC=Z(n)×256^n+{YL8[n]×256/G(17)+(YH8[n]+byte[n-1])×256^2/G(17)}×256^(n-1)+...+byte[1]×256×256^2/G(17)+byte[0]×256^2/G(17)//结合律

根据推导得到byte[n-1]的CRC = 上一字节的高8bit和本字节异或之后单独求CRC将得到的CRC和上一字节的CRC的低8bit(左移8bit)异或。

因此CRC字节型算法就是本字节的CRC码,等于上一字节CRC码的低8位左移8位,与上一字节CRC右移8位同本字节异或后所得的CRC码异或。

实现算法步骤如下:

1)CRC寄存器组初始化为全"0"(0x0000)。(注意:CRC寄存器组初始化值为CRC的初始值)

   2)CRC寄存器组低8bit向左移8位,并保存到CRC寄存器组。

   3)原CRC寄存器组高8位(右移8位)与数据字节(从低byte开始)进行异或运算,得出的结果左移2byte并与G(17)异或。

  4)异或结果与CRC寄存器组做异或运算,结果保存在CRC寄存器组。

  5)如果数据没有全部处理完,则重复步骤2)。

   6)得出CRC。

4.查表算法

查表算法是在CRC字节型基础上改进的,因为如果像上述字节型算法,程序运行浪费的时间太大,程序运行效率太低,对于一些对运行速率有特殊要求的程序,这样的算法显然不合人意,因此需要提高程序的运行速率。对于任意1byte数据,他的实际值有2^8=256种,因此在进行计算之前针对多项式G(n)对单byte可能出现的情况计算CRC,将CRC值保存在一个查询数组(查询表)中,在程序中根据实际计算的时候单byte的值作为数组的下标索引直接取数组值,这样就大大提高了程序的运行时间。

生成查询表(生成多项式简记式G(x)):从0-255,分别计算CRC并保存在对应下标的查询数组中,组成查询表

5.CRC算法实现例子

计算表格
/******************************************************************************
**根据生成多项式(gx)计算CRC16查表法的校验值
**
*/
void CRC16_CreateTable(unsigned short gx)
{

	unsigned short tmp[256] = {0},to_xor = 0;
	unsigned short i,j;

	for(i=0;i<256;i++)
	{
		to_xor = i << 8;
		for(j=0; j<8; j++)
		{
			if(to_xor&(1<<15))
			{
				to_xor= (to_xor << 1) ^ 0x1021;
			}else{
				to_xor <<= 1;
			}
			
		}
		
		tmp[i] = to_xor;
	}
}

1)查表法
优点是代码简单,容易看懂
/****************************************************************
**函数名:CalcCRC16_ccitt
**功能描述:计算输入数据的CRC16校验值
**参数:		pdata,要计算CRC的数据指针;len,pdata的长度;init,CRC初始值
**返回值:		返回计算的CRC值
*/
unsigned short CalcCRC16_ccitt(const unsigned char *pdata, unsigned int len, unsigned short init) 
{ 
	const unsigned short crctab[256] = { 
   0x0000,0x1021,0x2042,0x3063,0x4084,0x50A5,0x60C6,0x70E7, 
   0x8108,0x9129,0xA14A,0xB16B,0xC18C,0xD1AD,0xE1CE,0xF1EF, 
   0x1231,0x0210,0x3273,0x2252,0x52B5,0x4294,0x72F7,0x62D6, 
   0x9339,0x8318,0xB37B,0xA35A,0xD3BD,0xC39C,0xF3FF,0xE3DE, 
   0x2462,0x3443,0x0420,0x1401,0x64E6,0x74C7,0x44A4,0x5485, 
   0xA56A,0xB54B,0x8528,0x9509,0xE5EE,0xF5CF,0xC5AC,0xD58D, 
   0x3653,0x2672,0x1611,0x0630,0x76D7,0x66F6,0x5695,0x46B4, 
   0xB75B,0xA77A,0x9719,0x8738,0xF7DF,0xE7FE,0xD79D,0xC7BC, 
   0x48C4,0x58E5,0x6886,0x78A7,0x0840,0x1861,0x2802,0x3823, 
   0xC9CC,0xD9ED,0xE98E,0xF9AF,0x8948,0x9969,0xA90A,0xB92B, 
   0x5AF5,0x4AD4,0x7AB7,0x6A96,0x1A71,0x0A50,0x3A33,0x2A12, 
   0xDBFD,0xCBDC,0xFBBF,0xEB9E,0x9B79,0x8B58,0xBB3B,0xAB1A, 
   0x6CA6,0x7C87,0x4CE4,0x5CC5,0x2C22,0x3C03,0x0C60,0x1C41, 
   0xEDAE,0xFD8F,0xCDEC,0xDDCD,0xAD2A,0xBD0B,0x8D68,0x9D49, 
   0x7E97,0x6EB6,0x5ED5,0x4EF4,0x3E13,0x2E32,0x1E51,0x0E70, 
   0xFF9F,0xEFBE,0xDFDD,0xCFFC,0xBF1B,0xAF3A,0x9F59,0x8F78, 
   0x9188,0x81A9,0x0B1CA,0x0A1EB,0x0D10C,0x0C12D,0xF14E,0xE16F, 
   0x1080,0x00A1,0x30C2,0x20E3,0x5004,0x4025,0x7046,0x6067, 
   0x83B9,0x9398,0xA3FB,0xB3DA,0xC33D,0xD31C,0xE37F,0xF35E, 
   0x02B1,0x1290,0x22F3,0x32D2,0x4235,0x5214,0x6277,0x7256, 
   0xB5EA,0xA5CB,0x95A8,0x8589,0xF56E,0xE54F,0xD52C,0xC50D, 
   0x34E2,0x24C3,0x14A0,0x0481,0x7466,0x6447,0x5424,0x4405, 
   0xA7DB,0x0B7FA,0x8799,0x97B8,0xE75F,0xF77E,0xC71D,0xD73C, 
   0x26D3,0x36F2,0x0691,0x16B0,0x6657,0x7676,0x4615,0x5634, 
   0xD94C,0xC96D,0xF90E,0xE92F,0x99C8,0x89E9,0xB98A,0xA9AB, 
   0x5844,0x4865,0x7806,0x6827,0x18C0,0x08E1,0x3882,0x28A3, 
   0xCB7D,0xDB5C,0xEB3F,0xFB1E,0x8BF9,0x9BD8,0xABBB,0xBB9A, 
   0x4A75,0x5A54,0x6A37,0x7A16,0x0AF1,0x1AD0,0x2AB3,0x3A92, 
   0xFD2E,0xED0F,0xDD6C,0xCD4D,0xBDAA,0xAD8B,0x9DE8,0x8DC9, 
   0x7C26,0x6C07,0x5C64,0x4C45,0x3CA2,0x2C83,0x1CE0,0x0CC1, 
   0xEF1F,0xFF3E,0xCF5D,0xDF7C,0xAF9B,0xBFBA,0x8FD9,0x9FF8, 
   0x6E17,0x7E36,0x4E55,0x5E74,0x2E93,0x3EB2,0x0ED1,0x1EF0 }; 

	while (len--) { 
		 init = (init >> 8) ^ crctab[(init ^ *pdata++) & 0xFF]; 
	} 
	return init; 
}

2)计算法
计算法一般是为了节省程序占用内存空间,在内存空间成为程序运行的最大限制的时候才会使用,一般为了程序的运行效率,很少用到他
3)计算+查表
与查表法比较,他的运行效率相较高一些
/****************************************************************
**函数名:crc16
**功能描述:计算输入数据的CRC16校验值
**参数:		buf,要计算CRC的数据指针;n,buf的长度;init,CRC初始值定死为0
**返回值:		返回计算的CRC值(高字节在前低字节在后)
**说明:		适合大端字节序
*/
unsigned int crc16(unsigned char *buf,unsigned char n)
{ 
	unsigned char crctableh[256]={	//CRC高字节
	  0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,
		0x81,0x91,0xa1,0xb1,0xc1,0xd1,0xe1,0xf1,
		0x12,0x02,0x32,0x22,0x52,0x42,0x72,0x62,
		0x93,0x83,0xb3,0xa3,0xd3,0xc3,0xf3,0xe3,
		0x24,0x34,0x04,0x14,0x64,0x74,0x44,0x54,
		0xa5,0xb5,0x85,0x95,0xe5,0xf5,0xc5,0xd5,
		0x36,0x26,0x16,0x06,0x76,0x66,0x56,0x46,
		0xb7,0xa7,0x97,0x87,0xf7,0xe7,0xd7,0xc7,
		0x48,0x58,0x68,0x78,0x08,0x18,0x28,0x38,
		0xc9,0xd9,0xe9,0xf9,0x89,0x99,0xa9,0xb9,
		0x5a,0x4a,0x7a,0x6a,0x1a,0x0a,0x3a,0x2a,
		0xdb,0xcb,0xfb,0xeb,0x9b,0x8b,0xbb,0xab,
		0x6c,0x7c,0x4c,0x5c,0x2c,0x3c,0x0c,0x1c,
		0xed,0xfd,0xcd,0xdd,0xad,0xbd,0x8d,0x9d,
		0x7e,0x6e,0x5e,0x4e,0x3e,0x2e,0x1e,0x0e,
		0xff,0xef,0xdf,0xcf,0xbf,0xaf,0x9f,0x8f,
		0x91,0x81,0xb1,0xa1,0xd1,0xc1,0xf1,0xe1,
		0x10,0x00,0x30,0x20,0x50,0x40,0x70,0x60,
		0x83,0x93,0xa3,0xb3,0xc3,0xd3,0xe3,0xf3,
		0x02,0x12,0x22,0x32,0x42,0x52,0x62,0x72,
		0xb5,0xa5,0x95,0x85,0xf5,0xe5,0xd5,0xc5,
		0x34,0x24,0x14,0x04,0x74,0x64,0x54,0x44,
		0xa7,0xb7,0x87,0x97,0xe7,0xf7,0xc7,0xd7,
		0x26,0x36,0x06,0x16,0x66,0x76,0x46,0x56,
		0xd9,0xc9,0xf9,0xe9,0x99,0x89,0xb9,0xa9,
		0x58,0x48,0x78,0x68,0x18,0x08,0x38,0x28,
		0xcb,0xdb,0xeb,0xfb,0x8b,0x9b,0xab,0xbb,
		0x4a,0x5a,0x6a,0x7a,0x0a,0x1a,0x2a,0x3a,
		0xfd,0xed,0xdd,0xcd,0xbd,0xad,0x9d,0x8d,
		0x7c,0x6c,0x5c,0x4c,0x3c,0x2c,0x1c,0x0c,
		0xef,0xff,0xcf,0xdf,0xaf,0xbf,0x8f,0x9f,
		0x6e,0x7e,0x4e,0x5e,0x2e,0x3e,0x0e,0x1e,
	};
	unsigned char crctablel[256]={	//CRC低字节
	  0x00,0x21,0x42,0x63,0x84,0xa5,0xc6,0xe7,
		0x08,0x29,0x4a,0x6b,0x8c,0xad,0xce,0xef,
		0x31,0x10,0x73,0x52,0xb5,0x94,0xf7,0xd6,
		0x39,0x18,0x7b,0x5a,0xbd,0x9c,0xff,0xde,
		0x62,0x43,0x20,0x01,0xe6,0xc7,0xa4,0x85,
		0x6a,0x4b,0x28,0x09,0xee,0xcf,0xac,0x8d,
		0x53,0x72,0x11,0x30,0xd7,0xf6,0x95,0xb4,
		0x5b,0x7a,0x19,0x38,0xdf,0xfe,0x9d,0xbc,
		0xc4,0xe5,0x86,0xa7,0x40,0x61,0x02,0x23,
		0xcc,0xed,0x8e,0xaf,0x48,0x69,0x0a,0x2b,
		0xf5,0xd4,0xb7,0x96,0x71,0x50,0x33,0x12,
		0xfd,0xdc,0xbf,0x9e,0x79,0x58,0x3b,0x1a,
		0xa6,0x87,0xe4,0xc5,0x22,0x03,0x60,0x41,
		0xae,0x8f,0xec,0xcd,0x2a,0x0b,0x68,0x49,
		0x97,0xb6,0xd5,0xf4,0x13,0x32,0x51,0x70,
		0x9f,0xbe,0xdd,0xfc,0x1b,0x3a,0x59,0x78,
		0x88,0xa9,0xca,0xeb,0x0c,0x2d,0x4e,0x6f,
		0x80,0xa1,0xc2,0xe3,0x04,0x25,0x46,0x67,
		0xb9,0x98,0xfb,0xda,0x3d,0x1c,0x7f,0x5e,
		0xb1,0x90,0xf3,0xd2,0x35,0x14,0x77,0x56,
		0xea,0xcb,0xa8,0x89,0x6e,0x4f,0x2c,0x0d,
		0xe2,0xc3,0xa0,0x81,0x66,0x47,0x24,0x05,
		0xdb,0xfa,0x99,0xb8,0x5f,0x7e,0x1d,0x3c,
		0xd3,0xf2,0x91,0xb0,0x57,0x76,0x15,0x34,
		0x4c,0x6d,0x0e,0x2f,0xc8,0xe9,0x8a,0xab,
		0x44,0x65,0x06,0x27,0xc0,0xe1,0x82,0xa3,
		0x7d,0x5c,0x3f,0x1e,0xf9,0xd8,0xbb,0x9a,
		0x75,0x54,0x37,0x16,0xf1,0xd0,0xb3,0x92,
		0x2e,0x0f,0x6c,0x4d,0xaa,0x8b,0xe8,0xc9,
		0x26,0x07,0x64,0x45,0xa2,0x83,0xe0,0xc1,
		0x1f,0x3e,0x5d,0x7c,0x9b,0xba,0xd9,0xf8,
		0x17,0x36,0x55,0x74,0x93,0xb2,0xd1,0xf0,
	};
	
  unsigned char t;//存放CRC查表的下标索引
  union{
    unsigned char c[2];//CRC校验的高低byte
    unsigned int x;
  }data crc;
  crc.x = 0;
  while(n !=0)
  {
    t = crc.c[0]^*buf;	//单字节CRC值下标
    crc.c[0] = crc.c[1]^crctableh[t];//CRC高字节
    crc.c[1] = crctablel[t];//CRC低字节
    n--;
    buf++;
  }
  return ( crc.x );
}

详情参考:
1.http://www.lai18.com/content/10506404.html
2.http://www.eeworld.com.cn/mcu/article_2016110731273.html




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值