天天看點

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

Modbus協定時應用于電子控制器上的一種通用語言。通過此協定,控制器互相之間、控制器經由網絡/序列槽和其它裝置之間可以進行通信。它已經成為了一種工業标準。有了這個通信協定,不同的廠商生成的控制裝置就可以連城工業網絡,進行集中監控。

本文實作需要借用一個開源的NModbus庫來完成,通過在菜單欄,工具-----NuGet包管理器-----管了解決方案的NuGet程式包,安裝NModbus的開源庫。

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

本次執行個體的基本架構和實作效果如下所示:

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

可自動識别目前裝置的可用序列槽。

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

 Modbus RTU通信的具體的實作如下:

1using System;
  2using System.Collections;
  3using System.Collections.Generic;
  4using System.ComponentModel;
  5using System.Data;
  6using System.Drawing;
  7using System.Linq;
  8using System.Text;
  9using System.Threading.Tasks;
 10using System.Windows.Forms;
 11using Modbus.Device;
 12using System.Net.Sockets;
 13using System.Threading;
 14using System.IO.Ports;
 15using System.Drawing.Text;
 16using System.Windows.Forms.VisualStyles;
 17using System.Timers;
 18using System.CodeDom.Compiler;
 19 20namespace ModbusRtuMaster
 21{
 22publicpartialclass Form1 : Form
 23    {
 24#region 參數配置
 25privatestatic IModbusMaster master;
 26privatestatic SerialPort port;
 27//寫線圈或寫寄存器數組 28privatebool[] coilsBuffer;
 29privateushort[] registerBuffer;
 30//功能碼 31privatestring functionCode;
 32//功能碼序号 33privateint functionOder;
 34//參數(分别為從站位址,起始位址,長度) 35privatebyte slaveAddress;
 36privateushort startAddress;
 37privateushort numberOfPoints;
 38//序列槽參數 39privatestring portName;
 40privateint baudRate;
 41private Parity parity;
 42privateint dataBits;
 43private StopBits stopBits;
 44//自動測試标志位 45privatebool AutoFlag = false;
 46//擷取目前時間 47private System.DateTime Current_time;
 48 49//定時器初始化 50private System.Timers.Timer t = new System.Timers.Timer(1000);
 51 52privateconstint WM_DEVICE_CHANGE = 0x219;            //裝置改變            53privateconstint DBT_DEVICEARRIVAL = 0x8000;          //裝置插入 54privateconstint DBT_DEVICE_REMOVE_COMPLETE = 0x8004; //裝置移除 55 56#endregion 57 58 59public Form1()
 60        {
 61            InitializeComponent();
 62            GetSerialLstTb1();
 63        }
 64 65privatevoid Form1_Load(object sender, EventArgs e)
 66        {
 67//界面初始化 68             cmb_portname.SelectedIndex = 0;
 69             cmb_baud.SelectedIndex = 5;
 70             cmb_parity.SelectedIndex = 2;
 71             cmb_databBits.SelectedIndex = 1;
 72             cmb_stopBits.SelectedIndex = 0;
 73 74        }
 75 76#region 定時器
 77//定時器初始化,失能狀态 78privatevoid init_Timer()
 79        {
 80             t.Elapsed += new System.Timers.ElapsedEventHandler(Execute);
 81             t.AutoReset = true;//設定false定時器執行一次,設定true定時器一直執行 82             t.Enabled = false;//定時器使能true,失能false
 83//(); 84        }
 85 86privatevoid Execute(object source,System.Timers.ElapsedEventArgs e)
 87        {
 88//停止定時器後再打開定時器,避免重複打開 89            t.Stop();
 90//ExecuteFunction();可添加執行操作 91            ();
 92        }
 93#endregion 94 95#region 序列槽配置
 96///<summary> 97/// 序列槽參數擷取
 98///</summary> 99///<returns></傳回序列槽配置參數>100private SerialPort InitSerialPortParameter()
101        {
102if (cmb_portname.SelectedIndex < 0 || cmb_baud.SelectedIndex < 0 || cmb_parity.SelectedIndex < 0 || cmb_databBits.SelectedIndex < 0 || cmb_stopBits.SelectedIndex < 0)
103            {
104                 ("請選擇序列槽參數");
105returnnull;
106            }
107else108            {
109                 portName = ();
110                 baudRate = int.Parse(());
111112switch (())
113                {
114case"奇":
115                         parity = Parity.Odd;
116break;
117case"偶":
118                         parity = Parity.Even;
119break;
120case"無":
121                         parity = Parity.None;
122break;
123default:
124break;
125                }
126                 dataBits = int.Parse(());
127switch (())
128                {
129case"1":
130                         stopBits = StopBits.One;
131break;
132case"2":
133                         stopBits = StopBits.Two;
134break;
135default:
136break;
137                }
138139                 port = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
140return port;
141142            }
143        }
144#endregion145146#region 序列槽收/發
147privateasyncvoid ExecuteFunction()
148        {
149             Current_time = System.DateTime.Now;
150try151            {
152153if ( == false)
154                {
155                    port.Open();
156                }
157if (functionCode != null)
158                {
159switch (functionCode)
160                    {
161case"01 Read Coils"://讀取單個線圈162                            SetReadParameters();
163try164                            {
165                                 coilsBuffer = (slaveAddress, startAddress, numberOfPoints);
166                            }
167catch(Exception)
168                            {
169                                 ("參數配置錯誤");
170//();171                                 AutoFlag = false;
172break;
173                            }
174                             SetMsg("[" + ("yyyy-MM-dd HH:mm:ss" + "]" + ""));
175for (int i = 0; i < coilsBuffer.Length; i++)
176                            {
177                                 SetMsg(coilsBuffer[i] + "");
178                            }
179                             SetMsg("\r\n");
180break;
181case"02 Read DisCrete Inputs"://讀取輸入線圈/離散量線圈182                            SetReadParameters();
183try184                            {
185                                 coilsBuffer = (slaveAddress, startAddress, numberOfPoints);
186                            }
187catch(Exception)
188                            {
189                                 ("參數配置錯誤");
190                                 AutoFlag = false;
191break;
192                            }
193                             SetMsg("[" + ("yyyy-MM-dd HH:mm:ss" + "]" + ""));
194for (int i = 0; i < coilsBuffer.Length; i++)
195                            {
196                                 SetMsg(coilsBuffer[i] + "");
197                            }
198                             SetMsg("\r\n");
199break;
200case"03 Read Holding Registers"://讀取保持寄存器201                            SetReadParameters();
202try203                            {
204                                 registerBuffer = (slaveAddress, startAddress, numberOfPoints);
205                            }
206catch (Exception)
207                            {
208                                 ("參數配置錯誤");
209                                 AutoFlag = false;
210break;
211                            }
212                             SetMsg("[" + ("yyyy-MM-dd HH:mm:ss" + "]" + ""));
213for (int i = 0; i < registerBuffer.Length; i++)
214                            {
215                                 SetMsg(registerBuffer[i] + "");
216                            }
217                             SetMsg("\r\n");
218break;
219case"04 Read Input Registers"://讀取輸入寄存器220                            SetReadParameters();
221try222                            {
223                                 registerBuffer = (slaveAddress, startAddress, numberOfPoints);
224                            }
225catch (Exception)
226                            {
227                                 ("參數配置錯誤");
228                                 AutoFlag = false;
229break;
230                            }
231                             SetMsg("[" + ("yyyy-MM-dd HH:mm:ss" + "]" + ""));
232for (int i = 0; i < registerBuffer.Length; i++)
233                            {
234                                 SetMsg(registerBuffer[i] + "");
235                            }
236                             SetMsg("\r\n");
237break;
238case"05 Write Single Coil"://寫單個線圈239                            SetWriteParametes();
240await master.WriteSingleCoilAsync(slaveAddress, startAddress, coilsBuffer[0]);
241break;
242case"06 Write Single Registers"://寫單個輸入線圈/離散量線圈243                            SetWriteParametes();
244await master.WriteSingleRegisterAsync(slaveAddress, startAddress, registerBuffer[0]);
245break;
246case"0F Write Multiple Coils"://寫一組線圈247                            SetWriteParametes();
248await master.WriteMultipleCoilsAsync(slaveAddress, startAddress, coilsBuffer);
249break;
250case"10 Write Multiple Registers"://寫一組保持寄存器251                            SetWriteParametes();
252await master.WriteMultipleRegistersAsync(slaveAddress, startAddress, registerBuffer);
253break;
254default:
255break;
256                    }
257258                }
259else260                {
261                     ("請選擇功能碼!");
262                }
263                port.Close();
264            }
265catch (Exception ex)
266            {
267                port.Close();
268                ();
269            }
270        }
271#endregion272273///<summary>274/// 設定讀參數
275///</summary>276privatevoid SetReadParameters()
277        {
278if ( == "" ||  == "" ||  == "")
279            {
280                 ("請填寫讀參數!");
281            }
282else283            {
284                 slaveAddress = byte.Parse();
285                 startAddress = ushort.Parse();
286                 numberOfPoints = ushort.Parse();
287            }
288        }
289290///<summary>291/// 設定寫參數
292///</summary>293privatevoid SetWriteParametes()
294        {
295if ( == "" ||  == "" || txt_data.Text == "")
296            {
297                 ("請填寫寫參數!");
298            }
299else300            {
301                 slaveAddress = byte.Parse();
302                 startAddress = ushort.Parse();
303//判斷是否寫線圈304if (functionOder == 4 || functionOder == 6)
305                {
306string[] strarr = ('');
307                     coilsBuffer = newbool[];
308//轉化為bool數組309for (int i = 0; i < ; i++)
310                    {
311// strarr[i] == "0" ? coilsBuffer[i] = false : coilsBuffer[i] = true;312if (strarr[i] == "0")
313                        {
314                             coilsBuffer[i] = false;
315                        }
316else317                        {
318                             coilsBuffer[i] = true;
319                        }
320                    }
321                }
322else323                {
324//轉化ushort數組325string[] strarr = ('');
326                     registerBuffer = newushort[];
327for (int i = 0; i < ; i++)
328                    {
329                         registerBuffer[i] = ushort.Parse(strarr[i]);
330                    }
331                }
332            }
333        }
334335///<summary>336/// 建立委托,列印日志
337///</summary>338///<param name="msg"></param>339publicvoid SetMsg(string msg)
340        {
341             (new Action(() => { (msg); }));
342        }
343344///<summary>345/// 清空日志
346///</summary>347///<param name="sender"></param>348///<param name="e"></param>349privatevoid button2_Click(object sender, EventArgs e)
350        {
351            richTextBox1.Clear();
352        }
353354///<summary>355/// 單擊button1事件,序列槽完成一次讀/寫操作
356///</summary>357///<param name="sender"></param>358///<param name="e"></param>359privatevoid button1_Click(object sender, EventArgs e)
360        {
361//AutoFlag = false;
362//button_AutomaticTest.Enabled = true;363364try365            {
366//初始化序列槽參數367                InitSerialPortParameter();
368369                 master = (port);
370371372                ExecuteFunction();
373374            }
375catch (Exception)
376            {
377                 ("初始化異常");
378            }
379        }
380381///<summary>382/// 自動測試初始化
383///</summary>384privatevoid AutomaticTest()
385        {
386             AutoFlag = true;
387             button1.Enabled = false;
388389            InitSerialPortParameter();
390             master = (port);
391392             (() =>
393            {
394//初始化序列槽參數395396while (AutoFlag)
397                {
398399try400                    {
401402                        ExecuteFunction();
403404                    }
405catch (Exception)
406                    {
407                         ("初始化異常");
408                    }
409                     (500);
410                }
411            });
412        }
413414///<summary>415/// 讀取資料時,失能寫資料;寫資料時,失能讀資料
416///</summary>417///<param name="sender"></param>418///<param name="e"></param>419privatevoid comboBox1_SelectedIndexChanged(object sender, EventArgs e)
420        {
421if (comboBox1.SelectedIndex >= 4)
422            {
423                 groupBox2.Enabled = true;
424                 groupBox1.Enabled = false;
425            }
426else427            {
428                 groupBox1.Enabled = true;
429                 groupBox2.Enabled = false;
430            }
431//委托事件,在主線程中建立的控件,在子線程中讀取設定控件的屬性會出現異常,使用Invoke方法可以解決432             comboBox1.Invoke(new Action(() => { functionCode = (); functionOder =  }));
433        }
434435///<summary>436/// 将列印日志顯示到最新接收到的符号位置
437///</summary>438///<param name="sender"></param>439///<param name="e"></param>440privatevoid richTextBox1_TextChanged(object sender, EventArgs e)
441        {
442this.richTextBox1.SelectionStart = int.MaxValue;
443this.richTextBox1.ScrollToCaret();
444        }
445446///<summary>447/// 自動化測試
448///</summary>449///<param name="sender"></param>450///<param name="e"></param>451privatevoid button_AutomaticTest_Click(object sender, EventArgs e)
452        {
453             AutoFlag = false;
454             button_AutomaticTest.Enabled = false; //自動收發按鈕失能,避免從複開啟線程455if (AutoFlag == false)
456            {
457                AutomaticTest();
458459            }
460461        }
462463///<summary>464/// 序列槽關閉,停止讀/寫
465///</summary>466///<param name="sender"></param>467///<param name="e"></param>468privatevoid button_ClosePort_Click(object sender, EventArgs e)
469        {
470             AutoFlag = false;
471             button1.Enabled = true;
472             button_AutomaticTest.Enabled = true;
473             t.Enabled = false;//失能定時器474475if ()
476            {
477                port.Close();
478            }
479480        }
481482#region 序列槽下拉清單重新整理
483///<summary>484/// 重新整理下拉清單顯示
485///</summary>486privatevoid GetSerialLstTb1()
487        {
488//清除cmb_portname顯示489             cmb_portname.SelectedIndex = -1;
490            cmb_portname.Items.Clear();
491//擷取序列槽清單492string[] serialLst = ();
493if (serialLst.Length > 0)
494            {
495//取序列槽進行排序496                (serialLst);
497//将序列槽清單輸出到cmb_portname498                cmb_portname.Items.AddRange(serialLst);
499                 cmb_portname.SelectedIndex = 0;
500            }
501        }
502503///<summary>504/// 消息處理
505///</summary>506///<param name="m"></param>507protectedoverridevoid WndProc(ref Message m)
508        {
509switch ()                                  //判斷消息類型510            {
511case WM_DEVICE_CHANGE:                      //裝置改變消息512                    {
513                         GetSerialLstTb1();                  //裝置改變時重新花去序列槽清單514                    }
515break;
516            }
517base.WndProc(ref m);
518        }
519#endregion520521privatevoid label11_Click(object sender, EventArgs e)
522        {
523524        }
525526privatevoid txt_slave1_TextChanged(object sender, EventArgs e)
527        {
528529        }
530531privatevoid label7_Click(object sender, EventArgs e)
532        {
533534        }
535536privatevoid txt_startAddr1_TextChanged(object sender, EventArgs e)
537        {
538539        }
540541privatevoid label8_Click(object sender, EventArgs e)
542        {
543544        }
545546privatevoid txt_length_TextChanged(object sender, EventArgs e)
547        {
548549        }
550551    }
552 }
           

