2016年11月

循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误,它是利用除法及余数的原理来作错误侦测的。本文是基于Java语言来实现对Modbus数据的CRC校验的一种实现,可移植性强,主要针对公司嵌入式软件(或系统)传输到上位机的字节数组进行校验和上位机根据指定的数据生成CRC。

1、CRC循环冗余错误校验计算方法

ModbusCRC-16C(循环冗余错误校验)生成CRC-16校验字节的步聚如下:

  • (1)装入一个16位寄存器,所有数位均为1。
  • (2)该16位寄存器的高位字节与开始8位字节进行“异或”运算。运算结果放入这个16位寄存器。
  • (3)把这个16位寄存器向右移1位。
  • (4a)若向右(标记位)移出的数位是1,则用0xA001和这个寄存器进行“异或”运算。
  • (4b)若向右移出的数位是0,则返回(3)。
  • (5)重处处(3)和(4),直至移出8位。
  • (6)另外8位与该16位寄存器进行“异或”运算。
  • (7)重处处(3)-(6),直至该报文所有字节均与16位寄存器进行“异或”运算,并移位8次。
  • (8)这个16位寄存器的内容即2字节CRC错误校验。

2、代码实现

public class ModbusCRC {

    private byte[] mCRC = new byte[2];
    private byte[] source;
    private byte data;
    private byte[] reg = new byte[2];
    private byte[] ploy = new byte[2];
    private byte Op;

    public ModbusCRC(byte[] source) {
        this.source = source;
        //初始化多项式
        int temp = 0xA001;
        ploy = inttoByteArray(temp, 2);
        //初始化寄存器
        temp = 0xFFFF;
        reg = inttoByteArray(temp, 2);
        for (int i = 0; i < source.length; i++) {
            //获取数据
            data = source[i];
            //与寄存器中数据进行异或操作(特别注意是低位)
            reg[0] = (byte) (reg[0] ^ data);
            //移动数据
            for (int j = 0; j < 8; j++) {
                //获取数据的最后一位,即被移动出的数据判断是否与多项式异或
                Op = reg[0];
                //右移一位
                reg = inttoByteArray(byteArraytoInt(reg) >> 1, 2);
                //如果移出数据为1
                if ((Op & 0x01) == 1) {
                    //与多项式进行异或操作
                    reg[0] = (byte) (reg[0] ^ ploy[0]);
                    reg[1] = (byte) (reg[1] ^ ploy[1]);
                }
            }
        }
        mCRC = reg;
    }

    /**
     * 只获取CRC校验结果
     */
    public byte[] getCRC() {
        return mCRC;
    }

    /**
     * 获取带CRC检验的数据
     */
    public byte[] getSourceWithCRC() {
        byte[] sourceWithCRC = new byte[source.length + mCRC.length];
        System.arraycopy(source, 0, sourceWithCRC, 0, source.length);
        System.arraycopy(mCRC, 0, sourceWithCRC, source.length, mCRC.length);
        return sourceWithCRC;
    }

    /**
     * 整型转字节数组
     */
    private byte[] inttoByteArray(int iSource, int iArrayLen) {
        byte[] bLocalArr = new byte[iArrayLen];
        for (int i = 0; (i < 4) && (i < iArrayLen); i++) {
            bLocalArr[i] = (byte) (iSource >> 8 * i & 0xFF);
        }
        return bLocalArr;
    }

    /**
     * 字节数组转整型
     */
    private int byteArraytoInt(byte[] bRefArr) {
        int iOutcome = 0;
        byte bLoop;

        for (int i = 0; i < bRefArr.length; i++) {
            bLoop = bRefArr[i];
            iOutcome += (bLoop & 0xFF) << (8 * i);
        }
        return iOutcome;
    }

    /**
     * 输入带CRC的元数据,校验CRC是否正确
     *
     * 如果正确则返回true,反之返回false
     */
    public static boolean checkCRC(byte[] sourceWithCRC) {
        if (sourceWithCRC.length <= 2) {
            return false;
        }
        byte[] source = new byte[sourceWithCRC.length - 2];
        byte[] crcBytes = new byte[2];
        System.arraycopy(sourceWithCRC, source.length, crcBytes, 0, 2);
        System.arraycopy(sourceWithCRC, 0, source, 0, source.length);
        ModbusCRC crc = new ModbusCRC(source);
        return crc.getCRC()[0] == crcBytes[0] && crc.getCRC()[1] == crcBytes[1];
    }
}

3、使用方式

该工具使用简单,现在针对常用的场景进行介绍。

  • (1) 已经有带CRC校验的元数据需要校验CRC,可以直接使用该代码体内的静态方法,该方法直接返回boolean类型的结果。
    ModbusCRC.checkCRC(sourceWithCRC) 
  • (2) 对需要下发的数据进行CRC计算,如果需要进行计算CRC,则需要new 一个对象,使用对象的方式来进行操作,这样可以获取的内容比较丰富
    ModbusCRC crc = new ModbusCRC(source);
    //获取带CRC校验结果的数据
    crc.getSourceWithCRC();
    //只获取 CRC校验结果
    crc.getCRC();