工业现场有海量的老设备还在用 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 秒采集一次
}
代码要点说明
-
收发切换控制:RS485 是半双工通信,
preTransmission和postTransmission回调函数控制 DE/RE 引脚,确保发送时切换为发送模式,发送完毕后切回接收模式。 -
寄存器地址:
readHoldingRegisters(0x0000, 2)中的0x0000是寄存器起始地址,需要根据你的设备手册调整。不同厂家定义不同。 -
数据解析:示例假设寄存器值是实际值×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),线缆质量 |
排查步骤:
- 先用 USB-RS485 转换器 + Modbus Poll 软件测试你的设备,确认设备本身没问题
- 再用万用表测 RS485 总线 A-B 之间的电压:发送时应为 2~6V,空闲时接近 0V
- 检查波特率必须与设备一致(常见为 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,中间的协议转换逻辑其实就这么几行代码。
希望这篇博客文章对您有所帮助!