C# ModbusRTU、ModbusASCII及ModbusTCP返回数据解析为数值,功能码0x01-0x04使用1234/2143/3412/4321等字节顺序解析值

1.概述

既然谈到ModbusRTU、Modbus ASCII、ModbusTCP数据返回,那我们多少会对Modbus数据发送会有了解,所以本文不再对Modbus的基础知识进行阐述,如果想了解Modbus常用的发送指令,可以参看我的文章"C# ModbusRTU命令功能码"、“C# Modbus ASCII命令功能码"和"C# ModbusTCP命令功能码”。
Modbus常用的通信指令有8条,其中功能码0x01、0x02、0x03、0x04是查询指令,0x05、0x06、0x0F、0x10是写入指令,对应的返回数据也就有8种。写入指令0x05和0x06的返回数据跟发送指令完全相同(返回异常码除外),0x0F和0x10返回数据跟发送指令的前6个字节完全相同,LRC、CRC校验码不同(ModbusTCP无此校验),写入指令的返回数据相当于确认功能,它们解析为数值是没有意义的。所以我们着重谈的是功能码0x01至0x04的返回数据的值解析。

2.解析代码

Modbus功能码0x01和0x02是查询线圈和离散量状态的,所以返回值类型为bool类型,功能码0x03和0x04是查询保持寄存器和输入寄存器的值,返回类型是数值型的。由于不同的通信硬件设备的解码字节顺序不同,也会带来返回数据的值不同,例如Modicon PLC的整形与浮点型均按3412顺序,而PLC返回的原始数据为1234,解析出来的值自然不会正确。
首先声明四个个枚举,其中有几条枚举项现在用不上,但是我写上它是为了能列出来更多的Modbus指令和生成Modbus发送指令时使用的,无用的大家可自己过滤掉。

    /// <summary>
    /// Modbus的类型
    /// </summary>
    public enum ModbusType
    {
        /// <summary>
        /// Modbus serial RTU
        /// </summary>
        SERIAL_RTU = 0,

        /// <summary>
        /// Modbus serial ASCII
        /// </summary>
        SERIAL_ASCII = 1,

        /// <summary>
        /// Modbus TCP/IP
        /// </summary>
        TCP_IP = 2,
    }
    
    /// <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,//从站(服务器)故障
    }

    /// <summary>
    /// Modbus返回字节大小/类型/顺序
    /// </summary>
    public enum ByteOrder
    {
        NONE = 0,
        TWO_WORD_12 = 1,
        TWO_WORD_21 = 2,
        FOUR_INT_1234 = 3,
        FOUR_INT_1243 = 4,
        FOUR_INT_2134 = 5,
        FOUR_INT_2143 = 6,
        FOUR_INT_3412 = 7,
        FOUR_INT_3421 = 8,
        FOUR_INT_4312 = 9,
        FOUR_INT_4321 = 10,
        FOUR_FLOAT_1234 = 11,
        FOUR_FLOAT_1243 = 12,
        FOUR_FLOAT_2134 = 13,
        FOUR_FLOAT_2143 = 14,
        FOUR_FLOAT_3412 = 15,
        FOUR_FLOAT_3421 = 16,
        FOUR_FLOAT_4312 = 17,
        FOUR_FLOAT_4321 = 18
    }

