Modbus TCP 协议详解:报文结构、功能码与通信示例
Modbus TCP 协议详解:报文结构、功能码与通信示例
一、Modbus TCP 简介
1.1 什么是 Modbus TCP?
Modbus 最初由 Modicon 公司于 1979 年开发,广泛应用于工业自动化领域。随着以太网和 TCP/IP 技术的发展,Modbus TCP 被用于通过 TCP/IP 网络进行 Modbus 通信。
Modbus 是一种应用层通信协议。常见的传输形式包括 Modbus RTU、Modbus ASCII 和 Modbus TCP。其中,Modbus RTU 和 Modbus ASCII 通常用于串行通信,而 Modbus TCP 通常通过以太网和 TCP/IP 网络传输。
Modbus TCP 常见于以下设备和场景:
- PLC 与上位机之间的数据交换;
- HMI 与控制器之间的通信;
- 工业传感器、仪表和变频器的数据读取;
- 工业网关、嵌入式设备与监控系统之间的通信。
1.2 Modbus TCP 的通信模型
传统 Modbus 串行通信通常采用主从模式;Modbus TCP 则通常使用客户端(Client)和服务器(Server)的概念:
- 客户端主动建立 TCP 连接并发送 Modbus 请求;
- 服务器端接收请求、执行操作并返回响应;
- 一条 TCP 连接可以承载多次 Modbus 请求与响应;
- 服务器端通常使用 TCP 端口
502提供标准 Modbus TCP 服务。
Modbus TCP 的单元标识符(Unit Identifier)可用于标识目标设备,尤其是在通过 TCP 网关访问下游串行设备时。对于直接连接的 TCP 设备,该字段的具体用途取决于设备实现。
二、Modbus TCP 报文结构
Modbus TCP 应用报文通常由两部分组成:
+----------------------+----------------------+
| MBAP | PDU |
+----------------------+----------------------+
| 7 字节 | 长度可变 |
+----------------------+----------------------+
其中:
- MBAP(Modbus Application Protocol Header):Modbus TCP 报文头,固定为 7 字节;
- PDU(Protocol Data Unit):协议数据单元,由功能码和数据组成。
与 Modbus RTU 不同,Modbus TCP 报文不包含 RTU 格式的 CRC 校验字段。数据完整性由 TCP/IP 协议栈负责处理。
三、MBAP 报文头
3.1 MBAP 结构
MBAP 报文头长度固定为 7 字节,包含事务处理标识、协议标识、长度和单元标识符。
| 字段 | 长度 | 说明 |
|---|---|---|
| 事务处理标识(Transaction Identifier) | 2 字节 | 用于将响应与对应请求关联起来 |
| 协议标识(Protocol Identifier) | 2 字节 | Modbus TCP 通常为 0x0000 |
| 长度(Length) | 2 字节 | 表示后续单元标识符与 PDU 的总字节数 |
| 单元标识符(Unit Identifier) | 1 字节 | 标识目标单元或下游设备 |
3.2 事务处理标识
事务处理标识由客户端设置,服务器通常会在响应中原样返回该标识。客户端可借此判断响应对应哪一个请求。
例如:
请求:00 01 ...
响应:00 01 ...
这里的 00 01 是事务处理标识。它可以按请求递增,也可以由客户端使用其他管理策略分配,但应确保在需要匹配的请求之间不会产生歧义。
3.3 协议标识
对于标准 Modbus TCP 报文,协议标识为:
00 00
3.4 长度字段
长度字段是解析 Modbus TCP 报文时最重要的字段之一。
Length 表示紧随该字段之后的字节数,也就是:
[
\text{Length}=1+\text{PDU长度}
]
其中,额外的 1 字节是单元标识符。长度字段本身以及前面的事务处理标识、协议标识不计入 Length。
例如,读取 3 个保持寄存器的请求,其 PDU 包含:
- 功能码:1 字节;
- 起始地址:2 字节;
- 寄存器数量:2 字节。
因此 PDU 长度为 5 字节,Length 为:
[
1+5=6
]
其 MBAP 长度字段为 00 06。
3.5 单元标识符
单元标识符用于标识目标单元。通过 TCP 转串行网关访问 Modbus RTU 设备时,它通常用于指定下游串行设备地址。
如果是直接连接的 Modbus TCP 设备,单元标识符可能被设备忽略,或按厂商定义使用。
四、PDU 与常用功能码
PDU 由功能码和数据字段组成:
+----------+----------------+
| 功能码 | 数据 |
| 1 字节 | 长度可变 |
+----------+----------------+
功能码用于指明本次请求执行的操作。常用标准功能码如下:
| 功能码 | 名称 | 操作对象 | 说明 |
|---|---|---|---|
0x01 |
读线圈 | Coil | 读取一个或多个线圈状态 |
0x02 |
读离散量输入 | Discrete Input | 读取一个或多个离散输入状态 |
0x03 |
读保持寄存器 | Holding Register | 读取一个或多个保持寄存器 |
0x04 |
读输入寄存器 | Input Register | 读取一个或多个输入寄存器 |
0x05 |
写单个线圈 | Coil | 写入一个线圈状态 |
0x06 |
写单个保持寄存器 | Holding Register | 写入一个保持寄存器 |
0x0F |
写多个线圈 | Coil | 一次写入多个线圈状态 |
0x10 |
写多个保持寄存器 | Holding Register | 一次写入多个保持寄存器 |
Modbus 的四类常见数据对象如下:
| 对象 | 典型含义 | 标准操作权限 |
|---|---|---|
| 线圈(Coil) | 开关量输出 | 可读、可写 |
| 离散量输入(Discrete Input) | 开关量输入 | 只读 |
| 输入寄存器(Input Register) | 设备提供的测量值等 | 只读 |
| 保持寄存器(Holding Register) | 配置参数、控制值或其他数据 | 可读、可写 |
具体寄存器或线圈地址的含义由设备协议文档定义。
五、常见功能码报文示例
以下示例使用:
- 事务处理标识:
00 01; - 协议标识:
00 00; - 单元标识符:
01。
为了突出报文结构,示例均以十六进制字节表示。Modbus TCP 不包含 RTU CRC 字段。
5.1 功能码 0x01:读线圈
读取从指定起始地址开始的连续线圈状态。每个线圈状态占 1 bit。
请求格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
功能码 0x01 |
1 字节 |
| 起始地址 | 2 字节 |
| 线圈数量 | 2 字节 |
PDU 长度为 5 字节,因此 MBAP 的 Length 字段为 00 06。
响应格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
| 功能码 | 1 字节 |
| 字节计数 | 1 字节 |
| 线圈状态数据 | 按实际数量计算 |
线圈数据的字节数为:
[
N=\left\lceil\frac{Q}{8}\right\rceil
]
其中 (Q) 为读取的线圈数量。
示例:读取 8 个线圈
读取单元 0x01 中,从地址 0x0000 开始的 8 个线圈。
请求报文:
00 01 00 00 00 06 01 01 00 00 00 08
字段解析:
| 字段 | 数据 |
|---|---|
| 事务处理标识 | 00 01 |
| 协议标识 | 00 00 |
| Length | 00 06 |
| 单元标识符 | 01 |
| 功能码 | 01 |
| 起始地址 | 00 00 |
| 线圈数量 | 00 08 |
假设响应中第一个线圈为 ON,其余线圈为 OFF,则状态数据为 01。
响应报文:
00 01 00 00 00 04 01 01 01 01
其中:
- Length 为
00 04,表示单元标识符 1 字节,加上 PDU 的 3 字节; - 字节计数为
01; - 状态数据为
01。
线圈状态按照每个字节的低位优先排列,即 Bit 0 对应本字节中的第一个线圈。
5.2 功能码 0x02:读离散量输入
功能码 0x02 用于读取连续的离散量输入状态,其报文结构与 0x01 类似。
请求格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
功能码 0x02 |
1 字节 |
| 起始地址 | 2 字节 |
| 输入数量 | 2 字节 |
响应格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
| 功能码 | 1 字节 |
| 字节计数 | 1 字节 |
| 输入状态数据 | 按实际数量计算 |
示例:读取 8 个离散量输入
请求报文:
00 01 00 00 00 06 01 02 00 00 00 08
假设第一个输入为 ON,其余输入为 OFF,响应报文为:
00 01 00 00 00 04 01 02 01 01
其中,功能码 02 表示读取离散量输入,字节计数 01 表示状态数据占 1 字节,数据 01 表示第一个输入为 ON,其余输入为 OFF。
5.3 功能码 0x03:读保持寄存器
功能码 0x03 用于读取连续的保持寄存器。每个寄存器占 2 字节。
请求格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
功能码 0x03 |
1 字节 |
| 起始地址 | 2 字节 |
| 寄存器数量 | 2 字节 |
响应格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
| 功能码 | 1 字节 |
| 字节计数 | 1 字节 |
| 寄存器数据 | 每个寄存器 2 字节 |
读取 (N) 个寄存器时,数据长度为:
[
B=2N
]
示例:读取 3 个保持寄存器
读取单元 0x01 中,从地址 0x0000 开始的 3 个保持寄存器。
请求报文:
00 01 00 00 00 06 01 03 00 00 00 03
假设第一个寄存器的值为 0x0021,后两个寄存器的值为 0x0000,响应报文为:
00 01 00 00 00 09 01 03 06 00 21 00 00 00 00
响应解析:
| 字段 | 数据 |
|---|---|
| 功能码 | 03 |
| 字节计数 | 06 |
| 寄存器 0 | 00 21 |
| 寄存器 1 | 00 00 |
| 寄存器 2 | 00 00 |
Length 为 00 09,因为单元标识符占 1 字节,PDU 占 8 字节(功能码 1 字节、字节计数 1 字节、寄存器数据 6 字节)。
5.4 功能码 0x04:读输入寄存器
功能码 0x04 用于读取连续的输入寄存器。其报文结构与 0x03 类似,主要区别是读取的数据对象不同。
请求格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
功能码 0x04 |
1 字节 |
| 起始地址 | 2 字节 |
| 寄存器数量 | 2 字节 |
响应格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
| 功能码 | 1 字节 |
| 字节计数 | 1 字节 |
| 寄存器数据 | 每个寄存器 2 字节 |
示例:读取 3 个输入寄存器
请求报文:
00 01 00 00 00 06 01 04 00 00 00 03
假设第一个寄存器值为 0x0021,后两个寄存器值为 0x0000,响应报文为:
00 01 00 00 00 09 01 04 06 00 21 00 00 00 00
其中,06 表示后续寄存器数据占 6 字节。
5.5 功能码 0x05:写单个线圈
功能码 0x05 用于将指定线圈设置为 ON 或 OFF。
写入值的定义如下:
| 写入值 | 含义 |
|---|---|
FF 00 |
ON |
00 00 |
OFF |
请求格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
功能码 0x05 |
1 字节 |
| 线圈地址 | 2 字节 |
| 输出值 | 2 字节 |
正常响应会回显请求中的线圈地址和输出值。
示例:将地址 0 的线圈设置为 ON
请求报文:
00 01 00 00 00 06 01 05 00 00 FF 00
正常响应报文:
00 01 00 00 00 06 01 05 00 00 FF 00
5.6 功能码 0x06:写单个保持寄存器
功能码 0x06 用于向指定保持寄存器写入一个 16 位值。
请求格式
| 字段 | 长度 |
|---|---|
| MBAP | 7 字节 |
功能码 0x06 |
1 字节 |
| 寄存器地址 | 2 字节 |
| 写入值 | 2 字节 |
正常响应会回显请求中的寄存器地址和写入值。
示例:将地址 0 的保持寄存器设置为 0x000A
请求报文:
00 01 00 00 00 06 01 06 00 00 00 0A
正常响应报文:
00 01 00 00 00 06 01 06 00 00 00 0A
5.7 功能码 0x0F:写多个线圈
功能码 0x0F 用于一次写入多个连续线圈。每个线圈使用 1 bit 表示,多个线圈状态按位打包。
请求格式
| 字段 | 长度 | 说明 |
|---|---|---|
| MBAP | 7 字节 | 报文头 |
功能码 0x0F |
1 字节 | 写多个线圈 |
| 起始地址 | 2 字节 | 第一个线圈地址 |
| 线圈数量 | 2 字节 | 要写入的线圈总数 |
| 字节计数 | 1 字节 | 线圈数据占用的字节数 |
| 线圈数据 | N 字节 | 按位打包的状态 |
数据字节数为:
[
N=\left\lceil\frac{Q}{8}\right\rceil
]
其中 (Q) 为线圈数量。
响应格式
正常响应包含功能码、起始地址和线圈数量,不会回显全部线圈数据。
示例:将地址 0 开始的 4 个线圈全部设置为 ON
4 个线圈全部置为 ON 时,低 4 位均为 1,数据字节为 0F。
请求报文:
00 01 00 00 00 08 01 0F 00 00 00 04 01 0F
字段说明:
| 字段 | 数据 |
|---|---|
| Length | 00 08 |
| 单元标识符 | 01 |
| 功能码 | 0F |
| 起始地址 | 00 00 |
| 线圈数量 | 00 04 |
| 字节计数 | 01 |
| 线圈数据 | 0F |
正常响应报文:
00 01 00 00 00 06 01 0F 00 00 00 04
5.8 功能码 0x10:写多个保持寄存器
功能码 0x10 用于一次写入多个连续的保持寄存器。每个寄存器占 2 字节。
请求格式
| 字段 | 长度 | 说明 |
|---|---|---|
| MBAP | 7 字节 | 报文头 |
功能码 0x10 |
1 字节 | 写多个保持寄存器 |
| 起始地址 | 2 字节 | 第一个寄存器地址 |
| 寄存器数量 | 2 字节 | 要写入的寄存器数量 |
| 字节计数 | 1 字节 | 寄存器数据总字节数 |
| 寄存器数据 | 2 × N 字节 | 每个寄存器 2 字节 |
写入 (N) 个寄存器时,数据字节数为:
[
B=2N
]
响应格式
正常响应包含功能码、起始地址和寄存器数量,不会再次返回所有寄存器数据。
示例:向地址 0 的一个寄存器写入 0x000F
请求报文:
00 01 00 00 00 09 01 10 00 00 00 01 02 00 0F
字段说明:
| 字段 | 数据 |
|---|---|
| Length | 00 09 |
| 单元标识符 | 01 |
| 功能码 | 10 |
| 起始地址 | 00 00 |
| 寄存器数量 | 00 01 |
| 字节计数 | 02 |
| 寄存器数据 | 00 0F |
正常响应报文:
00 01 00 00 00 06 01 10 00 00 00 01
六、如何根据 MBAP 解析 Modbus TCP 报文
在编写 Modbus TCP 客户端或服务器时,不能简单地假设一次 recv() 就能接收到一整帧报文。
TCP 是面向字节流的协议,不保留应用层报文边界。一次接收可能只得到部分报文,也可能同时得到多帧报文。因此,需要根据 MBAP 中的 Length 字段判断一帧报文何时接收完整。
6.1 报文总长度计算
MBAP 固定为 7 字节,而 Length 字段表示单元标识符和 PDU 的总长度。
因此:
[
\text{报文总长度}=6+\text{Length}
]
这里的 6 字节是事务处理标识(2 字节)、协议标识(2 字节)和 Length 字段本身(2 字节)。
例如,Length 为 0x0006 时:
[
6+6=12\text{ 字节}
]
这与前面的读寄存器请求报文总长度一致。
6.2 推荐的接收流程
- 先接收至少 6 字节,取得事务处理标识、协议标识和 Length 字段。
- 解析 Length,计算整帧总长度
6 + Length。 - 继续接收,直到缓冲区中至少包含指定总长度的字节。
- 取出一帧进行解析,并保留缓冲区中可能属于下一帧的数据。
- 检查协议标识、事务处理标识、功能码和响应长度是否符合预期。
在实现时,还应对 Length 设置合理的上下限,避免异常数据导致缓冲区越界或无限等待。
七、Modbus TCP 与 Modbus RTU 的区别
| 对比项 | Modbus TCP | Modbus RTU |
|---|---|---|
| 常见传输方式 | TCP/IP,以太网 | 串行通信,如 RS-485 |
| 报文头 | 7 字节 MBAP | 从站地址等字段 |
| 帧结束判断 | 根据 MBAP 的 Length 字段 | 通常根据串行时序和帧格式判断 |
| CRC 校验 | 不使用 RTU CRC 字段 | 使用 CRC-16 |
| 事务标识 | 有 | 无 |
| 常见服务端口 | TCP 502 | 不适用 |
| 常见使用场景 | 以太网设备、上位机、网关 | PLC、仪表、串行总线设备 |
虽然两者使用相同或相近的功能码和数据对象,但它们的报文封装方式不同,不能直接把完整的 RTU 报文原样放进标准 Modbus TCP 报文中。
八、异常响应与通信处理
当服务器无法执行请求时,通常会返回异常响应。异常响应功能码为原功能码加上 0x80。
例如,功能码 0x03 的异常响应功能码为:
0x03 | 0x80 = 0x83
常见异常码如下:
| 异常码 | 名称 | 含义 |
|---|---|---|
0x01 |
Illegal Function | 不支持请求的功能码 |
0x02 |
Illegal Data Address | 请求的数据地址无效 |
0x03 |
Illegal Data Value | 请求中的数据值不合法 |
0x04 |
Server Device Failure | 服务器执行操作时发生故障 |
客户端实现中,除了处理异常响应,还应考虑:
- TCP 连接断开或连接超时;
- 请求发送成功但响应未及时到达;
- 事务处理标识与请求不匹配;
- 收到不完整报文或连续多帧数据;
- 返回的功能码、Length 或数据长度不符合预期。
九、总结
Modbus TCP 是在 TCP/IP 网络上传输 Modbus 应用数据的常用方式。理解其报文结构,是编写工业设备客户端、服务器或通信网关的基础。
需要重点掌握以下内容:
- MBAP 报文头固定为 7 字节,包含事务处理标识、协议标识、Length 和单元标识符。
- Length 字段表示单元标识符与 PDU 的总长度,因此报文总长度为
6 + Length。 - PDU由功能码和数据组成,常见功能码包括
0x01、0x03、0x05、0x06、0x0F和0x10。 - Modbus TCP 不使用 RTU CRC 字段,但仍需校验报文结构和响应内容。
- TCP 是字节流协议,程序必须根据 Length 字段组装完整报文,不能假设一次接收对应一帧。
- 地址映射与数据解释应以设备协议文档为准,特别是寄存器编号、单元标识符以及多寄存器数据的字序。
在实际开发中,只要正确处理 MBAP、PDU、TCP 收发缓冲区和异常响应,就可以建立相对可靠的 Modbus TCP 通信程序。
- 点赞
- 收藏
- 关注作者
评论(0)