工业数据采集器实战: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 转 RS4851¥5
调试用 Modbus 设备温湿度变送器 RS485 输出1¥35
连接线杜邦线若干1 套¥3
电源5V/1A 适配器1¥8
合计约 ¥76

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

接线说明

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

MAX485 引脚ESP32 引脚说明
DIGPIO17 (TX)数据输入
ROGPIO16 (RX)数据输出
DE + REGPIO4收发控制
VCC5V供电
GNDGND

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. $1

  2. $1

  3. $1

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. $1

  2. $1

  3. $1

问题 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,中间的协议转换逻辑其实就这么几行代码。

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