工业数据采集器实战:Modbus RTU 转 MQTT 协议桥接

工业数据采集器实战:Modbus RTU 转 MQTT 协议桥接

工业现场有海量的老设备还在用 Modbus RTU 协议通过 RS485 总线通信,这些设备本身没有联网能力。但你想把它们的数据推上云端做远程监控怎么办?最实际的方案就是搭一个协议桥接网关——把 Modbus RTU 数据读出来,转成 MQTT 发到云平台。

今天我们就用一块 ESP32 加一个 RS485 模块,从零搭建一个工业数据采集器。不需要 PLC,不需要昂贵的工业网关,成本不到一百块。

为什么需要 Modbus RTU 转 MQTT

Modbus RTU 是工业领域最基础的通信协议之一,大量传感器、仪表、变频器都支持它。但它有个致命缺点:只能在 RS485 总线上跑,传输距离有限,也没法直接上互联网

MQTT 恰恰反过来——轻量、基于 TCP/IP、天然适合云端。把两者打通,等于给传统工业设备装上了"翅膀"。

这个方案特别适合以下场景:

  • 工厂车间温度/压力/流量数据远程采集
  • 楼宇自控系统(BAS)数据上云
  • 农业大棚环境传感器联网
  • 旧设备改造,不想更换整套系统

硬件清单

部件 型号 数量 参考价格
主控板 ESP32-WROOM-32 开发板 1 ¥25
RS485 模块 MAX485 TTL 转 RS485 1 ¥5
调试用 Modbus 设备 温湿度变送器 RS485 输出 1 ¥35
连接线 杜邦线若干 1 套 ¥3
电源 5V/1A 适配器 1 ¥8
合计 约 ¥76

如果你已经有支持 Modbus RTU 的现场设备,温湿度变送器可以省掉。

接线说明

ESP32 到 MAX485 模块的接线很简单:

MAX485 引脚 ESP32 引脚 说明
DI GPIO17 (TX) 数据输入
RO GPIO16 (RX) 数据输出
DE + RE GPIO4 收发控制
VCC 5V 供电
GND GND

RS485 总线端接线:

  • A+ 接所有设备的 A 端(正端)
  • B- 接所有设备的 B 端(负端)
  • 总线两端各接一个 120Ω 终端电阻(线长超过 100 米时必须加)

⚠️ 注意事项:

  • 所有设备必须共地,否则通信不稳定
  • RS485 是差分信号,A/B 不能接反,接反会导致完全读不到数据
  • DE 和 RE 短接后接到同一个 GPIO,高电平发送,低电平接收

软件实现

我们使用 Arduino IDE + 两个关键库:

  • ModbusMaster — Modbus RTU 主站协议栈
  • PubSubClient — MQTT 客户端

先安装库,然后在 Arduino IDE 中搜索安装即可。

完整代码

#include 
#include 
#include 

// WiFi 配置
const char* ssid = "YourWiFi";
const char* password = "YourPassword";

// MQTT 配置
const char* mqtt_server = "broker.emqx.io";
const int mqtt_port = 1883;
const char* mqtt_client_id = "modbus-gateway-001";
const char* mqtt_topic = "factory/sensor/data";

// RS485 引脚
#define RX_PIN    16
#define TX_PIN    17
#define DE_RE_PIN 4

ModbusMaster node;
WiFiClient espClient;
PubSubClient mqtt(espClient);

// 控制 RS485 收发
void preTransmission() {
    digitalWrite(DE_RE_PIN, HIGH);
}

void postTransmission() {
    digitalWrite(DE_RE_PIN, LOW);
}

void setup() {
    Serial.begin(115200);

    // 初始化 RS485 控制引脚
    pinMode(DE_RE_PIN, OUTPUT);
    digitalWrite(DE_RE_PIN, LOW);

    // 初始化 Modbus 主站
    node.begin(1, Serial2);  // 从站地址 1
    node.preTransmission(preTransmission);
    node.postTransmission(postTransmission);

    // 配置 Serial2 的 RX/TX
    Serial2.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);

    // 连接 WiFi
    WiFi.begin(ssid, password);
    Serial.print("Connecting WiFi");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nWiFi connected: " + WiFi.localIP().toString());

    // 连接 MQTT
    mqtt.setServer(mqtt_server, mqtt_port);
    mqtt_connect();
}

void mqtt_connect() {
    while (!mqtt.connected()) {
        if (mqtt.connect(mqtt_client_id)) {
            Serial.println("MQTT connected");
        } else {
            Serial.print("MQTT connect failed: ");
            Serial.println(mqtt.state());
            delay(3000);
        }
    }
}

