本文本文主要说两件事,一是对于网上一些Demo的解释,借用网友思路的Demo,如有雷同纯属巧合。二是关于数据反转的问题。
在《CRC原理——为什么算出来的CRC校验码结果总不一样?》原文链接(https://www.jianshu.com/p/2551ea7dbb14)中解释了关于CRC的一些基本概念,这里谈谈通过高级语言编程的实现思路。
CRC的实现步骤(以crc16为例,宽度为16位):
(1)确定模型标准
(2)将待发送数据与预定初始值进行异或得到新的数据M
(2)在M后面(右侧)加上16个0得到数据N
(3)将N与模型标准除数进行异或,得到余数
(4)将余数与结果异或值进行异或,得到目标值
这里不含输入输出值的正反转。我们很容易会发现一些问题,当所处理的数据量不大时,这样的方法是没有问题的,但实际上我们一般传递的数据都是非常巨大的,如果通过上述方案,恐怕计算机的空间都不够我们使用的,所以我们需要用另一种方法,这里引用网友的思路(不知道谁的,仅适用于常用的8位数据传输,校验宽度为16位,无反转):
(1)预制一个16位的存储空间CRC,并赋初始值
(2)将要发送的数据打包成一个Byte数组(将数据分成多个Byte存储)
(3)将第一个数据左移8位并与CRC当前值进行异或,结果放入CRC
(4)判断当前CRC的最高位(MSB)是否为1,若为1,则左移一位,将MSB移出,并在LSB(最低位)补0,将新的数据与简记式Poly进行异或,结果存入CRC;
若MSB为0则只进行左移操作。
(5)重复步骤3-4直至8个数据移动完毕,此时CRC中的值就是我们要的校验码。
以下附上一段网上https://blog.csdn.net/u012923751/article/details/80352325的C代码
这里我再解释以下变量,如果采用的是左移方式的计算,多项式定为G(X)=X16+X15+X2+1(16#18005),则POLY采用的值应该是16#8005(简记式),其实我们可以看得到这个算法本身就是手工除法的做法了,不过还是需要验证一下。
我这里选用数据为16#AA,多项式沿用16#18005,初始值为0,结果异或值为0,不反转,其手工计算方法如下:
接下来我们验证一下C语言算法:
结果是一致的。
这种算法的好处是,解决了之前说的数据量的问题,每次只处理8个位,等到所有数据处理完后就能得到我们要的数值,其中应用了模2除法交换特性就不多说了。
这是最直接的计算方法,也非常容易理解,但是实际运用过程中,我们还会遇到数据反转的情况,这里以CRC_16 Modbus为例,其二项式为16#18005,初始值16#FFFF,结果异或值16#0000,输入值输出值均反转。
出现这种情况的原因其实就是因为Modbus在传输数据的时候是低位优先的,也就是它是从0位出去的,然后接收端是从0位接收的,因此,如果我们要进行计算,那就要重写一个算法,先把原始数据反转过来,这就增加了程序的工作量。因此,程序员发明了另一个方法,使用反转二项式来解决这个问题。
先用一个实例来说明一下,后面我们解释反转算法的合理性。
以下实例采用CRC16 Modbus 输入输出反转,初始值16#FFFF,多项式16#18005
反转算法的思路是:
(1)预制一个16位的存储空间Preset,并赋初始值(如16#FFFF)
(2)将要发送的数据打包成一个Byte数组(将数据分成多个Byte存储)
(3)将第一个数据左移8位并与CRC当前值进行异或,结果放入Preset
(4)判断当前CRC的最低位(LSB)是否为1,若为1,则右移一位,将LSB移出,并在MSB(最高位)补0,将新的数据与简记式16#A001(16#8005反转)进行异或,结果存入Preset;
若LSB为0则只进行右移操作。
(5)重复步骤3-4直至8个数据移动完毕,此时CRC中的值就是我们要的校验码。
以下为SCL源码:
反转计算其实是一种反向除法,利用的是模2除法不借位特性,但为什么这么做呢?
先做一个理论分析,假设我们发送数据16#AB(1010 1011),这是正向发送,但由于modbus的LSB优先传输特点,接收方实际收到的数据为1101 0101,但它还是会使用16#18005来进行计算,因此,两边其实是一个镜像关系。
语言能力有限,接下来就以16#AB(1010 1011)来验证一下算法的正确性,这里初始值设为0,方便计算,不影响程序:
首先是发送方:
然后再看接收方,接收方收到的数据应该为1101 0101 1000+0010 1111 1101
验算如下:
接收到的验算无余数,证明结果正确,至于反转为什么是16#A001而不是16#14001,与16#8005的结果是一样的,在这套算法中,首位的1被舍去了,反转算法则应该舍去末尾的1。