时间:2021-07-01 10:21:17 帮助过:16人阅读
在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
我不直接上一串数据,这样看着也累,我们从例子出发,现在我们需要读取线圈(离散量)操作,我想读取地址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功能码写线圈进行联合测试。
这个功能码和上面的一致,在本服务器里不支持这个功能码。发送和解析规则和上面的一致,不再赘述。
我们先讲解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
未完待续,,,
Modbus tcp 格式说明 通讯机制 附C#测试工具用于学习,测试
标签:lan 输入 api strong 分享图片 第一个 技术支持 logs 客户端程序