Modbus通信协议消息分析详解
一、Modbus协议概述
Modbus 是一种串行通信协议,由 Modicon(现为施耐德电气)于 1979 年开发,用于可编程逻辑控制器 (PLC) 之间的通信。经过四十多年的发展,它已成为工业自动化领域事实上的标准通信协议之一。
主要特点:
- 主/从架构:一个主主机和多个从主机,其中主主机发起请求,从主机被动响应。
- 开放协议:无需支付许可费;任何制造商都可以免费实施。
- 多种传输模式:RTU(二进制)、ASCII(文本) TCP(以太网)。
- 支持多种物理层:RS485(最常用)、RS232、TCP/IP(以太网)。
- 清晰的数据模型:四种对象类型——线圈、离散输入、输入寄存器和保持寄存器。
1.1三种协议变体的比较

1.2 协议规范约束

二、Modbus 数据模型和地址规则
2.1 四种类型的操作对象

2.2 地址表示(至关重要!)
不同的制造商采用不同的编码方案。 Modbus 地址这是实际应用中最常见的陷阱:

地址前缀含义(行业标准表示法):
- 从 0 开始:协议地址(十六进制),例如 0x0000
- 4 xxxx:维护寄存器;例如,40001 表示维护寄存器 0 的协议地址。
- 3xxxx:输入寄存器;例如,30001 表示输入寄存器 0 的协议地址。
- 0xxxx:线圈;例如,00001 表示线圈 0 的协议地址。
- 1xxxx:离散输入;例如,10001 表示离散输入 0 的协议地址。
2.3 大数据处理规则
1.1. 每次操作读取的最大寄存器数:125(功能码 0x03/0x04)
2.2. 每次操作读取的最大线圈/离散输入数:2000(功能代码 0x01/0x02)
3.3. 每次操作写入的最大寄存器数:123(功能码 0x10)
4.4. 每次操作写入的最大线圈数:1968(功能代码 0x0F)
5.5. 超出这些限制需要基于块的读写操作;确保交错轮询间隔以避免总线冲突。
三、Modbus RTU消息的详细分析
远程终端单元 (RTU) 是应用最广泛的终端单元。 Modbus传输协议 在工业应用中,采用二进制编码以实现高传输效率并与 RS485/RS232 串行接口兼容。帧之间至少需要 3.5 个字符的静默间隔来区分帧边界。
3.1 RTU的完整框架结构
RTU 中的完整指令数据帧由以下四个部分组成:

3.2 RTU 消息示例 – 读取保持寄存器(功能码 0x03)
[请求消息] 主设备读取从设备 1 的保持寄存器(起始地址 0x0001,读取 3 个寄存器):
01 03 00 01 00 03 54 0B
对请求消息进行逐字节分析:

[响应消息] 从站 1 返回来自三个寄存器的数据:01、03、06、02、2B、00、00、00、64、D5、87
逐字节解析响应消息:

3.3 标准功能代码的详细说明

3.4 各功能代码消息格式的详细说明
(1)读取线圈/读取离散输入(功能码 0x01/0x02)
[请求帧格式](共 8 字节)

[响应帧格式]

【位置分析示例】响应数据由 3 个字节组成(线圈 20–38):

(2)读取保持寄存器/读取输入寄存器(功能码 0x03/0x04)
[请求帧格式](共 8 字节)

[响应帧格式]

(3)写入单个保持寄存器(功能码 0x06)
[请求帧格式](共 8 字节)

[响应帧] 与请求帧相同,用于确认写入成功。
(4)写入多个维护寄存器(功能码 0x10)
[请求帧格式]

[响应帧格式](共 8 字节):[地址][0x10][起始地址(2 字节)][编号(2 字节)][CRC(2 字节)]
(5)写入单个线圈(功能码:0x05)
[请求帧格式](共 8 字节):写入时仅支持两个有效值:
- 0xFF00 – 线圈设置为开启(闭合)
- 0x0000 – 线圈设置为关闭(开路)

[响应帧] 与请求帧相同,用于确认写入成功。
IV. Modbus TCP 消息的详细分析
Modbus TCP 基于以太网 TCP/IP 协议运行,默认使用端口 502。与 RTU 模式不同,Modbus TCP 使用 MBAP(Modbus 应用协议)报头来定义消息边界,并且不需要 CRC 校验(而是依赖于 TCP 协议内置的错误检查机制)。
4.1 MBAP 头部结构(7 字节,包含在所有 TCP 数据包中)

4.2 Modbus TCP 消息示例
[请求消息] 读取从设备 1 的保持寄存器(起始地址 0x0000,读取 3 个值):
00 01 00 00 00 06 01 03 00 00 00 03
逐字段分析请求消息:

[响应消息] 从设备返回来自三个寄存器的数据:
00 01 00 00 00 09 01 03 06 02 2B 00 00 00 64
逐字段解析响应消息:

4.3 RTU 和 TCP 数据包结构的核心区别

