1,引入
ModbusTCP主要用于以太网通信,运行在tcp或udp 上,本文主要探究报文的实际交互解析和使用MThings 软件进行测试。Modbus相关的知识请参考以下文章:
Modbus Tcp请参考以下两篇博客:
MODBUS-TCP全解:有这一篇就够了_modbus tcp-优快云博客
一篇讲懂Modbus TCP协议,这样理解太简单了_modbus tcp协议详解-优快云博客
我们知道Modbus Tcp是不存在CRC校验的,其报文基本格式如下图:
交互过程由modbus tcp master(主站) 发送request 报文,向modbus tcp slave(从站)获取respone文,在请求报文中有6字节的头----事务处理标识符,协议标识符,长度
事务处理标识符(2 Bytes):
表示发送的request或respone次数,接收或发送需要对该值加1,测试发现,改字段并不严格要求加1操作。
协议标识符(2 Bytes):
默认0。
长度(2 Bytes):
标识除前6字节后面字节的总长度,可见其数据部分最长256字节。
我们知道主站可以管理多个从站,那从站之间如何区别啦,这就需要单元标识符了,可以理解为唯一标识一个从站,当然这个值是0用于标识广播地址,1-247为有效地址,248-255为保留地址。
其次就是该报文具体执行什么modbus操作的功能码了,目前用到的有如下:
READ_COILS_FUNCODE = 0x01, (读取线圈状态功能码)
READ_INPUT_COILS_FUNCODE = 0x02,(读取输入线圈状态功能码)
READ_REG_FUNCODE = 0x03, (读取holding保持寄存器功能码)
READ_INPUT_REG_FUNCODE = 0x04,(读取input输入寄存器功能码)
WRITE_SINGLE_COIL_FUNCODE = 0x05,(写单个线圈功能码)
WRITE_SINGLE_REG_FUNCODE = 0x06,(写单个寄存器功能码)
WRITE_MULTI_COILS_FUNCODE = 0x0F,(写多个线圈功能码)
WRITE_MULTI_REG_FUNCODE = 0x10,//16(写多个寄存器功能码)
后续的数据也有一定的格式,后续一一介绍。
2,modbus tcp 报文交互(功能码)
在modbus中,数据存储在四种基本数据类型中:离散输入、线圈、输入寄存器和保持寄存器,离散输入和线圈处理二进制信息,某一bit位代表一种状态ON/OFF, 而输入和保持寄存器处理数值数据,数值可以有整型数据、浮点数数据、字符串数据(ASCCII码传递)。
我们常遇到的就是读/写 寄存器数据,先介绍寄存器的读写。从站地址默认为1
读取holding保持寄存器数据(funcode: 0x03)
单个寄存器 (读取寄存器地址为100的寄存器中的值 --- 000a --> 10)
Request: [00 03 00 00 00 06] 01 03 00 64 00 01
Respone: [00 03 00 00 00 05] 01 03 02 00 0a
多个寄存器(读取寄存器地址为100起的3个寄存器中的值 0008 0009 000a---> 8|9|10)
Request : [00 0c 00 00 00 06] 01 03 00 64 00 03
Respone: [00 0c 00 00 00 09] 01 03 06 00 08 00 09 00 0a
所以请求报文中数据部分格式为:first寄存器地址 + 数量 (4 Bytes)
响应报文中数据部分格式为:数据长度 + n*数据值(2 Bytes) n:表示读取的寄存器数量
写保持寄存器数据:
写单个寄存器(funcode 0x06)(写地址为100的寄存器中的值 --- 0020 --> 32)
Request : [00 0e 00 00 00 06] 01 06 00 64 00 20
Respone: [00 0e 00 00 00 06] 01 06 00 64 00 20
写多个寄存器(funcode 0x10)(从地址为100的寄存器开始写3个寄存器分别32-33-34)
Request : [00 10 00 00 00 0d] 01 10 00 64 00 03 06 00 20 00 21 00 22
Respone: [00 11 00 00 00 06] 01 10 00 64 00 03
可见响应报文数据部分格式为:first寄存器地址 + 寄存器个数 (4 Bytes)
注意:
读取和写的寄存器funcode不一样
读取input输入寄存器 (funcode 0x04 ReadOnly)
单个寄存器
Request : [00 2c 00 00 00 06] 01 04 00 77 00 01
Respone: [00 2c 00 00 00 05] 01 04 02 00 0f
读取多个寄存器:(读取从119地址开始的连续8个寄存器的值)
Request: [00 2d 00 00 00 06] 01 04 00 77 00 08
Respone:[00 2d 00 00 00 13] 01 04 10 00 0f 00 10 00 11 00 07 00 08 00 09 00 0a 00 0b
读取离散线圈(funcode 0x02 ReadOnly)
Request: [00 0f 00 00 00 06] 01 02 00 6f 00 08
Respone:[00 10 00 00 00 04] 01 02 01 55
读取从线圈地址0x6f开始连续8个线圈状态,数据响应结果为0x55,转换为2进制为:01010101
即:从右往左 ->On-Off-On-Off-On-Off...
读or写线圈状态
读取单个线圈值(funcode 0x01)(first 线圈地址0x69,读取1个,值为ON)
Request: [00 07 00 00 00 06] 01 01 00 69 00 01
Respone:[00 07 00 00 00 04] 01 01 01 01
读取多个线圈值(first 线圈地址0x67,读取8个,值0xFF全为ON)
Request: [00 0b 00 00 00 06] 01 01 00 67 00 08
Respone: [00 0b 00 00 00 04] 01 01 01 FF
写单个线圈值(funcode 0x05),线圈状态只有0、1(向线圈地址0x67写1-ON)
Request: [00 18 00 00 00 06] 01 05 00 67 ff 00
Respone: [00 26 00 00 00 06] 01 05 00 67 ff 00
注意:值域为FF 00,如果写0则为00 00.
写多个线圈的值(funcode 0x0F)
Request: [00 12 00 00 00 08] 01 0f 00 67 00 07 01 55
-》其中最后一个01表示后续数据的长度,0x55代表写线圈的状态:1010101 (从低位开始写),0x07表示写线圈的个数
Respone: [00 12 00 00 00 06] 01 0f 00 67 00 07
响应数据最后几位回复线圈的地址+个数。
Request: [00 14 00 00 00 08] 01 0f 00 67 00 07 01 53
Respone:[00 14 00 00 00 06] 01 0f 00 67 00 07
数据是从右到左查看,数据位从0x67开始连续7个线圈值,不足1字节的其余位为0。
3,MThings 测试
后续补充,可参考其他文章