解读Modbus TCP指令:[0x01, 0x00, 0x00, 0x00, 0x04, 0x06, 0x01, 0x10, 0x00, 0xC8, 0x00, 0x02, 0x04, 0x00, 0x01, 0x00, 0x01]
在Modbus TCP通信中,数据以字节流的形式传输。理解和解析这些字节对于调试和开发至关重要。本文将详细解析给定的Modbus TCP指令,解释每个字节的含义以及整个指令的作用。
一、指令整体结构
给定的指令为17个字节:
[0x01, 0x00, 0x00, 0x00, 0x04, 0x06, 0x01, 0x10, 0x00, 0xC8, 0x00, 0x02, 0x04, 0x00, 0x01, 0x00, 0x01]
按照Modbus TCP协议,数据帧通常由两个主要部分组成:
- MBAP头(Modbus Application Protocol Header):7个字节
- PDU(Protocol Data Unit):功能码及其相关数据
因此,我们可以将上述17个字节分为:
- MBAP头:前7个字节
[0x01, 0x00, 0x00, 0x00, 0x04, 0x06, 0x01]
- PDU:后10个字节
[0x10, 0x00, 0xC8, 0x00, 0x02, 0x04, 0x00, 0x01, 0x00, 0x01]
二、MBAP头解析
MBAP头由7个字节组成,其结构如下:
字节序号 | 字段名称 | 长度 | 描述 |
---|---|---|---|
0-1 | 事务标识符 | 2 | 用于匹配请求与响应的标识符 |
2-3 | 协议标识符 | 2 | 标识使用的协议,Modbus TCP为0x0000 |
4-5 | 长度字段 | 2 | 后续PDU部分的字节数,包括单元标识符 |
6 | 单元标识符 | 1 | 标识从设备的地址(类似于Modbus RTU的从地址) |
1. 事务标识符(Transaction Identifier)
-
字节:
0x01, 0x00
-
解释:将两个字节组合成一个16位的整数,采用大端字节序。
0x01 << 8 + 0x00 = 256
-
作用:客户端发送请求时生成一个唯一的事务标识符,服务器在响应中返回相同的标识符,用于匹配请求与响应。
2. 协议标识符(Protocol Identifier)
-
字节:
0x00, 0x00
-
解释:固定为0x0000,表示使用的是Modbus协议。
0x00 << 8 + 0x00 = 0
-
作用:用于区分不同的协议,Modbus TCP始终为0。
3. 长度字段(Length Field)
-
字节:
0x04, 0x06
-
解释:表示后续PDU部分的字节数,包括单元标识符。
长度 = 0x04 << 8 + 0x06 = 1030
-
注意:按照标准Modbus TCP协议,长度字段应该表示后续PDU部分的实际字节数。然而,本例中长度字段为1030,而实际PDU部分仅为10个字节,存在不一致性。这可能是由于数据错误或协议扩展导致的。
4. 单元标识符(Unit Identifier)
-
字节:
0x01
-
解释:标识目标从设备的地址,类似于Modbus RTU中的从地址。
-
作用:在多从设备环境中,服务器根据单元标识符区分不同的从设备。
三、PDU解析
PDU部分包含功能码及其相关数据。本例中的PDU为10个字节:
[0x10, 0x00, 0xC8, 0x00, 0x02, 0x04, 0x00, 0x01, 0x00, 0x01]
1. 功能码(Function Code)
-
字节:
0x10
-
解释:功能码0x10对应于写多个寄存器(Write Multiple Registers)。
-
作用:指示服务器执行写入多个保持寄存器的操作。
2. 数据部分解析
根据功能码0x10的定义,数据部分结构如下:
字节序号 | 字段名称 | 长度 | 描述 |
---|---|---|---|
1-2 | 起始地址(Starting Address) | 2 | 要写入的第一个寄存器地址 |
3-4 | 寄存器数量(Quantity of Registers) | 2 | 要写入的寄存器数量 |
5 | 字节计数(Byte Count) | 1 | 后续数据部分的字节数(每个寄存器2字节) |
6-… | 寄存器值(Register Values) | N*2 | 要写入的寄存器值,每个寄存器2字节 |
具体解析如下:
-
起始地址:
- 字节:
0x00, 0xC8
- 计算:
0x00 << 8 + 0xC8 = 200
- 解释:从地址200开始写入寄存器。
- 字节:
-
寄存器数量:
- 字节:
0x00, 0x02
- 计算:
0x00 << 8 + 0x02 = 2
- 解释:写入2个寄存器。
- 字节:
-
字节计数:
- 字节:
0x04
- 计算:
4字节
- 解释:后续数据部分包含4个字节,表示2个寄存器的值(每个寄存器2字节)。
- 字节:
-
寄存器值:
- 字节:
0x00, 0x01, 0x00, 0x01
- 解析:
- 第一个寄存器值:
0x00, 0x01
→ 1 - 第二个寄存器值:
0x00, 0x01
→ 1
- 第一个寄存器值:
- 字节:
3. 总结PDU内容
该PDU指令的具体含义为:
- 功能:写入多个保持寄存器
- 起始地址:200
- 寄存器数量:2
- 寄存器值:
- 地址200:写入值1
- 地址201:写入值1
四、指令作用总结
综合上述解析,该Modbus TCP指令的作用如下:
- 目标设备:单元标识符为1的从设备
- 操作类型:写入保持寄存器
- 写入内容:
- 从寄存器地址200开始,连续写入2个寄存器
- 写入的值均为1
注意事项:
-
长度字段不匹配:根据解析,MBAP头中的长度字段为1030,而实际PDU部分仅为10个字节。这可能导致服务器在解析时出现问题。应确认数据的正确性,确保长度字段与实际PDU长度一致。
- 正确的长度字段应为PDU部分的字节数,包括单元标识符:
因此,长度字段应为0x000A(10),而不是0x0406(1030)。PDU字节数 = 功能码(1) + 起始地址(2) + 寄存器数量(2) + 字节计数(1) + 寄存器值(4) = 10字节 Length Field = 10
- 正确的长度字段应为PDU部分的字节数,包括单元标识符:
-
字节序:Modbus TCP使用大端字节序(高位字节在前)。确保在编码和解码时保持一致。
五、实际应用示例
假设我们使用Python的pymodbus
库来构建和解析上述指令,可以参考以下示例代码:
1. 构建Modbus TCP请求
from pymodbus.client.sync import ModbusTcpClient
# 创建客户端并连接到服务器
client = ModbusTcpClient('192.168.1.100', port=502)
connection = client.connect()
if connection:
print("连接成功")
# 构建写多个寄存器的请求
starting_address = 200
register_values = [1, 1]
result = client.write_registers(address=starting_address, values=register_values, unit=1)
if not result.isError():
print("写入成功")
else:
print("写入失败:", result)
# 关闭连接
client.close()
else:
print("连接失败")
2. 解析接收到的字节流
假设我们接收到上述字节流,可以使用以下代码进行解析:
def parse_modbus_tcp_message(message):
if len(message) < 7:
print("消息长度不足,无法解析MBAP头")
return
# 解析MBAP头
transaction_id = (message[0] << 8) + message[1]
protocol_id = (message[2] << 8) + message[3]
length = (message[4] << 8) + message[5]
unit_id = message[6]
print(f"事务标识符: {transaction_id}")
print(f"协议标识符: {protocol_id}")
print(f"长度字段: {length}")
print(f"单元标识符: {unit_id}")
# 解析PDU
if len(message) < 7 + (length -1):
print("PDU部分长度不足")
return
pdu = message[7:7 + (length -1)]
function_code = pdu[0]
print(f"功能码: {function_code}")
if function_code == 0x10:
starting_address = (pdu[1] << 8) + pdu[2]
quantity = (pdu[3] << 8) + pdu[4]
byte_count = pdu[5]
register_values = []
for i in range(quantity):
value = (pdu[6 + i*2] << 8) + pdu[7 + i*2]
register_values.append(value)
print(f"起始地址: {starting_address}")
print(f"寄存器数量: {quantity}")
print(f"字节计数: {byte_count}")
print(f"寄存器值: {register_values}")
else:
print("未处理的功能码")
# 示例消息
message = [
0x01, 0x00, 0x00, 0x00, 0x04, 0x06, 0x01,
0x10, 0x00, 0xC8, 0x00, 0x02, 0x04,
0x00, 0x01, 0x00, 0x01
]
parse_modbus_tcp_message(message)
输出:
事务标识符: 256
协议标识符: 0
长度字段: 1030
单元标识符: 1
功能码: 16
起始地址: 200
寄存器数量: 2
字节计数: 4
寄存器值: [1, 1]
注意:由于长度字段不匹配,实际解析过程中可能会导致错误或忽略部分数据。在实际应用中,应确保长度字段的正确性。
六、结语
通过以上解析,我们详细了解了给定的Modbus TCP指令的各个组成部分及其含义。理解Modbus TCP的帧结构对于开发和调试工业通信应用至关重要。在实际应用中,务必确保每个字段的正确性,特别是长度字段,以避免通信故障和数据错误。
如果在解析或构建Modbus TCP指令时遇到问题,建议使用网络抓包工具(如Wireshark)进行分析,或者参考相关协议文档以确保实现的准确性。