使用C#实现ModbusRTU通信

 

公共方法

  public class Common
   {
       /// <summary>
       /// 生成CRC校验码
       /// </summary>
       /// <param name="data"></param>
       /// <returns>返回CRC校验码</returns>
       public static byte[] CRC16(List<byte> data)
       {
           //CRC校验的基本步骤如下:

           //1.预置:CRC寄存器预置为0xFFFF。
           //2.数据输入:报文中除了CRC校验码以外的所有字节(包括设备地址、功能码和数据)按照顺序进行处理。
           //4.计算:对每一个字节,从最高位到最低位,将其与CRC寄存器当前的值进行异或运算。如果结果的最高位为1,则将寄存器的值左移一位并与0x8005进行异或运算;如果最高位为0,则只需左移一位。重复此过程,直至8位都处理完毕。然后继续处理下一个字节,直到所有字节都计算完毕。
           //4.结果:最后CRC寄存器中的值就是CRC校验码,通常在传输前转换为低字节在前(Little - Endian)的形式,并附加到报文的末尾。

           //当接收方收到报文时,会对整个报文(包括CRC校验码)使用相同的CRC计算流程。如果报文未被篡改,计算结果应为0x0000(考虑到了CRC码的加入和计算规则)。如果结果不是0x0000,则表明报文在传输过程中可能遭到了篡改或出现了错误。

           //运算
           ushort crc = 0xFFFF;
           for (int i = 0; i < data.Count; i++)
           {
               crc = (ushort)(crc ^ (data[i]));
               for (int j = 0; j < 8; j++)
               {
                   crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
               }
           }
           byte hi = (byte)((crc & 0xFF00) >> 8);  //
           byte lo = (byte)(crc & 0x00FF);         //

           return [lo, hi];
       }

       /// <summary>
       /// 错误码
       /// </summary>
       public static Dictionary<byte, string> ErrorCodes = new Dictionary<byte, string>
       {
           { 0x01, "非法的功能码"},
           { 0x02, "非法的数据地址"},
           { 0x03, "非法的数据值"},
           { 0x04, "从设备/服务器故障"},
           { 0x05, "确认,从设备/服务器需要一个耗时操作"},
           { 0x06, "从设备/服务器繁忙"},
           { 0x0A, "网关故障"},
           { 0x0B, "网关目标设备未能响应"},
       };
   }

 

要用到的枚举:

    /// <summary>
    /// 大小端模式
    /// </summary>
    public enum Endian
    { 
        /// <summary>
        /// 大端
        /// </summary>
        BigEndian,

        /// <summary>
        /// 小端
        /// </summary>
        LittleEndian

    }

    /// <summary>
    /// 功能码
    /// </summary>
    public enum FuncCode
    {
        ReadCoils=0x01,
        ReadDiscreteInputs = 0x02,
        ReadHoldingRegisters = 0x03,
        ReadInputRegisters = 0x04,
        WriteSingleCoil= 0x05,
        WriteSingleRegister = 0x06,
        WriteMultipleCoil = 0x0F,
        WriteMultipleRegisters = 0x10,
    }

 

方法:

功能码:0x01、0x02、0x03、0x05、0x06、0x0F、0x10

public class ModbusRTU
{
    private SerialPort _serialPort;
    private byte _slaveId;

    /// <summary>
    /// 
    /// </summary>
    /// <param name="serialPort">串口</param>
    /// <param name="slaveId">从站地址</param>
    public ModbusRTU(SerialPort serialPort, byte slaveId)
    {
        _serialPort = serialPort;
        _slaveId = slaveId;
    }

    /// <summary>
    /// 功能码0x01
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="count">读取数量</param>
    /// <returns>bool数组</returns>
    public bool[] ReadCoils(short startAddress, short count)
    {
        //1.发送请求
        //2.获取响应数据
        //3.分析返回结果。返回bool[]
        //3.1 校验
        //3.2 如果通过,取出数据并bool[]

        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);
        byte[] countBytes = BitConverter.GetBytes(count);

        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x01,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

