前言


本文将使用一个NuGet公开的组件技术来实现一个ModBus RTU的客户端,方便的对Modbus rtu的服务器进行读写,这个服务器可以是电脑端C#设计的,也可以是PLC实现的,也可以是其他任何支持这个通信协议的服务器。

github地址:https://github.com/dathlin/HslCommunication 如果喜欢可以star或是fork,还可以打赏支持。

联系作者及加群方式(激活码在群里发放):http://www.hslcommunication.cn/Cooperation

在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:

Install-Package HslCommunication

NuGet安装教程  http://www.cnblogs.com/dathlin/p/7705014.html

组件API地址:http://www.cnblogs.com/dathlin/p/7703805.html

特别感谢


  • 网友:陈恩富                  对float,int数据的读取测试,才修复了权重位颠倒的BUG。
  • 网友:U4幸福的蜗牛      发现了博客上错误的一个方法名称,已于2018年1月8日13:34:39更新。并反馈了一些特殊设备(modbus tcp服务器)的读取数据的BUG。已修复。

随便聊聊


此处的设计模式是客户端主动请求服务器数据,然后接收服务器的反馈数据,支持原生的指令收发,支持其他一些方便的API收发。特殊功能码需要使用原生收发的API,本组件支持如下的功能操作:

  • 0x01    读取线圈的操作,
  • 0x02    读取离散的操作,
  • 0x03    读取寄存器的值,
  • 0x05    写一个线圈操作,
  • 0x06    写一个寄存器值,
  • 0x0F    批量写线圈操作,
  • 0x10    批量写寄存器值,

如果你的设备需要这些功能之外的数据,可以使用原生API方法,但是这个方法的前提就是你对MODBUS 协议非常清晰才可以,如果你不了解这个协议,可以参照下面的博客说明:

http://blog.csdn.net/thebestleo/article/details/52269999

如果你需要搭建自己的ModBus服务器,可以参照这边文章:http://www.cnblogs.com/dathlin/p/7782315.html

访问测试项目

需要下载一个串口虚拟的软件

Virtual Serial Port Driver

下载地址:https://virtual-serial-port-driver.en.softonic.com/

然后虚拟化两个串口出来,COM4 ,COM5 默认是连接在一起的。这样我们就可以进行本地的测试了

在你开发自己的客户端程序之前,可以先用MODBUS测试工具进行测试,以下地址的一个开源项目就是基于这个组件开发的Modbus rtu测试工具,可直接用于读写测试。

ModbusTcpServer.zip   先启动服务,然后启动串口


下面的一个项目是这个组件的访问测试项目,您可以进行初步的访问的测试,免去了您写测试程序的麻烦,这个项目是和三菱,西门子PLC的访问写在一起的。可以同时参考。

