C#?NModbus?RTU通信實現(xiàn)方法詳解
Modbus協(xié)議時應(yīng)用于電子控制器上的一種通用語言。通過此協(xié)議,控制器相互之間、控制器經(jīng)由網(wǎng)絡(luò)/串口和其它設(shè)備之間可以進(jìn)行通信。它已經(jīng)成為了一種工業(yè)標(biāo)準(zhǔn)。有了這個通信協(xié)議,不同的廠商生成的控制設(shè)備就可以連城工業(yè)網(wǎng)絡(luò),進(jìn)行集中監(jiān)控。
本文實現(xiàn)需要借用一個開源的NModbus庫來完成,通過在菜單欄,工具-----NuGet包管理器-----管理解決方案的NuGet程序包,安裝NModbus的開源庫。
本次實例的基本框架和實現(xiàn)效果如下所示:
可自動識別當(dāng)前設(shè)備的可用串口。
Modbus RTU通信的具體的實現(xiàn)如下:
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Modbus.Device; using System.Net.Sockets; using System.Threading; using System.IO.Ports; using System.Drawing.Text; using System.Windows.Forms.VisualStyles; using System.Timers; using System.CodeDom.Compiler; namespace ModbusRtuMaster { public partial class Form1 : Form { #region 參數(shù)配置 private static IModbusMaster master; private static SerialPort port; //寫線圈或?qū)懠拇嫫鲾?shù)組 private bool[] coilsBuffer; private ushort[] registerBuffer; //功能碼 private string functionCode; //功能碼序號 private int functionOder; //參數(shù)(分別為從站地址,起始地址,長度) private byte slaveAddress; private ushort startAddress; private ushort numberOfPoints; //串口參數(shù) private string portName; private int baudRate; private Parity parity; private int dataBits; private StopBits stopBits; //自動測試標(biāo)志位 private bool AutoFlag = false; //獲取當(dāng)前時間 private System.DateTime Current_time; //定時器初始化 private System.Timers.Timer t = new System.Timers.Timer(1000); private const int WM_DEVICE_CHANGE = 0x219; //設(shè)備改變 private const int DBT_DEVICEARRIVAL = 0x8000; //設(shè)備插入 private const int DBT_DEVICE_REMOVE_COMPLETE = 0x8004; //設(shè)備移除 #endregion public Form1() { InitializeComponent(); GetSerialLstTb1(); } private void Form1_Load(object sender, EventArgs e) { //界面初始化 cmb_portname.SelectedIndex = 0; cmb_baud.SelectedIndex = 5; cmb_parity.SelectedIndex = 2; cmb_databBits.SelectedIndex = 1; cmb_stopBits.SelectedIndex = 0; } #region 定時器 //定時器初始化,失能狀態(tài) private void init_Timer() { t.Elapsed += new System.Timers.ElapsedEventHandler(Execute); t.AutoReset = true;//設(shè)置false定時器執(zhí)行一次,設(shè)置true定時器一直執(zhí)行 t.Enabled = false;//定時器使能true,失能false //t.Start(); } private void Execute(object source,System.Timers.ElapsedEventArgs e) { //停止定時器后再打開定時器,避免重復(fù)打開 t.Stop(); //ExecuteFunction();可添加執(zhí)行操作 t.Start(); } #endregion #region 串口配置 /// <summary> /// 串口參數(shù)獲取 /// </summary> /// <returns></返回串口配置參數(shù)> private SerialPort InitSerialPortParameter() { if (cmb_portname.SelectedIndex < 0 || cmb_baud.SelectedIndex < 0 || cmb_parity.SelectedIndex < 0 || cmb_databBits.SelectedIndex < 0 || cmb_stopBits.SelectedIndex < 0) { MessageBox.Show("請選擇串口參數(shù)"); return null; } else { portName = cmb_portname.SelectedItem.ToString(); baudRate = int.Parse(cmb_baud.SelectedItem.ToString()); switch (cmb_parity.SelectedItem.ToString()) { case "奇": parity = Parity.Odd; break; case "偶": parity = Parity.Even; break; case "無": parity = Parity.None; break; default: break; } dataBits = int.Parse(cmb_databBits.SelectedItem.ToString()); switch (cmb_stopBits.SelectedItem.ToString()) { case "1": stopBits = StopBits.One; break; case "2": stopBits = StopBits.Two; break; default: break; } port = new SerialPort(portName, baudRate, parity, dataBits, stopBits); return port; } } #endregion #region 串口收/發(fā) private async void ExecuteFunction() { Current_time = System.DateTime.Now; try { if (port.IsOpen == false) { port.Open(); } if (functionCode != null) { switch (functionCode) { case "01 Read Coils"://讀取單個線圈 SetReadParameters(); try { coilsBuffer = master.ReadCoils(slaveAddress, startAddress, numberOfPoints); } catch(Exception) { MessageBox.Show("參數(shù)配置錯誤"); //MessageBox.Show(e.Message); AutoFlag = false; break; } SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " ")); for (int i = 0; i < coilsBuffer.Length; i++) { SetMsg(coilsBuffer[i] + " "); } SetMsg("\r\n"); break; case "02 Read DisCrete Inputs"://讀取輸入線圈/離散量線圈 SetReadParameters(); try { coilsBuffer = master.ReadInputs(slaveAddress, startAddress, numberOfPoints); } catch(Exception) { MessageBox.Show("參數(shù)配置錯誤"); AutoFlag = false; break; } SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " ")); for (int i = 0; i < coilsBuffer.Length; i++) { SetMsg(coilsBuffer[i] + " "); } SetMsg("\r\n"); break; case "03 Read Holding Registers"://讀取保持寄存器 SetReadParameters(); try { registerBuffer = master.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints); } catch (Exception) { MessageBox.Show("參數(shù)配置錯誤"); AutoFlag = false; break; } SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " ")); for (int i = 0; i < registerBuffer.Length; i++) { SetMsg(registerBuffer[i] + " "); } SetMsg("\r\n"); break; case "04 Read Input Registers"://讀取輸入寄存器 SetReadParameters(); try { registerBuffer = master.ReadInputRegisters(slaveAddress, startAddress, numberOfPoints); } catch (Exception) { MessageBox.Show("參數(shù)配置錯誤"); AutoFlag = false; break; } SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " ")); for (int i = 0; i < registerBuffer.Length; i++) { SetMsg(registerBuffer[i] + " "); } SetMsg("\r\n"); break; case "05 Write Single Coil"://寫單個線圈 SetWriteParametes(); await master.WriteSingleCoilAsync(slaveAddress, startAddress, coilsBuffer[0]); break; case "06 Write Single Registers"://寫單個輸入線圈/離散量線圈 SetWriteParametes(); await master.WriteSingleRegisterAsync(slaveAddress, startAddress, registerBuffer[0]); break; case "0F Write Multiple Coils"://寫一組線圈 SetWriteParametes(); await master.WriteMultipleCoilsAsync(slaveAddress, startAddress, coilsBuffer); break; case "10 Write Multiple Registers"://寫一組保持寄存器 SetWriteParametes(); await master.WriteMultipleRegistersAsync(slaveAddress, startAddress, registerBuffer); break; default: break; } } else { MessageBox.Show("請選擇功能碼!"); } port.Close(); } catch (Exception ex) { port.Close(); MessageBox.Show(ex.Message); } } #endregion /// <summary> /// 設(shè)置讀參數(shù) /// </summary> private void SetReadParameters() { if (txt_startAddr1.Text == "" || txt_slave1.Text == "" || txt_length.Text == "") { MessageBox.Show("請?zhí)顚懽x參數(shù)!"); } else { slaveAddress = byte.Parse(txt_slave1.Text); startAddress = ushort.Parse(txt_startAddr1.Text); numberOfPoints = ushort.Parse(txt_length.Text); } } /// <summary> /// 設(shè)置寫參數(shù) /// </summary> private void SetWriteParametes() { if (txt_startAddr2.Text == "" || txt_slave2.Text == "" || txt_data.Text == "") { MessageBox.Show("請?zhí)顚憣憛?shù)!"); } else { slaveAddress = byte.Parse(txt_slave2.Text); startAddress = ushort.Parse(txt_startAddr2.Text); //判斷是否寫線圈 if (functionOder == 4 || functionOder == 6) { string[] strarr = txt_data.Text.Split(' '); coilsBuffer = new bool[strarr.Length]; //轉(zhuǎn)化為bool數(shù)組 for (int i = 0; i < strarr.Length; i++) { // strarr[i] == "0" ? coilsBuffer[i] = false : coilsBuffer[i] = true; if (strarr[i] == "0") { coilsBuffer[i] = false; } else { coilsBuffer[i] = true; } } } else { //轉(zhuǎn)化ushort數(shù)組 string[] strarr = txt_data.Text.Split(' '); registerBuffer = new ushort[strarr.Length]; for (int i = 0; i < strarr.Length; i++) { registerBuffer[i] = ushort.Parse(strarr[i]); } } } } /// <summary> /// 創(chuàng)建委托,打印日志 /// </summary> /// <param name="msg"></param> public void SetMsg(string msg) { richTextBox1.Invoke(new Action(() => { richTextBox1.AppendText(msg); })); } /// <summary> /// 清空日志 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { richTextBox1.Clear(); } /// <summary> /// 單擊button1事件,串口完成一次讀/寫操作 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { //AutoFlag = false; //button_AutomaticTest.Enabled = true; try { //初始化串口參數(shù) InitSerialPortParameter(); master = ModbusSerialMaster.CreateRtu(port); ExecuteFunction(); } catch (Exception) { MessageBox.Show("初始化異常"); } } /// <summary> /// 自動測試初始化 /// </summary> private void AutomaticTest() { AutoFlag = true; button1.Enabled = false; InitSerialPortParameter(); master = ModbusSerialMaster.CreateRtu(port); Task.Factory.StartNew(() => { //初始化串口參數(shù) while (AutoFlag) { try { ExecuteFunction(); } catch (Exception) { MessageBox.Show("初始化異常"); } Thread.Sleep(500); } }); } /// <summary> /// 讀取數(shù)據(jù)時,失能寫數(shù)據(jù);寫數(shù)據(jù)時,失能讀數(shù)據(jù) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { if (comboBox1.SelectedIndex >= 4) { groupBox2.Enabled = true; groupBox1.Enabled = false; } else { groupBox1.Enabled = true; groupBox2.Enabled = false; } //委托事件,在主線程中創(chuàng)建的控件,在子線程中讀取設(shè)置控件的屬性會出現(xiàn)異常,使用Invoke方法可以解決 comboBox1.Invoke(new Action(() => { functionCode = comboBox1.SelectedItem.ToString(); functionOder = comboBox1.SelectedIndex; })); } /// <summary> /// 將打印日志顯示到最新接收到的符號位置 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void richTextBox1_TextChanged(object sender, EventArgs e) { this.richTextBox1.SelectionStart = int.MaxValue; this.richTextBox1.ScrollToCaret(); } /// <summary> /// 自動化測試 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button_AutomaticTest_Click(object sender, EventArgs e) { AutoFlag = false; button_AutomaticTest.Enabled = false; //自動收發(fā)按鈕失能,避免從復(fù)開啟線程 if (AutoFlag == false) { AutomaticTest(); } } /// <summary> /// 串口關(guān)閉,停止讀/寫 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button_ClosePort_Click(object sender, EventArgs e) { AutoFlag = false; button1.Enabled = true; button_AutomaticTest.Enabled = true; t.Enabled = false;//失能定時器 if (port.IsOpen) { port.Close(); } } #region 串口下拉列表刷新 /// <summary> /// 刷新下拉列表顯示 /// </summary> private void GetSerialLstTb1() { //清除cmb_portname顯示 cmb_portname.SelectedIndex = -1; cmb_portname.Items.Clear(); //獲取串口列表 string[] serialLst = SerialPort.GetPortNames(); if (serialLst.Length > 0) { //取串口進(jìn)行排序 Array.Sort(serialLst); //將串口列表輸出到cmb_portname cmb_portname.Items.AddRange(serialLst); cmb_portname.SelectedIndex = 0; } } /// <summary> /// 消息處理 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { switch (m.Msg) //判斷消息類型 { case WM_DEVICE_CHANGE: //設(shè)備改變消息 { GetSerialLstTb1(); //設(shè)備改變時重新花去串口列表 } break; } base.WndProc(ref m); } #endregion private void label11_Click(object sender, EventArgs e) { } private void txt_slave1_TextChanged(object sender, EventArgs e) { } private void label7_Click(object sender, EventArgs e) { } private void txt_startAddr1_TextChanged(object sender, EventArgs e) { } private void label8_Click(object sender, EventArgs e) { } private void txt_length_TextChanged(object sender, EventArgs e) { } } }
在線程中對控件的屬性進(jìn)行操作可能會出現(xiàn)代碼異常,可以使用Invoke委托方法完成相應(yīng)的操作:
public void SetMsg(string msg) { richTextBox1.Invoke(new Action(() => { richTextBox1.AppendText(msg); })); }
在進(jìn)行自動讀/寫操作時,為避免多次點(diǎn)擊按鍵控件,多次重復(fù)建立新線程;在進(jìn)入自動讀寫線程中時,將對應(yīng)的按鍵控件失能,等待停止讀寫操作時再使能:
private void AutomaticTest() { AutoFlag = true; button1.Enabled = false; InitSerialPortParameter(); master = ModbusSerialMaster.CreateRtu(port); Task.Factory.StartNew(() => { //初始化串口參數(shù) while (AutoFlag) { try { ExecuteFunction(); } catch (Exception) { MessageBox.Show("初始化異常"); } Thread.Sleep(500); } }); }
自動獲取當(dāng)前設(shè)備的可用串口實現(xiàn)如下:
#region 串口下拉列表刷新 /// <summary> /// 刷新下拉列表顯示 /// </summary> private void GetSerialLstTb1() { //清除cmb_portname顯示 cmb_portname.SelectedIndex = -1; cmb_portname.Items.Clear(); //獲取串口列表 string[] serialLst = SerialPort.GetPortNames(); if (serialLst.Length > 0) { //取串口進(jìn)行排序 Array.Sort(serialLst); //將串口列表輸出到cmb_portname cmb_portname.Items.AddRange(serialLst); cmb_portname.SelectedIndex = 0; } } /// <summary> /// 消息處理 /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { switch (m.Msg) //判斷消息類型 { case WM_DEVICE_CHANGE: //設(shè)備改變消息 { GetSerialLstTb1(); //設(shè)備改變時重新花去串口列表 } break; } base.WndProc(ref m); } #endregion
對本次實例進(jìn)行測試需要使用到串口模擬軟件,串口模擬器可以到網(wǎng)上下載
Modbus從站需要完成一下兩步操作:
一、菜單欄Connection-----Connect
二、菜單欄Setup-----Slave Definition
最后需要運(yùn)行自己創(chuàng)建的Modbus RTU Master上位機(jī),完成相應(yīng)的配置:
實現(xiàn)的最終效果:
到此這篇關(guān)于C# NModbus RTU通信實現(xiàn)方法詳解的文章就介紹到這了,更多相關(guān)C# NModbus RTU通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#使用IronPython庫調(diào)用Python腳本
這篇文章介紹了C#使用IronPython庫調(diào)用Python腳本的方法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06C#固定大小緩沖區(qū)及使用指針復(fù)制數(shù)據(jù)詳解
這篇文章主要為大家介紹了C#固定大小緩沖區(qū)及使用指針復(fù)制數(shù)據(jù)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12