【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
}