            countBytes[1],//读取长度H
            countBytes[0],
        ];

        byte[] crcBytes = Common.CRC16(sendBytes);

        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = (int)Math.Ceiling(count * 1.0 / 8) + 5;//读取个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }

        #endregion

        #region 3.分析返回结果。返回short[]
        //3.1 校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }

        //3.2 如果通过,取出数据并bool[]
        List<byte> dataBytes = readBytes.GetRange(3, readCount - 5);
        bool[] tempBool = new bool[dataBytes.Count * 8];//临时变量
        //05 00
        //0000 0101
        //0000 0000
        //bool[] = [true,false,true,false,12个false]
        for (int i = 0; i < dataBytes.Count; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                //判断第j位是否为1
                tempBool[i * 8 + j] = (dataBytes[i] & (1 << j)) != 0;
            }
        }

        bool[] result = new bool[count];//最终结果
        Array.Copy(tempBool, result, count);
        return result;
        #endregion
    }

    /// <summary>
    /// 功能码0x02
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="count">读取数量</param>
    /// <returns>bool数组</returns>
    public bool[] ReadDiscreteInput(short startAddress, short count)
    {
        //1.发送请求
        //2.获取响应数据
        //3.分析返回结果。返回bool[]
        //3.1 校验
        //3.2 如果通过,取出数据并bool[]

        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);
        byte[] countBytes = BitConverter.GetBytes(count);

        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x02,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

            countBytes[1],//读取长度H
            countBytes[0],
        ];

        byte[] crcBytes = Common.CRC16(sendBytes);

        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = (int)Math.Ceiling(count * 1.0 / 8) + 5;//读取个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }

        #endregion

        #region 3.分析返回结果。返回bool[]
        //3.1 校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }

        //3.2 如果通过,取出数据并bool[]
        List<byte> dataBytes = readBytes.GetRange(3, readCount - 5);
        bool[] tempBool = new bool[dataBytes.Count * 8];//临时变量
        //05 00
        //0000 0101
        //0000 0000
        //bool[] = [true,false,true,false,12个false]
        for (int i = 0; i < dataBytes.Count; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                //判断第j位是否为1
                tempBool[i * 8 + j] = (dataBytes[i] & (1 << j)) != 0;
            }
        }

        bool[] result = new bool[count];//最终结果
        Array.Copy(tempBool, result, count);
        return result;
        #endregion
    }

    /// <summary>
    /// 读保持寄存器(16位)  功能码0x03
    /// </summary>
    /// <param name="startAddress">开始地址</param>
    /// <param name="count">要读的数字个数</param>
    /// <returns>数字数组</returns>
    /// <exception cref="Exception"></exception>
    public short[] ReadHoldingReg16(short startAddress, short count)
    {
        //1.发送请求
        //2.获取响应数据
        //3.分析返回结果。返回short[]
        //3.1 校验
        //3.2 如果通过,取出数据并short[]

        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);
        byte[] countRegs = BitConverter.GetBytes(count);//寄存器个数

        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x03,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

            countRegs[1],//读取长度H
            countRegs[0],
        ];

        byte[] crcBytes = Common.CRC16(sendBytes);

        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = count * 2 + 5;//读取字节个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }

        #endregion

        #region 3.分析返回结果。返回short[]
        //3.1 校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }

        //3.2 如果通过,取出数据并short[]
        List<byte> dataBytes = readBytes.GetRange(3, readCount - 5);

        short[] result = new short[count];
        for (int i = 0; i < count; i++)
        {
            //BitConverter 只支持小端
            List<byte> curBytes = dataBytes.GetRange(i * 2, 2);
            curBytes.Reverse();
            result[i] = BitConverter.ToInt16(curBytes.ToArray());
        }

        return result;
        #endregion
    }


    /// <summary>
    /// 读保持寄存器(32位)  功能码0x03
    /// </summary>
    /// <param name="startAddress">开始地址</param>
    /// <param name="count">要读的数字个数</param>
    /// <param name="endian">大小端模式。默认大端</param>
    /// <returns>数字数组</returns>
    /// <exception cref="Exception"></exception>
    public int[] ReadHoldingReg32(short startAddress, short count, Endian endian = Endian.BigEndian)
    {
        //1.发送请求
        //2.获取响应数据
        //3.分析返回结果。返回int[]
        //3.1 校验
        //3.2 如果通过,取出数据并int[]

        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);
        byte[] countRegs = BitConverter.GetBytes(count * 2);//寄存器个数

        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x03,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

            countRegs[1],//读取长度H
            countRegs[0],
        ];

        byte[] crcBytes = Common.CRC16(sendBytes);

        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = count * 4 + 5;//读取字节个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }

        #endregion

        #region 3.分析返回结果。返回short[]
        //3.1 校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }

        //3.2 如果通过,取出数据并int[]
        List<byte> dataBytes = readBytes.GetRange(3, readCount - 5);

        int[] result = new int[count];
        for (int i = 0; i < count; i++)
        {
            //BitConverter 只支持小端
            List<byte> curBytes = dataBytes.GetRange(i * 4, 4);
            if (endian == Endian.BigEndian)
            {
                curBytes.Reverse();
            }
            result[i] = BitConverter.ToInt32(curBytes.ToArray());
        }

        return result;
        #endregion
    }

    /// <summary>
    /// 读保持寄存器(32位 float)  功能码0x03
    /// </summary>
    /// <param name="startAddress">开始地址</param>
    /// <param name="count">要读的数字个数</param>
    /// <param name="endian">大小端模式。默认大端</param>
    /// <returns>数字数组</returns>
    /// <exception cref="Exception"></exception>
    public float[] ReadHoldingReg32Float(short startAddress, short count, Endian endian = Endian.BigEndian)
    {
        //1.发送请求
        //2.获取响应数据
        //3.分析返回结果。返回int[]
        //3.1 校验
        //3.2 如果通过,取出数据并int[]

        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);
        byte[] countRegs = BitConverter.GetBytes(count * 2);//寄存器个数

        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x03,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

            countRegs[1],//读取长度H
            countRegs[0],
        ];

        byte[] crcBytes = Common.CRC16(sendBytes);

        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = count * 4 + 5;//读取字节个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }

        #endregion

        #region 3.分析返回结果。返回short[]
        //3.1 校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }

        //3.2 如果通过,取出数据并int[]
        List<byte> dataBytes = readBytes.GetRange(3, readCount - 5);

        float[] result = new float[count];
        for (int i = 0; i < count; i++)
        {
            //BitConverter 只支持小端
            List<byte> curBytes = dataBytes.GetRange(i * 4, 4);
            if (endian == Endian.BigEndian)
            {
                curBytes.Reverse();
            }
            result[i] = BitConverter.ToSingle(curBytes.ToArray());
        }

        return result;
        #endregion
    }

    #region 写单线圈 功能码0x05
    /// <summary>
    /// 写单线圈 功能码0x05
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="value">写的值</param>
    public void WriteSingleCoil(short startAddress, bool value)
    {
        //1.发送请求
        //2.获取响应数据
        //3.校验
        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);

        byte[] writeValueBytes = value ? [0xFF, 0x00] : [0x00, 0x00];//写入的值bytes
        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x05,//功能码
            startBytes[1],//起始地址H
            startBytes[0],
        ];

        sendBytes.AddRange(writeValueBytes);

        byte[] crcBytes = Common.CRC16(sendBytes);

        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = 8;//读取字节个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }
        #endregion

        #region 3.校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }
        #endregion
    }
    #endregion

    #region 写多线圈 功能码0x0F
    /// <summary>
    /// 写多线圈 功能码0x0F
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="values">写的多个值</param>
    public void WriteMultipleCoil(short startAddress, bool[] values)
    {
        //1.发送请求
        //2.获取响应数据
        //3.校验
        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);

        byte[] valueCountBytes = BitConverter.GetBytes(values.Length);//值的个数bytes
        int bytesCount = (int)Math.Ceiling(values.Length * 1.0 / 8);//写入的字节个数
        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x0F,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

             valueCountBytes[1],//值的个数bytes
            valueCountBytes[0],

            (byte)bytesCount//写入的字节个数
        ];

        //将bool[]转成byte[]
        BitArray bitArray = new BitArray(values);
        byte[] writeBytes = new byte[bytesCount];
        bitArray.CopyTo(writeBytes, 0);

        sendBytes.AddRange(writeBytes);
        byte[] crcBytes = Common.CRC16(sendBytes);
        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = 8;//读取字节个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }
        #endregion

        #region 3.校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }
        #endregion
    }
    #endregion

    #region 写单个寄存器 功能码0x06
    /// <summary>
    /// 写单个寄存器 功能码0x06
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="value">写的多个值</param>
    public void WriteSingleReg(short startAddress, short value)
    {
        //1.发送请求
        //2.获取响应数据
        //3.校验
        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);

        byte[] valueBytes = BitConverter.GetBytes(value);//小端
        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x06,//功能码
            startBytes[1],//起始地址H
            startBytes[0],

            valueBytes[1],//写进去的值
             valueBytes[0],
        ];

        byte[] crcBytes = Common.CRC16(sendBytes);
        sendBytes.AddRange(crcBytes);

        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = 8;//读取字节个数

        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }
        #endregion

        #region 3.校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }

        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }
        #endregion
    }
    #endregion

    #region 写多个寄存器(32位int) 功能码0x10
    /// <summary>
    /// 写多个寄存器(32位int) 功能码0x10
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="values">写的多个值</param>
    /// <param name="endian">大小端</param>
    public void WriteMultipleReg32Int(short startAddress, int[] values, Endian endian = Endian.BigEndian)
    {
        //1.发送请求
        //2.获取响应数据
        //3.校验
        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);

        byte[] regCountbytes = BitConverter.GetBytes(values.Length * 2);

        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x10,//功能码
            startBytes[1],//起始地址H
            startBytes[0],
            regCountbytes[1],//寄存器数量
             regCountbytes[0],
             (byte)(values.Length*4)//字节数
        ];

        foreach (int i in values)
        {
            byte[] curBytes = BitConverter.GetBytes(i);//小端
            if (endian == Endian.BigEndian)//如果是大端
            {
                curBytes = curBytes.Reverse().ToArray();
            }
            sendBytes.AddRange(curBytes);
        }
        byte[] crcBytes = Common.CRC16(sendBytes);
        sendBytes.AddRange(crcBytes);
        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = 8;//读取字节个数
        List<byte> readBytes = new List<byte>();//返回的byte[]

        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }
        #endregion

        #region 3.校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }
        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }
        #endregion
    }
    #endregion

    #region 写多个寄存器(32位float) 功能码0x10
    /// <summary>
    /// 写多个寄存器(32位float) 功能码0x10
    /// </summary>
    /// <param name="startAddress">起始地址</param>
    /// <param name="values">写的多个值</param>
    /// <param name="endian">大小端</param>
    public void WriteMultipleReg32Float(short startAddress, float[] values, Endian endian = Endian.BigEndian)
    {
        //1.发送请求
        //2.获取响应数据
        //3.校验
        #region 1.发送请求
        byte[] startBytes = BitConverter.GetBytes(startAddress);
        byte[] regCountbytes = BitConverter.GetBytes(values.Length * 2);
        //发送的bytes
        List<byte> sendBytes = [
            _slaveId,//从机地址
            0x10,//功能码
            startBytes[1],//起始地址H
            startBytes[0],
            regCountbytes[1],//寄存器数量
             regCountbytes[0],
             (byte)(values.Length*4)//字节数
        ];
        foreach (float i in values)
        {
            byte[] curBytes = BitConverter.GetBytes(i);//小端
            if (endian == Endian.BigEndian)//如果是大端
            {
                curBytes = curBytes.Reverse().ToArray();
            }

            sendBytes.AddRange(curBytes);
        }
        byte[] crcBytes = Common.CRC16(sendBytes);
        sendBytes.AddRange(crcBytes);
        _serialPort.Write(sendBytes.ToArray(), 0, sendBytes.Count);
        #endregion

        #region 2.获取响应数据
        int readCount = 8;//读取字节个数
        List<byte> readBytes = new List<byte>();//返回的byte[]
        try
        {
            while (readBytes.Count < readCount)
            {
                readBytes.Add((byte)_serialPort.ReadByte());
            }
        }
        catch (Exception)
        {
            //异常处理,比如超时异常等
        }
        #endregion

        #region 3.校验
        if (readBytes[1] > 0x80)//出异常了
        {
            throw new Exception($"出异常了,异常信息:{Common.ErrorCodes[readBytes[2]]}");
        }
        List<byte> checkBytes = readBytes.GetRange(0, readBytes.Count - 2);
        //checkBytes[3] = 0x05;//测试篡改数据
        byte[] checkCodeBytes = Common.CRC16(checkBytes);
        checkBytes.AddRange(checkCodeBytes);
        if (!checkBytes.SequenceEqual(readBytes))
        {
            throw new Exception("校验失败");
        }
        #endregion
    }
    #endregion
}

 

