【物联网-ModBus-TCP/IP】
物联网-ModBus-TCP/IP
■ 1. ModBus-TCP/IP学习链接
■ 2. ModBus-TCP/IP简介
RS-485常与MODBUS协议结合使用,MODBUS协议定义了数据帧的结构和通信规则。
ModBus 网络只有一个主机,所有通信都由他发出
网络可支持 247 个之多的远程从属控制器
Modbus协议包括ASCII、RTU、TCP等,并没有规定物理层。此协议定义了控制器能够认识和使用的消息结构
Modbus的ASCII、RTU协议规定了消息、数据的结构、命令和就答的方式。
数据通讯采用 Maser/Slave方式,
Master端发出数据请求消息,
Slave端 接收到正确消息后就可以发送数据到Master端以响应请求;
Master端 也可以直接发消息修改Slave端的数据,实现双向读写。
Modbus协议需要对数据进行校验,串行协议中除有奇偶校验外,ASCII模式采用LRC校验,RTU模式采用16位CRC校验。
一种基于以太网TCP/IP的应用层协议——Modbus TCP/IP协议。
Modbus TCP是Modbus协议在以太网上的一种实现,它保留了Modbus协议的核心功能和数据模型,但对消息封装进行了调整,使其符合TCP/IP的要求。
在进行下一步之前我们首先对Modbus中的一些概念做一个了解。

■ 2.1 Modbus 存储区
存储的数据类型分为布尔量和寄存器
==Modbus规定了4个存储区,分别是0、1、3、4 ==
| 所属区 | 名称 | 地址范围(5位) | 数据类型 | 存储类型 | 通讯类型 | S7-200 SMART 数据区 |
|---|---|---|---|---|---|---|
| 0区 | 输出线圈(D0 ) | 00001-09999 | 位 | 布尔量(0-1) | 读/写 | Q0.0-Q31.7 |
| 1区 | 输入线圈(DI) | 10001-19999 | 位 | 布尔量(0-1) | 读 | I0.0-I31.7 |
| 2区 | 输出寄存器(A0) | 20001-29999 | 字 | 寄存器(16位) | 读/写 | AIW0-AIW110 |
| 3区 | 输入寄存器(AI) | 30001-39999 | 字 | 寄存器 16位) | 读 | T-T+2*(xxxx-1) |
■ 2.2 Modbus 地址映射到 CPU 地址
| Modbus 地址 | CPU 地址 |
|---|---|
| 00001 | Q0.0 |
| 00002 | Q0.1 |
| …… | …… |
| 10001 | I0.0 |
| 10002 | I0.1 |
| …… | …… |
| 30001 | AIW0 |
| 30002 | AIW2 |
| …… | …… |
| 40001 | Vx(保持寄存器起始地址) 例:VW0 |
| 40002 | Vx+2 =(保持寄存器起始地址+2)例:VW2 |
| …… …… |
■ 2.3 操作对象
| 对象 | 含义 |
|---|---|
| 线圈 | PLC的输出位,开关量,在Modbus中可读可写 |
| 离散量 | PLC的输入位,开关量,在Modbus中只读 |
| 输入寄存器 | PLC中只能从模拟量输入端改变的寄存器,在Modbus中只读 |
| 保持寄存器 | PLC中用于输出模拟量信号的寄存器,在Modbus中可读可写 |
■ 2.4 功能码
| 项目 | 功能码 | 说明 |
|---|---|---|
| 读操作 | ||
| 0x01: | 读输出线圈状态。 | |
| 0x02: | 读输入线圈状态。 | |
| 0x03: | 读输出寄存器的值。 | |
| 0x04: | 读输入寄存器的值 | |
| 写操作 | ||
| 0x05: | 写单个线圈状态。 | |
| 0x06: | 写单个寄存器的值。 | |
| 0x0F: | 写多个线圈状态。 | |
| 0x10: | 写多个寄存器的值。 |

■ 2.5 角色
主站(Master)角色:客户端(Client),主动发起通信请求。
从站(Slave)角色:服务器(Server)被动响应请求。
■ 2.6 字节序: Modbus使用大端序(Big-Endian)
Modbus使用大端序(Big-Endian)
■ 2.7 错误码

