本文作者:DurkBlue

JAVA实现CRC-16/MODBUS算法规则推荐

DurkBlue 07-24 48506
JAVA实现CRC-16/MODBUS算法规则摘要: 附录一:CRC 校验计算方式 1、CRC16 校验码的计算 (1) 预置 1 个 16 位的寄存器为10六进制 FFFF(即全为 1);称此寄存器为 CRC 寄...

附录一:CRC 校验计算方式 

1、CRC16 校验码的计算

(1) 预置 1 个 16 位的寄存器为10六进制 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 寄存器内容即为 CRC16 码。(注意得到的 CRC 码即为低前高后顺序)

附录二:通道数据转换 

1、数据转换步骤

在进行各通道数据解析时,应按以下步骤进行报文解析:

1)按“第二章->第一部分 通讯流程”要求接收监测设备的通讯报文。

2)解析报文 ID 号,确定设备点位。

3)按照《出厂配置表》找到各参数的数据传输通道,出厂配置表在出厂资料里。

4)按各通道数据配置要求进行数据计算,得到测量值。

2、数据转换示例

1)数据为正数

传输的数据为正数情况下,先将数据按格式转换为 16 进制整数,例如通道16表示水深,16进制编码为“00 00 09 17”,换算成二进制为“000000000000 100100010111”,其二进制的第一位为“0”,所以它的值为正数,此时则可以用 1 中的的方法换算出10进制值“2327”,最后将它除以 1000 后得到最终结果“2.327”。

2)数据为负数

负数传输方式为二进制补码,如通道1表示温度,十六进制编码为“FF FF FF 4A”, 换算成二进制“111111111111111111111111 01001010” ,其二进制的第一位为“1”,所以它的值为负数。 其具体换算步骤如下:

(1)将其二进制的的第一位替换为“0” 得到:“011111111111111111111111 01001010”

(2)后 31 位取反后得到:“000000000000000000000000 10110101”

(3)加上“1”后得到:“000000000000000000000000 10110110”

(4)按照 1 中的正数表示方法得到10进制值“182”

(5)因为是负值所以为“-182”

(6)结果除以 10,最终结果为“-18.2”

所以:00 00 09 17 →2.327m

FF FF FF 4A → -18.2℃

 

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的值。



package com.tehn.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寄存器,初始值为0xFFFF
        int 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位,最高位自动补0
                crc >>= 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 位高位自动补0
        while (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;
    }
}


此篇文章由DurkBlue发布,转载请注明来处
文章投稿或转载声明

来源:DurkBlue版权归原作者所有,转载请保留出处。本站文章发布于 07-24
温馨提示:文章内容系作者个人观点,不代表DurkBlue博客对其观点赞同或支持。

赞(0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

阅读
分享