CCITT CRC-16计算原理与实现 (2010-04-01 13:18:52)转载▼
标签: 杂谈
CRC的全称为Cyclic Redundancy Check,中文名称为循环冗余校验。它是一类重要的线性分组码,编码和解码方法简单,检错和纠错能力强,在通信领域广泛地用于实现差错控制。实际上,除数据通信外,CRC在其它很多领域也是大有用武之地的。例如我们读软盘上的文件,以及解压一个ZIP文件时,偶尔会碰到“Bad CRC”错误,由此它在数据存储方面的应用可略见一斑。
差错控制理论是在代数理论基础上建立起来的。这里我们着眼于介绍CRC的算法与实现,对原理只能捎带说明一下。若需要进一步了解线性码、分组码、循环码、纠错编码等方面的原理,可以阅读有关资料。
利用CRC进行检错的过程可简单描述为:在发送端根据要传送的k位二进制码序列,以一定的规则产生一个校验用的r位监督码(CRC码),附在原始信息后边,构成一个新的二进制码序列数共k+r位,然后发送出去。在接收端,根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错。这个规则,在差错控制理论中称为“生成多项式”。1 代数学的一般性算法
在代数编码理论中,将一个码组表示为一个多项式,码组中各码元当作多项式的系数。例如 1100101 表示为
1·x6+1·x5+0·x4+0·x3+1·x2+0·x+1,即 x6+x5+x2+1。
设编码前的原始信息多项式为P(x),P(x)的最高幂次加1等于k;生成多项式为G(x),G(x)的最高幂次等于r;CRC多项式为R(x);编码后的带CRC的信息多项式为T(x)。
发送方编码方法:将P(x)乘以xr(即对应的二进制码序列左移r位),再除以G(x),所得余式即为R(x)。用公式表示为
T(x)=xrP(x)+R(x)
接收方解码方法:将T(x)除以G(x),如果余数为0,则说明传输中无错误发生,否则说明传输有误。
举例来说,设信息码为1100,生成多项式为1011,即P(x)=x3+x2,G(x)=x3+x+1,计算CRC的过程为
xrP(x) x3(x3+x2) x6+x5 x
-------- = ---------- = -------- = (x3+x2+x) + --------
G(x) x3+x+1 x3+x+1 x3+x+1
即 R(x)=x。注意到G(x)最高幂次r=3,得出CRC为010。
如果用竖式除法,计算过程为
1110
-------
1011 /1100000 (1100左移3位)
1011
----
1110
1011
-----
1010
1011
-----
0010
0000
----
010
因此,T(x)=(x6+x5)+(x)=x6+x5+x, 即 1100000+010=1100010
如果传输无误,
T(x) x6+x5+x
------ = --------- = x3+x2+x,
G(x) x3+x+1
无余式。回头看一下上面的竖式除法,如果被除数是1100010,显然在商第三个1时,就能除尽。
上述推算过程,有助于我们理解CRC的概念。但直接编程来实现上面的算法,不仅繁琐,效率也不高。实际上在工程中不会直接这样去计算和验证CRC。
下表中列出了一些见于标准的CRC资料:名称 生成多项式 简记式* 应用举例 CRC-4 x4+x+1 ITU G.704 CRC-12 x12+x11+x3+x+1 CRC-16 x16+x12+x2+1 1005 IBM SDLC CRC-ITU** x16+x12+x5+1 1021 ISO HDLC, ITU X.25, V.34/V.41/V.42, PPP-FCS CRC-32 x32+x26+x23+...+x2+x+1 04C11DB7 ZIP, RAR, IEEE 802 LAN/FDDI, IEEE 1394, PPP-FCS CRC-32c x32+x28+x27+...+x8+x6+1 1EDC6F41 SCTP
* 生成多项式的最高幂次项系数是固定的1,故在简记式中,将最高的1统一去掉了,
如04C11DB7实际上是104C11DB7。
** 前称CRC-CCITT。ITU的前身是CCITT。4.CRC算法的实现
---------------
要用程序实现CRC算法,考虑对第2节的长除法做一下变换,依然是M = 11100110,G = 1011,
其系数r为3。11001100                 ------------------------
1011 )11100110000                1011.......                      ----.......                         1010......                    1011......      ----......                1110...                 1011...                    ------...                     1010..                  1011..                   -------                      100  <---校验码      程序可以如下实现:1)将Mx^r的前r位放入一个长度为r的寄存器;2)如果寄存器的首位为1,将寄存器左移1位(将Mx^r剩下部分的MSB移入寄存器的LSB),再与G的后r位异或,否则仅将寄存器左移1位(将Mx^r剩下部分的MSB移入寄存器的LSB);3)重复第2步,直到M全部Mx^r移入寄存器;4)寄存器中的值则为校验码。
基于以上算法,我们可以看一下上面例子的程序计算过程:(r=3)首先,111 00110000前三位进入寄存器,即111这时寄存器首位为1,执行第2步,移位成110 0110000,这时寄存器中为前三位110,将其与011(生成多项式后三位)异或,得101 0110000.然后继续第2步,101首位为1,移位010 110000,然后010与011异或,得  001 110000
前面两个0,连续以为2次且不用计算异或,得111 0000,接着移位110 000,异或得101 000第一位为1,移位得010 00,前三位异或得001 00最后因为前面两个0,直接移位两次后得寄存器中的内容100,这时Mx^r位的所有内容都移入寄存器,运算结束,记得检验码为100。(关键先判断首位是否为1,然后移位,然后计算)111 00110000移位->1 110 0110000011101 0110000  -->101第一位为1,移位且计算1 010 110000011001 110000-->001第一位第二位均为0,移位2次00 111 0000-->111第一位为1,移位且计算1 110 000011101 000-->101第一位为1,移位且计算1 010 00011001 00-->移位2次得100
用CRC16-CCITT的生成多项式0x1021,其C代码(本文所有代码假定系统为32位,且都在VC6上编译通过)如下:
unsigned short do_crc(unsigned char *message, unsigned int len)
{int i, j;unsigned short crc_reg;crc_reg = (message[0] << 8) + message[1];for (i = 0; i < len; i++){if (i < len - 2)for (j = 0; j <= 7; j++){if ((short)crc_reg < 0)crc_reg = ((crc_reg << 1) + (message[i + 2] >> (7 - i))) ^ 0x1021;elsecrc_reg = (crc_reg << 1) + (message[i + 2] >> (7 - i));     }elsefor (j = 0; j <= 7; j++){if ((short)crc_reg < 0)crc_reg = (crc_reg << 1) ^ 0x1021;elsecrc_reg <<= 1;            }        }return crc_reg;
}
显然,每次内循环的行为取决于寄存器首位。由于异或运算满足交换率和结合律,以及与0异或无影响,消息可以不移入寄存器,而在每次内循环的时候,寄存器首位再与对应的消息位异或。改进的代码如下:
unsigned short do_crc(unsigned char *message, unsigned int len)
{int i, j;unsigned short crc_reg = 0;unsigned short current;for (i = 0; i < len; i++){current = message[i] << 8;for (j = 0; j < 8; j++){if ((short)(crc_reg ^ current) < 0)crc_reg = (crc_reg << 1) ^ 0x1021;elsecrc_reg <<= 1;current <<= 1;           }}return crc_reg;
}
以上的讨论中,消息的每个字节都是先传输MSB,CRC16-CCITT标准却是按照先传输LSB,消息右移进寄存器来计算的。只需将代码改成判断寄存器的LSB,将0x1021按位颠倒后(0x8408)与寄存器异或即可,如下所示:
unsigned short do_crc(unsigned char *message, unsigned int len)
{int i, j;unsigned short crc_reg = 0;unsigned short current;for (i = 0; i < len; i++){current = message[i];for (j = 0; j < 8; j++){if ((crc_reg ^ current) & 0x0001)crc_reg = (crc_reg >> 1) ^ 0x8408;elsecrc_reg >>= 1;current >>= 1;           }}return crc_reg;
}
该算法使用了两层循环,对消息逐位进行处理,这样效率是很低的。为了提高时间效率,通常的思想是以空间换时间。考虑到内循环只与当前的消息字节和crc_reg的低字节有关,对该算法做以下等效转换:
unsigned short do_crc(unsigned char *message, unsigned int len)
{int i, j;unsigned short crc_reg = 0;unsigned char  index;unsigned short to_xor;for (i = 0; i < len; i++){index = (crc_reg ^ message[i]) & 0xff;to_xor = index;      for (j = 0; j < 8; j++){if (to_xor & 0x0001)to_xor = (to_xor >> 1) ^ 0x8408;elseto_xor >>= 1;          }crc_reg = (crc_reg >> 8) ^ to_xor;}return crc_reg;
}
现在内循环只与index相关了,可以事先以数组形式生成一个表crc16_ccitt_table,使得to_xor = crc16_ccitt_table[index],于是可以简化为:
unsigned short do_crc(unsigned char *message, unsigned int len)
{unsigned short crc_reg = 0;while (len--)crc_reg = (crc_reg >> 8) ^ crc16_ccitt_table[(crc_reg ^ *message++) & 0xff];return crc_reg;
}
crc16_ccitt_table通过以下代码生成:
int main()
{unsigned char index = 0;unsigned short to_xor;int i;printf("unsigned short crc16_ccitt_table[256] =\n{");while (1){if (!(index % 8))printf("\n");to_xor = index;      for (i = 0; i < 8; i++){if (to_xor & 0x0001)to_xor = (to_xor >> 1) ^ 0x8408;elseto_xor >>= 1;          }           printf("0x%04x", to_xor);if (index == 255){printf("\n");break;}else{printf(", ");index++;}}printf("};");return 0;
}
生成的表如下:
unsigned short crc16_ccitt_table[256] =
{
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};
这样对于消息unsigned char message[len],校验码为:unsigned short code = do_crc(message, len);
并且按以下方式发送出去:message[len] = code & 0x00ff;message[len + 1] = (code >> 8) & 0x00ff;接收端对收到的len + 2字节执行do_crc,如果没有差错发生则结果应为0。
在一些传输协议中,发送端并不指出消息长度,而是采用结束标志,考虑以下几种差错:1)在消息之前,增加1个或多个0字节;2)消息以1个或多个连续的0字节开始,丢掉1个或多个0;3)在消息(包括校验码)之后,增加1个或多个0字节;4)消息(包括校验码)以1个或多个连续的0字节结尾,丢掉1个或多个0;显然,这几种差错都检测不出来,其原因就是如果寄存器值为0,处理0消息字节(或位),寄存器值不变。为了解决前2个问题,只需寄存器的初值非0即可,对do_crc作以下改进:unsigned short do_crc(unsigned short reg_init, unsigned char *message, unsigned int len)
{unsigned short crc_reg = reg_init;while (len--)crc_reg = (crc_reg >> 8) ^ crc16_ccitt_table[(crc_reg ^ *message++) & 0xff];return crc_reg;
}
在CRC16-CCITT标准中reg_init = 0xffff,为了解决后2个问题,在CRC16-CCITT标准中将计算出的校验码与0xffff进行异或,即:unsigned short code = do_crc(0xffff, message, len);code ^= 0xffff;message[len] = code & 0x00ff;message[len + 1] = (code >> 8) & 0x00ff;  显然,现在接收端对收到的所有字节执行do_crc,如果没有差错发生则结果应为某一常值GOOD_CRC。其满足以下关系:unsigned char p[]= {0xff, 0xff};GOOD_CRC = do_crc(0, p, 2);
其结果为GOOD_CRC = 0xf0b8。
在同一程序中验证如下(放在main函数中可试验):unsigned char p[]= {0xa0,0xb0,0xff, 0xff};unsigned short crc;       crc= do_crc(0xffff, p, 2);  //计算前两位的CRC码crc^=0xffff;     //对其取反p[2]=crc&0x00ff;   //将计算的CRC码加到信息序列后面p[3]=crc>>8&0x00ff;printf("p[2]=%x,p3=%x\n",p[2],p[3]);crc=do_crc(0xffff,p,4);   //对信息码+CRC码共同计算得出CRC=0xf0b8printf("crc is %x\n",crc);
假设发送的信息是p[0],p[1];低位先发,对其计算的CRC加到信息码后面
然后对信息码+CRC码共同计算CRC值,此时应该是常数0xf0b8。不管信息码如何变化,内容和长度都可变,只要把计算的CRC码加进去一起计算CRC,就应该是得该常数GOOD_CRC。

