【日志】CRC终于搞成了

来源:互联网 发布:网站快速优化排名 编辑:程序博客网 时间:2024/05/19 13:09

今晚看了好久CRC,最后还有没有很明白。但是做为一个做工程的,有结果就好了。

我要用的不是标准的CRC公式,是X8+1这个,呵呵。下面开始总结。

CRC用到的主要是模2除法,开始看得一头雾水,只有把这个弄清楚了,后来才有了思路。才知道CRC的计算过程。(此处是重点,我费了很多劲儿理解,省去若干字。)不知道计算过程,程序是绝对不能看懂的。

还有这么一句话:多项式的MSB略去不记,因其只对商有影响而不影响余数。 就是说对于CRC-CCITT=X16+X12+X5+1,可以只用0x1021,bit16位的1不要了,只留下bit12、5、0。(参考一篇modbus的说明)

记住这两点,再参考下面一段话:

生成CRC-16校验字节的步骤如下:
①装如一个16位寄存器,所有数位均为1。
②该16位寄存器的高位字节与开始8位字节进行“异或”运算。运算结果放入这个16位寄存器。
③把这个16寄存器向右移一位。
④若向右(标记位)移出的数位是1,则生成多项式1010000000000001和这个寄存器进行“异或”
运算;若向右移出的数位是0,则返回③。
⑤重复③和④,直至移出8位。
⑥另外8位与该十六位寄存器进行“异或”运算。
⑦重复③~⑥,直至该报文所有字节均与16位寄存器进行“异或”运算,并移位8次。
⑧这个16位寄存器的内容即2字节CRC错误校验,被加到报文的最高有效位。

别的都是弯路,以上是最清楚的、实惠的解释了。本文最后,附上另一位网游的文章,也给我很大启发。

下面是成功的程序:

unsigned short crc(unsigned char *addr, int num)
{
 int i;
 while (num--)
 {
  crc8 ^= *addr++;
  for (i = 0; i < 8; i++)
  {
   if (crc8 & 128)
   {
    crc8 <<= 1;
    crc8 ^= 0x01;  //1010 0000 0000 0001b
   }
   else
   {
    crc8 <<= 1;
   }
  }
 }
 return crc8;
}

另外这样也行:

unsigned short crc(unsigned char *addr, int num)
{
 //unsigned short crc8 = 0xFF;
 int i;
 while (num--)
 {
  crc8 ^= *addr++;
  for (i = 0; i < 8; i++)
  {
   if (crc8 & 1)
   {
    crc8 >>= 1;
    crc8 ^= 0x80;  //1010 0000 0000 0001b
   }
   else
   {
    crc8 >>= 1;
   }
  }
 }
 return crc8;
}

可能是因为我的x8+1只有一位的原因吧,或者什么其他的原因,我已经不愿意思考下去了。好累。注意:其中两个关键的地方就是移位1.操作不同;2.生成多项式对应的字节值不同。调整这两个变量,我想,能实现不同生成多项式的CRC算法。

另外还有查表的方式,希望以后再用到,我可以很快的搞懂。

转:

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);
A
15 14 1 3 12 1 1 + 1 0 9 8 7 6 5 4 + 3 2 1 0 +
执行结果 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
ouyangxianping@263.net

原创粉丝点击