Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试相关的知识,希望对你有一定的参考价值。

前言:


 之前的博客介绍了如何用C#来读写modbus tcp服务器的数据,文章:http://www.cnblogs.com/dathlin/p/7885368.html

当然也有如何创建一个服务器文章:http://www.cnblogs.com/dathlin/p/7782315.html

但是上面的两篇文章是已经封装好的API,只要调用就可以实现功能了,对于想了解modbus tcp的原理的人可能就不适合了,最近有不少网友的想要了解这个协议,所以在这里再写一篇介绍Modbus tcp的文章,不过这篇文章是简易版本的,未来我再研究深入的话,再开一篇高级版,在简易版中,就略去了成功标志位及其他数据标志,这些到等到后面再说。

先分享一下,我自己学习的地址来源:http://blog.csdn.net/thebestleo/article/details/52269999  声明:本文并非转载,并非照搬原文章,是在我参照原博客的基础上,理解了基本的modbus通讯,并结合自己的理解,重新写一篇更好入门的文章,此处贴出原作者的帖子以示尊重知识产权,原文章有些地方有一点错误,而且早就停止更新了,也没有提供方便的测试工具,官方的modbus 测试工具是试验版本的,需要购买序列号才可以,所以此处提供我自己的测试工具,地址如下,下面的介绍的例子都是基于这个工具来实现的。

https://github.com/dathlin/ModBusTcpTools/raw/master/download/download.zip

关于该测试工具也是开放源代码的,如果想要查看源代码:https://github.com/dathlin/ModBusTcpTools

技术支持QQ群:592132877

 

准备条件:


在上面的测试工具下载之前,需要一些额外的知识补充,此处不管你是学习什么语言的,对于socket通信层来说,其实是一样的,下面的讲解的内容是直接基于底层的,无关语法的操作。

但是需要你对字节概念非常清晰,一般都是byte数组,一个byte有8个位,这个也要非常的清晰,如果连byte是什么都搞不清楚,那么对本文下面的内容理解会非常的吃力,那么还是建议你再看看计算机原理这些书,对于socket通信,每种语言都有不同的写法,但是所有的语言都有两个共同点,都能实现把数据发送到socket和从socket接收数据,至于这个如果去做,就参照你自己需要使用的语言了,此处不做这方面的说明了。

关于十六进制文本,在本文的下面的内容上,所有的byte字节数组都表示成十六进制形式,比如 FF 10 代表2个字节,一个是255,另一个是16。

byte[] temp = new byte[2];
temp[0] = 0xFF;
temp[1] = 0x10;

如果将上述的temp看作是读取到的线圈的数据,那么转换规则如下:

先将上述数据转化成二进制 : 1111 1111    (第一个byte,我们从高位写到地位)             0001 0000   (第二个byte,我们从高位写到地位)

对应的线圈就是,线圈7-线圈0,,,,第二个byte对应的线圈是,线圈15-线圈8     这里一定要好好理解,从byte上来说,temp[0]是地位,temp[1]是高位,深入到每个byte里面的二进制,高位在前,低位在后。

在C#里等同于下面的代码,和C语言,java也是非常的相近,还算比较好理解。

如果我说,发送 00 00 00 00 00 06 FF 01 00 00 00 01 到socket上去,那么也就是:

byte[] temp = new temp[12];
temp[0] = 0x00;
temp[1] = 0x00;
temp[2] = 0x00;
temp[3] = 0x00;
temp[4] = 0x00;
temp[5] = 0x06;
temp[6] = 0xFF;
temp[7] = 0x01;
temp[8] = 0x00;
temp[9] = 0x00;
temp[10] = 0x00;
temp[11] = 0x01;

socket.Send(temp);

先不要管上面的数据是什么含义,知道上面的代码是啥含义就行了。接下来就是下载上面的测试工具,开始真正的学习modbus tcp协议了!

 

测试工具初始化


先运行Server.exe文件,端口里输入502,然后点击启动服务即可,如下:

