前言
本文将使用一个NuGet公开的组件技术来实现一个ModBus RTU的客户端,方便的对Modbus rtu的服务器进行读写,这个服务器可以是电脑端C#设计的,也可以是PLC实现的,也可以是其他任何支持这个通信协议的服务器。
github地址: 如果喜欢可以star或是fork,还可以打赏支持。
在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:
Install-Package HslCommunication
NuGet安装教程
组件API地址:
特别感谢
- 网友:陈恩富 对float,int数据的读取测试,才修复了权重位颠倒的BUG。
- 网友:U4幸福的蜗牛 发现了博客上错误的一个方法名称,已于2018年1月8日13:34:39更新。并反馈了一些特殊设备(modbus tcp服务器)的读取数据的BUG。已修复。
随便聊聊
此处的设计模式是客户端主动请求服务器数据,然后接收服务器的反馈数据,支持原生的指令收发,支持其他一些方便的API收发。特殊功能码需要使用原生收发的API,本组件支持如下的功能操作:
- 0x01 读取线圈的操作,
- 0x02 读取离散的操作,
- 0x03 读取寄存器的值,
- 0x05 写一个线圈操作,
- 0x06 写一个寄存器值,
- 0x0F 批量写线圈操作,
- 0x10 批量写寄存器值,
如果你的设备需要这些功能之外的数据,可以使用原生API方法,但是这个方法的前提就是你对MODBUS 协议非常清晰才可以,如果你不了解这个协议,可以参照下面的博客说明:
如果你需要搭建自己的ModBus服务器,可以参照这边文章:
访问测试项目
需要下载一个串口虚拟的软件
下载地址:
然后虚拟化两个串口出来,COM4 ,COM5 默认是连接在一起的。这样我们就可以进行本地的测试了
在你开发自己的客户端程序之前,可以先用MODBUS测试工具进行测试,以下地址的一个开源项目就是基于这个组件开发的Modbus rtu测试工具,可直接用于读写测试。
先启动服务,然后启动串口
下面的一个项目是这个组件的访问测试项目,您可以进行初步的访问的测试,免去了您写测试程序的麻烦,这个项目是和三菱,西门子PLC的访问写在一起的。可以同时参考。
下载地址为:
Reference
ModBus组件所有的功能类都在 HslCommunication.ModBus命名空间,所以再使用之前先添加
using HslCommunication.ModBus;using HslCommunication;
How to Use
实例化:
在使用读写功能之前必须先进行实例化:
private ModbusRtu busRtuClient = new ModbusRtu( station );
注意:在Modbus服务器的设备里,大部分的设备都是从地址0开始的,有些特殊的设备是从地址1开始的,所以本组件里面,默认从地址0开始,如果想要从地址1开始,那么就需要如下的配置:
busRtuClient.AddressStartWithZero = False;
然后接下来需要初始化参数,对串口来说,通常的参数有串口名称,波特率,数据位,停止位,校验位,提供了一个委托设置的方式
try { busRtuClient.SerialPortInni( sp => { sp.PortName = "COM5"; sp.BaudRate = 9600; sp.DataBits = 8; sp.StopBits = System.IO.Ports.StopBits.One; sp.Parity = System.IO.Ports.Parity.None; } ); busRtuClient.Open( ); // 打开 } catch (Exception ex) { MessageBox.Show( ex.Message ); }
关闭的话,调用如下的方法
busRtuClient.Close( );
以下代码演示常用的读写操作,为了方便起见,不再对IsSuccess判断,一般都是成功的:
private void userButton30_Click(object sender, EventArgs e) { // 读取操作 bool coil100 = busRtuClient.ReadCoil("100").Content; // 读取线圈100的通断 short short100 = busRtuClient.ReadInt16("100").Content; // 读取寄存器100的short值 ushort ushort100 = busRtuClient.ReadUInt16("100").Content; // 读取寄存器100的ushort值 int int100 = busRtuClient.ReadInt32("100").Content; // 读取寄存器100-101的int值 uint uint100 = busRtuClient.ReadUInt32("100").Content; // 读取寄存器100-101的uint值 float float100 = busRtuClient.ReadFloat("100").Content; // 读取寄存器100-101的float值 long long100 = busRtuClient.ReadInt64("100").Content; // 读取寄存器100-103的long值 ulong ulong100 = busRtuClient.ReadUInt64("100").Content; // 读取寄存器100-103的ulong值 double double100 = busRtuClient.ReadDouble("100").Content; // 读取寄存器100-103的double值 string str100 = busRtuClient.ReadString("100", 5).Content;// 读取100到104共10个字符的字符串 // 写入操作 busRtuClient.WriteCoil("100", true);// 写入线圈100为通 busRtuClient.Write("100", (short)12345);// 写入寄存器100为12345 busRtuClient.Write("100", (ushort)45678);// 写入寄存器100为45678 busRtuClient.Write("100", 123456789);// 写入寄存器100-101为123456789 busRtuClient.Write("100", (uint)123456778);// 写入寄存器100-101为123456778 busRtuClient.Write("100", 123.456);// 写入寄存器100-101为123.456 busRtuClient.Write("100", 12312312312414L);//写入寄存器100-103为一个大数据 busRtuClient.Write("100", 12634534534543656UL);// 写入寄存器100-103为一个大数据 busRtuClient.Write("100", 123.456d);// 写入寄存器100-103为一个双精度的数据 busRtuClient.Write("100", "K123456789"); }
下面再分别讲解严格的操作,以及批量化的复杂的读写操作,假设你要读取1000个M,循环读取1千次可能要3秒钟,如果用了下面的批量化读取,只需要50ms,但是需要你对字节的原理比较熟悉才能得心应手的处理
读取线圈API:
在此处举例读取地址为0,长度为10的线圈数量,读取出来的数据已经自动转化成了bool数组,方便的进行二次处理:
private void userButton8_Click(object sender,EventArgs e) { HslCommunication.OperateResultread = busRtuClient.ReadCoil("0", 10); if(read.IsSuccess) { bool coil_0 = read.Content[0]; bool coil_1 = read.Content[1]; bool coil_2 = read.Content[2]; bool coil_3 = read.Content[3]; bool coil_4 = read.Content[4]; bool coil_5 = read.Content[5]; bool coil_6 = read.Content[6]; bool coil_7 = read.Content[7]; bool coil_8 = read.Content[8]; bool coil_9 = read.Content[9]; } else { MessageBox.Show(read.ToMessageShowString()); } }
当然也可以用组件提供的数据转换API实现数据提取:
读取离散数据:
读取离散数据和读取线圈的代码几乎是一致的,处理方式也是一致的,只是方法名称改成了:
private void userButton8_Click(object sender,EventArgs e) { HslCommunication.OperateResultread = busRtuClient.ReadDiscrete("0", 10); if(read.IsSuccess) { bool coil_0 = read.Content[0]; bool coil_1 = read.Content[1]; bool coil_2 = read.Content[2]; bool coil_3 = read.Content[3]; bool coil_4 = read.Content[4]; bool coil_5 = read.Content[5]; bool coil_6 = read.Content[6]; bool coil_7 = read.Content[7]; bool coil_8 = read.Content[8]; bool coil_9 = read.Content[9]; } else { MessageBox.Show(read.ToMessageShowString()); } }
读取寄存器数据:
假设我们需要读取地址为0,长度为10的数据,也即是10个数据,每个数据2个字节,总计20个字节的数据。下面解析数据前,先进行了假设,你在解析自己的数据前可以参照下面的解析
private void userButton10_Click(object sender, EventArgs e) { HslCommunication.OperateResultread = busRtuClient.Read("0", 10); if (read.IsSuccess) { // 共返回20个字节,每个数据2个字节,高位在前,低位在后 // 在数据解析前需要知道里面到底存了什么类型的数据,所以需要进行一些假设: // 前两个字节是short数据类型 short value1 = busTcpClient.ByteTransform.TransInt16(read.Content, 0); // 接下来的2个字节是ushort类型 ushort value2 = busTcpClient.ByteTransform.TransUInt16(read.Content, 2); // 接下来的4个字节是int类型 int value3 = busTcpClient.ByteTransform.TransInt32(read.Content, 4); // 接下来的4个字节是float类型 float value4 = busTcpClient.ByteTransform.TransFloat(read.Content, 8); // 接下来的全部字节,共8个字节是规格信息 string speci = Encoding.ASCII.GetString(read.Content, 12, 8); // 已经提取完所有的数据 } else { MessageBox.Show(read.ToMessageShowString()); } }
写一个线圈:
写一个线圈,这个相对比较简单,假设我们需要写入线圈0,为通
private void userButton11_Click(object sender, EventArgs e) { HslCommunication.OperateResult write = busRtuClient.WriteCoil("0", true); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
写一个寄存器:
写一个寄存器的操作也是非常的方便,在这里提供了三个重载的方法,允许使用三种方式写入:分别写入,short,ushort,byte三种:
private void userButton12_Click(object sender, EventArgs e) { short value = -1234; HslCommunication.OperateResult write = busRtuClient.WriteOneRegister("0", value); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
private void userButton12_Click(object sender, EventArgs e) { ushort value = 56713; HslCommunication.OperateResult write = busRtuClient.WriteOneRegister("0", value); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
private void userButton12_Click(object sender, EventArgs e) { // 0x00为高位,0x10为低位 HslCommunication.OperateResult write = busRtuClient.WriteOneRegister("0", 0x00, 0x10); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
批量写入线圈:
private void userButton13_Click(object sender, EventArgs e) { // 线圈0为True,线圈1为false,线圈2为true.....等等,以此类推,数组长度多少,就写入多少线圈 bool[] value = new bool[] { true, false, true, true, false, false }; HslCommunication.OperateResult write = busRtuClient.WriteCoil("0", value); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
批量写入寄存器:
第一种情况写入一串short数组,这种情况比较简单:
private void userButton14_Click(object sender, EventArgs e) { short[] value = new short[] { -1234, 467, 12345 }; HslCommunication.OperateResult write = busRtuClient.Write("0", value); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
第二情况写入一串ushort数组,也是比较简单:
private void userButton14_Click(object sender, EventArgs e) { ushort[] value = new ushort[] { 46789, 467, 12345 }; HslCommunication.OperateResult write = busRtuClient.Write("0", value); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
比较复杂的是写入自定义的数据,按照上述读取寄存器,比如我需要写入寄存器0,寄存器1共同组成的一个int数据,那么我们这么写:
private void userButton15_Click(object sender, EventArgs e) { int value = 12345678;// 等待写入的一个数据 HslCommunication.OperateResult write = busRtuClient.Write("0", value); if (write.IsSuccess) { // 写入成功 textBox1.Text = "写入成功"; } else { MessageBox.Show(write.ToMessageShowString()); } }
其他数据参考这个就行,如果有不明白的,可以联系上面的QQ群。
究极数据操作,使用原生的报文来操作数据:
传入一个字节数组,数据内容和原生的数据一致,比如我要通过原生API读取寄存器地址为0,长度为3的数据,那么字节的HEX标识形式为 01 03 00 00 00 03 不包括CRC校验码
private void button26_Click( object sender, EventArgs e ) { try { OperateResultread = busRtuClient.ReadBase( HslCommunication.Serial.SoftCRC16.CRC16(HslCommunication.BasicFramework.SoftBasic.HexStringToBytes( "01 03 00 00 00 03" )) ); if (read.IsSuccess) { textBox11.Text = "结果:" + HslCommunication.BasicFramework.SoftBasic.ByteToHexString( read.Content,' ' ); } else { MessageBox.Show( "读取失败:" + read.ToMessageShowString( ) ); } } catch (Exception ex) { MessageBox.Show( "读取失败:" + ex.Message ); } }
上述代码在操作时用了一个转化机制,输入为十六进制的文本,转化为byte[]数据,中间的分割符可以为空格,可以为'-',也可以为',','_'等等等等,调用了组件基础的数据转化功能。