【.NET6+Modbus】Modbus TCP协议解析、仿真环境以及基于.NET实现基础通信

【.NET6+Modbus】Modbus TCP协议解析、仿真环境以及基于.NET实现基础通信

 

 前言:随着工业化的发展,目前越来越多的开发,从互联网走向传统行业。其中,工业领域也是其中之一,包括各大厂也都在陆陆续续加入工业4.0的进程当中。

工业领域,最核心的基础设施,应该是与下位硬件设备或程序进行通信有关的了,而下位机市场基本上是PLC的天下。而PLC产品就像编程语言一样,类型繁多,协议也多种多样。例如,西门子PLC最常用的S7协议、施耐德PLC最常用的Modbus协议、以及标准工业通信协议CIP协议等等。而多种通信协议里面,基于以太网通信的居多。以太网通信的里面,通用协议除了CIP协议,就属于Modbus TCP协议了。

接下来的内容,我会以从头开发一个简单的基于modbus tcp通信的案例,来实现一个基础的通信功能。

 

有关环境:

开发环境: VS 2022企业版

运行环境: Win 10 专业版

.NET 环境版本: .NET 6

 

【备注】 源码在文末 

 

1、新建一个基于.NET 6带控制器的webapi项目,以及一个类库项目。如下图所示,新建以后的项目目录结构。

 

 

2、由于modbus tcp通信实际上就是一个socket通信,所以在类库项目下,先创建了一个Modbus服务类,并且提供一个基于socket通信连接的方法。socket连接以后,需要返回socket实例拿来使用。

 

 

3、为了方便一点,再新增一个通用的返回信息类,用于存储一些返回信息使用。

 

 

 

4、基于以上的返回信息类,咱对连接方法进行稍微改造一下,让它看起来更方便一点。这样可以用来验证连接是否正常,以及返回对应的异常信息,好做进一步处理。

 

 

 

5、Modbus TCP请求的报文规则,一些解析信息如下:

站地址:默认0x01, 除非PLC告诉我们其他站地址。

功能码:代表读写数据时候指定的读写方法等。例如读取线圈的功能码是0x01。

地址和读取长度:地址目前个人在施耐德物理的PLC环境上,不能超过30000。同时,单次读写长度不能超过248个byte,否则PLC可能会飘。当然,也可能将来一些PLC可以支持更长的批量数据读写,目前在施耐德PLC环境下不支持(具体型号忘记了,有点久了,当前身边没得PLC了,等下会使用仿真工具来做环境)。

头部校验(消息唯一识别码):0~65535,用于PLC服务端进行区分不同的客户端而使用的一组数据标识,不同的客户端必须保证标识码不重合。例如多个客户端同时存在时候,发起的通信请求,必须保持不一样的识别码,否则Modbus服务端有可能会因为不知是哪个客户端发起的请求而导致信息乱了。

无(协议标识):默认0,代表是Modbus协议。

数据长度:发送的报文的长度,刚好是6位,所以可以写成固定值0x06。(写入的规则不一样,此处固定值只当作读取时候使用)

 

 

 

6、根据协议的一些具体内容,写一个存储功能码和异常返回码的数据类,用于后期做通信时候传参和通信数据验证使用。有关协议具体内容,如下代码所示。

 

 

7、由于异常码是byte数据,直接验证可能会麻烦一点,为了可以直观一些,此处再新增一个用于解析Modbus返回的异常信息的方法,用于备用。

 

 

 

8、根据协议规则,提供一些参数,并先搭建一个简单的方法框架,用来可以进行读取线圈的功能。包含简单的报文数据拆分以及报文发送和接收。由于发送报文长度不能超过248byte(1 bool大小 == 1 byte,如果是其他类型,需要做其他长度换算),所以当长度超过时候,做个简单的算法进行拆分再发送,防止发生不必要的异常。以下做一个读取线圈(Bool类型数据)的简单方法。

 

 

 

 

9、根据上方提供的协议报文组装规则,进行开发一个通用的报文组织方法。有高低位之分,所以对于占用2byte的数据,需要进行"倒装"。

 

 

 

