当前位置: 首页 > news >正文

【C#】Winform调用NModbus实现Modbus TCP 主站通讯

一、前言

Modbus是一种串行通信协议,是工业领域全球最流行的协议之一。

1.1 环境

系统:Win11
工具:Visual Studio 2022
.Net 版本:.Net Framework4.6.0
依赖库:NModbus 3.0.81

1.2 协议类型

Modbus RTU:一种二进制协议,采用紧凑的数据帧格式,通信效率较高。通常用于串行通信链路,如RS - 485或RS - 232 ,在工业自动化领域应用广泛。

Modbus ASCII:采用ASCII码进行数据传输,数据帧可读性强,但传输效率相对较低,同样基于串行通信。

​Modbus TCP/IP:基于以太网和TCP/IP协议栈,将Modbus协议封装在TCP/IP协议中,适用于通过网络进行远程通信的场合,是目前工业以太网中常用的通信协议之一。

1.3 通信模式

​主从模式:在Modbus网络中,有一个主设备(通常是控制器或上位机)和多个从设备(如传感器、执行器等)。主设备发起通信请求,从设备根据请求进行响应,从设备不能主动向主设备发送数据。

1.4 程序功能

1、连接从站服务。
2、写入数值到指定寄存器
3、定时读取寄存器值
4、定时心跳检测通讯状态。

二、运行界面

在这里插入图片描述

三、代码

public partial class ModbusTCP : Form
{

    #region 字段
    // Modbus服务器的IP地址和端口
    private string ipAddress = "127.0.0.1";
    // 端口号
    private int port = 502;
    // 从站地址
    private byte slaveId = 1;
    // 读取保持寄存器的起始地址和数量
    ushort startAddress = 0;
    ushort numRegisters = 10;
    // 写入寄存器的地址和值
    ushort writeAddress = 0;
    ushort writeValue = 0;
    // 连接状态
    private bool isConnected = false;
    // 创建TcpClient
    private TcpClient tcpClient = null;
    // 创建modbus
    private ModbusFactory factory = null;
    // Modbus主站
    private IModbusMaster master = null;
    // 任务定时器
    Timer taskTimer = null;
    // 心跳定时器
    private Timer heartbeatTimer = null;

    #endregion

    #region 初始化加载
    public ModbusTCP()
    {
        InitializeComponent();
        CenterToParent();
        CenterToScreen();
    }
    private void MainForm_Load(object sender, EventArgs e)
    {
        Initialize();
    }
    private void ModbusTCP_FormClosing(object sender, FormClosingEventArgs e)
    {
        isConnected = false;
        taskTimer?.Stop();
        tcpClient?.Close();
        heartbeatTimer?.Stop();
    }
    #endregion

    /// <summary>
    /// 初始化
    /// </summary>
    public void Initialize()
    {
        InitializeControlsState();
        UpdataControlsState();
        dataGridView.Columns[0].Width = 100;
        dataGridView.Columns[1].Width = 100;
        dataGridView.Columns[0].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
        dataGridView.Columns[1].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
        dataGridView.RowHeadersVisible = false;

        //数据表格
        dataGridView.Rows.Add(new object[] { 0, 0 });
        dataGridView.Rows.Add(new object[] { 1, 0 });
        dataGridView.Rows.Add(new object[] { 2, 0 });
        dataGridView.Rows.Add(new object[] { 3, 0 });
        dataGridView.Rows.Add(new object[] { 4, 0 });
        dataGridView.Rows.Add(new object[] { 5, 0 });
        dataGridView.Rows.Add(new object[] { 6, 0 });
        dataGridView.Rows.Add(new object[] { 7, 0 });
        dataGridView.Rows.Add(new object[] { 8, 0 });
        dataGridView.Rows.Add(new object[] { 9, 0 });

        //定时读取值
        taskTimer = new Timer();
        taskTimer.Interval = 100;
        taskTimer.Tick += Timer_Tick;

        // 心跳任务
        heartbeatTimer = new Timer();
        heartbeatTimer.Interval = 1000;
        heartbeatTimer.Tick += HeartbeatTimer_Tick;
    }

