CRC的校验原理随着数据采集系统的功能日益强大,以及微型计算机的普及,在现代工业中,利用微机进行数据通讯的工业控制应用得也越来越广泛。
特别是在大规模高精度数据采集系统中,对数据进行分析和计算将占用很大一部分单片机的资源,可以将采集到的数据通过串行通讯方式传送给PC机,由PC机来完成数据的处理工作。
但是由于传输距离、现场状况等诸多可能出现的因素的影响,计算机与受控设备之间的通讯数据常会发生无法预测的错误。
为了防止错误所带来的影响,在数据的接收端必须进行差错校验。
虽然差错校验也可以完全由硬件来承担,但由于单片机和PC都具有很强的软件编程能力,这就为实施软件的差错校验提供了前提条件,而软件的差错校验有经济实用并且不增加硬件开销的优点。
1 、CRC法的原理传统的差错检验法有:奇偶校验法,校验和法,行列冗余校验法等。
这些方法都是在数据后面加一定数量的冗余位同时发送出去,例如在单片机的通讯方式2和3中,TB8就可以作为奇偶校验位同数据一起发送出去,在数据的接收端通过对数据信息进行比较、判别或简单的求和运算,然后将所得和接收到的冗余位进行比较,若相等就认为数据接收正确,否则就认为数据传送过程中出现错误。
但是冗余位只能反映数据行或列的奇偶情况,这类检验方法对数据行或列的偶数个错误不敏感,漏判的概率很高。
因此,此种方法的可靠性就差。
循环冗余码校验英文名称为Cyclical Redundancy Check,简称CRC。
它是利用除法及余数的原理来作错误侦测(Error Detecting)的。
实际应用时,发送装置计算出CRC值并随数据一同发送给接收装置,接收装置对收到的数据重新计算CRC并与收到的CRC相比较,若两个CR C值不同,则说明数据通讯出现错误。
由于这种方法取得校验码的方式具有很强的信息覆盖能力,所以它是一种效率极高的错误校验法。
错误的概率几乎为零。
在很多的仪器设备中都采用这种冗余校验的通讯规约。
根据应用环境与习惯的不同,CRC又可分为以下几种标准:①CRC-12码;②CRC-16码;③CRC-CCITT码;④CRC-32码。
CRC-12码通常用来传送6-bit字符串。
CRC-16及CRC-CCITT码则是用来传送8-b it字符,其中CRC-16为美国采用,而CRC-CCITT为欧洲国家所采用。
CRC-32码大都被采用在一种称为Point-to-Point的同步传输中。
2、CRC校验码的生成过程2.1原理分析过程假设数据传输过程中需要发送15位的二进制信息g=101001110100001,这串二进制码可表示为代数多项式g(x) = x^14 + x^12 + x^9 + x^8 + x^7 + x^5 + 1,其中g中第k位的值,对应g(x)中x^k的系数。
将g(x)乘以x^m,既将g后加m个0,然后除以m阶多项式h(x),得到的(m-1)阶余项r(x)对应的二进制码r就是CRC编码。
h(x)可以自由选择或者使用国际通行标准,一般按照h(x)的阶数m,将CRC算法称为CRC-m,比如CRC-32、CRC-64等。
国际通行标准可参看/wiki/Cyclic_redundancy_check g(x)和h(x)的除运算,可以通过g和h做xor(异或)运算。
比如将11001与10101做xor运算:明白了xor运算法则后,举一个例子使用CRC-8算法求101001110100001的效验码。
CRC-8标准的h(x) = x^8 + x^7 + x^6 + x^4 + x^2 + 1,既h是9位的二进制串111010101。
经过迭代运算后,最终得到的r是10001100,这就是CRC效验码。
通过示例,可以发现一些规律,依据这些规律调整算法:1. 每次迭代,根据gk的首位决定b,b是与gk进行运算的二进制码。
若gk的首位是1,则b=h;若gk的首位是0,则b=0,或者跳过此次迭代,上面的例子中就是碰到0后直接跳到后面的非零位。
2. 每次迭代,gk的首位将会被移出,所以只需考虑第2位后计算即可。
这样就可以舍弃h的首位,将b取h的后m位。
比如CRC-8的h是111010101,b只需是11010101。
3. 每次迭代,受到影响的是gk的前m位,所以构建一个m位的寄存器S,此寄存器储存gk的前m位。
每次迭代计算前先将S的首位抛弃,将寄存器左移一位,同时将gk的后一位加入寄存器。
若使用此种方法,计算步骤如下:※蓝色表示寄存器S的首位,是需要移出的,b根据S的首位选择0或者h。
黄色是需要移入寄存器的位。
S'是经过位移后的S。
2.2查表法同样是上面的那个例子,将数据按每4位组成1个block,这样g就被分成6个block。
下面的表展示了4次迭代计算步骤,灰色背景的位是保存在寄存器中的。
经4次迭代,B1被移出寄存器。
被移出的部分,不是我们关心的,我们关心的是这4次迭代对B2和B3产生了什么影响。
注意表中红色的部分,先作如下定义:B23 = 00111010b1 = 00000000b2 = 01010100b3 = 10101010b4 = 11010101b' = b1 xor b2 xor b3 xor b44次迭代对B2和B3来说,实际上就是让它们与b1,b2,b3,b4做了xor计算,既: B23 xor b1 xor b2 xor b3 xor b4可以证明xor运算满足交换律和结合律,于是:B23 xor b1 xor b2 xor b3 xor b4 = B23 xor (b1 xor b2 xor b3 xor b4) = B23 xor b' = 00111010 ^ 00101011 = 00010001b1是由B1的第1位决定的,b2是由B1迭代1次后的第2位决定(既是由B1的第1和第2位决定),同理,b3和b4都是由B1决定。
通过B1就可以计算出b'。
另外,B1由4位组成,其一共2^4有种可能值。
于是我们就可以想到一种更快捷的算法,事先将b'所有可能的值,16个值可以看成一个表;这样就可以不必进行那4次迭代,而是用B1查表得到b'值,将B1移出,B3移入,与b'计算,然后是下一次迭代。
可看到每次迭代,寄存器中的数据以4位为单位移入和移出,关键是通过寄存器前4位查表获得,这样的算法可以大大提高运算速度。
上面的方法是半字节查表法,另外还有单字节和双字节查表法,原理都是一样的——事先计算出2^8或2^16个b'的可能值,迭代中使用寄存器前8位或16位查表获得b'。
2.3反向算法之前讨论的算法可以称为正向CRC算法,意思是将g左边的位看作是高位,右边的位看作低位。
G的右边加m个0,然后迭代计算是从高位开始,逐步将低位加入到寄存器中。
在实际的数据传送过程中,是一边接收数据,一边计算CRC码,正向算法将新接收的数据看作低位。
逆向算法顾名思义就是将左边的数据看作低位,右边的数据看作高位。
这样的话需要在g的左边加m个0,h也要逆向,例如正向CRC-16算法h=0x4c11db8,逆向CRC-16算法h=0xedb88320。
b的选择0还是h,由寄存器中右边第1位决定,而不是左边第1位。
寄存器仍旧是向左位移,就是说迭代变成从低位到高位。
参数,可以将上次计算的crc结果传入函数中作为这次计算的初始值,这对大数据块的CRC 计算是很有用的,不需要一次将所有数据读入内存,而是读一部分算一次,全读完后就计算完了。
这对内存受限系统还是很有用的。
1.#define POLY 0x10212./*3. * Calculating CRC-16 in 'C'4. * @para addr, start of data5. * @para num, length of data6. * @para crc, incoming CRC7.*/8.uint16_t crc16(unsigned char *addr, int num, uint16_t crc)9.{10.int i;11.for (; num > 0; num--) /* Step through bytes in memory */12. {13. crc = crc ^ (*addr++ << 8); /* Fetch byte from memory, XOR intoCRC top byte*/14.for (i = 0; i < 8; i++) /* Prepare to rotate 8 bits */15. {16.if (crc & 0x8000) /* b15 is set... */17. crc = (crc << 1) ^ POLY; /* rotate and XOR with polynomic*/18.else/* b15 is clear... */19. crc <<= 1; /* just rotate */20. } /* Loop for 8 bits */21. crc &= 0xFFFF; /* Ensure CRC remains 16-bit value */22. } /* Loop until num=0 */23.return(crc); /* Return updated CRC */24.}上面的算法对数据流逐位进行计算,效率很低。
实际上仔细分析CRC计算的数学性质后我们可以多位多位计算,最常用的是一种按字节查表的快速算法。
该算法基于这样一个事实:计算本字节后的CRC码,等于上一字节余式CRC码的低8位左移8位,加上上一字节CRC右移8位和本字节之和后所求得的CRC码。
如果我们把8位二进制序列数的CRC(共256个)全部计算出来,放在一个表里,编码时只要从表中查找对应的值进行处理即可。
按照这个方法,可以有如下的代码:1./*2.crc.h3.*/4.5.#ifndef CRC_H_INCLUDED6.#define CRC_H_INCLUDED7.8./*9.* The CRC parameters. Currently configured for CCITT.10.* Simply modify these to switch to another CRC Standard.11.*/12./*13.#define POLYNOMIAL 0x800514.#define INITIAL_REMAINDER 0x000015.#define FINAL_XOR_VALUE 0x000016.*/17.#define POLYNOMIAL 0x102118.#define INITIAL_REMAINDER 0xFFFF19.#define FINAL_XOR_VALUE 0x000020.21./*22.#define POLYNOMIAL 0x102123.#define POLYNOMIAL 0xA00124.#define INITIAL_REMAINDER 0xFFFF25.#define FINAL_XOR_VALUE 0x000026.*/27.28./*29.* The width of the CRC calculation and result.30.* Modify the typedef for an 8 or 32-bit CRC standard.31.*/32.typedef unsigned short width_t;33.#define WIDTH (8 * sizeof(width_t))34.#define TOPBIT (1 << (WIDTH - 1))35.36./**37. * Initialize the CRC lookup table.38. * This table is used by crcCompute() to make CRC computation faster.39. */40.void crcInit(void);41.42./**43. * Compute the CRC checksum of a binary message block.44. * @para message, 用来计算的数据45. * @para nBytes, 数据的长度46. * @note This function expects that crcInit() has been called47. * first to initialize the CRC lookup table.48. */49.width_t crcCompute(unsigned char * message, unsigned int nBytes);50.51.#endif // CRC_H_INCLUDED1./*2. *crc.c3. */4.5.#include "crc.h"6./*7.* An array containing the pre-computed intermediate result for each8.* possible byte of input. This is used to speed up the computation.9.*/10.static width_t crcTable[256];11.12./**13. * Initialize the CRC lookup table.14. * This table is used by crcCompute() to make CRC computation faster.15. */16.void crcInit(void)17.{18. width_t remainder;19. width_t dividend;20.int bit;21./* Perform binary long division, a bit at a time. */22.for(dividend = 0; dividend < 256; dividend++)23. {24./* Initialize the remainder. */25. remainder = dividend << (WIDTH - 8);26./* Shift and XOR with the polynomial. */27.for(bit = 0; bit < 8; bit++)28. {29./* Try to divide the current data bit. */30.if(remainder & TOPBIT)31. {32. remainder = (remainder << 1) ^ POLYNOMIAL;33. }34.else35. {36. remainder = remainder << 1;37. }38. }39./* Save the result in the table. */40. crcTable[dividend] = remainder;41. }42.} /* crcInit() */43.44./**45. * Compute the CRC checksum of a binary message block.46. * @para message, 用来计算的数据47. * @para nBytes, 数据的长度48. * @note This function expects that crcInit() has been called49. * first to initialize the CRC lookup table.50. */51.width_t crcCompute(unsigned char * message, unsigned int nBytes)52.{53. unsigned int offset;54. unsigned char byte;55. width_t remainder = INITIAL_REMAINDER;56./* Divide the message by the polynomial, a byte at a time. */57.for( offset = 0; offset < nBytes; offset++)58. {59. byte = (remainder >> (WIDTH - 8)) ^ message[offset];60. remainder = crcTable[byte] ^ (remainder << 8);61. }62./* The final remainder is the CRC result. */63.return (remainder ^ FINAL_XOR_VALUE);64.} /* crcCompute() */1.uint crctable[]={ //CRC计算用表2. 0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,3. 0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,4. 0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,5. 0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,6. 0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,7. 0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,8. 0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,9. 0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,10. 0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,11. 0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,12. 0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,13. 0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,14. 0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,15. 0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,16. 0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,17. 0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,18. 0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,19. 0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,20. 0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,21. 0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,22. 0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,23. 0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,24. 0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,25. 0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,26. 0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,27. 0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,28. 0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,29. 0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,30. 0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,31. 0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,32. 0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,33. 0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x404034.};。