View Code

線上程中對控件的屬性進行操作可能會出現代碼異常,可以使用Invoke委托方法完成相應的操作:

publicvoid SetMsg(string msg)
{
(new Action(() => { (msg); }));
}
           

在進行自動讀/寫操作時,為避免多次點選按鍵控件,多次重複建立新線程;在進入自動讀寫線程中時,将對應的按鍵控件失能,等待停止讀寫操作時再使能:

privatevoid AutomaticTest()
{
    AutoFlag = true;
    button1.Enabled = false;

    InitSerialPortParameter();
    master = (port);

    (() =>
    {
        //初始化序列槽參數while (AutoFlag)
        {
            
            try
            {

                ExecuteFunction();
            
            }
            catch (Exception)
            {
                ("初始化異常");
            }
            (500);
        }
    });
}
           

自動擷取目前裝置的可用序列槽實作如下:

#region 序列槽下拉清單重新整理
///<summary>/// 重新整理下拉清單顯示
        ///</summary>privatevoid GetSerialLstTb1()
{
    //清除cmb_portname顯示
    cmb_portname.SelectedIndex = -1;
    cmb_portname.Items.Clear();
    //擷取序列槽清單string[] serialLst = ();
    if (serialLst.Length > 0)
    {
        //取序列槽進行排序        (serialLst);
        //将序列槽清單輸出到cmb_portname        cmb_portname.Items.AddRange(serialLst);
        cmb_portname.SelectedIndex = 0;
    }
}