    private void HeartbeatTimer_Tick(object sender, EventArgs e)
    {
        try
        {
            // 发送心跳请求(这里假设发送一个简单的读取请求作为心跳)
            ushort[] dummyArray = master.ReadHoldingRegisters(slaveId, 0, 1);
            // 检查心跳响应是否有效(可以根据返回值来判断)
            if (dummyArray == null || dummyArray.Length != 1 || dummyArray[0] != 0)
            {
                UpdataMessage("心跳失败,断开连接...");
                isConnected = false;
                taskTimer.Stop();
                tcpClient.Close();
                UpdataControlsState();
            }
        }
        catch (Exception ex)
        {
            UpdataMessage("心跳失败...");
            isConnected = false;
            taskTimer.Stop();
            tcpClient.Close();
            UpdataControlsState();
        }
    }

    /// <summary>
    /// 初始化控件状态
    /// </summary>
    public void InitializeControlsState()
    {
        tbx_SlaveID.Text = slaveId.ToString();
        tbx_IPAddress.Text = ipAddress;
        tbx_TargetPort.Text = port.ToString();
        tbx_StartAddress.Text = startAddress.ToString();
        tbx_ReadLength.Enabled = false;
        tbx_ReadLength.Text = numRegisters.ToString();
        tbx_WriteAddress.Text = writeAddress.ToString();
        tbx_WriteValue.Text = writeValue.ToString();
    }

    private void UpdataControlsState()
    {
        if (isConnected)
        {
            btn_Connect.Text = "断开";
            btn_WriteData.Enabled = true;
            tbx_IPAddress.Enabled = false;
            tbx_TargetPort.Enabled = false;
            tbx_SlaveID.Enabled = false;
            tbx_ReadLength.Enabled=false;
        }
        else
        {
            btn_Connect.Text = "连接";
            btn_WriteData.Enabled = false;
            tbx_IPAddress.Enabled = true;
            tbx_TargetPort.Enabled = true;
            tbx_SlaveID.Enabled = true;
            tbx_ReadLength.Enabled = false;
        }
    }

    /// <summary>
    /// 定时器方法
    /// </summary>
    private void Timer_Tick(object sender, EventArgs e)
    {
        try
        {
            if (isConnected)
            {
                // 读取保持寄存器
                ushort[] array = master.ReadHoldingRegisters(slaveId, startAddress, numRegisters);
                // 输出读取到的寄存器值
                for (int i = 0; i < array.Length; i++)
                {
                    dataGridView.Rows[i].Cells[0].Value = (startAddress + i);
                    dataGridView.Rows[i].Cells[1].Value = array[i];
                }
            }
        }
        catch (Exception ex)
        {
            UpdataMessage("");
        }
    }
    /// <summary>
    ///  连接
    /// </summary>
    private void btn_Connect_Click(object sender, EventArgs e)
    {
        try
        {
            if (!isConnected)
            {
                tcpClient = new TcpClient(ipAddress, port);
                factory = new ModbusFactory();
                master = factory.CreateMaster(tcpClient);
                taskTimer.Start();
                heartbeatTimer?.Start();
                isConnected = true;
                UpdataControlsState();
                UpdataMessage("连接成功...");
            }
            else
            {
                isConnected = false;
                master = null;
                taskTimer.Stop();
                tcpClient.Close();
                UpdataControlsState();
                UpdataMessage("断开连接...");
                heartbeatTimer?.Stop();
            }
        }
        catch (Exception ex)
        {
            isConnected = false;
            taskTimer?.Stop();
            heartbeatTimer?.Stop();
            tcpClient?.Close();
            UpdataControlsState();
            UpdataMessage("连接失败...");
            UpdataMessage($"{ex.Message}");
        }
    }
    