void loop() {
    if (!mqtt.connected()) {
        mqtt_connect();
    }
    mqtt.loop();

    // 读取 Modbus 寄存器(保持寄存器 0x0000 开始,读 2 个寄存器)
    uint8_t result = node.readHoldingRegisters(0x0000, 2);

    if (result == node.ku8MBSuccess) {
        // 假设寄存器 0 = 温度(×10), 寄存器 1 = 湿度(×10)
        float temperature = node.getResponseBuffer(0x00) / 10.0;
        float humidity = node.getResponseBuffer(0x01) / 10.0;

        // 构建 JSON 消息
        String payload = "{";
        payload += "\"temperature\":" + String(temperature, 1) + ",";
        payload += "\"humidity\":" + String(humidity, 1) + ",";
        payload += "\"timestamp\":" + String(millis());
        payload += "}";

        // 发布到 MQTT
        if (mqtt.publish(mqtt_topic, payload.c_str())) {
            Serial.println("Published: " + payload);
        }
    } else {
        Serial.print("Modbus error: ");
        Serial.println(result, HEX);
    }

    delay(5000);  // 每 5 秒采集一次
}

代码要点说明

  1. 收发切换控制:RS485 是半双工通信,preTransmissionpostTransmission 回调函数控制 DE/RE 引脚,确保发送时切换为发送模式,发送完毕后切回接收模式。

  2. 寄存器地址readHoldingRegisters(0x0000, 2) 中的 0x0000 是寄存器起始地址,需要根据你的设备手册调整。不同厂家定义不同。

  3. 数据解析:示例假设寄存器值是实际值×10(即 235 表示 23.5°C),这是工业传感器的常见做法。你需要根据设备的寄存器定义文档来调整解析逻辑。

MQTT 消息格式

采集器每 5 秒发布一条 JSON 消息到 factory/sensor/data

{
  "temperature": 23.5,
  "humidity": 65.2,
  "timestamp": 123456789
}

在云端你可以用 Node-RED、InfluxDB + Grafana,甚至直接用 Home Assistant 订阅这个 topic 来做数据可视化和告警。

常见问题排查

问题 1:Modbus 读取返回错误码

这是最常见的坑。错误码对照表:

错误码 含义 排查方向
0xE2 无效应答 检查波特率、从站地址是否正确
0xE4 超时 检查 A/B 线是否接反,终端电阻是否到位
0xE6 数据校验失败 检查串口参数(8N1),线缆质量

排查步骤:

  1. 先用 USB-RS485 转换器 + Modbus Poll 软件测试你的设备,确认设备本身没问题
  2. 再用万用表测 RS485 总线 A-B 之间的电压:发送时应为 2~6V,空闲时接近 0V
  3. 检查波特率必须与设备一致(常见为 9600 或 19200)

问题 2:MQTT 连接反复断开

  • 确认 ESP32 的 WiFi 信号强度足够(-70dBm 以上)
  • 检查 MQTT broker 是否开启了匿名连接(broker.emqx.io 支持匿名,但企业环境可能需要用户名密码)
  • 如果数据量大,减小 mqtt.setBufferSize() 的值或增加 mqtt.setKeepAlive() 时间

问题 3:数据跳变严重

  • RS485 总线过长时,信号反射会导致数据异常,检查终端电阻(120Ω)是否在总线两端正确安装
  • 确认所有设备的共地连接可靠
  • 软件层面可以加一个简单滤波:连续读 3 次取中值,或者用移动平均

问题 4:ESP32 频繁重启

可能是看门狗触发的。常见原因:

  • loop() 中有长时间阻塞操作(比如 WiFi 连接超时太久)
  • 堆栈溢出,尝试在 setup() 中增加 ESP.getFreeHeap() 监控内存
  • 建议在 Modbus 读取失败时增加重试次数限制(比如最多重试 3 次)

进阶扩展

完成基本采集后,你还可以继续扩展:

  • 多从站轮询:修改 node.begin() 的从站地址,用数组轮询多个设备
  • 断线缓存:添加 SD 卡模块,网络断开时本地缓存数据
  • OTA 升级:启用 ESP32 OTA 功能,远程更新固件
  • TLS 加密:MQTT 连接启用 TLS,确保数据传输安全

总结

用 ESP32 + RS485 模块搭建 Modbus RTU 转 MQTT 网关,成本低、部署快、维护简单。对于中小规模的工业物联网改造项目,这比买商业工业网关划算得多。核心思路就是:用 ModbusMaster 读寄存器数据,用 PubSubClient 发 MQTT,中间的协议转换逻辑其实就这么几行代码。

希望这篇博客文章对您有所帮助!