C# Modbus ASCII命令功能码

1.概述

Modbus是一种串行通信协议,主要应用于电子控制器上的一种通用语言。常用的ModbusRTU、Modbus ASCII和ModbusTCP,Modbus ASCII相对来说要用的少一些。
Modbus ASCII协议是一种主从式串行异步半双工通信协议,是基于RS485\RS422\RS232物理层的通信的,在协议通信中每个字符通信格式规定为1个起始位、7个数据位、1个校验位、1个停止位,并且有LRC错误校验。
Modbus RTU采用的是字节流(字节值为0x00至0xFF)传输,多数是无法用文本显示出来的(可打印出来的字符在0x20至0x7E之间,只有95个),而Modbus ASCII采用的是可打印字符0到9(字节为0x30至0x39)、A至F(字节为0x41至0x46)、冒号(字节0x3A)、回车符(字节为0x0D)、换行符(字节为0x0A)进行传输,也可以说是文本传输。Modbus ASCII其实跟ModbusRTU很相似,其寄存器种类及主要功能命令都一样,只是传输数据的格式略有区别,简单说除了有效数据以可打印的ASCII码通信外,就是在ModbusRTU的数据帧前面加了帧头冒号(:),把ModbusRTU的CRC校验去掉,换成LRC校验,并增加了结束符回车换行,其它数据内容完全一样。请参看下面两张图:
在这里插入图片描述
在这里插入图片描述
从图中可以看出,ModbusRTU传输的字节流就是Modbus指令,对应的ASCII码可以说是乱码,多数不是打印字符。而Modbus ASCII是用可显示(可打印)的字符组装成Modbus指令,把它们转换为对应的ASCII码值,加上帧头冒号的ASCII码值(0x3A)和帧尾的回车换行符的ASCII码值(0x0D、0x0A),包装成字节流形式的信息帧作为最终指令,再传输发送出去(返回信息帧也是一样的格式)。因此,形成Modbus ASCII命令功能码,我们首先要用可显示ASCII码字符拼写指令,然后再将它们转换为相应的ASCII值进行发送,同样收到返回信息后,也要先将ASCII值转换为对应的ASCII可显示字符,就能看出具体是哪个功能码返回的信息了,然后再把这些ASCII可显示字符转换对应的多数不可显示的Modbus字节流返回信息。
由于两个可显示ASCII字符对应的一个不可显示字节,每个ASCII字符又占一个字节,所以Modbus ASCII的信息帧就比ModbusRTU要长一倍多,一般情况下,Modbus功能码0x01、0x02、0x03、0x04的ModbusRTU指令是8个字节,而Modbus ASCII的指令就会达到17个字节,所以传输效率要低一些,适用于对传输速率要求不高的场景。
ModbusRTU和ModbusTCP不涉及ASCII字符转换,可以用字节流直接形成指令,具体可参考我另外两篇文章“C# ModbusRTU命令功能码”和"C# ModbusTCP命令功能码"。

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

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

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

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

4.Modbus ASCII的八条命令生成代码

在前面我已经简单介绍了Modbus ASCII指令形成的基本原理了,它实际就是对ModbusRTU的8条指令做了相应的修改,一是把CRC替换为LRC,二是增加帧头和帧尾,三是转换为可显示的ASCII字符,四是再转换为对应的ASCII值的byte数组。和我在ModbusRTU生成的八条功能代码一样,本文的ModbusASCII的八条代码一样是byte数组类型,不涉及Modbus总线传输部分,因为它可以即可以应用于串口通信,也可以应用于RS232/485/422转网络传输的环境,如想了解串行总线的传输代码,请参考我另一篇文章"C# 串口发送ModbusRTU、ModbusASCII的8条主要功能代码并获取返回的响应信息"。
下面我们还是需要先声明三个枚举,其中有几条枚举项现在用不上,其实还是为了能列出来更多的Modbus指令和在取Modbus返回数据时使用的。

    /// <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,//从站(服务器)故障
        
    }

下面的代码就是Modbus ASCII的主要八条功能码指令的代码了,返回类型是byte数组,可以直接代入程序中使用。

    #region ModbusASCII
    
    public class ModbusASCII
    {
        #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 内部方法
        /// <summary>
        /// byte[]转换为等效的十六进制字符串,例如byte example = 32,转换字符串为"20"
        /// </summary>
        /// <param name="source">byte[]原数组</param>
        /// <param name="formatStr">转换的格式,默认为十六进制</param>
        private string BytesToHexString(byte[] source)
        {
            StringBuilder pwd = new StringBuilder();
            foreach (byte btStr in source) { pwd.AppendFormat("{0:X2}", btStr); }
            return pwd.ToString();
        }

        #endregion

        #region 读取线圈,功能码0x01
        /// <summary>
        /// 生成ModbusASCII读取线圈状态指令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[7];
            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位是LRC校验
                command[6] = LRC8.LRCCalc(command, 0, 6);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 读取离散量,功能码0x02
        /// <summary>
        /// 生成ModbusASCII读取离散量状态指令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[7];
            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位是LRC校验
                command[6] = LRC8.LRCCalc(command, 0, 6);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 读取保持寄存器,功能码0x03
        /// <summary>
        /// 生成ModbusASCII读取保持寄存器指令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[7];
            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位是LRC校验
                command[6] = LRC8.LRCCalc(command, 0, 6);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 读取输入寄存器,功能码0x04
        /// <summary>
        /// 生成ModbusASCII读取输入寄存器指令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[7];
            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位是LRC校验
                command[6] = LRC8.LRCCalc(command, 0, 6);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 写单个线圈,功能码0x05
        /// <summary>
        /// 生成ModbusASCII写单个线圈指令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[7];
            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位是LRC校验
                command[6] = LRC8.LRCCalc(command, 0, 6);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 写单个保持寄存器,功能码0x06
        /// <summary>
        /// 生成ModbusASCII写单个保持寄存器指令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[7];
            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位是LRC校验
                command[6] = LRC8.LRCCalc(command, 0, 6);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 写多个线圈,功能码0x0F
        /// <summary>
        /// 生成ModbusASCII写多个线圈指令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[7 + 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, .../中间N位都是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);

                //最后一位是LRC校验值
                command[7 + ValuesBytesCount] = LRC8.LRCCalc(command, 0, 7 + ValuesBytesCount);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

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

        }
        #endregion

        #region 写多个保持寄存器,功能码0x10
        /// <summary>
        /// 生成ModbusASCII写多个保持寄存器指令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[7 + 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);
                }

                //最后一位是LRC校验值
                command[7 + ValuesBytesCount] = LRC8.LRCCalc(command, 0, 7 + ValuesBytesCount);

                //转换为16进制字符串后,组装成ModbusASCII命令格式,首字符为":",末尾字符为回车换行
                string cmdString = ":" + BytesToHexString(command) + "\r\n";
                //把ModbusASCII指令格式转换为等效的byte[]
                byte[] AsciiCmd = Encoding.ASCII.GetBytes(cmdString);

                return AsciiCmd;

            }
            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校验的方法LRC8.LRCCalc()本文没有列出来,如果需要,可以参考我另一篇“C# Modbus ASCII的LRC校验代码”的文章。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值