    /// <summary>
    ///  写入数据
    /// </summary>
    private void btn_WriteData_Click(object sender, EventArgs e)
    {
        master.WriteSingleRegister(slaveId, writeAddress, writeValue);
        UpdataMessage($"从站ID:{slaveId},写入数据:地址:{writeAddress} ,值:{writeValue}");
    }

    /// <summary>
    /// 更新操作消息
    /// </summary>
    private void UpdataMessage(string message)
    {
        tbx_Output.BeginInvoke(new Action(() =>
        {
            tbx_Output.AppendText($"{DateTime.Now.ToString()}{message}\r\n");
        }));
    }

    #region 文本变更
    /// <summary>
    /// 起始地址
    /// </summary>
    private void tbx_StartAddress_TextChanged(object sender, EventArgs e)
    {
        if (ushort.TryParse(tbx_StartAddress.Text, out ushort address))
        {
            startAddress = address;
        }
    }
    /// <summary>
    /// 读取长度
    /// </summary>
    private void tbx_ReadLength_TextChanged(object sender, EventArgs e)
    {
        if (ushort.TryParse(tbx_ReadLength.Text, out ushort length))
        {
            numRegisters = length;
        }
    }
    /// <summary>
    /// 写入地址
    /// </summary>
    private void tbx_WriteAddress_TextChanged(object sender, EventArgs e)
    {
        if (ushort.TryParse(tbx_WriteAddress.Text, out ushort address))
        {
            writeAddress = address;
        }
    }
    /// <summary>
    /// 写入值
    /// </summary>
    private void tbx_WriteValue_TextChanged(object sender, EventArgs e)
    {
        if (ushort.TryParse(tbx_WriteValue.Text, out ushort address))
        {
            writeValue = address;
        }
    }
    /// <summary>
    /// 从站ID
    /// </summary>
    private void tbx_SlaveID_TextChanged(object sender, EventArgs e)
    {
        if (byte.TryParse(tbx_SlaveID.Text, out byte address))
        {
            slaveId = address;
        }
    }
    #endregion
}

相关文章:

  • QT Quick(C++)跨平台应用程序项目实战教程 2 — 环境搭建和项目创建
  • 批量删除 PPT 空白幻灯片页面
  • 【系统架构设计师】操作系统 - 特殊操作系统 ③ ( 微内核操作系统 | 单体内核 操作系统 | 内核态 | 用户态 | 单体内核 与 微内核 对比 )
  • 霍尔传感器与电流互感器的区别
  • 校园论坛系统自动化测试报告
  • 看盘细节系列 篇四:集合竞价低开3%以上
  • Cursor的使用感受,帮你使用好自动化编程工具,整理笔记
  • vscode查看文件历史git commit记录
  • FPGA中级项目5——VGA part1
  • idea集成git
  • C++学习之QT实现取证小软件首页
  • 汇能感知高品质的多光谱相机VSC02UA
  • jmeter将返回的数据写入csv文件
  • CCF-CSP第25次认证第二题——出行计划【NA!重难点在于理解为什么答案是 cnt1−cnt2】
  • 【开源宝藏】30天学会CSS - DAY3 第三课 滑动文本+变色
  • 【简单有效!】Gradio利用html插件实现video视频流循环播放
  • WebSocket:开启实时通信的新篇章
  • 图论——Prim算法
  • Nacos集群部署与高可用架构实战指南
  • 【RHCE】awk文本处理
  • 在循环往复的拍摄中,重新发现世界
  • 荣盛发展去年亏损约84.43亿元,要“过苦日子、紧日子”
  • 央媒谈多地景区试水“免费开放”:盲目跟风会顾此失彼
  • 泽连斯基公布与特朗普会晤细节,强调实现全面、无条件停火
  • 湖南娄底市长曾超群,已任娄底市委书记
  • 对外投资增长、消费市场持续升温,中国经济砥砺前行