CRC-16/MODBUS的多项式为:x16+x15+x2+1(8005),宽度为16。运算时,首先将一个16位的寄存器预置为11111111 11111111,然后连续把数据帧中的每个字节中的8位与该寄存器的当前值进行运算。仅仅每个字节的8位数据位参与生成CRC。
在生成CRC时,每个字节的8位与寄存器中的内容进行异或,然后将结果向低位位移,高位则用0补充,最低位(LSB)移出并检测,如果是1,该寄存器就与一个预设的固定值(0A001H)进行一次异或运算,如果低位为0,不作任何处理。
上述处理重复进行,直到执行完了8次位移操作,当最后一位移完以后,下一个8位字节与寄存器当前的值进行异或运算,同样进行上述的另一轮8次位移异或才做,当数据帧中的所有字节都做了处理,生成最终值就是CRC值。
生成CRC的流程为:
1. 预置一个16位寄存器位FFFFH,称之为CRC寄存器。
2. 把数据帧中第一个字节的8位与CRC寄存器中的低字节进行异或运算,结果存回CRC寄存器。
3. 将CRC寄存器向右移1位,最高位以0填充,最低位移出并监测。
4. 如果最低位为0:重复第3步(下一次移位),如果最低位为1:将CRC寄存器与一个预设的固定值(0A001H)进行异或运算。
5. 重复第3步和第4步直到8次位移,这样就处理完了一个完整的8位。
6. 重复第2步到第5步来处理下一个字节,知道处理完校验位前所有的数据。
7. 最终CRC寄存器得值就是CRC的值。
C++代码实现
1 #include <stdio.h> 2 #include<iostream> 3 #include<string> 4 #include<sstream> 5 using namespace std; 6 7 unsigned short getCrc16(unsigned char *arr, int len); 8 9 string intToHexStr(int data); 10 11 int main() 12 { 13 unsigned char arr[] = {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00}; 14 int len = 10; 15 int crcInt; 16 crcInt = getCrc16(arr, len); 17 printf( "crc16 int is: %d ", crcInt ); 18 string crcStr; 19 crcStr = intToHexStr(crcInt); 20 cout << "crc16 hex is: " << crcStr.c_str() <<endl; 21 return 0; 22 } 23 24 /** 25 * 进行CRC-6/MODBUS计算 26 */ 27 unsigned short getCrc16(unsigned char *arr, int len) 28 { 29 unsigned short ax,lsb; 30 int i,j; 31 ax = 0xFFFF; 32 for (i = 0; i < len; i++) 33 { 34 ax ^= arr[i]; 35 for (j = 0; j < 8; j++) 36 { 37 lsb = ax & 0x0001; 38 ax = ax >> 1; 39 if (lsb != 0) 40 { 41 ax ^= 0xA001; 42 } 43 } 44 } 45 return ax; 46 } 47 48 /** 49 * 将int类型数据转换成16进制字符串 50 */ 51 string intToHexStr(int data) 52 { 53 /*** 将int类型数据转换成16进制字符串 ***/ 54 string hexStr; 55 ostringstream temp; 56 temp<<hex<<data; 57 hexStr = temp.str(); 58 59 /*** 将小写转大写 ***/ 60 int i; 61 int len = hexStr.size(); 62 for (i = 0; i < len; i++) { 63 hexStr[i] = toupper(hexStr[i]); 64 } 65 66 /*** 保证将字符串长度为4 ***/ 67 while (hexStr.size() < 4) { 68 hexStr = "0" + hexStr; 69 } 70 71 /*** 返回 ***/ 72 return hexStr; 73 }
JAVA代码实现
1 package com.yanwu.demo.utils; 2 3 import org.apache.commons.lang3.RandomUtils; 4 import org.apache.commons.lang3.StringUtils; 5 6 import java.io.BufferedReader; 7 import java.io.InputStreamReader; 8 import java.net.URL; 9 import java.net.URLConnection; 10 11 /** 12 * @author <a herf="mailto:yanwu0527@163.com">yanwu</a> 13 * @date 2019-08-26 14:22. 14 * <p> 15 * description: 16 * 本方法使用CRC-16/MODBUS算法 17 */ 18 @SuppressWarnings("all") 19 public class Crc16Util { 20 private static final Integer ONE = 1; 21 private static final Integer TWO = 2; 22 private static final Integer HEX = 16; 23 private static final String NUL = ""; 24 private static final String SPACE = " "; 25 private static final String ASCII = "US-ASCII"; 26 27 /** 28 * 根据报文byte数组,获取CRC-16 16进制字符串<p> 29 * 48 4C 01 00 01 00 00 05 00 00 >> 0xE647 30 * 31 * @param data 报文数组 32 * @return CRC值(16进制) 33 */ 34 public static String getCrc16HexStr(String data) { 35 return crcToHexStr(getCrc16(data)); 36 } 37 38 /** 39 * 根据报文byte数组,获取CRC-16 int值<p> 40 * 48 4C 01 00 01 00 00 05 00 00 >> 58951 41 * 42 * @param data 报文数组 43 * @return CRC值(10进制) 44 */ 45 public static int getCrc16(String data) { 46 if (StringUtils.isBlank(data)) { 47 // ----- 校验:报文字符串不能为空,否则抛异常 48 throw new RuntimeException("The string cannot be empty!"); 49 } 50 return getCrc16(hexStrToByteArr(data)); 51 } 52 53 /** 54 * 根据报文byte数组,获取CRC-16 16进制字符串<p> 55 * {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00} >> 0xE647 56 * 57 * @param data 报文数组 58 * @return CRC值(16进制) 59 */ 60 public static String getCrc16HexStr(byte[] data) { 61 return crcToHexStr(getCrc16(data)); 62 } 63 64 /** 65 * 根据报文byte数组,获取CRC-16 int值<p> 66 * {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00} >> 58951 67 * 68 * @param data 报文数组 69 * @return CRC值(10进制) 70 */ 71 public static int getCrc16(byte[] data) { 72 if (data.length == 0) { 73 // ----- 校验:报文数组不能为空,否则抛异常 74 throw new RuntimeException("The array cannot be empty!"); 75 } 76 // ----- 预置一个CRC寄存器,初始值为0xFFFF 77 int crc = 0xFFFF; 78 byte byteLen; 79 boolean flag; 80 for (byte item : data) { 81 // ----- 循环,将每数据帧中的每个字节与CRC寄存器中的低字节进行异或运算 82 crc ^= ((int) item & 0x00FF); 83 byteLen = 8; 84 while (byteLen > 0) { 85 // ----- 判断寄存器最后一位是 1 :[true: 1; false: 0] 86 flag = (crc & ONE) == ONE; 87 // ----- 将寄存器右移1位,最高位自动补0 88 crc >>= 1; 89 if (flag) { 90 // ----- 如果右移出来的位是 1:将寄存器与固定值 0xA001 异或运算 91 // ----- 如果右移出来的位是 0:不做处理,进行下一次右移 92 // ----- 直到处理完整个字节的8位 93 crc ^= 0xA001; 94 } 95 byteLen--; 96 } 97 } 98 // ----- 最终寄存器得值就是CRC的值,返回 99 return crc; 100 } 101 102 /** 103 * 将16进制字符串转换为16进制Byte数组<p> 104 * 48 4C 01 00 01 00 00 05 00 00 >> {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00} 105 * 106 * @param str 报文字符串 107 * @return 报文数组 108 */ 109 private static byte[] hexStrToByteArr(String str) { 110 str = str.replaceAll(SPACE, NUL); 111 int strLen = str.length(); 112 if ((strLen & ONE) == ONE) { 113 // ----- 报文字符串必须是以一个字节为单位(两位为一个字节),所以当去除所有空格后的报文长度为单数时说明报文错误 114 throw new RuntimeException("Incorrect message format!"); 115 } 116 byte[] result = new byte[strLen / TWO]; 117 // ----- 两位一个字节 118 for (int i = 0; i < strLen; i += TWO) { 119 String temp = str.substring(i, i + TWO); 120 result[i / TWO] = (byte) Integer.parseInt(temp, HEX); 121 } 122 return result; 123 } 124 125 /** 126 * 将CRC-16值转换成16进制字符串,且保持最小长度为4位<p> 127 * 58951 >> E647 128 * 129 * @param data CRC值(10进制) 130 * @return CRC值(16进制) 131 */ 132 private static String crcToHexStr(int data) { 133 String crcStr = Integer.toHexString(data).toUpperCase(); 134 int size = 4 - crcStr.length(); 135 StringBuilder builder = new StringBuilder(); 136 // ---- 长度不够 4 位高位自动补0 137 while (size > 0) { 138 builder.append("0"); 139 size--; 140 } 141 return builder.append(crcStr).toString(); 142 } 143 144 /** 145 * 输出16进制与长度, 提供给 C++ CRC校验方法 测试 代码使用 146 * 147 * @param str 16进制字符串 148 */ 149 private static void printHexStr(String str) { 150 String[] split = str.split(SPACE); 151 StringBuilder builder = new StringBuilder(); 152 builder.append(" unsigned char arr[] = {"); 153 for (int i = 0; i < split.length; i++) { 154 builder.append("0x").append(split[i]); 155 if (i < split.length - 1) { 156 builder.append(", "); 157 } 158 } 159 builder.append("};"); 160 System.out.println(builder.toString()); 161 System.out.println(" int len = " + split.length + ";"); 162 } 163 164 /** 165 * 测试CRC获取 166 * 167 * @param args 168 */ 169 public static void main(String[] args) throws Exception { 170 String str = "48 4C 01 00 01 00 00 05 00 00"; 171 // ----- 输出16进制数组给 C++ 测试使用 172 Crc16Util.printHexStr(str); 173 // ----- 获取CRC-16的值 174 System.out.println("crc16 int is: " + Crc16Util.getCrc16(str)); 175 System.out.println("crc16 hex is: " + Crc16Util.getCrc16HexStr(str)); 176 // ----- 与 http://www.ip33.com/crc.html 进行结果验证 177 check(); 178 } 179 180 private static void check() { 181 char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 182 // ----- 校验的次数 183 int size = Integer.MAX_VALUE; 184 int len = 0, min = 10, max = 500; 185 StringBuilder thisData = new StringBuilder(); 186 StringBuilder httpData = new StringBuilder(); 187 while (size > 0) { 188 thisData.delete(0, thisData.length()); 189 httpData.delete(0, httpData.length()); 190 len = RandomUtils.nextInt(min, max); 191 while (len > 0) { 192 char h = chars[RandomUtils.nextInt(0, 16)]; 193 char l = chars[RandomUtils.nextInt(0, 16)]; 194 httpData.append(h).append(l); 195 thisData.append(h).append(l); 196 if (len != 1) { 197 httpData.append("+"); 198 thisData.append(SPACE); 199 } 200 len--; 201 } 202 String thisCrc = getCrc16HexStr(thisData.toString()); 203 String httpCrc = getCrcByUrl(httpData.toString()); 204 System.out.println("this: " + thisCrc + " -> " + thisData.toString()); 205 System.out.println("http: " + httpCrc + " -> " + httpData.toString()); 206 if (!thisCrc.equals(httpCrc)) { 207 throw new RuntimeException("ERROR!!!"); 208 } 209 size--; 210 } 211 } 212 213 public static String getCrcByUrl(String data) { 214 BufferedReader in = null; 215 String result = ""; 216 try { 217 String path = "http://api.ip33.com/crc/c?width=16&poly=8005&init=FFFF&xor=0000&refin=true&refout=true&data=" + data; 218 URL realUrl = new URL(path); 219 URLConnection connection = realUrl.openConnection(); 220 connection.setRequestProperty("accept", "*/*"); 221 connection.setRequestProperty("connection", "Keep-Alive"); 222 connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); 223 connection.connect(); 224 in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); 225 String line; 226 while ((line = in.readLine()) != null) { 227 result += line; 228 } 229 System.out.println("responce -> " + result); 230 int index = result.indexOf(""hex": "") + 8; 231 return result.substring(index, index + 4); 232 } catch (Exception e) { 233 e.printStackTrace(); 234 } finally { 235 try { 236 if (in != null) { 237 in.close(); 238 } 239 } catch (Exception e2) { 240 e2.printStackTrace(); 241 } 242 } 243 return null; 244 } 245 246 }