技术分享图片

然后运行Client.exe程序,在Ip地址里输入127.0.0.1,端口里输入502,点击配置即可,我们看到,如果你的服务器程序运行在了别的电脑上,甚至是云端,只要客户端的ip修改成服务器的ip,端口号对应上,就可以访问到服务器的数据了。

技术分享图片

特殊测试不用去管,和我们现在学习的东西不一致。

 

功能码详细解释


对于modbus来说,涉及的功能码也就是0x01,0x02,0x03,0x05,0x06,0x0F,0x10了,其实分类来说,就只有两种,线圈和寄存器,也就是位读写和字读写,首先需要清楚的是功能码不一样,对应数据的解析规则也不一样,下面就针对不同的情况来说。

首先说明的是,modbus协议呢,最终目的还是为了实现数据交互,既然是数据交互,那就是包含了数据读和写,我们把我们的想法转化成一串数据,发送给设备(或者叫服务器),它返回一串数据,根据规则解析出来,这样就得到了我们真正想要的数据。下面就来第一个想法实现吧。

另外,在modbus服务器端,数据是使用地址的方式来公开的,这很好理解,服务器端保存了很多数据,你想要访问某个数据肯定需要指定唯一的身份标识,从连续的地址来区分数据是最常用的做法,不仅好理解,还便于扩展,比如你还可以读取连续地址的数据块。如果采用字符串名字来标识数据,就没有这个特点。

对于位操作来说(各种线圈和离散量),一个地址代表了一个bool变量,即 0 和 1,要么通要么断,就好比一些普通的开关。

对于寄存器来说,一个地址代表了2个byte,共有65536种方式,可以满足大多数日常使用了,比如我们读取地址0的寄存器,返回 00 00 及代表寄存器0数据为0,如果返回 01 00 ,那么代表寄存器0数据为 256 

 

功能码0x01:


我不直接上一串数据,这样看着也累,我们从例子出发,现在我们需要读取线圈(离散量)操作,我想读取地址0的线圈是否是通还是断的。我们有了这个功能需求后,就可以根据需求来写出特殊的指令了。根据协议指定,需要填写长度为12的byte数组

byte[0]    byte[1]    byte[2]   byte[3]   byte[4]   byte[5]   byte[6]   byte[7]   byte[8]   byte[9]   byte[10]   byte[11]  

byte[0]    byte[1]   : 消息号---------随便指定,服务器返回的数据的前两个字和这个一样

byte[2]   byte[3]   :modbus标识,强制为0即可

byte[4]   byte[5]    :指示排在byte[5]后面所有字节的个数,也就是总长度-6

byte[6]: 站号,随便指定,00  -- FF 都可以

byte[7] :功能码,这里就需要填入我们的真正的想法了

byte[8]  byte[9]  :起始地址,比如我们想读取地址0的数据,就填 00 00 ,如果我们想读取地址1000的数据,怎么办,填入 03 E8 ,也就是将1000转化十六进制填进去。

byte[10]  byte[11] :指定想读取的数据长度,比如我们就想读取地址0的一个数据,这里就写 00 01,如果我们想读取地址0-999共计一个数据的长度,就写 03 E8。和起始地址是一样的。

 

有了上面的格式之后,接下来我们就按照格式来填写数据吧,我们需要读取地址0的数据,那么指定如下

00 00 00 00 00 06 FF 01 00 00 00 01

消息号设为0,站号FF,功能码01,地址01,长度01:将上面的指令在客户端程序里进行输入,点击发送,这样就在下面的响应框里接收到服务器反馈的数据,我们最终需要的信息就在反馈的数据里了。

技术分享图片

前面是接收到数据的时间,自动忽略,那么返回的数据就是 00 00 00 00 00 04 FF 01 01 00 共计10个字节的数据,ok,这玩意到底是什么意思呢,我们来分别解析下:

byte[0]  byte[1] : 消息号,我们之前写发送指令的时候,是多少,这里就是多少。

