最近看到一個實現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