///<summary>/// 消息處理
        ///</summary>///<param name="m"></param>protectedoverridevoid WndProc(ref Message m)
{
    switch ()                                  //判斷消息類型    {
        case WM_DEVICE_CHANGE:                      //裝置改變消息            {
                GetSerialLstTb1();                  //裝置改變時重新花去序列槽清單            }
            break;
    }
    base.WndProc(ref m);
}
#endregion
           

對本次執行個體進行測試需要使用到序列槽模拟軟體,序列槽模拟器可以到網上下載下傳,也可以通過以下連結進行下載下傳:

連結:

提取碼:xy4m 

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

 Modbus從站模拟器下載下傳連結:

連結:

提取碼:06i9

Modbus從站需要完成一下兩步操作:

一、菜單欄Connection-----Connect

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

二、菜單欄Setup-----Slave Definition

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

 最後需要運作自己建立的Modbus RTU Master上位機,完成相應的配置:

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

 實作的最終效果:

android 序列槽通信_C# NModbus RTU通信實作 - 熊來闖一闖

完整的工程可通過以下連結下載下傳:

連結:

提取碼:s2m6

本人初次學習Modbus通信,相關方面的解析可能還不夠到位,如存在相關問題,歡迎一塊讨論完成,一起學習一起進步!

繼續閱讀