C# ModbusRTU命令功能码

1.概述

Modbus是由Schneider Electric(施耐德电气)为PLC(可编程逻辑控制器)通信而研发的一种串行通信协议,是应用于电子控制器上的一种通用语言。常用的有两种ModbusRTU和ModbusTCP,还有一种Modbus ASCII,但是相对来说要用的少一些,如需了解,可参考我另一篇文章“C# Modbus ASCII命令功能码”。
Modbus RTU 协议是一种主从式串行异步半双工通信协议,是基于RS485\RS422\RS232物理层的通信的,在协议通信中每个字符通信格式规定为1个起始位、8个数据位、1个校验位、1\2个停止位,并且有CRC错误校验。
所谓主从,我们直接可以理解为发送请求的是主,也就是Master,常指我们用的上位机(电脑);那么响应请求的就是从,即Slave也就是PLC等485硬件设备,ModbusRTU指令中从站地址就是指的这些设备。
有人说Modbus服务器端和客户端,实际用的是网络通信用语,和上面说的主从一回事,服务器端是响应请求的,自然指的就是PLC等硬件设备,客户端是发送请求的,也就是指我们用的电脑了。在Modbus中,Slave和Server意思相同,Master和Client意思相同。

2.Modbus RTU 通信协议寄存器种类

寄存器种类说明与PLC比较举例说明
线圈状态 Coil Status数字量输出、继电器输出,可读可写DO数字量输出或内部读写位变量电磁阀输出、继电器
离散量输入 Input Status数字量输入,只读DI数字量输入或内部只读位量按钮输入、拨码开关、接近开关
保持寄存器 Holding Register输出参数、保持参数,可读可写AO模拟量输出或内部读写寄存器模拟量输出设定、变量阀输出大小
输入寄存器 Input Register输入参数,只读AI模拟量输入或内部只读寄存器模拟量输入、现场工程量信号采集

3.ModbusRTU的八条主要功能码命令

功能码作 用
0x01读线圈
0x02读离散量输入
0x03读保持寄存器
0x04读输入寄存器
0x05写单个线圈
0x06写单个寄存器
0x0F写多个线圈
0x10写多个寄存器

4.ModbusRTU的八条命令生成代码

