CRC校驗源碼分析

點擊文件名下載 CRC校驗源碼分析.pdf
--------以上部分請勿修改!-------------

CRC校驗源碼分析

這兩天做項目,需要用到CRC校驗。以前沒搞過這東東,以爲挺簡單的。結果看看別人提供
的彙編源程序,居然看不懂。花了兩天時間研究了一下CRC校驗,希望我寫的這點東西能夠
幫助和我有同樣困惑的朋友節省點時間。

先是在網上下了一堆亂七八遭的資料下來,感覺都是一個模樣,全都是從CRC的
數學原理開始,一長串的表達式看的我頭暈。第一次接觸還真難以理解。這些東西不想在這
裏講,隨便找一下都是一大把。我想根據源代碼來分析會比較好懂一些。

費了老大功夫,才搞清楚CRC根據”權”(即多項表達式)的不同而相應的源代碼
也有稍許不同。以下是各種常用的權。
CRC8=X8+X5+X4+1
CRC-CCITT=X16+X12+X5+1
CRC16=X16+X15+X5+1
CRC12=X12+X11+X3+X2+1
CRC32=X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1

以下的源程序全部以CCITT爲例。其實本質都是一樣,搞明白一種,其他的都是小菜。

圖1,圖2說明了CRC校驗中CRC值是如何計算出來的,體現的多項式正是X16+X12+X5+1。
Serial Data即是需要校驗的數據。從把數據移位開始計算,將數據位(從最低的數據位開
始)逐位移入反向耦合移位寄存器(這個名詞我也不懂,覺得蠻酷的,就這樣寫了,嘿)。當
所有數據位都這樣操作後,計算結束。此時,16位移位寄存器中的內容就是CRC碼。


圖1 生成CRC-CCITT的移位寄
存器的作用原理


圖2 用於計算CRC_CCITT的移
位寄存器的電路配置

圖中進行XOR運算的位與多項式的表達相對應。
X5代表Bit5,X12代表Bit12,1自然是代表Bit0,X16比較特別,是指移位寄存器移出的數
據,即圖中的DATA OUT。可以這樣理解,與數據位做XOR運算的是上次CRC值的Bit15。
根據以上說明,可以依葫蘆畫瓢的寫出以下程序。(程序都是在keil C 7.10下調試的)

typedef unsigned char uchar;
typedef unsigned int uint;

code uchar crcbuff [] = { 0x00,0x00,0x00,0x00,0x06,0x0d,0xd2,0xe3};
uint crc;
// CRC碼
void main(void)
{
uchar *ptr;
crc = 0;
// CRC 初值
ptr = crcbuff;
// 指向第一個Byte數據
crc = crc16l(ptr,8);

while(1);
}

uint crc16l(uchar *ptr,uchar len) //
ptr爲數據指針,len爲數據長度
{
uchar i;
while(len--)
{
for(i=0x80; i!=0; i>>=1)
{
if((crc&0x8000)!=0) {crc<<=1; crc^=0x1021;}
1-1
else crc<<=1;
1-2
if((*ptr&i)!=0) crc^=0x1021;
1-3
}
ptr++;
}
return(crc);
}

執行結果 crc = 0xdbc0;
程序1-1,1-2,1-3可以理解成移位前crc 的Bit15與數據對應的Bit(*ptr&i)做XOR運算,根據
此結果來決定是否執行 crc^=0x1021。只要明白兩次異或運算與原值相同,就不難理解這個
程序。

很多資料上都寫了查表法來計算,當時是怎麼也沒想通。其實蠻簡單的。假設通過移位處理
了8個bit的數據,相當於把之前的CRC碼的高字節(8bit)全部移出,與一個byte的數據做XOR
運算,根據運算結果來選擇一個值(稱爲餘式),與原來的CRC碼再做一次XOR運算,就可以得
到新的CRC碼。

不難看出,餘式有256種可能的值,實際上就是0~255以X16+X12+X5+1爲權得到的CRC碼,可
以通過函數crc16l來計算。以1爲例。

code test[]={0x01};
crc = 0;
ptr = test;
crc = crc16l(ptr,1);

執行結果 crc = 1021,這就是1對應的餘式。

進一步修改函數,我這裏就懶得寫了,可得到X16+X12+X5+1的餘式表。

code uint crc_ta[256]={ // X16+X12+X5+1 餘式表
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, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 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, 0xb7fa, 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
};

