Modbus RTU协议在工业物联网中的完全解析与实战 前言工业现场90%以上的PLC、传感器、仪表还在跑Modbus RTU。搞工业物联网Modbus是绕不过去的坎。本文从协议原理到代码实战结合有人物联网USR-G780 DTU彻底讲透。一、Modbus RTU协议帧结构┌─────────┬───────────┬──────────┬──────────┬───────┬───────┐ │ 地址码 │ 功能码 │ 数据区 │ CRC校验 │ │ │ │ 1 Byte │ 1 Byte │ N Bytes │ 2 Bytes │ │ │ └─────────┴───────────┴──────────┴──────────┴───────┴───────┘地址码Slave Address范围1~247每个从机唯一地址0为广播地址所有从机执行但不回复功能码Function Code功能码名称操作对象01读线圈开关量输出02读离散输入开关量输入03读保持寄存器模拟量最常用04读输入寄存器模拟量输入05写单线圈单路控制06写单寄存器单参数设置15写多线圈批量控制16写多寄存器批量参数CRC校验Modbus RTU用CRC-16校验多项式 0xA001。这保证了工业环境强干扰下的数据完整性。def modbus_crc(data): Modbus CRC-16校验 crc 0xFFFF for byte in data: crc ^ byte for _ in range(8): if crc 0x0001: crc (crc 1) ^ 0xA001 else: crc 1 return crc.to_bytes(2, little)二、实战一Python读取Modbus温湿度传感器 使用Python RS485转USB读取Modbus RTU传感器数据 硬件有人物联网USR-G780 DTU透传模式或USB转RS485模块 import serial import struct import time class ModbusRTUMaster: def __init__(self, portCOM3, baudrate9600, timeout1): self.ser serial.Serial( portport, baudratebaudrate, bytesize8, parityN, stopbits1, timeouttimeout ) def read_holding_registers(self, slave_id, start_addr, count): 功能码03读保持寄存器 # 构造请求帧 request bytearray([ slave_id, # 地址码 0x03, # 功能码 (start_addr 8) 0xFF, # 起始地址高字节 start_addr 0xFF, # 起始地址低字节 (count 8) 0xFF, # 寄存器数量高字节 count 0xFF # 寄存器数量低字节 ]) # 追加CRC request modbus_crc(request) # 发送 self.ser.write(request) time.sleep(0.1) # 接收响应 response self.ser.read(5 count * 2) if len(response) 5: raise TimeoutError(从机无响应) # 验证CRC if modbus_crc(response[:-2]) ! response[-2:]: raise ValueError(CRC校验失败) # 解析数据 data_bytes response[3:-2] registers [] for i in range(0, len(data_bytes), 2): reg (data_bytes[i] 8) | data_bytes[i1] registers.append(reg) return registers def write_single_register(self, slave_id, addr, value): 功能码06写单个寄存器 request bytearray([ slave_id, 0x06, (addr 8) 0xFF, addr 0xFF, (value 8) 0xFF, value 0xFF ]) request modbus_crc(request) self.ser.write(request) time.sleep(0.1) response self.ser.read(8) return len(response) 8 def registers_to_float(high_reg, low_reg): 两个16位寄存器 → 32位浮点数大端序 raw (high_reg 16) | low_reg return struct.unpack(f, struct.pack(I, raw))[0] # 使用示例 if __name__ __main__: master ModbusRTUMaster(portCOM3, baudrate9600) # 读取从机地址1的保持寄存器从0x0000开始读4个 regs master.read_holding_registers(slave_id1, start_addr0, count4) # 解析数据假设前两个存温度后两个存湿度 temperature registers_to_float(regs[0], regs[1]) humidity registers_to_float(regs[2], regs[3]) print(f温度: {temperature:.1f}°C) print(f湿度: {humidity:.1f}%RH)三、实战二Modbus RTU → MQTT JSON 转换DTU场景当你用4G DTU如有人USR-G780把Modbus数据发到云端时需要做格式转换 modbus_to_mqtt.py 运行在边缘网关或服务器上将Modbus RTU数据转换为MQTT JSON import json import paho.mqtt.client as mqtt from datetime import datetime # Modbus点位映射表 POINT_MAP { 温度传感器_01: { slave_id: 1, start_addr: 0, count: 4, fields: [ {name: temperature, reg_index: 0, type: float32}, {name: humidity, reg_index: 2, type: float32} ] }, 电表_01: { slave_id: 2, start_addr: 100, count: 6, fields: [ {name: voltage, reg_index: 0, type: float32}, {name: current, reg_index: 2, type: float32}, {name: power, reg_index: 4, type: float32} ] } } def collect_and_publish(master, mqtt_client): 采集所有设备数据并发布MQTT for device_name, config in POINT_MAP.items(): try: regs master.read_holding_registers( slave_idconfig[slave_id], start_addrconfig[start_addr], countconfig[count] ) # 解析字段 payload { device: device_name, timestamp: datetime.now().isoformat(), data: {} } for field in config[fields]: if field[type] float32: value registers_to_float( regs[field[reg_index]], regs[field[reg_index] 1] ) else: value regs[field[reg_index]] payload[data][field[name]] round(value, 2) # 发布MQTT topic f/iot/{device_name}/data mqtt_client.publish(topic, json.dumps(payload), qos1) print(f[OK] {device_name}: {payload[data]}) except Exception as e: print(f[ERR] {device_name}: {e}) # MQTT连接 mqttc mqtt.Client(client_idmodbus_gateway) mqttc.connect(broker.emqx.io, 1883, 60) mqttc.loop_start()四、常见排查方法问题1读不到数据# 用最基础的指令测试从机是否存在 test_frame bytearray([0x01, 0x03, 0x00, 0x00, 0x00, 0x01]) test_frame modbus_crc(test_frame) ser.write(test_frame) response ser.read(7) print(response.hex()) # 有回复说明通信正常没回复检查接线/波特率/地址问题2A/B线接反万用表测量A线通常3-5VB线通常0-1V交换A/B线试试是最快的方法问题3波特率不对常见波特率9600、19200、38400、115200用串口助手逐个试直到读到不乱码的数据问题432位浮点数数值异常注意字节序大端/小端、ABCD/CDAB等排列用已知值反推比如发个25.5看寄存器原始值五、有人物联网DTU的Modbus优化有人物联网USR-G780等DTU内置了Modbus自动采集功能 轮询间隔可配**1-3600秒批量读取一次配置读取多个从机的多个寄存器自动上报采集后自动转MQTT JSON上报无需写代码通过配置工具即可完成