上面我们简单描述了ModbusRTU的一些基本知识,是为了我们生成ModbusRTU指令做准备,至于更多关于ModbusRTU的技术内容,大家可以自己网上查询,这里不再赘述。
ModbusRTU这8条常用指令,其实与ModbusTCP大致相同,只不过ModbusTCP多了一个7个字节的MBAP报文头和少了CRC校验,有兴趣了解的朋友可以参看我另一篇“C# ModbusTCP命令功能码”的文章。
本文介绍的是生成ModbusRTU主要八条命令的代码,它是在总线传输前的指令代码集,类型是二进制数组,不包含串口传输部分(如果需要串口传输代码,请参考我的文章"C# 串口发送ModbusRTU、ModbusASCII的8条主要功能代码并获取返回的响应信息"),但它是是串口传输的必不可少的一项,同样适用于采用ModbusRTU协议的485/422/232转网络的采集器(有人叫集中器),这种产品在多数环境下比Modbus总线更容易部署,对于熟悉网络的技术人员来说更加容易实现,避免了Modbus总线无法实现的星形连接方式,也不用担心增加120欧的终端电阻等方式,当然并不是说Modbus总线连接方式不好,Modbus总线连接有它独有的优势,这里只是就某种环境而言的。
在写这八条ModbusRTU指令代码之前,我们得先声明两个枚举,其中有几条枚举项现在用不上,但是我写上它是为了能列出来更多的Modbus指令和在取ModbusRTU返回数据时使用的。

    /// <summary>
    /// Modbus指令代码
    /// </summary>
    public enum ModbusCodes
    {
        READ_COILS = 0x01,                      //读取线圈
        READ_DISCRETE_INPUTS = 0x02,            //读取离散量输入
        READ_HOLDING_REGISTERS = 0x03,          //读取保持寄存器
        READ_INPUT_REGISTERS = 0x04,            //读取输入寄存器
        WRITE_SINGLE_COIL = 0x05,               //写单个线圈
        WRITE_SINGLE_REGISTER = 0x06,           //写单个保持寄存器
        READ_EXCEPTION_STATUS = 0x07,           //读取异常状态
        DIAGNOSTIC = 0x08,                      //回送诊断校验
        GET_COM_EVENT_COUNTER = 0x0B,           //读取事件计数
        GET_COM_EVENT_LOG = 0x0C,               //读取通信事件记录
        WRITE_MULTIPLE_COILS = 0x0F,            //写多个线圈
        WRITE_MULTIPLE_REGISTERS = 0x10,        //写多个保持寄存器
        REPORT_SLAVE_ID = 0x11,                 //报告从机标识(ID)
        READ_FILE_RECORD = 0x14,                //读文件记录
        WRITE_FILE_RECORD = 0x15,               //写文件记录
        MASK_WRITE_REGISTER = 0x16,             //屏蔽写寄存器
        READ_WRITE_MULTIPLE_REGISTERS = 0x17,   //读写多个寄存器
        READ_FIFO_QUEUE = 0x18,                 //读取队列
        READ_DEVICE_IDENTIFICATION = 0x2B       //读取设备标识
    }

    /// <summary>
    /// 错误代码
    /// </summary>
    public enum Errors
    {
        NO_ERROR = 0,//无错误
        EXCEPTION_UNKNOWN = 1,//未知异常
        EXCEEDING_MODBUSCODE_RANGE = 2,//超出ModbusCode范围
        UNPROCESSED_MODBUSCODE = 3,//没有处理的ModbusCode
        WRONG_RESPONSE_ADDRESS = 4,//响应地址错误
        WRONG_RESPONSE_REGISTERS = 5,//响应寄存器错误
        WRONG_RESPONSE_VALUE = 6,//响应值错误
        WRONG_CRC = 7,//CRC16校验错误
        TOO_MANY_REGISTERS_REQUESTED = 8,//请求的寄存器数量太多
        ZERO_REGISTERS_REQUESTED = 9,//零寄存器请求
        EXCEPTION_ILLEGAL_FUNCTION = 20,//非法的功能码
        EXCEPTION_ILLEGAL_DATA_ADDRESS = 21,//非法的数据地址
        EXCEPTION_ILLEGAL_DATA_VALUE = 22,//非法的数据值
        EXCEPTION_SLAVE_DEVICE_FAILURE = 23,//从站(服务器)故障
        
    }

