根据以上说明,可以依葫芦画瓢的写出以下程序。(程序都是在 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
反过来,一切都那么陌生,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; |