byte[2]  byte[3]:必须都为0,代表这是modbus 通信

byte[4]  byte[5]:指示byte[5]后面的所有字节数,你数数看是不是4个?所以这里是00 04,如果后面共有100个,那么这里就是 00 64

byte[6]:站号,之前我们写了FF,那么这里也就是FF

byte[7]:功能码,我们之前写了01的功能码,这里也是01,和我们发送的指令是一致的

byte[8]:指示byte[8]后面跟随的字节数量,因为跟在byte[8]后面的就是真实的数据,我们最终想要的结果就在byte[8]后面

byte[9]:真实的数据,哈哈,这肯定就是我们真正想要的东西了,我们知道一个byte有8位,但是我们只读取了一个位数据,所有这里的有效值只是byte[9]的最低位,二进制为 0000 0000 我们看到最低位为0,所以最终我们读取的地址0的线圈为断。

 

假设我们读取地址10,开始的共10个线圈呢,那么会返回什么?所以我们发送 00 00 00 00 00 06 FF 01 00 0A 00 0A

我们接收到了:00 00 00 00 00 05 FF 01 02 00 00      前面的8个字节的信息参照上面的分析,是一致的,我们就针对后面三个字节着重分析。我们读取了10个位,那么一个字节可以表示8个位,那么我们的结果至少需要2个byte才能表示完,所以最终的数据肯定是2个字节,那么02就是后面的字节数量,也就是真实的数据长度。

最后2个字节就是我们最终的想要的数据了!接下来就是怎么去解析了,我们还是需要先写成二进制先,才能一个一个分析:00 00 二进制如下

   0               0               0             0            0           0           0            0                                  0             0              0            0           0          0          0            0                       

线圈17     线圈16     线圈15    线圈14   线圈13  线圈12  线圈11    线圈10                             无效         无效          无效        无效       无效      无效     线圈19    线圈18

至此我们获取到了我们最终的数据!因为此处服务器都是0,所以所有的线圈都是断,等会可以结合05功能码写线圈进行联合测试。

 

功能码0x02:


这个功能码和上面的一致,在本服务器里不支持这个功能码。发送和解析规则和上面的一致,不再赘述。

 

功能码0x05:


我们先讲解05功能码,这个功能码是实现数据写入,它能实现什么功能呢,我们可以利用这个功能码来指定某个线圈通或断,具体怎么操作呢,有了之前01功能码的经验,下面的代码看起来就顺利多了。

比如我要指定地址0的寄存器为通: 00 00 00 00 00 06 FF 05 00 00 FF 00    前面的含义都是一致的,我们就分析 05 00 00 FF 00

05 是功能码, 00 00 是我们指定的地址,如果我们想写地址1000为通,那么就为 03 E8,至于FF 00是规定的数据,如果你想地址线圈通,就填这个值,想指定线圈为断,就填 00 00 ,其他任何的值都对结果无效。

然后我们看看写入的操作服务器返回了什么 ?  我们看到也是  00 00 00 00 00 06 FF 05 00 00 FF 00   因为在你写入的操作中,是不带读取数据的,所以服务器会直接复制一遍你的指令并返回。

 

下面再举例一些方便理解(我们只需要指定地址及是否通断的情况即可):

写入地址100为通: 00 00 00 00 00 06 FF 05 00 64 FF 00   

写入地址1000为断:00 00 00 00 00 06 FF 05 03 E8 00 00

 

功能码0x0F:


未完待续,,,

 

以上是关于Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试的主要内容,如果未能解决你的问题,请参考以下文章

C# 编写pc与西门子1500通讯,用modbus tcp协议

modbus tcp和modbusrtu的区别

异形Modbus客户端 和 异形modbus服务器之间的通讯 侦听模式的modbus-tcp客户端通讯

基于ModBus-TCP/IT 台达PLC 通讯协议解析

Modbus TCP 示例报文

详解TCP/UDP模式下的MODBUS协议转换