下面就是ModbusRTU的主要八条命令代码,返回类型均是byte[],可直接代入程序运行。

    #region Modbus RTU
    
    public class ModbusRTU
    {
        #region 常量
        /// <summary>
        /// 可读取的线圈的最大数量
        /// </summary>
        public const ushort MAX_COILS_IN_READ_NUM = 2000;

        /// <summary>
        /// 可读取的离散量的最大数量
        /// </summary>
        public const ushort MAX_DISCRETE_INPUTS_IN_READ_NUM = 2000;

        /// <summary>
        /// 可读取的保持寄存器的最大数量
        /// </summary>
        public const ushort MAX_HOLDING_REGISTERS_IN_READ_NUM = 125;

        /// <summary>
        /// 可读取的输入寄存器的最大数量
        /// </summary>
        public const ushort MAX_INPUT_REGISTERS_IN_READ_NUM = 125;

        /// <summary>
        /// 可写入的线圈的最大数量
        /// </summary>
        public const ushort MAX_COILS_IN_WRITE_NUM = 1968;

        /// <summary>
        /// 可写入的保持寄存器的最大数量
        /// </summary>
        public const ushort MAX_HOLDING_REGISTERS_IN_WRITE_NUM = 123;

        /// <summary>
        /// 可读取读/写的保持寄存器的最大数量
        /// </summary>
        public const ushort MAX_HOLDING_REGISTERS_TO_READ_IN_READWRITE_NUM = 125;

        /// <summary>
        /// 可写取读/写的保持寄存器的最大数量
        /// </summary>
        public const ushort MAX_HOLDING_REGISTERS_TO_WRITE_IN_READWRITE_NUM = 121;
        #endregion

        #region 变量
        /// <summary>
        /// Modbus错误
        /// </summary>
        public Errors error;
        #endregion

        #region 读取线圈,功能码0x01
        /// <summary>
        /// 生成ModbusRTU读取线圈状态指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Numbers">读取线圈的数量</param>
        /// <returns>读取线圈状态的指令byte[]</returns>
        public byte[] ReadCoilStatus_0x01(byte SlaveId, ushort StartAddress, ushort Numbers)
        {
            error = Errors.NO_ERROR;
            if (Numbers < 1)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Numbers > MAX_COILS_IN_READ_NUM)
            {
                error = Errors.TOO_MANY_REGISTERS_REQUESTED;
                return null;
            }

            byte[] command = new byte[8];
            try
            {

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x01
                command[1] = (byte)ModbusCodes.READ_COILS;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是线圈数量
                Array.Copy(BitConverter.GetBytes(Numbers).Reverse().ToArray(), 0, command, 4, 2);
                //第7位和第8位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                Array.Copy(crc16, 0, command, 6, 2);
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

            return command;
        }
        #endregion

        #region 读取离散量,功能码0x02
        /// <summary>
        /// 生成ModbusRTU读取离散量状态指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Numbers">读取离散量的数量</param>
        /// <returns>读取离散量状态的指令byte[]</returns>
        public byte[] ReadInputStatus_0x02(byte SlaveId, ushort StartAddress, ushort Numbers)
        {
            error = Errors.NO_ERROR;
            if (Numbers < 1)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Numbers > MAX_DISCRETE_INPUTS_IN_READ_NUM)
            {
                error = Errors.TOO_MANY_REGISTERS_REQUESTED;
                return null;
            }

            byte[] command = new byte[8];
            try
            {

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x02
                command[1] = (byte)ModbusCodes.READ_DISCRETE_INPUTS;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是线圈数量
                Array.Copy(BitConverter.GetBytes(Numbers).Reverse().ToArray(), 0, command, 4, 2);
                //第7位和第8位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                Array.Copy(crc16, 0, command, 6, 2);
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

            return command;

        }
        #endregion

        #region 读取保持寄存器,功能码0x03
        /// <summary>
        /// 生成ModbusRTU读取保持寄存器指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Numbers">读取保持寄存器的数量</param>
        /// <returns>读取保持寄存器的指令byte[]</returns>
        public byte[] ReadHoldingRegister_0x03(byte SlaveId, ushort StartAddress, ushort Numbers)
        {
            error = Errors.NO_ERROR;
            if (Numbers < 1)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Numbers > MAX_HOLDING_REGISTERS_IN_READ_NUM)
            {
                error = Errors.TOO_MANY_REGISTERS_REQUESTED;
                return null;
            }

            byte[] command = new byte[8];
            try
            {

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x03
                command[1] = (byte)ModbusCodes.READ_HOLDING_REGISTERS;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是线圈数量
                Array.Copy(BitConverter.GetBytes(Numbers).Reverse().ToArray(), 0, command, 4, 2);
                //第7位和第8位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                Array.Copy(crc16, 0, command, 6, 2);
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

            return command;
        }
        #endregion

        #region 读取输入寄存器,功能码0x04
        /// <summary>
        /// 生成ModbusRTU读取输入寄存器指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Numbers">读取输入寄存器的数量</param>
        /// <returns>读取输入寄存器的指令byte[]</returns>
        public byte[] ReadInputRegister_0x04(byte SlaveId, ushort StartAddress, ushort Numbers)
        {
            error = Errors.NO_ERROR;
            if (Numbers < 1)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Numbers > MAX_INPUT_REGISTERS_IN_READ_NUM)
            {
                error = Errors.TOO_MANY_REGISTERS_REQUESTED;
                return null;
            }

            byte[] command = new byte[8];
            try
            {

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x04
                command[1] = (byte)ModbusCodes.READ_INPUT_REGISTERS;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是线圈数量
                Array.Copy(BitConverter.GetBytes(Numbers).Reverse().ToArray(), 0, command, 4, 2);
                //第7位和第8位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                Array.Copy(crc16, 0, command, 6, 2);
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

            return command;
        }
        #endregion

        #region 写单个线圈,功能码0x05
        /// <summary>
        /// 生成ModbusRTU写单个线圈指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Value">单个线圈是开启(true)还是关闭(false)</param>
        /// <returns>写单个线圈的指令byte[]</returns>
        public byte[] WriteSingleCoil_0x05(byte SlaveId, ushort StartAddress, bool Value)
        {
            error = Errors.NO_ERROR;
            byte[] command = new byte[8];
            try
            {

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x05
                command[1] = (byte)ModbusCodes.WRITE_SINGLE_COIL;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是开关标识,0xFF00为开,0x0000为关
                command[4] = Value ? (byte)0xFF : (byte)0x00;
                command[5] = 0x00;
                //第7位和第8位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                Array.Copy(crc16, 0, command, 6, 2);
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

            return command;
        }
        #endregion

        #region 写单个保持寄存器,功能码0x06
        /// <summary>
        /// 生成ModbusRTU写单个保持寄存器指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Value">单个保持寄存器的值,类型是无符号16位整数</param>
        /// <returns>写单个保持寄存器的指令byte[]</returns>
        public byte[] WriteSingleRegister_0x06(byte SlaveId, ushort StartAddress, ushort Value)
        {
            error = Errors.NO_ERROR;
            byte[] command = new byte[8];
            try
            {
                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x06
                command[1] = (byte)ModbusCodes.WRITE_SINGLE_REGISTER;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是开关标识,0xFF00为开,0x0000为关
                Array.Copy(BitConverter.GetBytes(Value).Reverse().ToArray(), 0, command, 4, 2);
                //第7位和第8位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 6);
                Array.Copy(crc16, 0, command, 6, 2);
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

            return command;
        }
        #endregion

        #region 写多个线圈,功能码0x0F
        /// <summary>
        /// 生成ModbusRTU写多个线圈指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Values">多个线圈的写入状态,从起始地址线圈开始依次排列</param>
        /// <returns>写多个线圈的指令byte[]</returns>
        public byte[] WriteMultipleCoil_0x0F(byte SlaveId, ushort StartAddress, bool[] Values)
        {
            error = Errors.NO_ERROR;
            if (Values == null)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Values.Length < 1)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Values.Length > MAX_COILS_IN_WRITE_NUM)
            {
                error = Errors.TOO_MANY_REGISTERS_REQUESTED;
                return null;
            }

            try
            {
                //线圈数值长度(按字节算)
                byte ValuesBytesCount = Convert.ToByte(Math.Ceiling((double)Values.Count() / 8));
                //byte ValueBytesCount = (byte)((Value.Length / 8) + ((Value.Length % 8) == 0 ? 0 : 1));
                byte[] command = new byte[8 + 1 + ValuesBytesCount];

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x06
                command[1] = (byte)ModbusCodes.WRITE_MULTIPLE_COILS;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是线圈数值长度(接位数算)
                Array.Copy(BitConverter.GetBytes((ushort)Values.Length).Reverse().ToArray(), 0, command, 4, 2);
                //第7位是线圈数值长度(字节数)
                command[6] = ValuesBytesCount;

                /*   后面是线圈ValueBytesCount个字节的数值,在Modbus中是以大端法排列,每个字节的二进值对应的线圈顺序是
                 *   【87654321】 【16 15 14 13 12 11 10 9】 【24 23 22 21 20 19 18 17】 ......(每个【】代表1个字节)。
                 *   我们使用BitArray类将传入的Values(bool数组)转换为ModbusRTU所需要的大端byte数组。BitArray是位数组,
                 *   位的排列是小端法,所以当用参数Value以bool数组初始化时,它会跟bool数组一样,索引0对应bool数组的索引0,
                 *   1对应1,依次往后排列。当它用CopyTo方法转换为byte数组时,byte数组二进制值就会变成上面说的那样的顺序---
                 *   【87654321】 【16 15 14 13 12 11 10 9】 【24 23 22 21 20 19 18 17】 ......
                 *   例如:我们输入一个bool数组,new bool[]{false,ture,false,false,false,false,false,true},
                 *   相当于0100 0001,用BitArray转换为byte[]后,会成为1000 0010这样的值。
                 */
                byte[] dataValue = new byte[ValuesBytesCount];
                BitArray Ba = new BitArray(Values);
                Ba.CopyTo(dataValue, 0);
                //把线圈值按字节加入command中
                Array.Copy(dataValue, 0, command, 7, ValuesBytesCount);

                //最后两位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 7 + ValuesBytesCount);
                Array.Copy(crc16, 0, command, 7 + ValuesBytesCount, 2);

                return command;
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }
        }
        #endregion

        #region 写多个保持寄存器,功能码0x10
        /// <summary>
        /// 生成ModbusRTU写多个保持寄存器指令byte数组
        /// </summary>
        /// <param name="SlaveId">从站号(服务器端)</param>
        /// <param name="StartAddress">起始地址</param>
        /// <param name="Values">多个保持寄存器的值(无符号16位),从起始地址开始依次排列</param>
        /// <returns>写多个保持寄存器的指令byte[]</returns>
        public byte[] WriteMultipleRegister_0x10(byte SlaveId, ushort StartAddress, ushort[] Values)
        {
            error = Errors.NO_ERROR;
            if (Values == null)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Values.Length < 1)
            {
                error = Errors.ZERO_REGISTERS_REQUESTED;
                return null;
            }
            if (Values.Length > MAX_HOLDING_REGISTERS_IN_WRITE_NUM)
            {
                error = Errors.TOO_MANY_REGISTERS_REQUESTED;
                return null;
            }

            try
            {
                //保持寄存器的字节数
                byte ValuesBytesCount = (byte)(Values.Length * 2);

                byte[] command = new byte[8 + 1 + ValuesBytesCount];

                //第1位是从站站号
                command[0] = SlaveId;
                //第2位是功能码0x06
                command[1] = (byte)ModbusCodes.WRITE_MULTIPLE_REGISTERS;
                //第3和第4位起始地址(大端法)
                Array.Copy(BitConverter.GetBytes(StartAddress).Reverse().ToArray(), 0, command, 2, 2);
                //第5位和第6位是写入保持寄存器的数量
                Array.Copy(BitConverter.GetBytes((ushort)Values.Length).Reverse().ToArray(), 0, command, 4, 2);
                //第7位是写入保持寄存器的字节数
                command[6] = ValuesBytesCount;

                //将写入寄存器的值加入command中
                for (int i = 0; i < Values.Length; i++)
                {
                    Array.Copy(BitConverter.GetBytes(Values[i]).Reverse().ToArray(), 0, command, 7 + i * 2, 2);
                }

                //最后两位是CRC16校验
                byte[] crc16 = CRC16.CRCCalc(command, 0, 7 + ValuesBytesCount);
                Array.Copy(crc16, 0, command, 7 + ValuesBytesCount, 2);

                return command;
            }
            catch
            {
                error = Errors.EXCEPTION_UNKNOWN;
                return null;
            }

        }
        #endregion


    }
    #endregion

