请教KepServer与Modbus TCP通讯问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了请教KepServer与Modbus TCP通讯问题相关的知识,希望对你有一定的参考价值。
参考技术A 欧陆 ?我买 康奈德 4~20 ma 数据采集模块 转 MODBUS)TCP
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:
未完待续,,,
以上是关于请教KepServer与Modbus TCP通讯问题的主要内容,如果未能解决你的问题,请参考以下文章
Modbus RTU/ASCll+TCP+OPC电脑模拟+串口调试软件对接方案
本人想通过moxa nport5650实现下位机modbus与上位机wincc通讯,从wincc采集nport里数据该怎么做?