C语言实现CRC-16 MODBUS校验码的生成,浅谈CRC-16检验码

关于CRC校验码

CRC又称循环冗余校验,是用来检测或校验数据传输或者保存是否出现错误的一种方法。我也看了很多关于CRC校验码的文章,如果有没看懂的请看CRC码计算及校验原理的最通俗诠释这一篇文章,文章向我们介绍了CRC校验码是通过什么方法计算出来的,文章挺通俗易懂的。校验码的长度可以根据自己的意愿来定,也可以根据国际上的标准来定,根据自己的实际情况来定,校验码越长,出错的概率肯定就越小。

校验码的唯一性问题

为什么说校验码也会出错呢,这就涉及到校验码的唯一性问题的探讨上了,我们知道,我们要发一串数据,校验码是由我们这一串数据的大小得到的,一串数据便对应一串校验码,那么校验码是不是唯一的呢?两串数据是不是得到的校验码就一定不相同呢?答案显然是校验码不具有唯一性!我本篇写的CRC-16校验码是一个16位的校验码,即形如0xFFFF,16位的校验码是由65536个,即只有16位的十六进制数只有65536种组合。当我们不同的数据大于65536种时,那么必定有两个数据用的是一样的校验码,这和366个人里面必定有两个人生日在同一天是一个道理。当然不同的数据小于65536种时也有可能会出现相同校验码的情况。

既然校验码不具有唯一性,是不是说校验码有是也会出错呢?答案是肯定的。比如在传输数据过程中数据发生可变化,但是变化后的数据串和变化前的数据串对应的校验码是同一个,这时校验码的功能就失效了,起不到检验数据是否发生变化的作用了,最终校验成功,得到了错误的数据。但是大家可以想一想,16位的校验码有65536种,数据发生变化的后正好与原数据对应的校验码相同是万分之一的概率,可以视为小概率事件,所以大家可以不用考虑这种情况带来的问题。但是如果你用的校验码比较短,是5位的检验码,校验码的种类只有32种,也就是说不通的数据大于32时就会出现校验码相同的情况,这时校验码失效的可能性便大大增加了。所以要根据实际情况来考虑用什么样的校验码。也可以采取一些其他的措施,比如奇偶校验与CRC校验同时使用,当两个校验码均无误后则说明数据传输无误,这肯定是可以提高准确度的。

校验码代码的实现

可能大家在寻找关于CRC-16校验码遇到的最多的话就是下面这些话

CRC16 Modbus计算原理

1) 预置 1 个 16 位的寄存器为十六进制FFFF(即全为 1) , 称此寄存器为 CRC寄存器。

2) 把第一个 8 位二进制数据 (通信信息帧的第一个字节) 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器。

3) 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位。

4) 如果移出位为 0, 重复第 3 步 ( 再次右移一位); 如果移出位为 1, CRC 寄存器与多项式A001 ( 1010 0000 0000 0001) 进行异或。

5) 重复步骤 3 和步骤 4, 直到右移 8 次,这样整个8位数据全部进行了处理。

6) 重复步骤 2 到步骤 5, 进行通信信息帧下一个字节的处理。

7) 将该通信信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换。

8) 最后得到的 CRC寄存器内容即为 CRC码。

每次文章开头都是这一段话“啪”的一声甩在我的脸上,当然步骤确实是这样,可是我当时刚开始看的时候也是一头雾水。到现在我还是有些部分没有搞懂,大家请看我实现CRC-16 的C代码。

/******************************************************
代码里的 uint8_t 由 typedef unsigned char uint8_t;重定义
uint16_t 由 typedef unsigned short int uint16_t;重定义
******************************************************/
#define length_8 8    //定义一个宏,为传入8位16进制数的个数
uint8_t TxBuffer[length_8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};   //要发送的数据数组
/* ----------------------生成16位的CRC----------------------- */
uint16_t CRC_16_Producter_8(uint8_t *temp)
{
	uint8_t i,j;
	uint16_t CRC_1 = 0xFFFF;          //声明CRC寄存区,也就是步骤1
	for(i = 0;i < length_8;i++)       //这里的for循环说的是步骤6中的重复步骤 2 到步骤 5
	{
		CRC_1 ^= temp[i];            //这里就是步骤2,进行异或运算
		for(j = 0;j < 8;j++)         //用来将异或后的低八位全部移出的for循环
		{
			if(CRC_1 & 0x01)         //判断低八位的最后一位是否为1,为1时执行下列语句,也就是步骤3说的移位判断与步骤5说的右移8次
			{
				/*一定要先移位,再异或*/
				CRC_1 >>=1;          //移位后再异或,就是步骤4
				CRC_1 ^= 0xA001;     //0xA001为0x8005的逆序
			}
			else                    //若不为1,则直接移位。
			{
				CRC_1 >>=1;
			}
		}
	}
	//printf("%04x\r\n",CRC_1);     //用于打印检测CRC校验码
	return(CRC_1);
}

上述代码的到的CRC-16校验码是高位在前,低位在后。我们在将校验码附到数据串后面传输过去的时候要低位在前,高位在后。多以有步骤7的调换高八低八的操作。这一步我是写的另一个函数实现的。代码如下:

uint8_t low_8,High_8;          //定义CRC的低八位和高八位
/* -------- -------将CRC分为高八位和低八位--------- --------- */
void Uint16_tToTwoUint8_t(void)
{
	uint16_t crc;
	crc = CRC_16_Producter_8(TxBuffer);   //此语句会用到生成CRC的函数,所以此函数之前要先声明CRC产生函数
	uint8_t a = 0xFF;
	low_8 = a & crc;
    High_8 = (crc >> 8) & a;
}

验证校验码的正确性

这样CRC生成校验码的代码就完成了,那我们怎么知道自己的校验码正不正确呢?反正不能一个一个取算吧,而且校验码越长,计算量越大,太不现实。这里我给大家提供一个CRC校验码的网址:CRC(循环冗余校验)在线计算。这样我们就可以计算自己生成的校验码正确与否了。

一些疑问

其实自己还是有很多地方不明白,比如为什么是与A001进行异或,CRC-16 MODBUS的公式为 X 16 + X 15 + X 2 + 1 X^{16}+X^{15}+X^{2}+1 X16+X15+X2+1那么除数不应该是11000000000000101吗?17位的除数为什么是与A001进行异或呢?

本篇文章介绍可自己对CRC的一些看法和自己写的一些代码,希望大家多来讨论交流。

你可能感兴趣的:(C语言,c语言,keil,mdk)