■ 3. Modbus TCP/IP的报文帧
ModbusTCP的数据帧可分为两部分:MBAP+PDU
MBAP:包含事务标识符、协议标识符、消息长度、设备地址
Modbus PDU:原始Modbus协议的数据部分,包括功能码、数据地址和数据值。

■ MBAP报文头
| 事务处理标识 | 协议标识 | 长度 | 单元标识符(设备地址) |
|---|---|---|---|
| 2字节 | 2字节 | 2字节 | 1字节 |
| 内容 | 含义 |
|---|---|
| 事务处理标识 | 可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文 |
| 协议标识符 | 00 00 表示Modbus TCP协议 |
| 长度 | 表示接下来的数据长度,单位为字节。 |
| 单元标识符 | 可以理解为设备地址 |
■ 帧结构PDU
PDU由功能码+数据组成。功能码为1字节,数据长度不定,由具体功能决定。
■ ADU详细结构
■ 0x01:读线圈
在从站中读连续线圈状态,ON=1,OFF=0。
请求:
| MBAP | 功能码 | 起始地址H 起始地址L | 线圈数量H 线圈数量L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
响应:
| MBAP | 功能码 | 线圈数据长度 | 数据 |
|---|---|---|---|
| 7字节 | 1字节 | 1字节 | 线圈数据 |
这是一个请求读线圈输出20-38的实例:

实验一:如:数据长度为0x01个字节,数据为0x01,第一个线圈为ON,其余为OFF。
主站(客户端):发送 00 01 00 00 00 06 01 01 00 00 00 08
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
01 功能码 0x01:读线圈
00 00 起始地址
00 08 线圈数量 (位)
主站(客户端):接收 00 01 00 00 00 04 01 01 01 01
00 01 事务标识符
00 00 协议标识符
00 04 后面报文长度
01 地址
01 功能码 0x01:读线圈
01 线圈数据长度 线圈数据长度=1+(线圈数量-1)/8
01 数据
实验二:自己验证OK
■ 0x02:读离散量输入
在从站中读连续离散量输入状态,ON=1,OFF=0。
请求:
| MBAP | 功能码 | 起始地址H 起始地址L | 离散量输入数量H 离散量输入数量L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
响应:
| MBAP | 功能码 | 离散量输入数据长度 | 数据 |
|---|---|---|---|
| 7字节 | 1字节 | 1字节 长度=1+(离散量输入数量-1)/8 | 离散量输入数据 |
这是一个请求读取离散量输入197-218的实例:

实验一:如:在从站(服务器)0x01中,读取开始地址为0x0000的线圈数据,读0x0008位。
主站(客户端):发送 00 01 00 00 00 06 01 02 00 00 00 08
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
02 功能码 0x02:读离散量输入
00 00 起始地址
00 08 离散量输入数量 多少位
主站(客户端):接收 00 01 00 00 00 04 01 02 01 01
00 01 事务标识符
00 00 协议标识符
00 04 后面报文长度
01 地址
02 功能码 0x02:读离散量输入
01 数据长度 数据长度=1+(离散量输入-1)/8
01 数据
实验二:自己验证OK
在这里插入代码片
实验一:
如:在从站(服务器)0x01中,读取开始地址为0x0000的线圈数据,读0x0008位。
主站(客户端):发送 00 01 00 00 00 06 01 02 00 00 00 08
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
02 功能码 0x02:读离散量输入
00 00 起始地址
00 08 离散量输入数量
主站(客户端):接收 00 01 00 00 00 04 01 02 01 01
00 01 事务标识符
00 00 协议标识符
00 04 后面报文长度
01 地址
02 功能码 0x02:读离散量输入
01 数据长度 长度=1+(离散量输入数量-1)/8
01 数据
实验二:自己验证OK
在这里插入代码片
■ 0x03:读保持寄存器
请求
| MBAP | 功能码 | 起始地址H 起始地址L | 寄存器数量H 寄存器数量L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
响应:
| MBAP | 功能码 | 数据长度 | 数据 |
|---|---|---|---|
| 7字节 | 1字节 | 1字节 数据长度=寄存器数量*2 | 数据 |
==这是一个请求读寄存器108-110的实例: ==

