Modbus协议时应用于电子控制器上的一种通用语言。通过此协议,控制器相互之间、控制器经由网络/串口和其它设备之间可以进行通信。它已经成为了一种工业标准。有了这个通信协议,不同的厂商生成的控制设备就可以连城工业网络,进行集中监控。

本文实现需要借用一个开源的NModbus库来完成,通过在菜单栏,工具-----NuGet包管理器-----管理解决方案的NuGet程序包,安装NModbus的开源库。

本次实例的基本框架和实现效果如下所示:

可自动识别当前设备的可用串口。

Modbus RTU通信的具体的实现如下:

  1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.ComponentModel;
5 using System.Data;
6 using System.Drawing;
7 using System.Linq;
8 using System.Text;
9 using System.Threading.Tasks;
10 using System.Windows.Forms;
11 using Modbus.Device;
12 using System.Net.Sockets;
13 using System.Threading;
14 using System.IO.Ports;
15 using System.Drawing.Text;
16 using System.Windows.Forms.VisualStyles;
17 using System.Timers;
18 using System.CodeDom.Compiler;
19
20 namespace ModbusRtuMaster
21 {
22 public partial class Form1 : Form
23 {
24 #region 参数配置
25 private static IModbusMaster master;
26 private static SerialPort port;
27 //写线圈或写寄存器数组
28 private bool[] coilsBuffer;
29 private ushort[] registerBuffer;
30 //功能码
31 private string functionCode;
32 //功能码序号
33 private int functionOder;
34 //参数(分别为从站地址,起始地址,长度)
35 private byte slaveAddress;
36 private ushort startAddress;
37 private ushort numberOfPoints;
38 //串口参数
39 private string portName;
40 private int baudRate;
41 private Parity parity;
42 private int dataBits;
43 private StopBits stopBits;
44 //自动测试标志位
45 private bool AutoFlag = false;
46 //获取当前时间
47 private System.DateTime Current_time;
48
49 //定时器初始化
50 private System.Timers.Timer t = new System.Timers.Timer(1000);
51
52 private const int WM_DEVICE_CHANGE = 0x219; //设备改变
53 private const int DBT_DEVICEARRIVAL = 0x8000; //设备插入
54 private const int DBT_DEVICE_REMOVE_COMPLETE = 0x8004; //设备移除
55
56 #endregion
57
58
59 public Form1()
60 {
61 InitializeComponent();
62 GetSerialLstTb1();
63 }
64
65 private void 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 //定时器初始化,失能状态
78 private void init_Timer()
79 {
80 t.Elapsed += new System.Timers.ElapsedEventHandler(Execute);
81 t.AutoReset = true;//设置false定时器执行一次,设置true定时器一直执行
82 t.Enabled = false;//定时器使能true,失能false
83 //t.Start();
84 }
85
86 private void Execute(object source,System.Timers.ElapsedEventArgs e)
87 {
88 //停止定时器后再打开定时器,避免重复打开
89 t.Stop();
90 //ExecuteFunction();可添加执行操作
91 t.Start();
92 }
93 #endregion
94
95 #region 串口配置
96 /// <summary>
97 /// 串口参数获取
98 /// </summary>
99 /// <returns></返回串口配置参数>
100 private SerialPort InitSerialPortParameter()
101 {
102 if (cmb_portname.SelectedIndex < 0 || cmb_baud.SelectedIndex < 0 || cmb_parity.SelectedIndex < 0 || cmb_databBits.SelectedIndex < 0 || cmb_stopBits.SelectedIndex < 0)
103 {
104 MessageBox.Show("请选择串口参数");
105 return null;
106 }
107 else
108 {
109 portName = cmb_portname.SelectedItem.ToString();
110 baudRate = int.Parse(cmb_baud.SelectedItem.ToString());
111
112 switch (cmb_parity.SelectedItem.ToString())
113 {
114 case "奇":
115 parity = Parity.Odd;
116 break;
117 case "偶":
118 parity = Parity.Even;
119 break;
120 case "无":
121 parity = Parity.None;
122 break;
123 default:
124 break;
125 }
126 dataBits = int.Parse(cmb_databBits.SelectedItem.ToString());
127 switch (cmb_stopBits.SelectedItem.ToString())
128 {
129 case "1":
130 stopBits = StopBits.One;
131 break;
132 case "2":
133 stopBits = StopBits.Two;
134 break;
135 default:
136 break;
137 }
138
139 port = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
140 return port;
141
142 }
143 }
144 #endregion
145
146 #region 串口收/发
147 private async void ExecuteFunction()
148 {
149 Current_time = System.DateTime.Now;
150 try
151 {
152
153 if (port.IsOpen == false)
154 {
155 port.Open();
156 }
157 if (functionCode != null)
158 {
159 switch (functionCode)
160 {
161 case "01 Read Coils"://读取单个线圈
162 SetReadParameters();
163 try
164 {
165 coilsBuffer = master.ReadCoils(slaveAddress, startAddress, numberOfPoints);
166 }
167 catch(Exception)
168 {
169 MessageBox.Show("参数配置错误");
170 //MessageBox.Show(e.Message);
171 AutoFlag = false;
172 break;
173 }
174 SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " "));
175 for (int i = 0; i < coilsBuffer.Length; i++)
176 {
177 SetMsg(coilsBuffer[i] + " ");
178 }
179 SetMsg("\r\n");
180 break;
181 case "02 Read DisCrete Inputs"://读取输入线圈/离散量线圈
182 SetReadParameters();
183 try
184 {
185 coilsBuffer = master.ReadInputs(slaveAddress, startAddress, numberOfPoints);
186 }
187 catch(Exception)
188 {
189 MessageBox.Show("参数配置错误");
190 AutoFlag = false;
191 break;
192 }
193 SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " "));
194 for (int i = 0; i < coilsBuffer.Length; i++)
195 {
196 SetMsg(coilsBuffer[i] + " ");
197 }
198 SetMsg("\r\n");
199 break;
200 case "03 Read Holding Registers"://读取保持寄存器
201 SetReadParameters();
202 try
203 {
204 registerBuffer = master.ReadHoldingRegisters(slaveAddress, startAddress, numberOfPoints);
205 }
206 catch (Exception)
207 {
208 MessageBox.Show("参数配置错误");
209 AutoFlag = false;
210 break;
211 }
212 SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " "));
213 for (int i = 0; i < registerBuffer.Length; i++)
214 {
215 SetMsg(registerBuffer[i] + " ");
216 }
217 SetMsg("\r\n");
218 break;
219 case "04 Read Input Registers"://读取输入寄存器
220 SetReadParameters();
221 try
222 {
223 registerBuffer = master.ReadInputRegisters(slaveAddress, startAddress, numberOfPoints);
224 }
225 catch (Exception)
226 {
227 MessageBox.Show("参数配置错误");
228 AutoFlag = false;
229 break;
230 }
231 SetMsg("[" + Current_time.ToString("yyyy-MM-dd HH:mm:ss" + "]" + " "));
232 for (int i = 0; i < registerBuffer.Length; i++)
233 {
234 SetMsg(registerBuffer[i] + " ");
235 }
236 SetMsg("\r\n");
237 break;
238 case "05 Write Single Coil"://写单个线圈
239 SetWriteParametes();
240 await master.WriteSingleCoilAsync(slaveAddress, startAddress, coilsBuffer[0]);
241 break;
242 case "06 Write Single Registers"://写单个输入线圈/离散量线圈
243 SetWriteParametes();
244 await master.WriteSingleRegisterAsync(slaveAddress, startAddress, registerBuffer[0]);
245 break;
246 case "0F Write Multiple Coils"://写一组线圈
247 SetWriteParametes();
248 await master.WriteMultipleCoilsAsync(slaveAddress, startAddress, coilsBuffer);
249 break;
250 case "10 Write Multiple Registers"://写一组保持寄存器
251 SetWriteParametes();
252 await master.WriteMultipleRegistersAsync(slaveAddress, startAddress, registerBuffer);
253 break;
254 default:
255 break;
256 }
257
258 }
259 else
260 {
261 MessageBox.Show("请选择功能码!");
262 }
263 port.Close();
264 }
265 catch (Exception ex)
266 {
267 port.Close();
268 MessageBox.Show(ex.Message);
269 }
270 }
271 #endregion
272
273 /// <summary>
274 /// 设置读参数
275 /// </summary>
276 private void SetReadParameters()
277 {
278 if (txt_startAddr1.Text == "" || txt_slave1.Text == "" || txt_length.Text == "")
279 {
280 MessageBox.Show("请填写读参数!");
281 }
282 else
283 {
284 slaveAddress = byte.Parse(txt_slave1.Text);
285 startAddress = ushort.Parse(txt_startAddr1.Text);
286 numberOfPoints = ushort.Parse(txt_length.Text);
287 }
288 }
289
290 /// <summary>
291 /// 设置写参数
292 /// </summary>
293 private void SetWriteParametes()
294 {
295 if (txt_startAddr2.Text == "" || txt_slave2.Text == "" || txt_data.Text == "")
296 {
297 MessageBox.Show("请填写写参数!");
298 }
299 else
300 {
301 slaveAddress = byte.Parse(txt_slave2.Text);
302 startAddress = ushort.Parse(txt_startAddr2.Text);
303 //判断是否写线圈
304 if (functionOder == 4 || functionOder == 6)
305 {
306 string[] strarr = txt_data.Text.Split(' ');
307 coilsBuffer = new bool[strarr.Length];
308 //转化为bool数组
309 for (int i = 0; i < strarr.Length; i++)
310 {
311 // strarr[i] == "0" ? coilsBuffer[i] = false : coilsBuffer[i] = true;
312 if (strarr[i] == "0")
313 {
314 coilsBuffer[i] = false;
315 }
316 else
317 {
318 coilsBuffer[i] = true;
319 }
320 }
321 }
322 else
323 {
324 //转化ushort数组
325 string[] strarr = txt_data.Text.Split(' ');
326 registerBuffer = new ushort[strarr.Length];
327 for (int i = 0; i < strarr.Length; i++)
328 {
329 registerBuffer[i] = ushort.Parse(strarr[i]);
330 }
331 }
332 }
333 }
334
335 /// <summary>
336 /// 创建委托,打印日志
337 /// </summary>
338 /// <param name="msg"></param>
339 public void SetMsg(string msg)
340 {
341 richTextBox1.Invoke(new Action(() => { richTextBox1.AppendText(msg); }));
342 }
343
344 /// <summary>
345 /// 清空日志
346 /// </summary>
347 /// <param name="sender"></param>
348 /// <param name="e"></param>
349 private void button2_Click(object sender, EventArgs e)
350 {
351 richTextBox1.Clear();
352 }
353
354 /// <summary>
355 /// 单击button1事件,串口完成一次读/写操作
356 /// </summary>
357 /// <param name="sender"></param>
358 /// <param name="e"></param>
359 private void button1_Click(object sender, EventArgs e)
360 {
361 //AutoFlag = false;
362 //button_AutomaticTest.Enabled = true;
363
364 try
365 {
366 //初始化串口参数
367 InitSerialPortParameter();
368
369 master = ModbusSerialMaster.CreateRtu(port);
370
371
372 ExecuteFunction();
373
374 }
375 catch (Exception)
376 {
377 MessageBox.Show("初始化异常");
378 }
379 }
380
381 /// <summary>
382 /// 自动测试初始化
383 /// </summary>
384 private void AutomaticTest()
385 {
386 AutoFlag = true;
387 button1.Enabled = false;
388
389 InitSerialPortParameter();
390 master = ModbusSerialMaster.CreateRtu(port);
391
392 Task.Factory.StartNew(() =>
393 {
394 //初始化串口参数
395
396 while (AutoFlag)
397 {
398
399 try
400 {
401
402 ExecuteFunction();
403
404 }
405 catch (Exception)
406 {
407 MessageBox.Show("初始化异常");
408 }
409 Thread.Sleep(500);
410 }
411 });
412 }
413
414 /// <summary>
415 /// 读取数据时,失能写数据;写数据时,失能读数据
416 /// </summary>
417 /// <param name="sender"></param>
418 /// <param name="e"></param>
419 private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
420 {
421 if (comboBox1.SelectedIndex >= 4)
422 {
423 groupBox2.Enabled = true;
424 groupBox1.Enabled = false;
425 }
426 else
427 {
428 groupBox1.Enabled = true;
429 groupBox2.Enabled = false;
430 }
431 //委托事件,在主线程中创建的控件,在子线程中读取设置控件的属性会出现异常,使用Invoke方法可以解决
432 comboBox1.Invoke(new Action(() => { functionCode = comboBox1.SelectedItem.ToString(); functionOder = comboBox1.SelectedIndex; }));
433 }
434
435 /// <summary>
436 /// 将打印日志显示到最新接收到的符号位置
437 /// </summary>
438 /// <param name="sender"></param>
439 /// <param name="e"></param>
440 private void richTextBox1_TextChanged(object sender, EventArgs e)
441 {
442 this.richTextBox1.SelectionStart = int.MaxValue;
443 this.richTextBox1.ScrollToCaret();
444 }
445
446 /// <summary>
447 /// 自动化测试
448 /// </summary>
449 /// <param name="sender"></param>
450 /// <param name="e"></param>
451 private void button_AutomaticTest_Click(object sender, EventArgs e)
452 {
453 AutoFlag = false;
454 button_AutomaticTest.Enabled = false; //自动收发按钮失能,避免从复开启线程
455 if (AutoFlag == false)
456 {
457 AutomaticTest();
458
459 }
460
461 }
462
463 /// <summary>
464 /// 串口关闭,停止读/写
465 /// </summary>
466 /// <param name="sender"></param>
467 /// <param name="e"></param>
468 private void button_ClosePort_Click(object sender, EventArgs e)
469 {
470 AutoFlag = false;
471 button1.Enabled = true;
472 button_AutomaticTest.Enabled = true;
473 t.Enabled = false;//失能定时器
474
475 if (port.IsOpen)
476 {
477 port.Close();
478 }
479
480 }
481
482 #region 串口下拉列表刷新
483 /// <summary>
484 /// 刷新下拉列表显示
485 /// </summary>
486 private void GetSerialLstTb1()
487 {
488 //清除cmb_portname显示
489 cmb_portname.SelectedIndex = -1;
490 cmb_portname.Items.Clear();
491 //获取串口列表
492 string[] serialLst = SerialPort.GetPortNames();
493 if (serialLst.Length > 0)
494 {
495 //取串口进行排序
496 Array.Sort(serialLst);
497 //将串口列表输出到cmb_portname
498 cmb_portname.Items.AddRange(serialLst);
499 cmb_portname.SelectedIndex = 0;
500 }
501 }
502
503 /// <summary>
504 /// 消息处理
505 /// </summary>
506 /// <param name="m"></param>
507 protected override void WndProc(ref Message m)
508 {
509 switch (m.Msg) //判断消息类型
510 {
511 case WM_DEVICE_CHANGE: //设备改变消息
512 {
513 GetSerialLstTb1(); //设备改变时重新花去串口列表
514 }
515 break;
516 }
517 base.WndProc(ref m);
518 }
519 #endregion
520
521 private void label11_Click(object sender, EventArgs e)
522 {
523
524 }
525
526 private void txt_slave1_TextChanged(object sender, EventArgs e)
527 {
528
529 }
530
531 private void label7_Click(object sender, EventArgs e)
532 {
533
534 }
535
536 private void txt_startAddr1_TextChanged(object sender, EventArgs e)
537 {
538
539 }
540
541 private void label8_Click(object sender, EventArgs e)
542 {
543
544 }
545
546 private void txt_length_TextChanged(object sender, EventArgs e)
547 {
548
549 }
550
551 }
552 }

