CRC16 C語言實現


最近看到一個實現crc16的小程序,剛開始,不明覺厲,於是花了一個周末去know how。 

 

CRC(Cyclic Redundancy Check)循環冗余校驗是常用的數據校驗方法。

  先說說什么是數據校驗。數據在傳輸過程(比如通過網線在兩台計算機間傳文件)中,由於傳輸信道的原因,可能會有誤碼現象(比如說發送數字5但接收方收到的卻是6),如何發現誤碼呢?方法是發送額外的數據讓接收方校驗是否正確,這就是數據校驗。最容易想到的校驗方法是和校驗,就是將傳送的數據(按字節方式)加起來計算出數據的總和,並將總和傳給接收方,接收方收到數據后也計算總和,並與收到的總和比較看是否相同。如果傳輸中出現誤碼,那么總和一般不會相同,從而知道有誤碼產生,可以讓發送方再發送一遍數據。

CRC校驗也是添加額外數據做為校驗碼,這就是CRC校驗碼,那么CRC校驗碼是如何得到的呢?

  非常簡單,CRC校驗碼就是將數據除以某個固定的數(比如ANSI-CRC16中,這個數是0x18005),所得到的余數就是CRC校驗碼。

  那這里就有一個問題,我們傳送的是一串字節數據,而不是一個數據,怎么將一串數字變成一個數據呢?這也很簡單,比如說2個字節B1,B2,那么對應的數就是(B1<<8)+B2;如果是3個字節B1,B2,B3,那么對應的數就是((B1<<16)+(B2<<8)+B3),比如數字是0x01,0x02,0x03,那么對應的數字就是0x10203;依次類推。如果字節數很多,那么對應的數就非常非常大,不過幸好CRC只需要得到余數,而不需要得到商。

  從上面介紹的原理我們可以大致知道CRC校驗的准確率,在CRC8中出現了誤碼但沒發現的概率是1/256,CRC16的概率是1/65536,而CRC32的概率則是1/2^32,那已經是非常小了,所以一般在數據不多的情況下用CRC16校驗就可以了,而在整個文件的校驗中一般用CRC32校驗。【1】

對於lte通信系統, 我們一般采用生成多項式: x16+x12+x5+1, crc16的計算流程可以按照下面的手動計算得到:

【2】

 

根據上圖,我們得到按照bit來計算的算法:

1.擴大數據流(左移16位) 
2.創建一個16位的寄存器,初始值為數據流前16位 
3. 
while(比特流最后一位未讀入寄存器) 

if(首位為0) 

左移一位,低位從比特流中下一位讀取 

else 

和h(x)進行異或運算 


這樣最后16位寄存器中的數據就是求得的16位CRC碼。 

代碼實現如下:

/* input: 1)data:  data ptr in unsigned char type

2)  data_len_bit: data len in bits

output: crc: crc16 result 

*/
unsigned short crc16(unsigned char* data, unsigned short data_len_bit)
{
int i;
unsigned short crc;
unsigned char temp;
unsigned short bit_shift;

// initialize crc little endian
*((char *)&crc) = *(data+1);
*((char *)&crc + 1) = *data;

for(i=0;i<data_len_bit;i++){

if (i<data_len_bit-16){
temp = *(data+2+(i>>3)); // calculate the position of bit in which byte
}else{
temp = 0x00;
}

bit_shift = 7 - (i%8); // calculate bit shift of bit in the specified byte

if (crc>>15)
{
crc = (crc << 1) | (temp>>bit_shift & 0x01); // shift crc one bit and fi ll in a data bit
crc ^= CRC16_POLY; // xor with poly equations.
}else{
crc = (crc << 1) | (temp>>bit_shift & 0x01);
}
}

return crc;
}

 

對於上面按照bit來計算的程序來說,如果source data bit太多, 就顯得不夠有效。於是按照字節的算法被提出。 原理理解可以看【3】

 

簡單來講, 就是假如有byte[0], byte[1]兩個source bytes. 可以先將byte[0]后面加16個0,然后做crc16,得到一個16bit的crc. 做完byte[0]后,得到16bit的crc后,該16個bit的左8bit和byte[1]做異或, 然后對該異或的8bit后面加16bit,再做crc16,得到第二個crc, 但是不要忘記,該crc的左8bit 和第一個crc的右8bit有位置重合,那么就需要將重合的8bit做異或,得到最后的crc.

 

 字節型算法的一般描述為:本字節的CRC碼,等於上一字節CRC碼的低8位左移8位,與上一字節CRC右移8位同本字節異或后所得的CRC碼異或。   
    字節型算法如下:
    1)CRC寄存器組初始化為全"0"(0x0000)。(注意:CRC寄存器組初始化全為1時,最后CRC應取反。)
    2)CRC寄存器組向左移8位,並保存到CRC寄存器組。
    3)原CRC寄存器組高8位(右移8位)與數據字節進行異或運算,得出一個指向值表的索引。
    4)索引所指的表值與CRC寄存器組做異或運算。
    5)數據指針加1,如果數據沒有全部處理完,則重復步驟2)。
    6)得出CRC。【4】

代碼實現:

unsigned short CRC16_LUT[256];

// to calculate the value in CRC16_LUT
void crc16_init(){
unsigned short data;
unsigned short temp;
unsigned short i;
unsigned short crc;

for (data=0;data<256;data++)
{
crc = 0x0000;

temp = data << 8;

crc = crc ^ temp;
for (i=0;i<8;i++){
if (crc&0x8000){
crc = crc << 1;
crc = crc ^ CRC16_POLY;
}else {
crc = crc << 1;
}
}

CRC16_LUT[data] = crc;

}

return;
}

 


unsigned short crc16_byte(unsigned char *data, unsigned short data_len)
{
unsigned short crc;
unsigned short data_len_byte, data_len_bit;
unsigned short i, crc16_lut_idx;
unsigned short crc_high, crc_low;
unsigned char temp_data;

data_len_byte = data_len >> 3;
data_len_bit = data_len & 0x0007;

crc=0x0000;

for(i=0;i<data_len_byte;i++){
crc_high = crc >> 8;
temp_data=*(data+i);
crc16_lut_idx = crc_high ^ temp_data;

crc_low = crc << 8;
crc = crc_low ^ CRC16_LUT[crc16_lut_idx];
}


if (data_len_bit != 0){
crc_high = crc >> (16 - data_len_bit);
temp_data=(*(data+data_len_byte)) >> (8-data_len_bit);
crc16_lut_idx = crc_high ^ temp_data;

crc_low = crc << data_len_bit;
crc = crc_low ^ CRC16_LUT[crc16_lut_idx];
}

return crc;
}

 

 

【1】https://www.cnblogs.com/sparkbj/articles/6027670.html

【2】http://wdhdmx.iteye.com/blog/1464269

【3】https://blog.csdn.net/huang_shiyang/article/details/50881305

【4】http://www.cnblogs.com/wlei588/p/5993287.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM