C# Modbus的CRC16校验代码

1.简述

Modbus常用的有ModbusRTU、Modbus ASCII和ModbusTCP。其中ModbusTCP不涉及自身协议的校验,另外两种Modbus的校验方法分为两种,一种是对每个单独报文的奇偶校验,另一个是对每帧数据的帧校验。

(1)、奇偶校验
奇偶校验有奇校验和偶校验(如果无校验,则报文中默认有两个停止位)两种。ModbusRTU报文有11位(1位起始位,8位数据位,1位奇偶校验位,1位停止位),Modbus ASCII报文有10位(1位起始位,7位数据位,1位奇偶校验位,1位停止位),如下图:
ModbusRTU报文格式
Modbus ASCII 报文格式
图中的奇偶校验位采用奇校验:数据位有奇数个1,则该位为0;如果数据位有偶数个1,则该位为1,这样数据位和奇偶校验位就共有奇数个1.
图中的奇偶校验位采用偶校验:数据位有偶数个1,则该位为0;如果数据位有奇数个1,则该位为1,这样数据位和奇偶校验位就共有偶数个1.
例如:字节数据为1010 0101,其中1的数量是4,是偶数,如果是偶校验,奇偶校验位就为0;如果是奇校验,奇偶校验位就为1。

(2)、帧校验
在ModbusRTU和Modbus ASCII多数用的是帧校验,帧校验有两种,一种是ModbusRTU采用的CRC(循环冗余校验),另一种是Modbus ASCII采用的LRC( 纵向冗余校验),这两种校验方式具体内容我们不再赘述,现在主要谈ModbusRTU通信协议的CRC校验的具体实现代码。ModbusRTU通讯数据(信息帧)的格式为:从站地址码(1byte)+功能码(1byte)+数据区(N bytes)+CRC校验码(2 bytes)。CRC寄存器为16位,它是经过CRC运算,然后高低位进行交换形成的。

2.CRC16代码

下面CRC16类是将CRC计算后高低位进行交换的结果,高位放在返回的crc16[0],低位在crc16[1],可以顺序使用而不必进行byte数组的Reverse。
CRC16类有三个重载方法,一个是参数为byte数组,另一个是通过偏移量,截取指定数量的byte数组,第三个是参数为字符串。其中参数为字符串的要注意,字符串参数只能以16进制表示,支持以逗号和空格为间隔的输入字符串,同时表示16进制的字符串元素可以加0X或0x,不加也可以。比如"01 03 40 00 00 02”、“01, 03, 04, 43, 00, 1A”、“0X01, 0X02, 00, 03, 0x00, 0x0B"均符合要求,但是不能将逗号和空格一起使用,如” 01 0X03, 0x00 02 00,2F"这是错误的。

	#region 16位CRC校验
    public static class CRC16
    {
        /// <summary>
        /// CRC校验,参数data为byte数组
        /// </summary>
        /// <param name="data">校验数据,字节数组</param>
        /// <returns>字节0是高8位,字节1是低8位</returns>
        public static byte[] CRCCalc(byte[] data)
        {
            //crc计算赋初始值
            int crc = 0xffff;
            for (int i = 0; i < data.Length; i++)
            {
                crc = crc ^ data[i];
                for (int j = 0; j < 8; j++)
                {
                    int temp;
                    temp = crc & 1;
                    crc = crc >> 1;
                    crc = crc & 0x7fff;
                    if (temp == 1)
                    {
                        crc = crc ^ 0xa001;
                    }
                    crc = crc & 0xffff;
                }
            }
            //CRC寄存器的高低位进行互换
            byte[] crc16 = new byte[2];
            //CRC寄存器的高8位变成低8位,
            crc16[1] = (byte)((crc >> 8) & 0xff);
            //CRC寄存器的低8位变成高8位
            crc16[0] = (byte)(crc & 0xff);
            return crc16;
        }
        
        /// <summary>
        /// CRC校验,参数为空格或逗号间隔的字符串
        /// </summary>
        /// <param name="data">校验数据,逗号或空格间隔的16进制字符串(带有0x或0X也可以),逗号与空格不能混用</param>
        /// <returns>字节0是高8位,字节1是低8位</returns>
        public static byte[] CRCCalc(string data)
        {
            //分隔符是空格还是逗号进行分类,并去除输入字符串中的多余空格
            IEnumerable<string> datac = data.Contains(",") ? data.Replace(" ", "").Replace("0x", "").Replace("0X", "").Trim().Split(',') : data.Replace("0x", "").Replace("0X", "").Split(' ').ToList().Where(u => u != "");
            List<byte> bytedata = new List<byte>();
            foreach (string str in datac)
            {
                bytedata.Add(byte.Parse(str, System.Globalization.NumberStyles.AllowHexSpecifier));
            }
            byte[] crcbuf = bytedata.ToArray();
            //crc计算赋初始值
            return CRCCalc(crcbuf);
        }
        
        /// <summary>
        ///  CRC校验,截取data中的一段进行CRC16校验
        /// </summary>
        /// <param name="data">校验数据,字节数组</param>
        /// <param name="offset">从头开始偏移几个byte</param>
        /// <param name="length">偏移后取几个字节byte</param>
        /// <returns>字节0是高8位,字节1是低8位</returns>
        public static byte[] CRCCalc(byte[] data, int offset, int length)
        {
            byte[] Tdata = data.Skip(offset).Take(length).ToArray();
            return CRCCalc(Tdata);
        }
    }
    #endregion

3.使用例子

以Modbus查询保存寄存器(功能码0x03)为例构建一条ModbusRTU指令

			//从站地址
            byte slaveAddress = 1;
            //功能码
            byte functionCode = 3;
            //保持寄存器开始地址,16位寄存器需要把int转为ushort。因通信格式是高位在前,所以要Reverse()
            int start = 0x4000;
            byte[] Starts = BitConverter.GetBytes((ushort)start).Reverse().ToArray();
            //访问保存寄存器数量
            int number = 2;
            byte[] Numbers = BitConverter.GetBytes((ushort)number).Reverse().ToArray();//
            //Modbus功能码03通讯指令共8个字节
            byte[] command = new byte[8];
            command[0] = slaveAddress;
            command[1] = functionCode;
            Array.Copy(Starts, 0, command, 2, Starts.Length);//Starts.Length实际就是2
            Array.Copy(Numbers, 0, command, 4, Numbers.Length);//Numbers.Length实际也是2
            //计算CRC值
            byte[] bdata = new byte[6];
            Array.Copy(command, 0, bdata, 0, bdata.Length);
            //CRC校验码,crc16[0]为高位,crc16[1]为低位,所以不用再Reverse()
            byte[] crc16 = CRC16.CRCCalc(bdata);
            
            //或者直接用截取一段byte[]的CRC16重载
            //byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                
            //将CRC校验码写入指令command
            Array.Copy(crc16, 0, command, 6, crc16.Length);
            /*
            至此,便构建完成查询保持寄存器(功能码0x03)的通讯指令command
            */

4.验证

下面我们进行验证,以16进制字符串形式输出cmd

			//输出16进制结果
            StringBuilder resultCmd = new StringBuilder();
            foreach (var one in command)
            {
                resultCmd.Append(string.Format("{0:X2}", one));
                resultCmd.Append(" ");
            }
            string cmd = resultCmd.ToString().Trim();

在VS中运行,得到cmd为下图,CRC16位校验码是:D1 CB
在这里插入图片描述
本人用command指令与ModbusRTU从站设备通信,返回结果证明了此指令正确,因此也说明了CRC类生成的CRC校验码完全正确。

  • 29
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值