根據這個思路,可以寫出以下程序:
uint table_crc(uchar *ptr,uchar len) // 字節查表法求
CRC
{
uchar da;
while(len--!=0)
{
da=(uchar) (crc/256); // 以8位二進制數暫存CRC的
高8位
crc<<=8; // 左移8位
crc^=crc_ta[da^*ptr]; // 高字節和當前數據XOR再查

ptr++;
}
return(crc);
}

本質上CRC計算的就是移位和異或。所以一次處理移動幾位都沒有關係,只要做相應的處理
就好了。
下面給出半字節查表的處理程序。其實和全字節是一回事。

code uint crc_ba[16]={
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
};

uint ban_crc(uchar *ptr,uchar len)
{
uchar da;
while(len--!=0)
{
da = ((uchar)(crc/256))/16;
crc <<= 4;
crc ^=crc_ba[da^(*ptr/16)];
da = ((uchar)(crc/256)/16);
crc <<= 4;
crc ^=crc_ba[da^(*ptr&0x0f)];
ptr++;
}
return(crc);
}

crc_ba[16]和crc_ta[256]的前16個餘式是一樣的。

其實講到這裏,就已經差不多了。反正當時我以爲自己是懂了。結果去看別人的源代碼的時
候,也是說採用CCITT,但是是反相的。如圖3






圖3
CRC-CCITT反相運算

反過來,一切都那麼陌生,faint.吐血,吐血。
仔細分析一下,也可以很容易寫出按位異或的程序。只不過由左移變成右移。

uint crc16r(unsigned char *ptr, unsigned char len)
{
unsigned char i;
while(len--!=0)
{
for(i=0x01;i!=0;i <<= 1)
{
if((crc&0x0001)!=0) {crc >>= 1; crc ^= 0x8408;}
else crc >>= 1;
if((*ptr&i)!=0) crc ^= 0x8408;
}
ptr++;
}
return(crc);
}

0x8408就是CCITT的反轉多項式。
套用別人資料上的話
“反轉多項式是指在數據通訊時,信息字節先傳送或接收低位字節,如重新排位影響CRC計
算速度,故設反轉多項式。”


code uchar crcbuff [] = { 0x00,0x00,0x00,0x00,0x06,0x0d,0xd2,0xe3};
反過來就是
code uchar crcbuff_fan[] = {0xe3,0xd2,0x0d,0x06,0x00,0x00,0x00,0x00};

crc = 0;
ptr = crcbuff_fan;
crc = crc16r(ptr,8);

執行結果 crc = 0x5f1d;

如想驗證是否正確,可改
code uchar crcbuff_fan_result[] =
{0xe3,0xd2,0x0d,0x06,0x00,0x00,0x00,0x00,0x1d,0x5f};
ptr = crcbuff_fan_result;
crc = crc16r(ptr,10);

執行結果 crc = 0; 符合CRC校驗的原理。
請注意0x5f1d在數組中的排列中低位在前,正是反相運算的特點。不過當時是
把我搞的暈頭轉向。

在用半字節查表法進行反相運算要特別注意一點,因爲是右移,所以CRC移出的
4Bit與數據XOR的操作是在CRC的高位端。因此餘式表的產生是要以下列數組通過修改函數
crc16r產生。
code uchar ban_fan[]=
{0,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0};

得出餘式表
code uint fan_yushi[16]={
0x0000, 0x1081, 0x2102, 0x3183,
0x4204, 0x5285, 0x6306, 0x7387,
0x8408, 0x9489, 0xa50a, 0xb58b,
0xc60c, 0xd68d, 0xe70e, 0xf78f
};

uint ban_fan_crc(uchar *ptr,uchar len)
{
uchar da;
while(len--!=0)
{
da = (uchar)(crc&0x000f);
crc >>= 4;
crc ^= fan_yushi [da^(*ptr&0x0f)];
da = (uchar)(crc&0x000f);
crc >>= 4;
crc ^= fan_yushi [da^(*ptr/16)];
ptr++;
}
return(crc);
}

主程序中
crc = 0;
ptr = crcbuff_fan;
crc = ban_fan_crc(ptr,8);

執行結果crc = 0x5f1d;

反相運算的全字節查表法就很容易了,懶的寫了。

圖1,圖2是用protel DXP畫的,自己感覺太醜了,哪位哥們知道哪種工具畫這種東西最方
便,通知一聲。嘿。




freebirds
[email protected]




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