在线程中对控件的属性进行操作可能会出现代码异常,可以使用Invoke委托方法完成相应的操作:

 public void SetMsg(string msg)
{
richTextBox1.Invoke(new Action(() => { richTextBox1.AppendText(msg); }));
}

在进行自动读/写操作时,为避免多次点击按键控件,多次重复建立新线程;在进入自动读写线程中时,将对应的按键控件失能,等待停止读写操作时再使能:

private void AutomaticTest()
{
AutoFlag = true;
button1.Enabled = false; InitSerialPortParameter();
master = ModbusSerialMaster.CreateRtu(port); Task.Factory.StartNew(() =>
{
//初始化串口参数 while (AutoFlag)
{ try
{ ExecuteFunction(); }
catch (Exception)
{
MessageBox.Show("初始化异常");
}
Thread.Sleep(500);
}
});
}

自动获取当前设备的可用串口实现如下:

#region 串口下拉列表刷新
/// <summary>
/// 刷新下拉列表显示
/// </summary>
private void GetSerialLstTb1()
{
//清除cmb_portname显示
cmb_portname.SelectedIndex = -1;
cmb_portname.Items.Clear();
//获取串口列表
string[] serialLst = SerialPort.GetPortNames();
if (serialLst.Length > 0)
{
//取串口进行排序
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: //设备改变消息
{
GetSerialLstTb1(); //设备改变时重新花去串口列表
}
break;
}
base.WndProc(ref m);
}
#endregion