10、发送报文以后,返回的报文含有校验信息:发送的数据报文的第7位的数据,加上 0x80 以后,跟返回的报文的第7位byte数据如果一致,则代表当前通信上可能有异常。异常码在接收的响应报文的第8位。

所以可以继续写一个验证是否成功的校验方法:

 

 

11、由于返回的数据也都是byte数据,以上读取的线圈值(布尔值),就需要提供一个数据类型转换的功能用于把byte数组转换为bool数组。

 

 

12、对读取线圈的最开始的方法,进行一些完善以后的代码如下。响应报文长度是 发送数据长度*2+9 。

 

 

13、接下来做一个简单的测试。准备一下仿真环境,进行本地的测试,看看是否可以连通。先准备两个工具,一个是 modbus poll,另一个是modbus slave。一个用来模拟服务端环境,另一个可用来模拟数据收发验证。

备注】:由于网上存在很多爬虫爬取博客文章到各个地方的,所以如果有需要这俩工具的小伙伴,可以点击该文章的 原文链接:【https://www.cnblogs.com/weskynet/p/16121383.html】的最下方的QQ群号进行加群进行获取,或者在文章最后提供的个人微信号,加我个人微信私发也可以。

 

 

 

14、两边都设置为读写单个线圈的功能,用于测试以上线圈读取的代码的功能。

 

 

15、两边都设置为modbus tcp连接方式。Slave站点启动以后,默认为本地,poll工具上的IP地址选择本地即可。如果是真实PLC环境,则填写真实PLC地址。

 

 

 

16、测试两边是否通信上。给任意一个地址写入一个true,可以看到另一边也同步更新,说明通信是通的了。

【注意】modbus工具,poll和slave工具默认占用了消息唯一标识码,大概是1~5左右的固定值,所以使用该工具期间,建议程序上的唯一消息识别码设置为5以上,以防止通信干扰。

 

 

 

17、接下来就可以继续完善代码进行验证了。先新增ModbusService的接口IModbusService,用于实现依赖注入。然后在program.cs文件里面进行服务注册。

 

 

 

18、新建一个控制器,用来进行模拟实验。有关代码和注释如图所示。

 

 

 

19、进行读取一个长度试试效果。结果是数据不支持,说明报文有问题。

 

 

 

20、通过断点,找到问题所在,上面的代码里面,length经过简单算法计算以后等于0,此处需要用的应该是newLength变量的值。

 

 

 

21、再次测试,地址从1开始,读取两个地址,结果符合预期。

 

 

22、再测试一下,从0开始读取30个,并随即设置若干个是True的值。

 

 

 

23、其他的写入、以及其他类型读写,基本类似。由于篇幅有限,就不继续进行一步一步操作的截图了。读取的,选好类型,报文格式都是一样的,唯一有差别的是写入的报文。下面是写入单个线圈值的报文。线圈当前仅支持一个一个写入。

 

 

 

24、写入寄存器的规则会有些偏差,协议规则如下图。

 

【备注】以上图的标题,我写错了,应该是 “写入寄存器”报文协议,懒得换图了,大佬们看的时候自己辨别哈~

 读取线圈当作引导,其他类型也都异曲同工,大佬们可以自行尝试。

 

 另外说点,如果是生产环境下使用,建议把客户端连接做成【长连接】,不然重复创建连接比较耗费资源,耗时也会因为新建连接而占用一大半。同时,如果是多线程访问,使用同一个客户端连接,必须加锁,否则会干扰数据;如果是多线程,不同客户端,就要保证每个消息识别码必须不同,如果存在同一个识别码,很容易发生数据异常等情况。

 

有关源码:

ModbusService源码:

 View Code

控制器源码:

 View Code

功能码和异常码:

 View Code

 

 

 

好了,以上就是该文章的全部内容。如果觉得有帮助,欢迎一键三连啊~~ 如果有兴趣,也可以加我私人微信,欢迎大佬来我微信群做客。私人微信:【18500591275】

posted on   漫思  阅读(680)  评论(5编辑  收藏  举报

相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
历史上的今天:
2017-06-26 《AngularJS》5个实例详解Directive(指令)机制

导航

< 2025年2月 >
26 27 28 29 30 31 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 1
2 3 4 5 6 7 8
点击右上角即可分享
微信分享提示