花了6個小時邊學邊寫的Modbus通訊案例,通訊方式包括RTU,ASCII,TCP,UTP。
案例圖:
using Modbus.Data; using Modbus.Device; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO.Ports; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using static System.Windows.Forms.AxHost; namespace MyModbusPS { public partial class Form1 : Form { private ModbusMaster master;//主站 private ModbusSlave slave;//從站 public delegate void Logdelegate(string log); private Logdelegate Log; public Form1() { InitializeComponent(); InotMasterUI(); InotSlaveUI(); Log = new Logdelegate((s) => { string time = DateTime.Now.ToString("HH:mm:ss"); Slave_RZ.AppendText($"{time}:{s} \r\n"); Slave_RZ.SelectionStart = Slave_RZ.Text.Length; Slave_RZ.ScrollToCaret(); }); } #region 初始化主站UI資料 /// <summary> /// 初始化主站UI資料 /// </summary> private void InotMasterUI() { Master_TXFS.Items.Add("SerialIPort"); Master_TXFS.Items.Add("TCP"); Master_TXFS.Items.Add("UDP"); Master_TXFS.SelectedIndex = 0; Master_IP.Text = "COM1"; Master_DK.Text = ""; Master_RTU.Checked = true; Master_dataGridView.AllowUserToAddRows = false; Master_dataGridView.RowHeadersWidth = 60; Master_dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; Master_dataGridView.AllowUserToOrderColumns = false; //Master_dataGridView.Columns[1].SortMode=DataGridViewColumnSortMode.NotSortable; Master_dataGridView.Columns.Add("Alias", "Alias"); Master_dataGridView.Columns.Add("00000", "00000"); for (int i = 0; i < 10; i++) { Master_dataGridView.Rows.Add(); Master_dataGridView.Rows[i].HeaderCell.Value = i.ToString(); } //從站地址 Master_CZDZ.Text = "1"; } #endregion #region 主站通訊方式選擇事件 /// <summary> /// 主站通訊方式選擇事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_TXFS_SelectedIndexChanged(object sender, EventArgs e) { if (Master_TXFS.SelectedIndex == 0) { Master_IP.Text = "COM1"; Master_DK.Text = ""; Master_RTU.Visible = true; Master_ASCII.Visible = true; Master_RTU.Checked = true; } else { Master_IP.Text = "127.0.0.1"; Master_DK.Text = "8080"; Master_RTU.Visible = false; Master_ASCII.Visible = false; Master_RTU.Checked = false; Master_ASCII.Checked = false; } } #endregion #region 連線 /// <summary> /// 連線 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_LJ_Click(object sender, EventArgs e) { if (Master_LJ.Text == "連線") { StartMaster(); } else { StopMaster(); } } /// <summary> /// 連線 /// </summary> private void StartMaster() { try { if (Master_TXFS.SelectedIndex == 0)//開啟RTU或Ascii { SerialPort port = new SerialPort(); port.PortName = Master_IP.Text;//通訊方式 port.BaudRate = 9600;//波特率 port.DataBits = 8;//資料位 port.Parity = Parity.Even;//奇偶校驗 port.StopBits = StopBits.One;//停止位 port.Open(); if (Master_RTU.Checked) { master = ModbusSerialMaster.CreateRtu(port); } else if (Master_ASCII.Checked) { master = ModbusSerialMaster.CreateAscii(port); } } else if (Master_TXFS.SelectedIndex == 1)//開啟TCP { string ip = Master_IP.Text; ushort port = ushort.Parse(Master_DK.Text); TcpClient tcpClient = new TcpClient(ip, port); master = ModbusIpMaster.CreateIp(tcpClient); } else if (Master_TXFS.SelectedIndex == 2)//開啟UTP { string ip = Master_IP.Text; ushort port = ushort.Parse(Master_DK.Text); UdpClient udpClient = new UdpClient(ip, port); master = ModbusIpMaster.CreateIp(udpClient); } master.Transport.ReadTimeout = 100;//讀取資料超時100ms master.Transport.WriteTimeout = 100;//寫入資料超時100ms master.Transport.Retries = 3;//重試次數 master.Transport.WaitToRetryMilliseconds = 10;//重試間隔 Master_LJ.Text = "停止"; } catch (Exception e) { MessageBox.Show($"連線失敗:{e.Message}"); } } private void StopMaster() { master.Dispose(); master = null; Master_LJ.Text = "連線"; } #endregion #region 03讀一個或多個報持暫存器 /// <summary> /// 03讀一個或多個報持暫存器 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_03_Click(object sender, EventArgs e) { if (master == null) return; try { byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 ushort StartAddress = 0; //從0開始讀 ushort EndAddress = 10; //讀10個 ushort[] data = master.ReadHoldingRegisters(slaveld, StartAddress, EndAddress); for (int i = 0; i < 10; i++) { Master_dataGridView.Rows[i].Cells[1].Value = data[i]; } } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region 06寫單個保持暫存器 /// <summary> /// 06寫單個保持暫存器 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_06_Click(object sender, EventArgs e) { if (master == null) return; try { byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 ushort Address = 9;//寫入值的地址 ushort value = 99;//寫入的值(寫死了) master.WriteSingleRegister(slaveld, Address, value);//寫單個保持暫存器 } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region 16寫多個保持暫存器 /// <summary> /// 16寫多個保持暫存器 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_16_Click(object sender, EventArgs e) { if (master == null) return; try { byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 ushort Address = 1;//起始位置 ushort[] value = { 11, 22, 33 };//寫入的值 master.WriteMultipleRegisters(slaveld, Address, value);//寫多個保持暫存器 } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region 01讀多個線圈狀態 /// <summary> /// 01讀多個線圈狀態 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_01_Click(object sender, EventArgs e) { if (master == null) return; try { byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 ushort Address = 0;//起始位置 ushort value = 10;//讀取的值 bool[] data = master.ReadCoils(slaveld, Address, value);//讀多個線圈狀態 for (int i = 0; i < data.Length; i++) { Master_dataGridView.Rows[i].Cells[1].Value = data[i] == true ? 1 : 0; } } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region 05寫單個線圈狀態 /// <summary> /// 05寫單個線圈狀態 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { if (master == null) return; try { byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 ushort Address = 1;//寫入值的地址 bool value = true;//寫入的值 master.WriteSingleCoil(slaveld, Address, value);//寫單個線圈狀態 } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region 15寫多個線圈狀態 /// <summary> /// 15寫多個線圈狀態 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Master_15_Click(object sender, EventArgs e) { if (master == null) return; try { byte slaveld = byte.Parse(Master_CZDZ.Text);//從站位置 ushort Address = 1;//起始位置 bool[] value = { true, true, true };//寫入的值 master.WriteMultipleCoils(slaveld, Address, value);//寫多個線圈狀態 } catch (Exception ex) { MessageBox.Show(ex.Message); } } #endregion #region 初始化從站UI資料 /// <summary> /// 初始化主站UI資料 /// </summary> private void InotSlaveUI() { Slave_TXFS.Items.Add("SerialIPort"); Slave_TXFS.Items.Add("TCP"); Slave_TXFS.Items.Add("UDP"); Slave_TXFS.SelectedIndex = 0; Slave_IP.Text = "COM2"; Slave_DK.Text = ""; Slave_dataGridView.AllowUserToAddRows = false; Slave_dataGridView.RowHeadersWidth = 60; Slave_dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; Slave_dataGridView.AllowUserToOrderColumns = false; //Master_dataGridView.Columns[1].SortMode=DataGridViewColumnSortMode.NotSortable; Slave_dataGridView.Columns.Add("Alias", "Alias"); Slave_dataGridView.Columns.Add("00000", "00000"); for (int i = 0; i < 10; i++) { Slave_dataGridView.Rows.Add(); Slave_dataGridView.Rows[i].HeaderCell.Value = i.ToString(); } //從站地址 Slave_CZDZ.Text = "1"; //表型別 Slave_BLX.Items.Add(ModbusDataType.HoldingRegister);//保持暫存器,讀寫 Slave_BLX.Items.Add(ModbusDataType.InputRegister);//輸入暫存器,只讀 Slave_BLX.Items.Add(ModbusDataType.Coil);//線圈,讀寫 Slave_BLX.Items.Add(ModbusDataType.Input);//離撒輸入,只讀 Slave_BLX.SelectedIndex = 0; } #endregion #region 從站通訊方式選擇事件 /// <summary> /// 從站通訊方式選擇事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Slave_TXFS_SelectedIndexChanged(object sender, EventArgs e) { if (Slave_TXFS.SelectedIndex == 0) { Slave_IP.Text = "COM2"; Slave_DK.Text = ""; Slave_RTU.Visible = true; Slave_ASCII.Visible = true; Slave_RTU.Checked = true; } else { Slave_IP.Text = "127.0.0.1"; Slave_DK.Text = "8080"; Slave_RTU.Visible = false; Slave_ASCII.Visible = false; Slave_RTU.Checked = false; Slave_ASCII.Checked = false; } } #endregion #region 開啟 /// <summary> /// 開啟 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Slave_KQ_Click(object sender, EventArgs e) { if (Slave_KQ.Text == "開啟") { StartSlave(); } else { StopSlave(); } } private void StartSlave() { try { byte slaveld = byte.Parse(Slave_CZDZ.Text);//從站位置 if (Slave_TXFS.SelectedIndex == 0)//開啟RTU或Ascii { SerialPort port = new SerialPort(); port.PortName = Slave_IP.Text;//通訊方式 port.BaudRate = 9600;//波特率 port.DataBits = 8;//資料位 port.Parity = Parity.Even;//奇偶校驗 port.StopBits = StopBits.One;//停止位 port.Open(); if (Slave_RTU.Checked) { slave = ModbusSerialSlave.CreateRtu(slaveld, port); } else if (Slave_ASCII.Checked) { slave = ModbusSerialSlave.CreateAscii(slaveld, port); } } else if (Slave_TXFS.SelectedIndex == 1)//開啟TCP { string ip = Slave_IP.Text; ushort port = ushort.Parse(Slave_DK.Text); TcpListener tcpListener = new TcpListener(IPAddress.Parse(ip),port); //tcpListener.Start(); slave = ModbusTcpSlave.CreateTcp(slaveld, tcpListener); } else if (Slave_TXFS.SelectedIndex == 2)//開啟UTP { string ip = Slave_IP.Text; ushort port = ushort.Parse(Slave_DK.Text); IPEndPoint iPEnd=new IPEndPoint(IPAddress.Parse(ip), port); UdpClient udpClient = new UdpClient(iPEnd); slave = ModbusUdpSlave.CreateUdp(slaveld,udpClient); } // slave.Transport.ReadTimeout = 100;//讀取資料超時100ms //slave.Transport.WriteTimeout = 100;//寫入資料超時100ms slave.Transport.Retries = 3;//重試次數 slave.Transport.WaitToRetryMilliseconds = 10;//重試間隔 Task.Factory.StartNew(() => { slave.Listen();//開啟監聽 }); Slave_KQ.Text = "停止"; //訂閱一些事件 //從站收到主站請求時 slave.ModbusSlaveRequestReceived += new EventHandler<ModbusSlaveRequestEventArgs>((s, e) => { this.BeginInvoke(Log, "ModbusSlaveRequestReceived"); }); //從站寫完資料之後的事件 slave.WriteComplete += new EventHandler<ModbusSlaveRequestEventArgs>((s, e) => { this.BeginInvoke(Log, "WriteComplete"); }); //資料寫入事件 slave.DataStore.DataStoreWrittenTo += new EventHandler<DataStoreEventArgs>((s, e) => { this.BeginInvoke(Log, "DataStoreWrittenTo"); }); //資料讀取事件 slave.DataStore.DataStoreReadFrom += new EventHandler<DataStoreEventArgs>((s, e) => { this.BeginInvoke(Log, "DataStoreReadFrom"); }); } catch (Exception e) { MessageBox.Show($"開啟失敗:{e.Message}"); } } private void StopSlave() { slave.Dispose(); slave = null; Slave_KQ.Text = "開啟"; } #endregion #region 寫入暫存器或線圈 /// <summary> /// 寫入暫存器或線圈 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Slave_dataGridView_CellEndEdit(object sender, DataGridViewCellEventArgs e) { string table = Slave_BLX.SelectedItem.ToString();//獲取從站儲存表 object value = Slave_dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value;//獲取當前dataGrid單後設資料 if (value != null) { ushort v = ushort.Parse(value.ToString()); ModbusDataType dataType = (ModbusDataType)Enum.Parse(typeof(ModbusDataType), table); switch (dataType) { case ModbusDataType.HoldingRegister: slave.DataStore.HoldingRegisters[e.RowIndex + 1] = v; break; case ModbusDataType.InputRegister: slave.DataStore.InputRegisters[e.RowIndex + 1] = v; break; case ModbusDataType.Coil: slave.DataStore.CoilDiscretes[e.RowIndex + 1] = v != 0; break; case ModbusDataType.Input: slave.DataStore.InputDiscretes[e.RowIndex + 1] = v != 0; break; default: break; } } } #endregion #region 表型別選擇 /// <summary> /// 表型別選擇 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Slave_BLX_SelectedIndexChanged(object sender, EventArgs e) { if (slave == null) return; string table = Slave_BLX.SelectedItem.ToString(); ModbusDataType t = (ModbusDataType)Enum.Parse(typeof(ModbusDataType), table); switch (t) { case ModbusDataType.HoldingRegister: for (int i = 0; i < 10; i++) { Slave_dataGridView.Rows[i].Cells[1].Value = slave.DataStore.HoldingRegisters[i + 1]; } break; case ModbusDataType.InputRegister: for (int i = 0; i < 10; i++) { Slave_dataGridView.Rows[i].Cells[1].Value = slave.DataStore.InputRegisters[i + 1]; } break; case ModbusDataType.Coil: for (int i = 0; i < 10; i++) { int v = slave.DataStore.CoilDiscretes[i + 1] == true ? 1 : 0; Slave_dataGridView.Rows[i].Cells[1].Value = v; } break; case ModbusDataType.Input: for (int i = 0; i < 10; i++) { int v= slave.DataStore.InputDiscretes[i + 1]==true ? 1 : 0; Slave_dataGridView.Rows[i].Cells[1].Value = v; } break; } } #endregion #region 重新整理 /// <summary> /// 重新整理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Slave_SX_Click(object sender, EventArgs e) { Slave_BLX_SelectedIndexChanged(null, null); } #endregion #region 窗體關閉 /// <summary> /// 窗體關閉 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if(master!=null) { master.Dispose(); } if(slave!=null) { slave.Dispose(); } } #endregion } }