源代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 using HslCommunication.ModBus;using HZH_Controls.Controls;using ModbusDemo.Helper;using Sunny.UI;using System;using System.Collections.Concurrent;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;using ConfigHelper = ModbusDemo.Helper.ConfigHelper;using ArrowDirection = HZH_Controls.Controls.ArrowDirection;namespace ModbusDemo { public partial class Form1 : UIForm { private ModbusRtu _modbusRtu; private CancellationTokenSource _ctsCollect = new CancellationTokenSource(); private bool _isCollect = false ; private BlockingCollection<short []> _collection = new BlockingCollection<short []>(500 ); public Form1 () { InitializeComponent(); InitConfig(); InitModbus(); InitUiRefresh(); } private void InitUiRefresh () { Task.Run(async () => { while (true ) { _collection.TryTake(out var data); if (data != null ) { Invoke(new Action(() => { if (data.Length > 8 ) { ucMeter1.Value = data[0 ]; ucMeter2.Value = data[1 ]; ucThermometer2.Value = data[3 ]; ucArrowLoss.Direction = data[7 ] == 0 ? ArrowDirection.Left : ArrowDirection.Right; ucThermometer1.Value = data[8 ]; } })); } await Task.Delay(10 ); } }); } private void InitModbus () { _modbusRtu = new ModbusRtu(); _modbusRtu.SerialPortInni(sp => { sp.PortName = ConfigHelper.Current.PortName; sp.BaudRate = ConfigHelper.Current.BaudRate; sp.DataBits = ConfigHelper.Current.DataBits; sp.Parity = ConfigHelper.Current.Parity; sp.StopBits = ConfigHelper.Current.StopBits; sp.RtsEnable = ConfigHelper.Current.RtsEnable; }); _modbusRtu.ReceiveTimeOut = ConfigHelper.Current.ReceiveTimeOut; _modbusRtu.AddressStartWithZero = ConfigHelper.Current.AddressStartWithZero; _modbusRtu.DataFormat = ConfigHelper.Current.DataFormat; _modbusRtu.Station = ConfigHelper.Current.Station; _modbusRtu.Crc16CheckEnable = ConfigHelper.Current.Crc16CheckEnable; } private void InitConfig () { ConfigHelper.Current.Load(); } private void btnConnect_Click (object sender, EventArgs e ) { try { var result = _modbusRtu.Open(); if (result.IsSuccess) { MessageBox.Show("称重设备 PSD900 连接成功" ); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } private void uiButton1_Click (object sender, EventArgs e ) { MessageBox.Show("测试Demo" ); } private void btnCollect_Click (object sender, EventArgs e ) { if (_modbusRtu == null ) { btnCollect.BackColor = System.Drawing.Color.Green; MessageBox.Show("请先连接称重设备" ); return ; } if (_isCollect) { btnCollect.Text = "开始采集" ; btnCollect.BackColor = System.Drawing.Color.Green; _ctsCollect?.Cancel(); } else { btnCollect.Text = "停止采集" ; btnCollect.BackColor = System.Drawing.Color.Red; _ctsCollect = new CancellationTokenSource(); Task.Run(async () => { while (!_ctsCollect.Token.IsCancellationRequested) { try { var result = await _modbusRtu.ReadInt16Async("1" , 9 ); if (result.IsSuccess) { if (!_collection.TryAdd(result.Content)) { _collection.TryTake(out var data); } } await Task.Delay(100 ); } catch (Exception exception) { Console.WriteLine(exception); } } }, _ctsCollect.Token); } _isCollect = !_isCollect; } private void ucThermometer1_Load (object sender, EventArgs e ) { } } }
这段代码是一个使用 C# 编写的 Windows Forms 程序,用于通过 Modbus RTU 协议 与设备通信,读取或写入数据,并更新到界面控件上。代码使用了 HslCommunication、HZH_Controls 和 Sunny.UI 这些第三方控件库。
下面我会 逐行解释 代码,尽量用通俗易懂的语言说明每一部分的作用。
1. 引用命名空间 1 2 3 4 5 6 7 8 9 10 using System;using System.Collections.Concurrent;using System.Threading;using System.Threading.Tasks;using System.Windows.Forms;using HslCommunication.ModBus;using HZH_Controls.Controls;using Sunny.UI;using ArrowDirection = HZH_Controls.Controls.ArrowDirection;using ConfigHelper = TulingModbus.Helper.ConfigHelper;
System
:基础类库,提供常用功能。
System.Collections.Concurrent
:提供线程安全的集合类,比如 BlockingCollection
。
System.Threading
和 System.Threading.Tasks
:用于多线程、异步编程。
System.Windows.Forms
:Windows 窗体应用程序的基础。
HslCommunication.ModBus
:用于 Modbus 通信的库。
HZH_Controls.Controls
和 Sunny.UI
:第三方 UI 控件库。
ArrowDirection
:定义箭头方向的枚举。
ConfigHelper
:自定义的配置帮助类,用来读取配置文件。
2. 命名空间和类定义 1 2 3 4 5 6 7 8 namespace TulingModbus { public partial class Form1 : UIForm { private ModbusRtu _modbusRtu; private CancellationTokenSource _ctsCollect = new CancellationTokenSource(); private bool _isCollect = false ; private BlockingCollection<short []> _collection = new BlockingCollection<short []>(500 );
定义了一个命名空间 TulingModbus
。
Form1
是主窗体类,继承自 UIForm
(Sunny.UI 的窗体类)。
_modbusRtu
:Modbus RTU 协议通信对象。
_ctsCollect
:取消令牌源,用于控制采集任务的启停。
_isCollect
:表示是否正在采集数据。
_collection
:线程安全的数据队列,最多存储 500 条数据,用于在采集线程和 UI 线程之间传递数据。
3. 构造函数 1 2 3 4 5 6 7 public Form1 (){ InitializeComponent(); InitConfig(); InitModbus(); InitUiRefresh(); }
InitializeComponent()
:初始化窗体控件(由设计器自动生成)。
InitConfig()
:加载配置文件(如串口参数等)。
InitModbus()
:初始化 Modbus RTU 通信参数。
InitUiRefresh()
:启动一个后台任务,用于从队列中取出数据并刷新界面。
4. 初始化界面刷新任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private void InitUiRefresh (){ Task.Run(async () => { while (true ) { _collection.TryTake(out var data); if (data != null ) { Invoke(new Action(() => { if (data.Length >8 ){ ucMeter1.Value = data[0 ]; ucMeter2.Value = data[1 ]; ucThermometer2.Value = data[3 ]; ucArrowLoss.Direction = data[7 ]==0 ? ArrowDirection.Left:ArrowDirection.Right; ucThermometer1.Value = data[8 ]; } })); } await Task.Delay(10 ); } }); }
启动一个后台任务,不断从 _collection
队列中取出数据。
如果数据不为空,就使用 Invoke
在 UI 线程中更新控件:
ucMeter1
、ucMeter2
:仪表控件,显示数值。
ucThermometer2
:温度计控件。
ucArrowLoss.Direction
:根据 data[7]
的值判断箭头方向。
ucThermometer1
:另一个温度计控件。
每次处理完数据后等待 10 毫秒。
5. 加载配置 1 2 3 4 private void InitConfig (){ ConfigHelper.Current.Load(); }
调用 ConfigHelper
的 Load
方法加载配置信息(比如串口号、波特率等)。
6. 初始化 Modbus 通信 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private void InitModbus (){ _modbusRtu = new ModbusRtu(); _modbusRtu.SerialPortInni(sp => { sp.PortName = ConfigHelper.Current.PortName; sp.BaudRate = ConfigHelper.Current.BaudRate; sp.DataBits = ConfigHelper.Current.DataBits; sp.Parity = ConfigHelper.Current.Parity; sp.StopBits = ConfigHelper.Current.StopBits; sp.RtsEnable = ConfigHelper.Current.RtsEnable; }); _modbusRtu.ReceiveTimeOut = ConfigHelper.Current.ReceiveTimeOut; _modbusRtu.AddressStartWithZero = ConfigHelper.Current.AddressStartWithZero; _modbusRtu.DataFormat = ConfigHelper.Current.DataFormat; _modbusRtu.Station = ConfigHelper.Current.Station; _modbusRtu.Crc16CheckEnable = ConfigHelper.Current.Crc16CheckEnable; }
创建一个 ModbusRtu
对象。
配置串口参数(如端口号、波特率等),这些参数从配置文件中读取。
设置 Modbus RTU 的一些通信参数,如超时时间、地址是否从0开始、数据格式、站号、CRC校验开关等。
7. 连接按钮点击事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void btnConnect_Click (object sender, EventArgs e ){ try { var result = _modbusRtu.Open(); if (result.IsSuccess) { MessageBox.Show("称重设备 PSD900 连接成功" ); } } catch (Exception ex) { MessageBox.Show(ex.Message); } }
当用户点击“连接”按钮时,尝试打开串口连接 Modbus 设备。
如果连接成功,弹出提示框。
出现异常时,显示错误信息。
8. 采集按钮点击事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 private void btnCollect_Click (object sender, EventArgs e ){ if (_modbusRtu == null ) { btnCollect.BackColor = System.Drawing.Color.Green; MessageBox.Show("请先连接称重设备" ); return ; } if (_isCollect) { btnCollect.Text = "开始采集" ; btnCollect.BackColor = System.Drawing.Color.Green; _ctsCollect?.Cancel(); } else { btnCollect.Text = "停止采集" ; btnCollect.BackColor = System.Drawing.Color.Red; _ctsCollect = new CancellationTokenSource(); Task.Run(async () => { while (!_ctsCollect.Token.IsCancellationRequested) { try { var result = await _modbusRtu.ReadInt16Async("1" , 9 ); if (result.IsSuccess) { if (!_collection.TryAdd(result.Content)) { _collection.TryTake(out var data); } } await Task.Delay(100 ); } catch (Exception exception) { Console.WriteLine(exception); } } }, _ctsCollect.Token); } _isCollect = !_isCollect; }
如果未连接设备,提示用户先连接。
如果正在采集,就停止采集(取消令牌)。
如果没有在采集,就启动一个异步任务:
每隔 100 毫秒读取一次地址为 1
的 9 个寄存器数据。
如果读取成功,将数据加入队列 _collection
。
如果队列满了,就移除最早的数据。
改变按钮状态(文字和颜色)来表示当前是否在采集。
9. 模拟写入数据按钮点击事件(测试用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private void uiButton1_Click (object sender, EventArgs e ){ if (_modbusRtu == null ) { btnCollect.BackColor = System.Drawing.Color.Green; MessageBox.Show("请先连接称重设备" ); return ; } _ctsCollect = new CancellationTokenSource(); Task.Run(async () => { while (!_ctsCollect.Token.IsCancellationRequested) { try { var random = new Random(); var coefficient = (short )random.Next(1 ,50 ); var loss = (short )random.Next(-1 ,2 ); var percent = (short )random.Next(1 ,45 ); short [] data = new short [9 ] { (short )(coefficient), (short )(coefficient+2 ), (short )(coefficient+2 ), (short )(percent+2 ), (short )(percent*2 ), (short )(coefficient+2 ), (short )(coefficient+2 ), (short )(loss), (short )(percent), }; var result = await _modbusRtu.WriteAsync("1" ,data); await Task.Delay(100 ); } catch (Exception exception) { Console.WriteLine(exception); } } }, _ctsCollect.Token); }
这个按钮用于模拟向设备写入数据(用于测试)。
创建随机数据,构造一个 9 个 short
类型的数组。
使用 WriteAsync
方法将数据写入地址为 1
的寄存器。
每次写入后等待 100 毫秒。
✅ 总结 这段代码的主要功能是:
使用 Modbus RTU 协议与设备通信;
可以连接设备、读取数据、写入数据;
读取的数据会通过队列传给界面刷新任务;
使用了异步、多线程来避免界面卡顿;
提供了连接、采集、模拟写入等功能按钮;
使用了第三方 UI 控件库来美化界面。
如果你是初学者,可以把这个程序理解为:
一个连接 Modbus 设备的界面程序,能读取数据并显示在仪表、温度计、箭头上,也能模拟发送数据给设备。
如果你需要我进一步解释某个模块,比如 Modbus 协议、队列、异步任务、UI 控件等,也可以继续问我!
Modbus RTU协议与称重设备(PSD900)通信 代码理解