下面就是解析从站返回数据的方法了,返回类型是object,是因为我们上面说过,返回类型可能是bool型和数值型,所以最后根据需要自己去强制转换。考虑到功能码0x01和0x02可能查询是多个线圈或离散量,所以解析方法返回的数据就会是一个bool数组序列,功能码0x03和0x04查询的可能是多个保持寄存器或输入寄存器,其解析回来的数值根据设备字节顺序要求,会是byte、ushort、int和float数组。

    #region Modbus
    public class Modbus
    {
        #region 内部方法
        /// <summary>
        /// 16进制的字符串转换成等效的byte[]数组
        /// </summary>
        private byte[] HexStringToBytes(string source)
        {
            source = source.Replace(" ", "");
            if (source.Length % 2 == 1) return null;//字符串为奇数就无法转换,返回null

            byte[] buffer = new byte[source.Length / 2];
            for (int i = 0; i < source.Length; i += 2) buffer[i / 2] = Convert.ToByte(source.Substring(i, 2), 16);
            return buffer;
        }
        #endregion


        #region 解析ModbusRTU、Modbus ASCII、ModbusTCP命令返回的数据
        /// <summary>
        /// 在功能码0x01/0x02/0x03/0x04查询后,根据指定的字节顺序对从站(服务器端)返回的数据进行解析。
        /// 1、功能码0x01和0x02返回的数据是bool数组,顺序是低位地址到高位地址的开关状态。
        /// 2、功能码0x03和0x04返回的数据是byte、ushort、int、float数组,由byOrder参数决定。
        /// 3、功能码0x05、0x06、0x0F、0x10返回数据解析为数值没有意义,如果有其他用途可以再做补充。
        /// </summary>
        /// <param name="data">从站(服务器端)返回的全部数据,ModbusRTU包括CRC,ModbusASCII包括帧头冒号和帧尾\r\n</param>
        /// <param name="byteOrder">指定的字节顺序,只对0x03、0x04查询返回的数据有效</param>
        /// <param name="modbusType">Modbus协议类型,默认为ModbusRTU</param>
        /// <returns>解析后的bool/byte/ushort/int/float数组</returns>
        public object ParseModbusData(byte[] data, ByteOrder byteOrder = ByteOrder.NONE, ModbusType modbusType = ModbusType.SERIAL_RTU)
        {
            error = Errors.NO_ERROR;
            try
            {
                //0、根据连接类型,把原始数据进行转换为标准的byte[]
                List<byte> temp = new List<byte>();
                if (modbusType == ModbusType.SERIAL_RTU)
                {
                    temp.AddRange(data);
                }
                else if (modbusType == ModbusType.SERIAL_ASCII)
                {
                    //ModbusASCII帧头和帧尾检测合格后对原始数据进行转换
                    if (data[0] == 0x3A && data[data.Length - 2] == 0x0D && data[data.Length - 1] == 0x0A)
                    {
                        byte[] btemp = data.Skip(1).Take(data.Length - 3).ToArray();
                        string tempString = Encoding.ASCII.GetString(btemp);
                        temp.AddRange(HexStringToBytes(tempString));
                    }
                    else
                    {
                        error = Errors.WRONG_RESPONSE_VALUE;
                        return null;
                    }
                }
                else if (modbusType == ModbusType.TCP_IP)
                {
                    temp = data.Skip(6).Take(data.Length - 6).ToList();
                }

                byte[] source = temp.ToArray();

                //1、检测是否为返回命令数据
                bool isCode = false;
                foreach (ModbusCodes Mc in Enum.GetValues(typeof(ModbusCodes)))
                {
                    //不是返回命令,也不是返回异常码
                    if (source[1] == Mc.GetHashCode() || source[1] == (Mc.GetHashCode() + 0x80))
                    {
                        isCode = true;
                        break;
                    }
                }
                if (!isCode)
                {
                    error = Errors.EXCEEDING_MODBUSCODE_RANGE;
                    return null;
                }

                //2、检测CRC16或LRC是否正确(ModbusTCP无此校验)
                if (modbusType == ModbusType.SERIAL_RTU)//CRC16是否正确
                {
                    byte[] crc16Temp = CRC16.CRCCalc(source, 0, source.Length - 2);
                    if (crc16Temp[0] != source[source.Length - 2] && crc16Temp[1] != source[source.Length - 1])
                    {
                        error = Errors.WRONG_CRC;
                        return null;
                    }
                }

                if (modbusType == ModbusType.SERIAL_ASCII)//LRC是否正确
                {
                    byte lrc = LRC8.LRCCalc(source, 0, source.Length - 1);
                    if (lrc != source[source.Length - 1])
                    {
                        error = Errors.WRONG_LRC;
                        return null;
                    }
                }

                //3、解析功能码0x01,0x02查询线圈或离散量开关量数据,所以用bool类型
                if (source[1] == 0x01 || source[1] == 0x02)
                {
                    //3.1、把有效数据从返回数据的数组中取出来
                    byte[] values = new byte[source[2]];
                    Array.Copy(source, 3, values, 0, values.Length);

                    //3.2、把数据放入BitArray数组,它会按byte值生成由低位到高位的bool数组,正好与Modbus返回开关量由低到高的顺序一致。
                    BitArray barray = new BitArray(values);

                    //3.3、生成bool数组,把BitArray生成的开关量取回,顺序是由低到高
                    bool[] results = new bool[barray.Length];
                    barray.CopyTo(results, 0);

                    //所以,功能码0x01和0x02返回的数据是bool数组,顺序是低位地址到高位地址的开关状态,bool数组是长度是以字节顺序
                    //转换的,所以是8(bit)的倍数,大于等于要查询的线圈或离散量的数量,从低到高取要查看的开关量数量即可,最后多余
                    //出来的是从站返回数据时自动补的0(false)。
                    return results;

                }

                //4、解析功能码0x03、0x04查询寄存器值的数据,在一些PLC、使用485传输或其他设备上,多数会取一个寄存器(两个字节)或
                //两个寄存器的值,用以表示电流、电压、温度或者加工时用到的参数等数据,那么就会涉及到取回值是int,还是float等类型。
                if (source[1] == 0x03 || source[1] == 0x04)
                {
                    //4.1、把有效数据从返回数据的数组中取出来
                    byte[] values = new byte[source[2]];
                    Array.Copy(source, 3, values, 0, values.Length);

                    //4.2、根据给定参数来决定返回值,如果没有指定类型,则原样返回取出来的数据
                    if (byteOrder == ByteOrder.NONE)
                    {
                        return values;
                    }

                    //4.3、目前只考虑WORD、int和float常用类型
                    //拆解字节类型与顺序枚举:byOrder[0]表示两个或四个字节,byOrder[1]表示类型是WORD、int、float,byOder[2]表示取值时的字节顺序
                    string[] byOrder = Enum.GetName(typeof(ByteOrder), byteOrder).Split('_');

                    //4.3.1、WORD类型(无符号)
                    if (byOrder[0] == "TWO" && byOrder[1] == "WORD")
                    {
                        //WORD类型必须是两个字节为一组,先把数据value两个两个分好组,如果有余数,则弃掉
                        ushort[] result = new ushort[values.Length / 2];

                        //假如返回零个或一个字节数据,那肯定有误,不可能发生,但是还是防止吧。
                        if (result.Length == 0)
                        {
                            error = Errors.WRONG_RESPONSE_REGISTERS;
                            return null;
                        }

                        if (byOrder[2] == "12")
                        {
                            for (int i = 0; i < result.Length; i++)
                            {
                                //BitConvert.ToUInt16转换byte数组,byte[0]是低字节,byte[1]是高字节
                                result[i] = BitConverter.ToUInt16(new byte[] { values[i * 2 + 1], values[i * 2] }, 0);
                                //上面语句相当于以下语句
                                //result[i] = (ushort)((values[i * 2] << 8) | (values[i * 2 + 1] & 0x00FF));
                            }

                            return result;
                        }

                        if (byOrder[2] == "21")
                        {
                            for (int i = 0; i < result.Length; i++)
                            {
                                //BitConvert.ToUInt16转换byte数组,byte[0]是低字节,byte[1]是高字节
                                result[i] = BitConverter.ToUInt16(new byte[] { values[i * 2], values[i * 2 + 1] }, 0);
                                //上面语句相当于以下语句
                                //result[i] = (ushort)((values[i * 2] & 0x00FF) | (values[i * 2 + 1] << 8));
                            }

                            return result;
                        }
                    }

                    //4.3.2、int类型
                    if (byOrder[0] == "FOUR" && byOrder[1] == "INT")
                    {
                        //解析返回的int数组
                        int[] result = new int[values.Length / 4];

                        //假如返回0个至3个字节数据,那肯定有误,不可能发生,但是还是防止吧。
                        if (result.Length == 0)
                        {
                            error = Errors.WRONG_RESPONSE_REGISTERS;
                            return null;
                        }

                        for (int i = 0; i < result.Length; i++)
                        {
                            byte No1 = values[i * 4];
                            byte No2 = values[i * 4 + 1];
                            byte No3 = values[i * 4 + 2];
                            byte No4 = values[i * 4 + 3];

                            switch (byOrder[2])
                            {
                                case "1234":
                                    //大端1234,在BitConvert.ToInt32时,输入的byte[]数组是按小端顺序,
                                    //即为从右向左赋值顺序是1234(4 <- 3 <- 2 <- 1)
                                    result[i] = BitConverter.ToInt32(new byte[] { No4, No3, No2, No1 }, 0);
                                    break;
                                case "1243":
                                    //大端1243,转为小端应从右向左赋值(3 <- 4 <- 2 <- 1)
                                    result[i] = BitConverter.ToInt32(new byte[] { No3, No4, No2, No1 }, 0);
                                    break;
                                case "2134":
                                    //大端2143,转小端也可以认为是把2143前后倒过来,即4312
                                    result[i] = BitConverter.ToInt32((new byte[] { No2, No1, No3, No4 }).Reverse().ToArray(), 0);
                                    break;
                                case "2143":
                                    result[i] = BitConverter.ToInt32(new byte[] { No3, No4, No1, No2 }, 0);
                                    break;
                                case "3412":
                                    result[i] = BitConverter.ToInt32(new byte[] { No2, No1, No4, No3 }, 0);
                                    break;
                                case "3421":
                                    result[i] = BitConverter.ToInt32(new byte[] { No1, No2, No4, No3 }, 0);
                                    break;
                                case "4312":
                                    result[i] = BitConverter.ToInt32(new byte[] { No2, No1, No3, No4 }, 0);
                                    break;
                                case "4321":
                                    result[i] = BitConverter.ToInt32(new byte[] { No1, No2, No3, No4 }, 0);
                                    break;

                            }
                        }

                        return result;
                    }

                    //4.3.3、float类型
                    if (byOrder[0] == "FOUR" && byOrder[1] == "FLOAT")
                    {
                        //解析返回的float数组
                        float[] result = new float[values.Length / 4];

                        //假如返回0个至3个字节数据,那肯定有误,不可能发生,但是还是防止吧。
                        if (result.Length == 0)
                        {
                            error = Errors.WRONG_RESPONSE_REGISTERS;
                            return null;
                        }

                        for (int i = 0; i < result.Length; i++)
                        {
                            byte No1 = values[i * 4];
                            byte No2 = values[i * 4 + 1];
                            byte No3 = values[i * 4 + 2];
                            byte No4 = values[i * 4 + 3];

                            switch (byOrder[2])
                            {
                                case "1234":
                                    //大端1234,在BitConvert.ToSingle)时,输入的byte[]数组是按小端顺序,
                                    //即为从右向左赋值顺序是1234(4 <- 3 <- 2 <- 1)
                                    result[i] = BitConverter.ToSingle(new byte[] { No4, No3, No2, No1 }, 0);
                                    break;
                                case "1243":
                                    //大端1243,转为小端应从右向左赋值(3 <- 4 <- 2 <- 1)
                                    result[i] = BitConverter.ToSingle(new byte[] { No3, No4, No2, No1 }, 0);
                                    break;
                                case "2134":
                                    //大端2143,转小端也可以认为是把2143前后倒过来,即4312
                                    result[i] = BitConverter.ToSingle((new byte[] { No2, No1, No3, No4 }).Reverse().ToArray(), 0);
                                    break;
                                case "2143":
                                    result[i] = BitConverter.ToSingle(new byte[] { No3, No4, No1, No2 }, 0);
                                    break;
                                case "3412":
                                    result[i] = BitConverter.ToSingle(new byte[] { No2, No1, No4, No3 }, 0);
                                    break;
                                case "3421":
                                    result[i] = BitConverter.ToSingle(new byte[] { No1, No2, No4, No3 }, 0);
                                    break;
                                case "4312":
                                    result[i] = BitConverter.ToSingle(new byte[] { No2, No1, No3, No4 }, 0);
                                    break;
                                case "4321":
                                    result[i] = BitConverter.ToSingle(new byte[] { No1, No2, No3, No4 }, 0);
                                    break;

                            }
                        }

                        return result;
                    }


                }

                //5、功能码0x05、0x06、0x0F、0x10指令的返回数据与发送时基本相同,0x05、0x06完全相同,0x0F、0x10前6个字节相同,CRC16不同。
                //所以取返回数据意义不大。
                if (source[1] == 0x05 || source[1] == 0x06 || source[1] == 0x0F || source[1] == 0x10)
                {
                    /*
                     * 有用时再写相关内容
                     */
                    error = Errors.UNPROCESSED_MODBUSCODE;
                    return null;
                }

                //6、异常处理
                if (source[1] > 0x80)
                {
                    switch (source[2])
                    {
                        case 1:
                            error = Errors.EXCEPTION_ILLEGAL_FUNCTION;//非法的功能码
                            break;

                        case 2:
                            error = Errors.EXCEPTION_ILLEGAL_DATA_ADDRESS;//非法的数据地址
                            break;

                        case 3:
                            error = Errors.EXCEPTION_ILLEGAL_DATA_VALUE;//非法的数据值
                            break;

                        case 4:
                            error = Errors.EXCEPTION_SLAVE_DEVICE_FAILURE;//从站(服务器)故障
                            break;
                    }

                    return null;
                }

                //7、其他ModbusRTU指令,这里不做解析
                error = Errors.UNPROCESSED_MODBUSCODE;
                return null;

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

3.调用举例

上面的解析数据方法返回类型为object,如何把它转为值类型呢?我们先看一下Modicon PLC取值时的字节解码顺序:

  • 32位整数解码顺序:调整双字元件的解码顺序,对于Modicon PLC,一般设置为“2-3412”顺序解码。
    32 位整数解码顺序 举例: 0x0000 0001
    0―1234 表示双字元件不做处理直接解码(默认值) 表示 1
    1―2143 表示双字元件高低字不颠倒,但字内高低字节颠倒 表示256
    2—3412表示双字元件高低字颠倒,但字内高低字节不颠倒 表示65536
    3—4321表示双字元件内4个字节全部颠倒 表示 16777216

  • 32位浮点数解码顺序:调整双字元件的解码顺序,对于Modicon PLC,一般设置为“2-3412”顺序解码。
    32 位浮点数解码顺序 举例:0x3F80 0000
    0―1234 表示双字元件不做处理直接解码(默认值) 表示 1.0
    1―2143 表示双字元件高低字不颠倒,但字内高低字节颠倒 表示-5.78564e-039
    2—3412表示双字元件高低字颠倒,但字内高低字节不颠倒 表示2.27795e-041
    3—4321表示双字元件内4 个字节全部颠倒 表示 4.60060e-041

我们就以Modicon PLC数值举例,分别构建ModbusRTUModbus ASCII的0x03功能码返回数据,调用解析方法来验证int和float的数值结果,下面先看ModbusRTU返回数据解析的代码。

            //数据值为 0x0000 0001
            byte[] sources = new byte[] { 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, 0x01, 0x3B, 0xF3 };
            Modbus MRTU = new Modbus();
            int[] backint1234 = (int[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_INT_1234, ModbusType.SERIAL_RTU);//输出结果是: 1
            int[] backint2143 = (int[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_INT_2143, ModbusType.SERIAL_RTU);//输出结果是: 256
            int[] backint3412 = (int[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_INT_3412, ModbusType.SERIAL_RTU);//输出结果是: 65536
            int[] backint4321 = (int[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_INT_4321, ModbusType.SERIAL_RTU);//输出结果是: 16777216

            //数据值为 0x3F80 0000
            sources = new byte[] { 0x01, 0x03, 0x04, 0x3F, 0x80, 0x00, 0x00, 0xF4, 0xCF };
            
            float[] backFloat1234 = (float[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_FLOAT_1234, ModbusType.SERIAL_RTU);//输出结果是: 1.0
            float[] backFloat2143 = (float[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_FLOAT_2143, ModbusType.SERIAL_RTU);//输出结果是: -5.78564e-039
            float[] backFloat3412 = (float[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_FLOAT_3412, ModbusType.SERIAL_RTU);//输出结果是: 2.27795e-041
            float[] backFloat4321 = (float[])MRTU.ParseModbusData(sources, ByteOrder.FOUR_FLOAT_4321, ModbusType.SERIAL_RTU);//输出结果是: 4.60060e-041

经过调用测试,程序运行结果完全正确。

我们再来看Modbus ASCII的0x03功能码返回数据解析例子,大家知道Modbus ASCII的返回数据同样是ASCII码可见字符的值,所以它的返回数据长度是ModbusRTU返回数据的2倍多。

            Modbus MASCII = new Modbus();
            //下面是Modbus ASCII可见字符的返回数据“: 01 03 04 00 00 00 01 F7 \r \n”的ASCII值
            byte[] dataASCII = new byte[] { 0x3A, 0x30, 0x31, 0x30, 0x33, 0x30, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x66, 0x37, 0x0D, 0x0A };
            
            int[] backint1234 = (int[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_INT_1234, ModbusType.SERIAL_ASCII);//1
            int[] backint2143 = (int[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_INT_2143, ModbusType.SERIAL_ASCII);//256
            int[] backint3412 = (int[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_INT_3412, ModbusType.SERIAL_ASCII);//65536
            int[] backint4321 = (int[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_INT_4321, ModbusType.SERIAL_ASCII);//16777216

            //数据值为 0x3F80 0000
            //下面是Modbus ASCII可见字符的返回数据“: 01 03 04 3F 80 00 00 39 \r \n”的ASCII值
            dataASCII = new byte[] { 0x3A, 0x30, 0x31, 0x30, 0x33, 0x30, 0x34, 0x33, 0x66, 0x38, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x39, 0x0D, 0x0A };

            float[] backFloat1234 = (float[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_FLOAT_1234, ModbusType.SERIAL_ASCII);//1.0
            float[] backFloat2143 = (float[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_FLOAT_2143, ModbusType.SERIAL_ASCII);//-5.78564e-039
            float[] backFloat3412 = (float[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_FLOAT_3412, ModbusType.SERIAL_ASCII);//2.27795e-041
            float[] backFloat4321 = (float[])MASCII.ParseModbusData(dataASCII, ByteOrder.FOUR_FLOAT_4321, ModbusType.SERIAL_ASCII);//4.60060e-041

同样在VS环境下测试结果完全正确。ModbusTCP返回数据与ModbusRTU基本一样,所以此处忽略。

4.说明

Modbus返回数据解析方法及调用我们已经完全结出代码,经过测试也是解析正确。这其中有几点要说明一下,1、结果返回如果null,请查看error的值来确定异常,如果error为Errors.UNPROCESSED_MODBUSCODE,则说明解析的不是功能码0x01-0x04的返回数据,如果error是Errors.EXCEPTION_UNKNOWN,那说明返回数据有问题。2、功能码0x01和0x02返回数据解析出来的是bool数组,它的长度要大于等于查询线圈或离散量的数量的,因为从站返回是以字节为单位的,请根据查询时的数量来自己截止,同时注意bool数组输出的开关量是从查询时的起始地址到高地址时行排列的,即bool[0]代表查询线圈或离散量的起始地址,bool[N]是高地址,是最后一个查询线圈或离散量的开关量值。3、代码中的CRC校验的方法CRC16.CRCCalc()和LRC校验方法LRC8.CRCCalc()本文没有列出来,如果需要,可以参考我另外两篇“C# Modbus的CRC16校验代码”和"C# Modbus ASCII的LRC校验代码"的文章。4、解析方法的第一个参数data,是Modbus返回的所有数据,不要自己加工后再代入,否则结果会异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值