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




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章