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++代码实现
#include <stdio.h>
#include<iostream>
#include<string>
#include<sstream>
using namespace std;unsigned short getCrc16(unsigned char *arr, int len); string intToHexStr(int data);int main()
{unsigned char arr[] = {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00};int len = 10;int crcInt;crcInt = getCrc16(arr, len);printf( "crc16 int is: %d\n", crcInt );string crcStr;crcStr = intToHexStr(crcInt);cout << "crc16 hex is: " << crcStr.c_str() <<endl;return 0;
}/*** 进行CRC-6/MODBUS计算*/
unsigned short getCrc16(unsigned char *arr, int len)
{unsigned short ax,lsb;int i,j;ax = 0xFFFF;for (i = 0; i < len; i++) {ax ^= arr[i];for (j = 0; j < 8; j++) {lsb = ax & 0x0001;ax = ax >> 1;if (lsb != 0) {ax ^= 0xA001;}}}return ax;
}/*** 将int类型数据转换成16进制字符串*/
string intToHexStr(int data)
{/*** 将int类型数据转换成16进制字符串 ***/string hexStr;ostringstream temp;temp<<hex<<data;hexStr = temp.str();/*** 将小写转大写 ***/int i;int len = hexStr.size();for (i = 0; i < len; i++) {hexStr[i] = toupper(hexStr[i]);}/*** 保证将字符串长度为4 ***/while (hexStr.size() < 4) {hexStr = "0" + hexStr;}/*** 返回 ***/return hexStr;
}
JAVA代码实现
package com.yanwu.demo.utils;import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;/*** @author <a herf="mailto:yanwu0527@163.com">yanwu</a>* @date 2019-08-26 14:22.* <p>* description:* 本方法使用CRC-16/MODBUS算法*/
@SuppressWarnings("all")
public class Crc16Util {private static final Integer ONE = 1;private static final Integer TWO = 2;private static final Integer HEX = 16;private static final String NUL = "";private static final String SPACE = " ";private static final String ASCII = "US-ASCII";/*** 根据报文byte数组,获取CRC-16 16进制字符串<p>* 48 4C 01 00 01 00 00 05 00 00 >> 0xE647** @param data 报文数组* @return CRC值(16进制)*/public static String getCrc16HexStr(String data) {return crcToHexStr(getCrc16(data));}/*** 根据报文byte数组,获取CRC-16 int值<p>* 48 4C 01 00 01 00 00 05 00 00 >> 58951** @param data 报文数组* @return CRC值(10进制)*/public static int getCrc16(String data) {if (StringUtils.isBlank(data)) {// ----- 校验:报文字符串不能为空,否则抛异常throw new RuntimeException("The string cannot be empty!");}return getCrc16(hexStrToByteArr(data));}/*** 根据报文byte数组,获取CRC-16 16进制字符串<p>* {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00} >> 0xE647** @param data 报文数组* @return CRC值(16进制)*/public static String getCrc16HexStr(byte[] data) {return crcToHexStr(getCrc16(data));}/*** 根据报文byte数组,获取CRC-16 int值<p>* {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00} >> 58951** @param data 报文数组* @return CRC值(10进制)*/public static int getCrc16(byte[] data) {if (data.length == 0) {// ----- 校验:报文数组不能为空,否则抛异常throw new RuntimeException("The array cannot be empty!");}// ----- 预置一个CRC寄存器,初始值为0xFFFFint crc = 0xFFFF;byte byteLen;boolean flag;for (byte item : data) {// ----- 循环,将每数据帧中的每个字节与CRC寄存器中的低字节进行异或运算crc ^= ((int) item & 0x00FF);byteLen = 8;while (byteLen > 0) {// ----- 判断寄存器最后一位是 1\0:[true: 1; false: 0]flag = (crc & ONE) == ONE;// ----- 将寄存器右移1位,最高位自动补0crc >>= 1;if (flag) {// ----- 如果右移出来的位是 1:将寄存器与固定值 0xA001 异或运算// ----- 如果右移出来的位是 0:不做处理,进行下一次右移// ----- 直到处理完整个字节的8位crc ^= 0xA001;}byteLen--;}}// ----- 最终寄存器得值就是CRC的值,返回return crc;}/*** 将16进制字符串转换为16进制Byte数组<p>* 48 4C 01 00 01 00 00 05 00 00 >> {0x48, 0x4C, 0x01, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x00}** @param str 报文字符串* @return 报文数组*/private static byte[] hexStrToByteArr(String str) {str = str.replaceAll(SPACE, NUL);int strLen = str.length();if ((strLen & ONE) == ONE) {// ----- 报文字符串必须是以一个字节为单位(两位为一个字节),所以当去除所有空格后的报文长度为单数时说明报文错误throw new RuntimeException("Incorrect message format!");}byte[] result = new byte[strLen / TWO];// ----- 两位一个字节for (int i = 0; i < strLen; i += TWO) {String temp = str.substring(i, i + TWO);result[i / TWO] = (byte) Integer.parseInt(temp, HEX);}return result;}/*** 将CRC-16值转换成16进制字符串,且保持最小长度为4位<p>* 58951 >> E647** @param data CRC值(10进制)* @return CRC值(16进制)*/private static String crcToHexStr(int data) {String crcStr = Integer.toHexString(data).toUpperCase();int size = 4 - crcStr.length();StringBuilder builder = new StringBuilder();// ---- 长度不够 4 位高位自动补0while (size > 0) {builder.append("0");size--;}return builder.append(crcStr).toString();}/*** 输出16进制与长度, 提供给 C++ CRC校验方法 测试 代码使用** @param str 16进制字符串*/private static void printHexStr(String str) {String[] split = str.split(SPACE);StringBuilder builder = new StringBuilder();builder.append(" unsigned char arr[] = {");for (int i = 0; i < split.length; i++) {builder.append("0x").append(split[i]);if (i < split.length - 1) {builder.append(", ");}}builder.append("};");System.out.println(builder.toString());System.out.println(" int len = " + split.length + ";");}/*** 测试CRC获取** @param args*/public static void main(String[] args) throws Exception {String str = "48 4C 01 00 01 00 00 05 00 00";// ----- 输出16进制数组给 C++ 测试使用Crc16Util.printHexStr(str);// ----- 获取CRC-16的值System.out.println("crc16 int is: " + Crc16Util.getCrc16(str));System.out.println("crc16 hex is: " + Crc16Util.getCrc16HexStr(str));// ----- 与 http://www.ip33.com/crc.html 进行结果验证check();}private static void check() {char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};// ----- 校验的次数int size = Integer.MAX_VALUE;int len = 0, min = 10, max = 500;StringBuilder thisData = new StringBuilder();StringBuilder httpData = new StringBuilder();while (size > 0) {thisData.delete(0, thisData.length());httpData.delete(0, httpData.length());len = RandomUtils.nextInt(min, max);while (len > 0) {char h = chars[RandomUtils.nextInt(0, 16)];char l = chars[RandomUtils.nextInt(0, 16)];httpData.append(h).append(l);thisData.append(h).append(l);if (len != 1) {httpData.append("+");thisData.append(SPACE);}len--;}String thisCrc = getCrc16HexStr(thisData.toString());String httpCrc = getCrcByUrl(httpData.toString());System.out.println("this: " + thisCrc + " -> " + thisData.toString());System.out.println("http: " + httpCrc + " -> " + httpData.toString());if (!thisCrc.equals(httpCrc)) {throw new RuntimeException("ERROR!!!");}size--;}}public static String getCrcByUrl(String data) {BufferedReader in = null;String result = "";try {String path = "http://api.ip33.com/crc/c?width=16&poly=8005&init=FFFF&xor=0000&refin=true&refout=true&data=" + data;URL realUrl = new URL(path);URLConnection connection = realUrl.openConnection();connection.setRequestProperty("accept", "*/*");connection.setRequestProperty("connection", "Keep-Alive");connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");connection.connect();in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));String line;while ((line = in.readLine()) != null) {result += line;}System.out.println("responce -> " + result);int index = result.indexOf("\"hex\": \"") + 8;return result.substring(index, index + 4);} catch (Exception e) {e.printStackTrace();} finally {try {if (in != null) {in.close();}} catch (Exception e2) {e2.printStackTrace();}}return null;}}
本文链接:https://my.lmcjl.com/post/15066.html
展开阅读全文
4 评论