C#自定义实现ModbusTCP主站通讯(二)

190 阅读4分钟
  • 摘要: 本文描述的内容是在上一篇文章的基础上增加读写int和float的数据功能、字节序读写功能(ABCD、BADC、CDAB、DCBA)。

  • 前言

    • 今天要说的是如何在上一篇文章《C#实现ModbusTCP通讯》案例的基础上添加新功能。
    • 废话不多说,主要添加了int、float数据的字节序读取功能。
    • 什么是字节序?就是字节排序,使用字面ABCD表示。
    • 每个字母代表一个字节(A=最高位字节,D=最低位字节)。
    • 如:大端序( 0x12345678 存储为 12 34 56 78),小端序(0x12345678 存储为 78 56 34 12)。
    • 排序方式有如下几种:
      • ABCD [A, B, C, D] 大端序
      • DCBA [D, C, B, A] 小端序
      • BADC [B, A, D, C]
      • CDAB [C, D, A, B]
    • 基本的ModbusTCP读取线圈和寄存器数据一般使用bool和ushort类型,只占用一个寄存器。
    • 如果要返回int、float类型的数据需要占用 2 个寄存器,其实就是读写两个寄存器,再根据不同的字节序和数据类型转换即可。

🌟运行环境

  • 操作系统: Windows11
  • 编程软件: Visual Studio 2022
  • .Net版本: .Net Framework 4.8.0

  • 一、🔍预览

    • (一)🚀界面、运行效果

      • 初始化字节序是ABCD。
      • 数据读取可以通过点击读取int、float按钮读取,需设置起始地址和长度读取。
      • 数据的写入根据起始地址和发送内容写入。
      • 如输入:12 点击写入按钮 (int或float)实现单个写入。输入[12,34,0,0,0]点击按钮实现写入多个数据。

