一、相关背景
1、CRC校验码的基本思想是利用线性编码理论,在发送端根据要传送的k位二进制码序列,以一定的规则产生一个校验用的监督码(既CRC码)r位,并附在信息后边,构成一个新的二进制码序列数共(k+ r)位,最后发送出去。在接收端,则根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错。
2、CRC的本质是模-2除法的余数,采用的除数不同,CRC的类型也就不一样。 实际代码使用中,模-2除法与 ^(异或) 效果相同,个人喜欢用无进位加法理解。
3、CRC校验有严格的校验方式,实际报文解析中,可能存在字节按位逆序的情况,但有些报文中经常不明确指出,下文中会使用一种我常用的方式。
4、本文是我查看网上的很多博客后的个人总结与理解,如果有错误或者代码处理不够优化的地方,请及时指出,及时更新。
二、CRC算法基本规则(正向)
1、根据标准选择初值(生成寄存器)。
2、将数据的第一个字节与寄存器高8位异或。
3、判断最高位,若该位为 0 左移一位,若为 1 左移一位再与多项式Hex码异或。
4、重复3操作直至8位全部移位计算结束。
5、重复将所有输入数据操作完成以上步骤,所得16位数即16位CRC校验码。
6、补充,这里的标准要格外注意,标准中会规定高地位以及是否按位反转。
三、CRC16_MODBUS协议规则
1、此处附加一个自己经常测试校验结果的在线网站,里面有各种协议标准的参数模型和在线校验:
http://www.ip33.com/crc.html
2、CRC16_MODBUS基本规则:
①字节高位在左,地位在右
②寄存器初始值为0xFFFF
③待测数据的每个字节操作前需要按位反转
④计算结果按位反转后与0x0000异或
⑤寄存器宽度为16
四、CRC16_MODBUS正向校验代码
//正序校验
public static String Crc16Left(String dataHexStr)
{
//字符串类型的16进制字符串转换为字节数组
byte[] data = CrcCheckTool.hex2Bytes(dataHexStr);
//初始化寄存器 这个转为short类型 是因为java中 short类占16个字节
short crci= (short) 0xffff;
int i,j;
//循环处理字节数组中的每一个字节
for ( j = 0; j < data.length; j++)
{
//字节逆序(一个字节占8位 我们从寄存器的最高位处理数据 所以数据左移8)写入寄存器需要与原值进行一次异或
crci ^= (CrcCheckTool.reverse8BITS(data[j])<<8);
for ( i = 0; i < 8; i++){
//判断即将左移 出的是不是1,如果是1则与多项式进行异或。(此处个人认为和小学学的除法算法中在结果上写1或者0类似)
if ((crci & 0x8000) == 0x8000){
crci <<= 1;
//将数据与约定的多项式做(模二除/异或)
crci ^= 0x8005;
}else{
crci <<= 1;
}
}
}
//结果值按位反转(结果值是一定小于多项式的16位,所以按位反转跟8位的方法不同) 然后与结果异或值异或
String res = Integer.toHexString(CrcCheckTool.reverse16BITS(crci)^0x0000);
return res;
}
//将16进制字符串转换为byte[]
public static byte[] hex2Bytes(String str) {
if(str == null || str.trim().equals("")) {
return new byte[0];
}
byte[] bytes = new byte[str.length() / 2];
for(int i = 0; i < str.length() / 2; i++) {
String subStr = str.substring(i * 2, i * 2 + 2);
bytes[i] = (byte) Integer.parseInt(subStr, 16);
}
return bytes;
}
//8位逆序反转 (逆序反转的方式有很多,之前看的帖子说这样运算最快,未实际验证)
private static int reverse8BITS(int b){
//交换1位
b =(b & 0xaa) >> 1 | (b & 0x55) << 1;
//交换2位
b =(b & 0xcc) >> 2 | (b & 0x33) << 2;
//交换4位
b =(b & 0xf0) >> 4 | (b & 0x0f) << 4;
return b;
}
//16位逆序反转
private static int reverse16BITS(int b){
//交换1位
b =(b & 0xaaaa) >> 1 | (b & 0x5555) << 1;
//交换2位
b =(b & 0xcccc) >> 2 | (b & 0x3333) << 2;
//交换4位
b =(b & 0xf0f0) >> 4 | (b & 0x0f0f) << 4;
//交换8位
b =(b & 0xff00) >> 8 | (b & 0x00ff) << 8;
return b;
}
五、CRC16_MODBUS逆向校验代码
网上对此种模式的校验很多,但大多是以逆序校验的方式进行的,与CRC的标准要求算法不同,我最开始分析的时候,很难看懂。
但是如果,仔细研究CRC校验的设计思想,实际操作是把原始数据整体看成二进制序列,统一进行的一次循环求余操作,其实与字节的左移处理与右移处理无关,当字节做右移处理时,代码如下
//逆序校验
public static String CRC16Right(String dataHexStr)
{
//字符串类型的16进制字符串转换为字节数组
byte[] data = DataModelFormatTool.hex2Bytes(dataHexStr);
//初始化寄存器 我们用数据的低八位进行处理,已经可以不用限制寄存器的个数了
//我测试了int long 都能达到预期的效果 但是short 反而数据错误 没想明白为什么。
int crci = 0xffff;
int i,j;
for ( j = 0; j < data.length; j++)
{
//此处&0x00ff的目的是为了将数据放入寄存器的低8位并与寄存器异或
crci ^= data[j] & 0x00ff;
for ( i = 0; i < 8; i++){
//判断即将右移的是不是1 如果是1则与多项式进行异或
if ((crci & 0X0001) == 0X0001){
crci >>= 1;
//将数据与约定的多项式做(模二除/异或) 数据流向变了此处的异或值实际是8005的逆序反转a001
crci ^= 0xa001;
}else{
crci >>= 1;
}
}
}
//结果值与0x0000异或 其实值不会改变,不异或对结果没有影响
String res = Integer.toHexString( crci^ 0x0000) ;
return res;
}
你会发现逆序校验减少了多次的逆序操作,只需要事先计算一次多项式的逆序值就可以了,对于整体的数据位来说,实际数据预算过程与结果没有变化,故逆序操作的实现方式更省资源。
六、crc的意义在于校验数据传输过程中无法规避的数据变化,通过事先约定好的校验多项式,来确保帧的数据格式正确。常见的crc校验模型有很多,如果不必要,不建议自己定义校验多项式,毕竟不用于加密,标准的多项式某种程度上能检验出更多的数据传输位错误。
七、感谢我的大学同学谢宁宁同学在数学方面对我的支持和帮助,并附加一些我查看的网址资料,如果有错误或者能帮我对逆序校验中的crci变为short类型会数据校验错误的问题解惑,请随时私信我。
参考依据:https://www.cnblogs.com/skullboyer/p/8342167.html (转) crc16几种标准校验算法及c语言代码
https://blog.csdn.net/scmuzi18/article/details/71641772 如何进行crc校验
https://wenku.baidu.com/view/75f880a85f0e7cd1852536ac.html 循环码(看了前两部分)
https://blog.csdn.net/pl0020/article/details/82760761 CRC计算流程分析
https://blog.csdn.net/haowwuu/article/details/78931566 CRC校验java版
https://blog.csdn.net/jakee304/article/details/2152655 对一个字符按bit位逆序(又称反转)