五、CRC-16 校验的详细说明
CRC(循环冗余校验)算法是 Modbus RTU 的帧验证机制,旨在检测消息传输过程中的错误。CRC-16 生成多项式为 x¹⁶ + x¹⁵ + x² + 1(等价于 0x8005);实际应用中,通常使用其逆多项式 0xA001。
5.1 CRC-16 计算步骤
6.1. 将16位CRC寄存器初始化为0xFFFF
7.2. 对第一个字节和 CRC 寄存器的低 8 位执行异或运算;将结果存储回 CRC 寄存器的低 8 位。
8.3. 将 CRC 寄存器右移 1 位,高位填充为 0;确定移出的位(LSB):
9.4. 如果移出位为 0:则继续右移一位
10.5. 如果移出位为 1:对 CRC 寄存器和 0xA001 执行异或运算
11.6. 重复步骤 3,直到处理完当前字节的所有 8 位。
12.7. 处理下一个字节,重复步骤 2-6,直到所有字节都处理完毕。13.8. 完成所有字节处理后,交换 CRC 寄存器的高字节和低字节,得到最终校验和。
14.9. 将最终校验和附加到消息末尾,低字节在前,高字节在后。
5.2 CRC-16 计算示例(Python 代码)
以下Python函数可直接用于计算Modbus RTU的CRC-16校验和:
def modbus_crc16(data: bytes) -> bytes:
"""计数 Modbus RTU CRC-16 校验码(返回前两个字节,最低有效位在前)"""
crc = 0xFFFF
对于数据中的每个字节:
CRC ^= 字节
for _ in range(8):
如果 CRC 和 0x0001:
crc = (crc >> 1) ^ 0xA001
别的:
CRC >>= 1
# 先返回低字节,再返回高字节
返回 bytes([crc & 0xFF, (crc >> 8) & 0xFF])
# 给出典型示例:计数 01 03 00 01 00 03 CRC
frame = bytes([0x01, 0x03, 0x00, 0x01, 0x00, 0x03])
crc = modbus_crc16(frame)
print(crc.hex()) # export:540b( 0x54 0x0B)
5.3 消息中 CRC 校验的位置
以请求消息 01 03 00 01 00 03 54 0B 为例:

接收器重新计算前 6 个字节的 CRC 值,并将其与数据包的最后 2 个字节进行比较。如果两者不同,则检测到传输错误,并且不返回响应。
Ⅵ.异常处理和错误代码详情
当从站无法执行来自主站的请求时(例如,由于不支持的功能码或地址超出范围),它会返回一个异常响应帧。异常响应功能码等于原始功能码 + 0x80(即,最高位设置为 1)。
6.1 异常应答格式 (RTU)
[从机地址][功能码 | 0x80][异常码][CRC-16]

6.2 标准异常代码的含义

6.3 异常反应示例
【场景】主站请求从从站 1 读取无效地址的寄存器,从站返回异常:
请求:01 03 00 20 00 01 XX XX(读取地址 0x0020 处的寄存器);
异常响应:01 83 02 C0 F1;
异常响应的逐字节分解:

六、Modbus ASCII 消息简介
Modbus ASCII 模式将所有数据字节编码为两个 ASCII 字符(例如,0x0F 编码为“0F”)。虽然可读性很高,但传输效率较低,主要用于调试目的。
7.1 ASCII 帧结构
完整的ASCII帧格式:

7.2LRC验证算法
计算纵向冗余校验 (LRC) 的步骤:
15.1. 对地址字段、功能码和数据字段的所有字节(不包括起始标记和结束标记)执行二进制求和。
16.2.忽略进位位(如果结果超过 255,则自动循环并仅保留低 8 位)。
17.3. 计算求和结果模 1 的补(即 0xFF − sum)。
18.4. 将补码加 1 得到最终的 LRC 校验码(1 字节)。
19.5. 将 LRC 校验码编码为两个 ASCII 字符,并将它们附加到消息的末尾。
八、实用技巧和常见问题
8.1 字节序(大端/微端)问题
这 Modbus 标准 使用大端字节序(高位字节在前)传输 16 位或更长的数据。某些设备(例如,某些基于 ARM 的设备)使用微端字节序;需要进行字节序转换以避免数值解析错误。

8.2 注册单元大小限制(PDU ≤ 253 字节)

8.3 通信超时和重试配置建议
推荐配置:
- 轮询间隔:设置为从设备最大响应时间的 2-3 倍(通常为 100-500 毫秒)• 超时时间:建议范围为 1000-3000 毫秒,可根据从设备响应速度进行调整
- 重试次数:建议限制为 3 次;如果失败则触发警报
- 多从站轮询:在从站之间安排交错的轮询间隔,以防止总线冲突。• 字节超时:在 RTU 模式下,超过 1.5 个字符时间的字符间隔被视为帧错误。
8.4 推荐的调试工具
附录:通用消息快速参考表
读取线圈(功能代码 0x01)
[请求]:从从站 1 读取起始地址 0(线圈 1)处的数据,获取 8 条线圈状态信息:01 01 00 00 00 08 3D CC
[响应]:八个线圈状态:0x55 (01010101B);
消息:01 01 01 55 94 36
向单个线圈写入数据(功能码 0x05)
[请求] 将地址 0 处的线圈设置为 ON(写入值 0xFF00):
消息:01 05 00 00 FF 00 8C 3A
[请求] 将地址 0 处的线圈设置为关闭(写入值 0x0000):
消息:01 05 00 00 00 00 CD CA
[响应] 与请求帧相同
向单个寄存器写入数据(功能码 0x06):
[请求] 将值 0x1234 写入地址为 0 的寄存器。
消息:01 06 00 00 12 34 XX XX(XX 为 CRC,待计算)。
[响应] 与请求帧相同。
写入多个寄存器(功能码 0x10)
[请求]:从地址 0 开始向两个寄存器写入值 0x1234 和 0xABCD。
消息:01 10 00 00 00 02 04 12 34 AB CD XX XX
[响应]:返回起始地址和写入的寄存器数量。
消息:01 10 00 00 00 02 41 C8
读取输入寄存器(功能码 0x04)
[请求]:从从机 1 读取起始地址 0 处的数据,读取两个输入寄存器
消息:01 04 00 00 00 02 71 CB
[响应]:返回两个输入寄存器值
消息:01 04 04 00 64 01 90 XX XX(返回值:100 和 400)