image.png


  • 二、代码

    • (一)MainForm代码

      • 添加按钮了读取写入int、float按钮,用于触发读取写入功能。增加字节序变更功能。
    #region 写入按钮事件
     private void btn_WriteInt_Click(object sender, EventArgs e)
     {
         try
         {
             int[] register = ParseArray<int>(rtbx_SendData.Text);
             if (register != null &&register.Length == 1)
             {
                 modbusTcp.WriteInt(modbusTcp.DataModel.ReadStartAddress, register[0]);
                 MessageUpdate($"[{rtbx_SendData.Text}]", Color.Green, $"# 写入 Int >");
             }
             else
             {
                 modbusTcp.WriteIntArray(modbusTcp.DataModel.ReadStartAddress, register);
                 StringBuilder appendInt = new StringBuilder();
                 for (int i = 0; i < register.Length; i++)
                 {
                     appendInt.Append(register[i] + " ");
                 }
                 MessageUpdate($"{appendInt.ToString()}", Color.Green, $"# 写入 Int >");
             }
         }
         catch (Exception ex)
         {
             MessageUpdate($"{ex.Message}", Color.Red, $"# 写入数据 >");
         }
     }
     private void btn_WriteFloat_Click(object sender, EventArgs e)
     {
    
         try
         {
             float[] register = ParseArray<float>(rtbx_SendData.Text);
             if (register != null && register.Length == 1)
             {
                 modbusTcp.WriteFloat(modbusTcp.DataModel.ReadStartAddress, register[0]);
                 MessageUpdate($"[{rtbx_SendData.Text}]", Color.Green, $"# 写入 Float >");
             }
             else
             {
                 modbusTcp.WriteFloatArray(modbusTcp.DataModel.ReadStartAddress, register);
                 StringBuilder appendInt = new StringBuilder();
                 for (int i = 0; i < register.Length; i++)
                 {
                     appendInt.Append(register[i] + " ");
                 }
                 MessageUpdate($"{appendInt.ToString()}", Color.Green, $"# 写入 Float >");
             }
         }
         catch (Exception ex)
         {
             MessageUpdate($"{ex.Message}", Color.Red, $"# 写入数据 >");
         }
     }
     #endregion
    
     #region 读取按钮事件
     private void btn_ReadInt_Click(object sender, EventArgs e)
     {
         int[] intArray = modbusTcp.ReadInt(modbusTcp.DataModel.ReadStartAddress, modbusTcp.DataModel.ReadDataLength);
         if (intArray == null) return;
         string tempArray = string.Empty;
         for (int i = 0; i < intArray.Length; i++)
         {
             tempArray += intArray[i]+" ";
         }
         MessageUpdate($"[{tempArray}]", Color.Green, $"# 读取 Int >");
     }
    
     private void btn_ReadFloat_Click(object sender, EventArgs e)
     {
         float[] intArray = modbusTcp.ReadFloat(modbusTcp.DataModel.ReadStartAddress, modbusTcp.DataModel.ReadDataLength);
         if (intArray == null) return;
         string tempArray = string.Empty;
         for (int i = 0; i < intArray.Length; i++)
         {
             tempArray += intArray[i] + " ";
         }
         MessageUpdate($"[{tempArray}]", Color.Green, $"# 读取 float >");
     }
     #endregion
    
  • (二)读写功能

    • 创建读写单个、多个int、float数据的方法,以及一些数据转换方法。
    #region 新增读写功能
    #region 写入
    /// <summary>
    /// 写整数
    /// </summary>
    public byte[] WriteInt(ushort startAddress, int data, byte unitId = 1)
    {
        if (_socket == null) return null;
        _socket.ReceiveTimeout = ReceiveTimeout;
        try
        {
            // 拆分int
            byte[] byteArray = ConvertIntToBytes(data);
            // 发送请求
            byte[] request = BuildMutiWriteRequest(0x10, startAddress, byteArray, 2, unitId);
            _socket.Send(request);
            // 请求报文
            OnRequestMessage(new ModbusMessageEvents(request, true));
            byte[] response = ResponseParse(1, 0x10);
            // 响应报文
            OnResponseMessage(new ModbusMessageEvents(response, true));
            return response;
        }
        catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
        {
            throw new TimeoutException($"读取操作超时({ReceiveTimeout}ms)", ex);
        }
    }
    /// <summary>
    /// 写浮点
    /// </summary>
    public byte[] WriteFloat(ushort startAddress, float data, byte unitId = 1)
    {
        if (_socket == null) return null;
        _socket.ReceiveTimeout = ReceiveTimeout;
        try
        {
            // 拆分float
            byte[] byteArray = ConvertFloatToBytes(data);
            // 发送请求
            byte[] request = BuildMutiWriteRequest(0x10, startAddress, byteArray, 2, unitId);
            _socket.Send(request);
            // 请求报文
            OnRequestMessage(new ModbusMessageEvents(request, true));
            byte[] response = ResponseParse(1, 0x10);
            // 响应报文
            OnResponseMessage(new ModbusMessageEvents(response, true));
            return response;
        }
        catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
        {
            throw new TimeoutException($"读取操作超时({ReceiveTimeout}ms)", ex);
        }
    }
    #endregion
    #region 读取
    /// <summary>
    /// 读取保持寄存器(功能码0x03 )
    /// </summary>
    public int[] ReadInt(ushort startAddress, int numberOfPoints, byte unitId = 1)
    {
        if (_socket == null) return null;
        _socket.ReceiveTimeout = ReceiveTimeout;
        try
        {
            //读取整数*2个数寄存器
            ushort number = (ushort)(2 * numberOfPoints);
            //发送请求
            byte[] request = BuildReadRequest(0x03, startAddress, number, unitId);
            _socket.Send(request);
            //请求报文
            OnRequestMessage(new ModbusMessageEvents(request));
            //响应接收
            byte[] response = ResponseParse(number, 0x03);
            //响应报文
            OnResponseMessage(new ModbusMessageEvents(response));
            //验证响应
            ValidateResponse(0x03, response, request);
            //获取读取数据
            int length = 4 * numberOfPoints;
            byte[] byteData = new byte[length];
            byte[] data = new byte[4];
            Array.Copy(response,9,byteData, 0, length);
            int[] intArray = new int[numberOfPoints];
            for (int i = 0; i < numberOfPoints; i++)
            {
                Array.Copy(byteData,i*4, data,0,4);
                intArray[i] = BitConverter.ToInt32(FormatOrder(data), 0);
            }
            return intArray;
        }
        catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
        {
            throw new TimeoutException($"读取操作超时({ReceiveTimeout}ms)", ex);
        }
    }
    /// <summary>
    /// 读取保持寄存器(功能码0x03 )
    /// </summary>
    public float[] ReadFloat(ushort startAddress, int numberOfPoints, byte unitId = 1)
    {
        if (_socket == null) return null;
        _socket.ReceiveTimeout = ReceiveTimeout;
        try
        {
            //读取整数*2个数寄存器
            ushort number = (ushort)(2 * numberOfPoints);
            //1、发送请求
            byte[] request = BuildReadRequest(0x03, startAddress, number, unitId);
            _socket.Send(request);
            //2、请求报文
            OnRequestMessage(new ModbusMessageEvents(request));
            //3、响应接收
            byte[] response = ResponseParse(number, 0x03);
            //4、响应报文
            OnResponseMessage(new ModbusMessageEvents(response));
            //5、验证响应
            ValidateResponse(0x03, response, request);
            //6、计算数据长度、获取读取数据
            int length = 4 * numberOfPoints;
            byte[] byteData = new byte[length];
            Array.Copy(response, 9, byteData, 0, length);
            //7、遍历转换结果
            byte[] data = new byte[4];
            float[] intArray = new float[numberOfPoints];
            for (int i = 0; i < numberOfPoints; i++)
            {
                Array.Copy(byteData, i * 4, data, 0, 4);
                intArray[i] = BitConverter.ToSingle(FormatOrder(data), 0);
            }
            //8、返回结果
            return intArray;
        }
        catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut)
        {
            throw new TimeoutException($"读取操作超时({ReceiveTimeout}ms)", ex);
        }
    }
    #endregion
    

  • 结语

    • 本文例展示了使用Scoket实现ModbusTCP主站通讯的一些说明及部分代码,实现通讯中常用的两种数据类型int、floa。
    • 完整案例文末连接下载。
    • 如果感兴趣的话可下载案例体验、测试程序bug,性能优化,也可以自行修改封装实现更多功能,案例代码仅供参考...

image.png


  • 最后

    • 【原文】:mp.weixin.qq.com/s/Cq3HTUmK1…
    • 【作者】:编程笔记in
    • 🚀项目地址gitee.com/incodenotes…
    • 如果你觉得这篇文章对你有帮助,不妨点个赞再走呗!
    • 如有其他疑问,欢迎评论区留言讨论!
    • 也可以加入微信公众号 【编程笔记in】 ,一起交流学习!

❖ 感 谢 您 的 关 注 ❖