下载地址为:HslCommunicationDemo.zip

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.OperateResult<bool[]> read = 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.OperateResult<bool[]> read = 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.OperateResult<byte[]> read = 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
{
OperateResult<byte[]> read = 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[]数据,中间的分割符可以为空格,可以为'-',也可以为',','_'等等等等,调用了组件基础的数据转化功能。

C# 开发Modbus Rtu客户端 modbus测试Demo,Modbus 串口通信 , 虚拟MODBUS-RTU测试的更多相关文章

  1. android 串口开发第二篇:利用jni实现android和串口通信

    一:串口通信简介 由于串口开发涉及到jni,所以开发环境需要支持ndk开发,如果未配置ndk配置的朋友,或者对jni不熟悉的朋友,请查看上一篇文章,android 串口开发第一篇:搭建ndk开发环境以 ...

  2. 串口屏Modbus协议,串口屏的modbus协议资料,串口屏modbus通讯协议开发,串口屏之modbus协议使用技巧

    串口屏Modbus协议,串口屏的modbus协议资料,串口屏modbus通讯协议开发,串口屏之modbus协议使用技巧 本例程中用51单片机作为Modbus从机,从机的设备地址为2,从机有4个寄存器, ...

  3. 初识Modbus TCP/IP-------------C#编写Modbus TCP客户端程序(一)

    转自:http://blog.csdn.net/thebestleo/article/details/52269999 首先我要说明一下,本人新手一枚,本文仅为同样热爱学习的同学提供参考,有不 对的地 ...

  4. 初识Modbus TCP/IP-------------C#编写Modbus TCP客户端程序(二)

    由于感觉上一次写的篇幅过长,所以新开一贴,继续介绍Modbus TCP/IP的初步认识, 书接上回 3).03(0x03)功能码--------读保持寄存器 请求与响应格式 这是一个请求读寄存器108 ...

  5. android gps开发必备资料(含测试demo下载)

    入门资料参考: How accurate is Android GPS? Part 1: Understanding Location Data How accurate is Android GPS ...

  6. RS485通信和Modbus协议(转)

    转自:http://www.51hei.com/bbs/dpj-23230-1.html 在工业控制.电力通讯.智能仪表等领域,通常情况下是采用串口通信的方式进行数据交换.最初采用的方式是RS232接 ...

  7. (转)【ASP.NET开发】获取客户端IP地址 via C#

    [ASP.NET开发]获取客户端IP地址 via C# 说明:本文中的内容是我综合博客园上的博文和MSDN讨论区的资料,再通过自己的实际测试而得来,属于自己原创的内容说实话很少,写这一篇是为了记录自己 ...

  8. 智能电视TV开发---直播视频客户端结构设计和实现

    在智能电视TV开发---客户端和服务器通信里面我们实现了客户端和服务端的简单通信,接下来我们做一个简单的客户端界面,来实现手机端来操控智能电视的TV端. 一.存储视频的结构设计 我们在做客户端的时候, ...

  9. 基于滴答清单 Web 开发的 PC 客户端

    基于滴答清单 Web 开发的 PC 客户端 关于「滴答清单」 滴答清单是一款不可多得的 GTD 效率工具,它有着清晰明了的界面设计.恰到好处的功能设置.稳定的同步服务,如果你还缺少一款简洁而有效的 G ...

随机推荐

  1. python爬虫——绕开杂乱无章的代码和堵住请求的302异常(2)

    淘宝那次抓包,居然发现不了要抓的url位置,三星中... 不过不怕,不就是没法快点分析出包嘛,下次用phantomJS硬杠,或者有时间慢慢分析也好. 今天挑战一个稍微好爬的网站:狗搬家(误) 打开后台 ...

  2. Spring Boot 数据库连接池 Druid

    简介 数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.数据库连接池正是针对这个问 ...

  3. Dubbo和Spring Cloud微服务架构对比

    https://blog.csdn.net/zhangweiwei2020/article/details/78646252

  4. 《Visual C# 从入门到精通》第二章方法和作用域——读书笔记

    第2章 方法和作用域 2.1创建方法 方法是一个基本的,强大的编程机制.可视为函数或者子程序相似的东西. 方法名是个有意义的标识符. 方法主体包含方法被调用时实际执行的语句. 声明一个方法的实例如下: ...

  5. 查漏补缺之——Java多线程

    复习面试题中遇到锁的内容当时大一学习的时候感觉懵懂,现在重新复习一下. 1.1多线程 1.1.1线程 1.什么是线程 线程是程序执行的一条路径,一个进程中包含多条进程 2.并行与并发 并行是两个任务同 ...

  6. MVC 深入讲解Routing _路由规则【八】

    一.客户端=>控制器 在项目中我们引用了system.web.routing, 如果第一个匹配成功了,那么后面的都不会再匹配. 1. routing的作用: 确定colltroller,确定ac ...

  7. POJ 1088 滑雪(模板题 DFS+记忆化)

    Description Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.Michael想知道 ...

  8. DAY8 文件操作(二)

    一.写 1.1写文件 # w:没有文件新建文件,有文件就清空文件 w = open('1.txt', 'w', encoding='utf-8') w.write('000\n') # 在写入大量数据 ...

  9. Angular 学习笔记 (Material Datepicker)

    https://material.angular.io/components/datepicker/overview 官网介绍很清楚了,这里记入一下我比较不熟悉的. 1. moment js Angu ...

  10. 《剑指offer》总结二 之二叉树

    目录 17.树的子结构(27ms,5836k) 18.二叉树的镜像(38ms) 22.从上往下打印二叉树(50ms,5832k) 24.二叉树中和为某一值的路径(26ms,5728k) 38.二叉树的 ...