ccitt crc16相关推荐

  1. 【CRC笔记】CRC-16 KERMIT C语言实现

    CRC笔记 CRC-16/KERMIT 简介及C语言实现 一.CRC-16 KERMIT算法简介 二.CRC-16/KERMIT算法基本信息 三.CRC-16/KERMIT算法的C语言实现 1.查表法 ...

  2. crc16的c语言函数 计算ccitt_CCITT CRC-16计算原理与实现CRC-ITU

    CCITT CRC-16 计算原理与实现 时间: 201 1 -08-28 22:37 :20 来源: 作者: CRC 的全称为 Cy clic Redundancy Check ,中文名称为循环冗余 ...

  3. CRC-16原理及通用的16位CRC校验算法代码

    CRC-16原理及通用的16位CRC校验算法代码 循环冗余码校验英文名称为Cyclical Redundancy Check,简称CRC.它是利用除法及余数的原理来作错误侦测(Error Detect ...

  4. c语言累加和校验_循环冗余校验(CRC)算法入门

    http://blog.csdn.net/liyuanbhu/article/details/7882789 前言 CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式.在嵌入式软件开发中,经常要 ...

  5. CRC校验,用于大家参考和日后查阅,内容引用github,非本人创作。

    C语言 /************************************************************************ Filename: crc.h* * Des ...

  6. 写给嵌入式程序员的循环冗余校验(CRC)算法入门引导

    CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式.在嵌入式软件开发中,经常要用到CRC 算法对各种数据进行校验.因此,掌握基本的CRC算法应是嵌入式程序员的基本技能.可是,我认识的嵌入式程序员 ...

  7. 循环冗余校验(CRC)算法入门引导

    写给嵌入式程序员的循环冗余校验(CRC)算法入门引导 前言 CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式.在嵌入式软件开发中,经常要用到CRC 算法对各种数据进行校验.因此,掌握基本的CR ...

  8. 循环冗余校验(CRC)算法

    作者Ross Williams,"A PAINLESS GUIDE TO CRC ERROR DETECTION ALGORITHMS". 本文的目标是介绍CRC算法的基本原理和实 ...

  9. CRC 算法的简单说明

    写给嵌入式程序员的循环冗余校验(CRC)算法入门引导 前言 CRC校验(循环冗余校验)是数据通讯中最常采用的校验方式.在嵌入式软件开发中,经常要用到CRC 算法对各种数据进行校验.因此,掌握基本的CR ...

最新文章

  1. 创业丨中国人工智能领域投资机构10强榜单
  2. 虎牙直播在微服务改造方面的实践和总结
  3. Ubuntu20.04中安装shutter
  4. android 关于内存优化的一些总结
  5. username is marked non-null but is null
  6. 针对大表 设计高效的存储过程【原理篇】 附最差性能sql语句进化过程客串
  7. 云计算论文集, Spark, 数据描述语言, 运维工具集
  8. 使用lucene实现简单的全文检索
  9. shutdown immediate ,无法关闭数据库的解决方案
  10. 解读Unity中的CG编写Shader系列二
  11. Drupal是如何避免页面缓存保存Message信息的
  12. 西门子PLC_s7-200免费学习视频教程
  13. mac的python怎么打中文空格_中英文排版空格问题解决方案
  14. 恭喜马斯克、纳德拉当选美国工程院院士,张宏江、方岱宁入选外籍院士
  15. 2021-2027全球与中国能源物流市场现状及未来发展趋势
  16. 手机学习利器Qpython
  17. 【Vegas原创】华为一键强制关闭后台应用的终极解决方法
  18. 计算机组成原理-流水线技术学习笔记1
  19. 【论文学习】STN —— Spatial Transformer Networks
  20. AndroidStudio使用之仿微信首页界面

热门文章

  1. 什么是区块链?区块链相关知识区入门
  2. 合泰HT32单片机串口通信
  3. 3422. 【NOIP2013模拟】水叮当的舞步
  4. 林仕鼎谈数据中心计算(三):学术界与工业界,以及未来展望
  5. Unity中鼠标的交互事件
  6. 关于Anaconda网络问题 An HTTP error occurred when trying to retrieve this URL.
  7. UDE上的这家潮电街区,惊艳了整个展会
  8. 第二次433M无线距离测试
  9. 好用的外贸企业邮箱哪个好?
  10. 计算机大赦天下教程,魔兽世界9.0大赦天下成就攻略 逃亡之魂获取方法[多图]