对本次实例进行测试需要使用到串口模拟软件,串口模拟器可以到网上下载,也可以通过以下链接进行下载:

链接:https://pan.baidu.com/s/1XRUIqTqZ9rwnYowyVyn4cQ
提取码:xy4m

Modbus从站模拟器下载链接:

链接:https://pan.baidu.com/s/1Bf0Qg50_d-XYlwQfzEY8ag
提取码:06i9

Modbus从站需要完成一下两步操作:

一、菜单栏Connection-----Connect

二、菜单栏Setup-----Slave Definition

最后需要运行自己创建的Modbus RTU Master上位机,完成相应的配置:

实现的最终效果:

完整的工程可通过以下链接下载:

链接:https://pan.baidu.com/s/1XkRAF6yxs19tu-LYLraCgA
提取码:s2m6

本人初次学习Modbus通信,相关方面的解析可能还不够到位,如存在相关问题,欢迎一块讨论完成,一起学习一起进步!

C# NModbus RTU通信实现的更多相关文章

  1. Modbus RTU 通信工具设计(转)

    Modbus RTU 通信工具设计 Modbus 是一个工业上常用的通讯协议.一种通讯约定. ModBus 协议是应用层报文传输协议(OSI 模型第7层),它定义了一个与通信层无关的协议数据单元(PD ...

  2. 单片机modebus RTU通信实现,采用C语言,可适用于单片机,VC,安卓等(转)

    源:单片机modebus RTU通信实现,采用C语言,可适用于单片机,VC,安卓等 //modebus_rtu.c /***************************************** ...

  3. Modbus RTU 通信应用案例

    如何打开项目归档文件 例程中的TIA博途项目文件与STEP 7项目文件均为归档文件,需要按如下方式打开: TIA博途项目文件 1. 打开TIA博途软件,通过软件左下方“项目视图”按钮切换至项目视图: ...

  4. 单片机modebus RTU通信实现,採用C语言,可适用于单片机,VC,安卓等

    当前使用的是STM32+ucos_ii编写的,能够移植到安卓以及VC .NET等方便移植使用,採用modebus poll測试过. 仅仅须要改动响应的通信接口就可以,方便多串口使用 //modebus ...

  5. FreeModbus 移植于STM32 实现Modbus RTU通信

    http://ntn314.blog.163.com/blog/static/161743584201233084434579/ 毕业设计自己要做个基于STM32的PLC能直接跑语句表的,现在看来好像 ...

  6. PC和单片机通过MODBUS RTU通信

    最近研究了一下MODBUS通信,在STC12C5A60S2单片机上实现了MODBUS协议的部分功能,方便上位机从单片机系统上获取数据,比如由单片机获取的温度.湿度.或者控制信号的状态等.有了MODBU ...

  7. S7-1200与S7-200 通信西门子链接

    只要这两从站的通讯格式时一样的,而且都为modbus rtu格式的话,是可以走modbus通讯.你在用主站在编程时直接调用modbus rtu通讯库.同时200做为从站,在程序里面将从站的程序写好. ...

  8. Modbus RTU新版本指令介绍

    Modbus RTU新版本指令介绍 TIA V13 SP1版本软件中提供了2个版本的Modbus RTU指令: 图1. 两个版本Modbus RTU指令 早期版本的Modbus RTU指令(图1. 中 ...

  9. Modbus RTU 介绍

    S7-1200 Modbus RTU 通信概述 Modbus具有两种串行传输模式:分别为ASCII和RTU.Modbus是一种单主站的主从通信模式,Modbus网络上只能有一个主站存在,主站在Modb ...

随机推荐

  1. is_mobile()判断手机移动设备

    is_mobile()判断手机移动设备.md is_mobile()判断手机移动设备 制作响应式主题时会根据不同的设备推送不同的内容,是基于移动设备网络带宽压力,避免全局接收pc端内容. functi ...

  2. 软件定义网络实验记录①--Mininet 源码安装和可视化拓扑工具

    第一步: 在 Ubuntu 系统的 home 目录下创建一个目录,目录名为自己的标识,包括但 不限于学号.姓名拼音等,目录不要包含中文. 第二步: 在创建的目录下,完成 Mininet 的源码安装. ...

  3. 048 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 10 案例——阶乘的累加和

    048 01 Android 零基础入门 01 Java基础语法 05 Java流程控制之循环结构 10 案例--阶乘的累加和 本文知识点:通过案例练习嵌套循环应用 案例练习--阶乘的累加和 案例题目 ...

  4. 003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程

    003 01 Android 零基础入门 01 Java基础语法 01 Java初识 03 Java程序的执行流程 Java程序长啥样? 首先编写一个Java程序 记事本编写程序 打开记事本 1.wi ...

  5. centos7卸载mariadb安装mysql

    卸载mariadb 1. 当前安装列表  rpm -qa | grep mariadb 2.卸载 rpm -e --nodeps mariadb-libs-5.5.56-2.el7.x86_64 3 ...

  6. matlab中figure 创建图窗窗口

    来源:https://ww2.mathworks.cn/help/matlab/ref/figure.html?searchHighlight=figure&s_tid=doc_srchtit ...

  7. JavaFX ComboBox的选中事项

    参考1:https://blog.csdn.net/mexel310/article/details/37909205 参考2:https://blog.csdn.net/maosijunzi/art ...

  8. 使用 .NET 进行游戏开发

    微软是一家综合性的网络公司,相信这点来说不用过多的赘述,没有人不知道微软这个公司,这些年因为游戏市场的回报,微软收购了很多的游戏公司还有独立工作室,MC我的世界就是最成功的的案例,现在市值是排在全世界 ...

  9. day60 Pyhton 框架Django 03

    day61 内容回顾 1.安装 1. 命令行: pip install django==1.11.18 pip install django==1.11.18 -i 源 2. pycharm sett ...

  10. 机器学习 KNN算法原理

    K近邻(K-nearst neighbors,KNN)是一种基本的机器学习算法,所谓k近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表.比如:判断一个人的人品,只需要观察 ...