一、前言
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;// 創建TcpClientprivate TcpClient tcpClient = null;// 創建modbusprivate 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
}