实验一:如:在从站0x01中,读取开始地址为0x0000的保持寄存器数据,读0x003个
主站(客户端):发送 00 01 00 00 00 06 01 03 00 00 00 03
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
03 功能码 0x03:读保持寄存器
00 00 起始地址
00 03 寄存器数量
主站(客户端):接收 00 01 00 00 00 09 01 03 06 00 21 00 00 00 00
00 01 事务标识符
00 00 协议标识符
00 09 后面报文长度
01 地址
03 功能码 0x03:读保持寄存器
06 数据长度 数据长度=寄存器数量*2
00 21 00 00 00 00 数据
实验二:自己验证OK
在这里插入代码片
■ 0x04:读输入寄存器
请求
| MBAP | 功能码 | 起始地址H 起始地址L | 寄存器数量H 寄存器数量L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
响应:
| MBAP | 功能码 | 数据长度 | 数据 |
|---|---|---|---|
| 7字节 | 1字节 | 1字节 | 数据 |
==这是一个请求读输入寄存器9的实例: ==

实验一:如:在从站0x01中,读取开始地址为0x0000的输入寄存器数据,读0x003个
主站(客户端):发送 00 01 00 00 00 06 01 04 00 00 00 03
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
04 功能码 0x04:读输入寄存器
00 00 起始地址
00 03 寄存器数量
主站(客户端):接收 00 01 00 00 00 09 01 04 06 00 21 00 00 00 00
00 01 事务标识符
00 00 协议标识符
00 09 后面报文长度
01 地址
04 功能码 0x04:读输入寄存器
06 数据长度 数据长度=寄存器数量*2
00 21 00 00 00 00 数据
实验二:自己验证OK
在这里插入代码片
■ 0x05:写单个线圈
请求
| MBAP | 功能码 | 线圈地址H 线圈地址L | 输出值H 输出值L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 0xFF00 请求输出为ON, 0x0000 请求输出为OFF |
响应:
| MBAP | 功能码 | 线圈地址H 线圈地址L | 输出值H 输出值L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
==这是一个请求写线圈173为ON的实例: ==

实验一:如:在从站(服务器)0x01中,将地址为0x0000 的线圈设置为ON
主站(客户端):发送 00 01 00 00 00 06 01 05 00 00 FF 00
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
05 功能码 0x05:写单个线圈
00 00 起始地址
FF 00 输出值 0xFF00请求输出为ON, 0x000请求输出为OFF
主站(客户端):接收 00 01 00 00 00 06 01 05 00 00 FF 00
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
05 功能码 0x05:写单个线圈
00 00 起始地址
FF 00 输出值 0xFF00请求输出为ON, 0x000请求输出为OFF
实验二:自己验证OK
在这里插入代码片
■ 0x06:写单个寄存器
在主站(客户端)将从站(服务器)中的一个保持寄存器设置
请求:
| MBAP | 功能码 | 保持寄存器地址H 保持寄存器地址L | 设置值H 设置值L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
响应:
| MBAP | 功能码 | 保持寄存器地址H 保持寄存器地址L | 设置值H 设置值L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
这是一个请求将十六进制00 03写入寄存器2的实例:

实验一:如:在从站(服务器)0x01中,将地址为0x0000的保持寄存器设置为0x000A。
主站(客户端):发送 00 01 00 00 00 06 01 06 00 00 00 0A
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
06 功能码 0x06:写单个保持寄存器
00 00 保持寄存器地址
00 0A 设置值
主站(客户端):接收 00 01 00 00 00 06 01 06 00 00 00 0A
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
06 功能码 0x06:写单个保持寄存器
00 00 保持寄存器地址
00 0A 设置值
实验二:自己验证OK
主站:修改 42008地址 word 修改为88
只能主站(客户端)修改值后同步到从站(服务器),从站(服务器)修改后没有同步到主站
主站(客户端):发送 00 00 00 00 00 06 01 06 07 D7 00 58
//42008 WORD 修改为88
00 00 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
06 功能码 0x06:写单个保持寄存器
07 D7 寄存器地址 0x07D7 = 2007
00 58 设置值 0x0058 = 88
主站(客户端):接收 00 00 00 00 00 06 01 06 07 D7 00 58
00 00 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
06 功能码 0x06:写单个保持寄存器
07 D7 寄存器地址 0x07D7 = 2007
00 58 设置值 0x0058 = 88
■ 0x0F:写多个线圈
请求
| MBAP | 功能码 | 线圈起始地址H 线圈起始地址L | 线圈数H 线圈数L | 线圈输出字节长度 | 线圈输出值 |
|---|---|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 | 1字节 线圈输出字节长度=1+(线圈数-1)/8 | (线圈输出字节长度)个字节 |
注意:线圈输出字节长度=1+(线圈数-1)/8
响应:
| MBAP | 功能码 | 线圈起始地址H 线圈起始地址L | 线圈数H 线圈数L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 |
这是一个请求从线圈20开始写入10个线圈的实例:

实验一:如:在从站(服务器)0x01中,将以地址0x0000开始连续的4个线圈设置为ON
主站(客户端):发送 00 01 00 00 00 08 01 0F 00 00 00 04 01 0F
00 01 事务标识符
00 00 协议标识符
00 08 后面报文长度
01 地址
0F 功能码
00 00 线圈起始地址
00 04 线圈数 (位)
01 线圈输出字节长度 线圈输出字节长度=1+(线圈数-1)/8
0F 线圈输出值
主站(客户端):接收 00 01 00 00 00 06 01 0F 00 00 00 04
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
0F 功能码
00 00 线圈起始地址
00 04 线圈数(位)
实验二:自己验证OK
在这里插入代码片
■ 0x10:写多个保持寄存器
请求
| MBAP | 功能码 | 寄存器起始地址H 寄存器起始地址L | 寄存器数量H 寄存器数量L | 数据字节长度 | 寄存器数据 |
|---|---|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 2字节 | 1字节 数据字节长度=寄存器数量×2 | (数据字节长度)个字节 |
响应:
| MBAP | 功能码 | 寄存器起始地址H 寄存器起始地址L | 寄存器数量H 寄存器数量L |
|---|---|---|---|
| 7字节 | 1字节 | 2字节 | 1字节 |
==这是一个请求将十六进制00 0A和01 02写入以2开始的两个寄存器的实例: ==

实验一:如:在从站(服务器)0x01中,向起始地址为0x0000,数量为0x0001的寄存器写入数据,数据长度为0x02,数据为0x000F
主站(客户端):发送 00 01 00 00 00 09 01 10 00 00 00 01 02 00 0F
00 01 事务标识符
00 00 协议标识符
00 09 后面报文长度
01 地址
10 功能码
00 00 寄存器起始地址
00 01 寄存器数量
02 寄存器数据字节长度 寄存器数据字节长度=寄存器数量×2
00 0F 寄存器数据
主站(客户端):接收 00 01 00 00 00 06 01 10 00 00 00 01
00 01 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
10 功能码
00 00 寄存器起始地址
00 01 寄存器数量
实验二:自己验证OK
在这里插入代码片
■ 4. Modbus TCP/IP 数据分析

主站(客户端):发送数据去读取从站数据 读取长度10个寄存器)并接收
Tx: --------------
00000000000601030000000A
Rx: --------------
(29)0000000000170103140001000A0014001E00280032003C00460050005A
Tx: --------------
00 00
00 00
00 06 后面报文长度
01 地址
03 功能码
00 00 起始地址
00 0A 读取长度 0xA=10个寄存器
Rx: --------------
00 00 事务标识符
00 00 协议标识符
00 17 后面报文长度 0x17 = 23个字节
01 地址
03 功能码
14 长度 数据的长度 0x14 = 20字节 和上面读取长度10对应上
00 01 00 0A 00 14 00 1E 00 28 00 32 00 3C 00 46 00 50 00 5A 寄存器的的值
从站(服务器):接收数据分析 并回复
Rx:00 00 00 00 00 06 01 03 00 00 00 0A
Tx:00 00 00 00 00 17 01 03 14 00 01 00 0A 00 14 00 1E 00 28 00 32 00 3C 00 46 00 50 00 5A
Rx:--------------
00 00 事务标识符
00 00 协议标识符
00 06 后面报文长度
01 地址
03 功能码
00 00 起始地址
00 0A 读取长度10个寄存器 一个寄存器2个字节 所以接收回来是10*2字节
Tx:--------------
00 00 事务标识符
00 00 协议标识符
00 17 后面报文长度 0x17 = 23个字节
01 地址
03 功能码
14 长度 数据的长度 0x14 = 20字节 和上面读取长度10对应上
00 01 00 0A 00 14 00 1E 00 28 00 32 00 3C 00 46 00 50 00 5A 寄存器的的值
更多推荐
所有评论(0)