C# NModbus RTU通信实现
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通信实现的更多相关文章
- Modbus RTU 通信工具设计(转)
Modbus RTU 通信工具设计 Modbus 是一个工业上常用的通讯协议.一种通讯约定. ModBus 协议是应用层报文传输协议(OSI 模型第7层),它定义了一个与通信层无关的协议数据单元(PD ...
- 单片机modebus RTU通信实现,采用C语言,可适用于单片机,VC,安卓等(转)
源:单片机modebus RTU通信实现,采用C语言,可适用于单片机,VC,安卓等 //modebus_rtu.c /***************************************** ...
- Modbus RTU 通信应用案例
如何打开项目归档文件 例程中的TIA博途项目文件与STEP 7项目文件均为归档文件,需要按如下方式打开: TIA博途项目文件 1. 打开TIA博途软件,通过软件左下方“项目视图”按钮切换至项目视图: ...
- 单片机modebus RTU通信实现,採用C语言,可适用于单片机,VC,安卓等
当前使用的是STM32+ucos_ii编写的,能够移植到安卓以及VC .NET等方便移植使用,採用modebus poll測试过. 仅仅须要改动响应的通信接口就可以,方便多串口使用 //modebus ...
- FreeModbus 移植于STM32 实现Modbus RTU通信
http://ntn314.blog.163.com/blog/static/161743584201233084434579/ 毕业设计自己要做个基于STM32的PLC能直接跑语句表的,现在看来好像 ...
- PC和单片机通过MODBUS RTU通信
最近研究了一下MODBUS通信,在STC12C5A60S2单片机上实现了MODBUS协议的部分功能,方便上位机从单片机系统上获取数据,比如由单片机获取的温度.湿度.或者控制信号的状态等.有了MODBU ...
- S7-1200与S7-200 通信西门子链接
只要这两从站的通讯格式时一样的,而且都为modbus rtu格式的话,是可以走modbus通讯.你在用主站在编程时直接调用modbus rtu通讯库.同时200做为从站,在程序里面将从站的程序写好. ...
- Modbus RTU新版本指令介绍
Modbus RTU新版本指令介绍 TIA V13 SP1版本软件中提供了2个版本的Modbus RTU指令: 图1. 两个版本Modbus RTU指令 早期版本的Modbus RTU指令(图1. 中 ...
- Modbus RTU 介绍
S7-1200 Modbus RTU 通信概述 Modbus具有两种串行传输模式:分别为ASCII和RTU.Modbus是一种单主站的主从通信模式,Modbus网络上只能有一个主站存在,主站在Modb ...
随机推荐
- echarts中折线图切换为数据视图(表格布局)表头无法对齐解决方法
dataView: { show: true, readOnly: true, option ...
- spring cloud consul 服务治理
对照系统安装响应consul文件(以window为例) 解压文件之后配置环境,进入Path添加文件所在目录, 测试:在文件所在目录下进入指令操作 输入 consul agent -dev 启动成功,在 ...
- RabbitMQ 3.6.12延迟队列简单示例
简介 延迟队列存储的消息是不希望被消费者立刻拿到的,而是等待特定时间后,消费者才能拿到这个消息进行消费.使用场景比较多,例如订单限时30分钟内支付,否则取消,再如分布式环境中每隔一段时间重复执行某操作 ...
- Centos-上传下载文件-rz sz
依赖: ssh协议.远程终端 .lrzsz软件包.window操作系统 安装 lrzsz 软件包 yum install -y lrzsz 下载命令 sz sz fileName 上传命令 rz 相关 ...
- 06 C语言变量
C语言变量 变量的本质 变量的本质其实是程序可操作的存储区的名称. C 中每个变量都有特定的类型,类型决定了变量存储的大小的范围,在范围内的值都可以存储在内存中,运算符可应用于变量上. 变量的名称可以 ...
- Arduino 跑马灯
参考: 1. https://blog.csdn.net/hunhun1122/article/details/70254606 2. http://www.51hei.com/arduino/392 ...
- matlab中exist 检查变量、脚本、函数、文件夹或类的存在情况
参考: 1.https://ww2.mathworks.cn/help/matlab/ref/exist.html?searchHighlight=exist&s_tid=doc_srchti ...
- matlab中ischar确定输入是否为字符数组
来源:https://ww2.mathworks.cn/help/matlab/ref/ischar.html?searchHighlight=ischar&s_tid=doc_srchtit ...
- DDOS、CC、sql注入,跨站攻击防御方法
web安全常见攻击解读--DDos.cc.sql注入.xss.CSRF 一,DDos https://www.cnblogs.com/sochishun/p/7081739.html#4111858 ...
- 【linux】基础命令一
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mount dir[] device[]umount devic[]maste ...