调用:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Btn01_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.ReadCoils(0, 1);
    }

    private void Btn02_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        bool[] rst = modbusRTU.ReadDiscreteInput( 0, 5);
    }

    /// <summary>
    /// 03功能码 16位
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Btn03_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.ReadHoldingReg16(0, 3);
    }

    private void Btn03_32_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.ReadHoldingReg32Float(0, 3, Endian.BigEndian);
    }

    private void Btn05_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.WriteSingleCoil(3, true);
    }

    private void Btn0F_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.WriteMultipleCoil(1, [true,true,true,false,false,true,true,false,true,true]);
    }

    private void Btn06_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.WriteSingleReg(9,456);
    }

    private void Btn10_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.WriteMultipleReg32Int(2, [123,456,789], Endian.LittleEndian);
    }

    private void Btn10_float_Click(object sender, RoutedEventArgs e)
    {
        SerialPort serialPort = new SerialPort("COM1");
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.Parity = Parity.None;
        serialPort.StopBits = StopBits.One;

        serialPort.ReadTimeout = 500;//读超时时间 单位毫秒
        serialPort.Open();

        ModbusRTU modbusRTU = new ModbusRTU(serialPort, 0x01);
        modbusRTU.WriteMultipleReg32Float(2, [(float)123.1, (float)456.5, (float)789.6]);
    }
}

 

posted @ 2026-03-26 13:41  野码  阅读(16)  评论(0)    收藏  举报