5.代码说明

以上代码需要注意几点:1、如果代码返回null,请查看error的值,如果是Errors.EXCEPTION_UNKNOWN,那么多数是由于数组Copy产生的,说明输入参数有问题。2、功能码0x0F(写多个线圈)的参数Values是bool数组,是对一组线圈操作的,顺序应按线圈地址从低到高进行赋值,即Values[0]是线圈的起始地址的开关量,Values[N]是线圈的最高地址的开关量。3、代码中的CRC校验的方法CRC16.CRCCalc()本文没有列出来,如果需要,可以参考我另一篇“C# Modbus的CRC16校验代码”的文章。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用 C# 编写的 Modbus RTU 协议写多个寄存器的示例代: ```csharp using System; using System.IO.Ports; namespace ModbusRTU_Write_Multiple_Registers { class Program { static void Main(string[] args) { SerialPort port = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One); port.Open(); // Modbus RTU 请求帧 byte slaveAddress = 1; // 从站地址 byte functionCode = 16; // 功能,16 表示写多个寄存器 ushort startingAddress = 0; // 起始地址,从 0 开始 ushort numberOfRegisters = 3; // 写入的寄存器数量 byte byteCount = (byte)(numberOfRegisters * 2); // 字节数,每个寄存器占用 2 字节 byte[] data = new byte[byteCount]; // 写入的数据 ushort[] values = new ushort[] { 100, 200, 300 }; // 写入的值 for (int i = 0; i < numberOfRegisters; i++) { byte[] bytes = BitConverter.GetBytes(values[i]); data[i * 2] = bytes[1]; data[i * 2 + 1] = bytes[0]; } byte[] requestFrame = new byte[7 + byteCount]; requestFrame[0] = slaveAddress; requestFrame[1] = functionCode; requestFrame[2] = (byte)(startingAddress >> 8); requestFrame[3] = (byte)startingAddress; requestFrame[4] = (byte)(numberOfRegisters >> 8); requestFrame[5] = (byte)numberOfRegisters; requestFrame[6] = byteCount; Array.Copy(data, 0, requestFrame, 7, byteCount); // 计算 CRC 校验 ushort crc = ModbusRTU.CalculateCRC(requestFrame, 0, requestFrame.Length - 2); requestFrame[requestFrame.Length - 2] = (byte)(crc & 0xFF); requestFrame[requestFrame.Length - 1] = (byte)(crc >> 8); // 发送请求帧 port.Write(requestFrame, 0, requestFrame.Length); // 接收响应帧 byte[] responseFrame = new byte[8]; int bytesRead = port.Read(responseFrame, 0, responseFrame.Length); if (bytesRead == responseFrame.Length) { // 校验响应帧的地址、功能和字节数是否与请求帧相同 if (responseFrame[0] == slaveAddress && responseFrame[1] == functionCode && responseFrame[2] == byteCount) { // 解析响应帧的 CRC 校验 ushort actualCRC = (ushort)(responseFrame[responseFrame.Length - 1] << 8 | responseFrame[responseFrame.Length - 2]); ushort expectedCRC = ModbusRTU.CalculateCRC(responseFrame, 0, responseFrame.Length - 2); if (actualCRC == expectedCRC) { Console.WriteLine("写入多个寄存器成功!"); } else { Console.WriteLine("CRC 校验错误!"); } } else { Console.WriteLine("响应帧错误!"); } } else { Console.WriteLine("接收响应帧失败!"); } port.Close(); } } public static class ModbusRTU { // 计算 CRC 校验 public static ushort CalculateCRC(byte[] bytes, int offset, int count) { ushort crc = 0xFFFF; for (int i = offset; i < offset + count; i++) { crc ^= bytes[i]; for (int j = 0; j < 8; j++) { if ((crc & 0x0001) == 0x0001) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return crc; } } } ``` 该示例代使用了 .NET Framework 自带的 `SerialPort` 类来打开串口,然后构造 Modbus RTU 请求帧,发送给从站设备,接收从站设备返回的 Modbus RTU 响应帧,并校验 CRC 校验。如果 CRC 校验正确,则表示写入多个寄存器成功。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值