CRC:Cyclic Redundant Check(循环冗余校验)
在数据链路层中,对于通信传输的差错检测一般有两种:使用纠错码(error-correcting code,又称前向纠错/FEC(Forward Error Correction ))或检错码,前者可用来检测哪些比特位有误,而后者只能用来检测传输的整体上是否出错,若出错则需请求重传。
典型的纠错码包括汉明码、二进制卷积码、里德所罗门码以及低密度奇偶校验码(LDPC)等等。而检错码的代表则有奇偶校验码、校验和(checksum)以及循环冗余校验。
循环冗余校验(CRC)是奇偶校验码的改进,又称为多项式编码,其原理为:发送方与接收方约定一个相同的多项式(POLY),如多项式x6+x4+x2+x+1对应的二进制码为1010111,然后将发送报文对改二进制码进行异或运算(即做除法),得到的余数则为CRC校验码,并附带在发送报文之后。接收方再将收到的报文对该二进制数进行异或运算,若没有余数(即能整除)则说明传输无误,否则需要重传。
CRC的多项式可以自由选择或者使用国际通行标准,一般按照多项式的阶数m,将CRC算法称为CRC-m,比如CRC-16、CRC-32,其中CRC-16被用于Modbus RTU通信协议中。
http://www.ip33.com/crc.html中可以计算包括Modbus在内的不同规则下的CRC校验码结果:
查表法计算CRC校验码
1.生成CRC16表
对于较长的报文,连续进行异或运算会消耗太多计算资源,特别是在性能受限的下位机上,因此计算CRC码一般不用直接计算法而是采用查表法。CRC16的表为0x00到0xff的256个整数,与多项式0x8005通过上述计算方式提前算好并生成,以便计算机在后续环节可以直接调用算好的数据,节省时间。
生成好的表如下:
const UINT16 auchCRC[256] =
{
0x0000,0xC0C1,0xC181,0x0140,0xC301,0x03C0,0x0280,0xC241,0xC601,0x06C0,0x0780,0xC741,0x0500,0xC5C1,0xC481,0x0440,0xCC01,0x0CC0,0x0D80,0xCD41,0x0F00,0xCFC1,0xCE81,0x0E40,0x0A00,0xCAC1,0xCB81,0x0B40,0xC901,0x09C0,0x0880,0xC841,0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41,0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040,0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441,0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840,0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40,0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041,0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441,0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840,0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40,0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041,0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440,0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841,0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41,0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040
};
表的生成算法可以参考:https://blog.csdn.net/m0_57585228/article/details/125088520
代码:
#define POLY 0xA001 // 生成多项式Ox8005的反序,即1000000000000101→1010000000000001,即A001
unsigned short auchCRC[256]; // CRC表
void make_crc_table(void) // 生成CRC表的函数
{
unsigned short c;
int i, j;
for (i = 0; i < 256; i++)
{
c = i;
for (j = 0; j < 8; j++)
{
if (c & 0x1) // 如果最低位为1,则右移一位并与生成多项式异或(商1)
c = (c >> 1) ^ POLY;
else // 如果最低位为0,则只右移一位,无需异或(商0)
c = c >> 1;
}
auchCRC[i] = c; // 将计算出的CRC校验码存入表中
}
}
2.单字节计算CRC16
根据Modbus CRC-16协议,其寄存器的初始值不为0000而是FFFF,且输入数据与输出数据均要反转。
单字节计算CRC16步骤(以0x01为例):
1.将字节取反后查表
0x01→0xfe,即254→查表得到0x8081
2.得到的结果的低八位取反,高八位不变
0x8081低八位为81,取反为7e→0x807e
3.得出CRC结果为0x807e
3.字节流计算CRC16
上述为一个字节即0x01的CRC码的计算,如果遇到要计算多个字节的字节流数据例如{0x01,0x02,0x03}该如何计算?
这里涉及到一些数学推导,参考:https://blog.csdn.net/G1036583997/article/details/10328933,归纳如下:
字节型算法的一般描述为:本字节的CRC码,等于上一字节CRC码的低8位左移8位,与上一字节CRC右移8位同本字节异或后所得的CRC码异或。其步骤可以简单展示为:
1.取上字节的CRC码的低八位,与本字节进行异或,得到下标index
2.拿index去查表,得到结果M
3.取上字节的CRC码的高八位,与M进行异或,得到结果T,即新算得的CRC码值
4.重复以上步骤,直到所有字节均已计算完成,则可以得到最后的CRC-16码
代码:
//计算CRC16码,使用查表法
UINT16 CRC16(u8 *data, int len)
{
UINT16 CRC = 0xffff;//0xff与数据异或就相当于取反,故只需要一开始把CRC初始值设置为0xffff即可
UINT8 index;
for (int i = 0; i < Len ; i++)
{
index = (CRC & 0xFF)^ data[i];//取上一字节的CRC低八位,与本字节异或
CRC >>= 8;//取上一字节的高八位
CRC ^= auchCRC[index];//与查表后的结果异或
}
return (CRC);