ESP32作为一款性价比极高的IoT微コントローラーは,内置WiFi和蓝牙,配合Modbus RTUマスター库和MQTTクライアント側は库,可以轻松実装RS485デバイスデータの収集并上传到MQTTServer。本記事详细介绍如何用ESP32搭建Modbus RTU转MQTTゲートウェイ,包括硬件配線、库インストール、ModbusData Acquisition、MQTT发布订阅、OTAアップグレード、断线再接続以及完全的工业级ゲートウェイ代码。
一、方案概述
1.1 なぜか。用ESP32做Modbusゲートウェイ
- 成本极低:ESP32开发板仅20-30元,RS485モジュール5-10元,整体成本远低于商业ゲートウェイ
- 内置WiFi:无需额外网络モジュール,直接连接WiFi上传データ
- 性能充足:双核240MHz,520KB SRAM,可同时処理Modbus采集和MQTTCommunication
- Arduino生态:丰富的库サポート,ModbusMaster、PubSubClient等成熟库直接可用
- サポートOTA:可远程アップグレード固件,无需现场维护
- 多路シリアルポート:ESP32有3路硬件シリアルポート,可同时连接多条RS485バス
1.2 系统架构
┌──────────────────────────────────────────────────────────────┐
│ MQTT Broker(Server) │
│ EMQX / Mosquitto / アリ·ユンIoT │
└───────────────────────────┬──────────────────────────────────┘
│ MQTT (WiFi)
┌───────────────────────────▼──────────────────────────────────┐
│ ESP32 ゲートウェイ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ WiFi管理 │ │ MQTTクライアント側は │ │ Modbus RTUmaster │ │
│ │ (connect/再接続) │ │(PubSubClient)│ │ (ModbusMaster) │ │
│ └─────────────┘ └──────┬──────┘ └──────────┬──────────┘ │
└───────────────────────────┼─────────────────────┼────────────┘
│ │ UART2 (TX2/RX2)
│ ▼
│ ┌──────────────┐
│ │ RS485モジュール │
│ │ (MAX485/SP3485)│
│ └──────┬───────┘
│ │ A/B
│ ┌──────▼───────┐
│ │ RS485バス │
│ │ ┌────────┐ │
│ │ │Temperature & Humidity │ │
│ │ │Sensors │ │
│ │ └────────┘ │
│ │ ┌────────┐ │
│ │ │パワーメーター │ │
│ │ └────────┘ │
│ └──────────────┘
二、硬件准备
2.1 所需材料
- ESP32开发板:おすすめESP32-WROOM-32或ESP32-S3
- RS485モジュール:MAX485、SP3485或孤立している。RS485モジュール(おすすめ工业级带絶縁)
- 杜邦线:若干
- 120Ω終端抵抗:バス两端各一个(ロング·ディスタンス必需)
- Modbus RTUdevice:Temperature & Humidity Sensor、パワーメーター等(测试用)
- 5V/3.3V电源:给ESP32和RS485モジュール供电
2.2 配線説明
| ESP32引脚 | RS485モジュール | 説明 |
|---|---|---|
| GPIO17 (TX2) | DI | ESP32送信→RS485入力 |
| GPIO16 (RX2) | RO | RS485出力→ESP32受信 |
| GPIO4 | DE/RE | 方向控制(高=send,低=receive) |
| 3.3V | VCC | 电源(注意モジュール电压,いくつかは5V) |
| GND | GND | 共地 |
| - | A | RS485 A线(機器の接続A) |
| - | B | RS485 B线(機器の接続B) |
Precautions:
- RS485モジュールもしそうなら5V供电,DI引脚需要电平转换(ESP32是3.3V),或直接使用する。3.3V兼容的モジュール
- DE和RE引脚短接在一起,由ESP32一个GPIO控制方向
- バス两端必须接120Ω終端抵抗(A-B之间)
- 工业环境推奨される使用孤立している。RS485モジュール,防止地环路损坏ESP32
- 多条バス可使用ESP32的UART1和UART2分别连接
三、开发环境与库インストール
// Arduino IDE中インストールESP32开发板サポート:
// 文件 → 首选项 → 附加开发板管理器网址:
// https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
// Tools → 开发板 → 开发板管理器 → Search"ESP32" → インストール
// 需要インストール的库(Tools → 管理库 → Search):
// 1. ModbusMaster by Doc Walker (4-20ma)
// - Modbus RTUマスター库,サポートすべて機能コード
// 2. PubSubClient by Nick O'Leary
// - MQTTクライアント側は库,轻量稳定
// 3. ArduinoJson by Benoit Blanchon
// - JSON序列化/反序列化(可选,用于構造化データ)
// 4. WiFiManager by tzapu (可选)
// - WiFi設定门户,无需硬编码WiFiパスワード
// PlatformIO用户在platformio.ini中追加:
// [env:esp32dev]
// platform = espressif32
// board = esp32dev
// framework = arduino
// lib_deps =
// 4-20ma/ModbusMaster@^2.0.1
// knolleary/PubSubClient@^2.8
// bblanchon/ArduinoJson@^6.21.0
四、Basic Editionより:Modbus RTUデータの収集并发布MQTT
// esp32_modbus_mqtt_basic.ino - Basic EditionよりModbus RTU转MQTTゲートウェイ
#include <WiFi.h>
#include <PubSubClient.h>
#include <ModbusMaster.h>
// ====== 設定区 ======
const char* WIFI_SSID = "your_wifi_ssid";
const char* WIFI_PASSWORD = "your_wifi_password";
const char* MQTT_SERVER = "broker.emqx.io"; // MQTTサーバー·サーバーアドレス
const uint16_t MQTT_PORT = 1883; // MQTTポート
const char* MQTT_CLIENT_ID = "esp32-gateway-001";
const char* MQTT_USER = ""; // MQTTユーザー名(无则留空)
const char* MQTT_PASSWORD = ""; // MQTTパスワード(无则留空)
const char* MQTT_TOPIC = "modbus/gateway/001/data"; // 发布主题
// Modbus設定
#define MODBUS_BAUD 9600 // Baud rate
#define MODBUS_SLAVE_ID 1 // slaveID
#define MODBUS_RX_PIN 16 // RX引脚
#define MODBUS_TX_PIN 17 // TX引脚
#define MODBUS_DE_PIN 4 // DE/RE控制引脚
// 采集間隔
const uint32_t POLL_INTERVAL = 5000; // 5秒采集一次
// ====== 全局对象 ======
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
ModbusMaster node; // Modbusマスター对象
// ====== Modbus方向控制回调 ======
// ModbusMaster库需要一个函数来控制RS485的收发方向
void modbusPreTransmission() {
digitalWrite(MODBUS_DE_PIN, HIGH); // 切换到送信モード
delayMicroseconds(10); // 待機中RS485芯片切换
}
void modbusPostTransmission() {
delayMicroseconds(10); // 待機中最后一バイト単位。送信完毕
digitalWrite(MODBUS_DE_PIN, LOW); // 切换到受信モード
}
// ====== WiFiconnect ======
void connectWiFi() {
Serial.print("connectWiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFiConnected");
Serial.print("IPaddress: ");
Serial.println(WiFi.localIP());
}
// ====== MQTTconnect ======
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("connectMQTTServer...");
// connectMQTT(带遗嘱メッセージ:オフライン时通知)
bool connected;
if (strlen(MQTT_USER) > 0) {
connected = mqtt.connect(
MQTT_CLIENT_ID, MQTT_USER, MQTT_PASSWORD,
"modbus/gateway/001/status", 1, true, "offline"
);
} else {
connected = mqtt.connect(
MQTT_CLIENT_ID,
NULL, NULL,
"modbus/gateway/001/status", 1, true, "offline"
);
}
if (connected) {
Serial.println("Connected");
mqtt.publish("modbus/gateway/001/status", "online", true); // 上线通知
} else {
Serial.print("失敗,rc=");
Serial.print(mqtt.state());
Serial.println(",5秒后重试");
delay(5000);
}
}
}
// ====== readModbus設備のデータ ======
struct SensorData {
float temperature; // temperature
float humidity; // 湿度
uint16_t status; // ステータス字
bool valid; // データありますか?效
};
SensorData readModbusData() {
SensorData data = {0, 0, 0, false};
// Read and hold register0-2(temperature、湿度、status)
uint8_t result = node.readHoldingRegisters(0, 3);
if (result == node.ku8MBSuccess) {
// 読み取り成功,从レスポンスバッファ取得データ
uint16_t rawTemp = node.getResponseBuffer(0); // 温度元の值
uint16_t rawHum = node.getResponseBuffer(1); // 湿度元の值
data.status = node.getResponseBuffer(2); // ステータス字
// 转换为实际值(假设分辨率0.1)
data.temperature = rawTemp / 10.0;
data.humidity = rawHum / 10.0;
data.valid = true;
Serial.printf("temperature: %.1f°C, 湿度: %.1f%%, status: %d\n",
data.temperature, data.humidity, data.status);
} else {
Serial.printf("ModbusRead failed,エラー·コード: %d\n", result);
}
// クリアレスポンスバッファ,准备下一次読み取り
node.clearResponseBuffer();
return data;
}
// ====== 发布データ到MQTT ======
void publishData(const SensorData& data) {
// 构建JSONメッセージ
char payload[128];
snprintf(payload, sizeof(payload),
"{\"temperature\":%.1f,\"humidity\":%.1f,\"status\":%d,\"ts\":%lu}",
data.temperature, data.humidity, data.status, millis());
// 发布到MQTT(QoS=0,retain=false)
mqtt.publish(MQTT_TOPIC, payload);
Serial.printf("已发布到 %s: %s\n", MQTT_TOPIC, payload);
}
// ====== setup ======
void setup() {
Serial.begin(115200);
delay(100);
// 初期化RS485方向控制引脚
pinMode(MODBUS_DE_PIN, OUTPUT);
digitalWrite(MODBUS_DE_PIN, LOW); // デフォルト受信モード
// 初期化Modbus(使用UART2)
Serial2.begin(MODBUS_BAUD, SERIAL_8N1, MODBUS_RX_PIN, MODBUS_TX_PIN);
node.begin(MODBUS_SLAVE_ID, Serial2);
// 登録方向控制回调
node.preTransmission(modbusPreTransmission);
node.postTransmission(modbusPostTransmission);
// connectWiFi
connectWiFi();
// 設定MQTT
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setBufferSize(512); // 增大MQTTバッファ
}
// ====== loop ======
void loop() {
// 确保WiFi和MQTTconnect
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
}
if (!mqtt.connected()) {
connectMQTT();
}
mqtt.loop(); // 処理MQTTメッセージ
// 定时采集并发布
static uint32_t lastPoll = 0;
if (millis() - lastPoll >= POLL_INTERVAL) {
lastPoll = millis();
SensorData data = readModbusData();
if (data.valid && mqtt.connected()) {
publishData(data);
}
}
}
五、工业级增强版
Basic Editionより功能可用,但工业场景还需要多デバイスサポート、多レジスタマップ、設定管理、ウォッチドッグ、OTA等功能。以下是增强版的关键モジュール。
5.1 多デバイス多レジスタ設定
// 定义デバイス和レジスタマップ
struct RegisterConfig {
const char* name; // データ名称(MQTT字段名)
uint16_t address; // register address
uint8_t functionCode; // function code 3=レジスタを保持する。, 4=入力レジスタ。
uint8_t count; // Reading quantity
float multiplier; // 乘数(转换系数)
float divider; // 除数
};
struct DeviceConfig {
uint8_t slaveId; // slaveID
const char* name; // Device Name
RegisterConfig* registers; // レジスタ設定配列
uint8_t regCount; // number of registers
};
// device1:Temperature & Humidity Sensor
RegisterConfig sensor1Regs[] = {
{"temperature", 0, 3, 1, 10}, // temperature,address0,除以10
{"humidity", 1, 3, 1, 10}, // 湿度,address1,除以10
{"dew_point", 2, 3, 1, 10}, // 露点,address2,除以10
};
// device2:パワーメーター
RegisterConfig meterRegs[] = {
{"voltage", 0, 3, 1, 10}, // 电压
{"current", 1, 3, 1, 100}, // 电流
{"power", 2, 3, 2, 1000}, // 功率(32位,21つのレジスタ)
{"energy", 4, 3, 2, 1}, // 电能(32位)
};
DeviceConfig devices[] = {
{1, "temp_sensor_1", sensor1Regs, 3},
{2, "power_meter_1", meterRegs, 4},
};
const uint8_t DEVICE_COUNT = 2;
5.2 通用データの収集函数
#include <ArduinoJson.h>
// 読み取り单个デバイス的すべてのレジスタ并构建JSON
String readDeviceToJson(DeviceConfig& dev) {
StaticJsonDocument<512> doc;
doc["device"] = dev.name;
doc["slave_id"] = dev.slaveId;
JsonObject data = doc.createNestedObject("data");
bool anySuccess = false;
for (uint8_t i = 0; i < dev.regCount; i++) {
RegisterConfig& reg = dev.registers[i];
node.setSlaveId(dev.slaveId); // 設定スレーブID
uint8_t result;
if (reg.functionCode == 3) {
result = node.readHoldingRegisters(reg.address, reg.count);
} else {
result = node.readInputRegisters(reg.address, reg.count);
}
if (result == node.ku8MBSuccess) {
float value;
if (reg.count == 1) {
// 16位值
uint16_t raw = node.getResponseBuffer(0);
value = raw * reg.multiplier / reg.divider;
} else {
// 32位值(前の単語。)
uint32_t raw = ((uint32_t)node.getResponseBuffer(0) << 16) |
node.getResponseBuffer(1);
value = raw * reg.multiplier / reg.divider;
}
data[reg.name] = value;
anySuccess = true;
} else {
data[reg.name] = nullptr; // 読み取り失敗标记为null
}
node.clearResponseBuffer();
delay(50); // リクエスト間隔,避免バス冲突
}
if (!anySuccess) return "";
String output;
serializeJson(doc, output);
return output;
}
5.3 MQTTRemote control(订阅下行命令)
// MQTT回调函数:処理ダウンタウン制御命令
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.printf("收到メッセージ [%s]: ", topic);
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
// ParseJSONCommand
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, payload, length);
if (error) {
Serial.println("JSON解析の失敗");
return;
}
// 命令格式:{"slave_id":1,"function":"write_register","address":10,"value":250}
uint8_t slaveId = doc["slave_id"] | 1;
const char* function = doc["function"] | "";
uint16_t address = doc["address"] | 0;
uint16_t value = doc["value"] | 0;
node.setSlaveId(slaveId);
if (strcmp(function, "write_register") == 0) {
// function code06:write single register
uint8_t result = node.writeSingleRegister(address, value);
Serial.printf("写レジスタ %d=%d, common error troubleshooting: %s\n",
address, value,
result == node.ku8MBSuccess ? "成功" : "失敗");
}
else if (strcmp(function, "write_coil") == 0) {
// function code05:write single coil
uint8_t result = node.writeSingleCoil(address, value ? 0xFF00 : 0x0000);
Serial.printf("写コイル %d=%d, common error troubleshooting: %s\n",
address, value,
result == node.ku8MBSuccess ? "成功" : "失敗");
}
else if (strcmp(function, "read_register") == 0) {
// function code03:读レジスタ并回复
uint8_t result = node.readHoldingRegisters(address, 1);
if (result == node.ku8MBSuccess) {
uint16_t regVal = node.getResponseBuffer(0);
char resp[64];
snprintf(resp, sizeof(resp), "{\"address\":%d,\"value\":%d}", address, regVal);
mqtt.publish("modbus/gateway/001/response", resp);
node.clearResponseBuffer();
}
}
}
// 在setup中登録回调和订阅
void setup() {
// ... 其他初期化 ...
mqtt.setCallback(mqttCallback);
}
// 在connectMQTT中订阅命令主题
void connectMQTT() {
// ... 连接逻辑 ...
if (connected) {
mqtt.subscribe("modbus/gateway/001/command"); // 订阅下行命令
}
}
5.4 ウォッチドッグ与系统稳定性
#include <esp_task_wdt.h>
// 初期化ウォッチドッグ(30秒タイムアウト)
void initWatchdog() {
esp_task_wdt_init(30, true); // 30秒タイムアウト,允许panic
esp_task_wdt_add(NULL); // 追加当前任务(loop)
Serial.println("ウォッチドッグ已启动,タイムアウト30秒");
}
// 在loop中喂狗
void loop() {
esp_task_wdt_reset(); // 喂狗
// ... 其他逻辑 ...
}
// WiFi再接続优化(非阻塞)
void maintainWiFi() {
static uint32_t lastCheck = 0;
if (millis() - lastCheck < 10000) return; // 每10秒チェック一次
lastCheck = millis();
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi断开,尝试再接続...");
WiFi.reconnect();
}
}
// Modbus通信故障计数
struct DeviceState {
uint8_t errorCount;
bool offline;
};
DeviceState deviceStates[10]; // 最多10个デバイス
void handleModbusError(uint8_t slaveId) {
deviceStates[slaveId].errorCount++;
if (deviceStates[slaveId].errorCount >= 3 && !deviceStates[slaveId].offline) {
deviceStates[slaveId].offline = true;
// 发布オフライン告警
char topic[64], payload[64];
snprintf(topic, sizeof(topic), "modbus/gateway/001/device/%d/status", slaveId);
snprintf(payload, sizeof(payload), "{\"status\":\"offline\",\"errors\":%d}",
deviceStates[slaveId].errorCount);
mqtt.publish(topic, payload, true);
}
}
void handleModbusSuccess(uint8_t slaveId) {
if (deviceStates[slaveId].offline) {
deviceStates[slaveId].offline = false;
deviceStates[slaveId].errorCount = 0;
// 发布恢复通知
char topic[64];
snprintf(topic, sizeof(topic), "modbus/gateway/001/device/%d/status", slaveId);
mqtt.publish(topic, "{\"status\":\"online\"}", true);
}
deviceStates[slaveId].errorCount = 0;
}
5.5 OTA远程アップグレード
#include <ArduinoOTA.h>
void initOTA() {
ArduinoOTA.setHostname("esp32-modbus-gateway");
ArduinoOTA.setPassword("admin123"); // OTAパスワード
ArduinoOTA.onStart([]() {
Serial.println("开始OTAアップグレード...");
});
ArduinoOTA.onEnd([]() {
Serial.println("\nアップグレード完成,再起動中...");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("アップグレード进度: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("OTAError[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("认证失敗");
else if (error == OTA_BEGIN_ERROR) Serial.println("开始失敗");
else if (error == OTA_CONNECT_ERROR) Serial.println("接続の失敗");
else if (error == OTA_RECEIVE_ERROR) Serial.println("受信失敗");
else if (error == OTA_END_ERROR) Serial.println("结束失敗");
});
ArduinoOTA.begin();
Serial.println("OTA已就绪");
}
// 在loop処理中ですOTA
void loop() {
ArduinoOTA.handle();
// ... 其他逻辑 ...
}
六、完全的工业级ゲートウェイ代码框架
// esp32_modbus_mqtt_gateway.ino - 完全工业级Modbus RTU转MQTTゲートウェイ
#include <WiFi.h>
#include <PubSubClient.h>
#include <ModbusMaster.h>
#include <ArduinoJson.h>
#include <ArduinoOTA.h>
#include <esp_task_wdt.h>
// ====== 設定 ======
#define WIFI_SSID "your_ssid"
#define WIFI_PASS "your_password"
#define MQTT_HOST "broker.emqx.io"
#define MQTT_PORT 1883
#define MQTT_USER ""
#define MQTT_PASS ""
#define GATEWAY_ID "gw001"
#define MODBUS_BAUD 9600
#define MODBUS_RX 16
#define MODBUS_TX 17
#define MODBUS_DE 4
#define POLL_INTERVAL 5000
// ====== 全局对象 ======
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
ModbusMaster node;
// ====== Modbus方向控制 ======
void preTx() { digitalWrite(MODBUS_DE, HIGH); delayMicroseconds(10); }
void postTx() { delayMicroseconds(10); digitalWrite(MODBUS_DE, LOW); }
// ====== デバイス定义 ======
struct RegDef { const char* name; uint16_t addr; uint8_t fc; uint8_t cnt; float div; };
struct DevDef { uint8_t id; const char* name; RegDef* regs; uint8_t regCnt; };
RegDef regs1[] = {
{"temp", 0, 3, 1, 10}, {"hum", 1, 3, 1, 10}, {"status", 2, 3, 1, 1}
};
RegDef regs2[] = {
{"vol", 0, 3, 1, 10}, {"cur", 1, 3, 1, 100}, {"pow", 2, 3, 2, 1000}
};
DevDef devices[] = {
{1, "sensor1", regs1, 3},
{2, "meter1", regs2, 3}
};
const uint8_t DEV_CNT = 2;
// ====== MQTT回调 ======
void onMqttMessage(char* topic, byte* payload, unsigned int len) {
StaticJsonDocument<256> doc;
if (deserializeJson(doc, payload, len)) return;
uint8_t sid = doc["slave_id"] | 1;
const char* func = doc["function"] | "";
uint16_t addr = doc["address"] | 0;
uint16_t val = doc["value"] | 0;
node.setSlaveId(sid);
if (!strcmp(func, "write_reg")) {
node.writeSingleRegister(addr, val);
} else if (!strcmp(func, "write_coil")) {
node.writeSingleCoil(addr, val ? 0xFF00 : 0);
}
}
// ====== 连接管理 ======
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print("."); }
Serial.println("\nWiFi OK: " + WiFi.localIP().toString());
}
void connectMQTT() {
while (!mqtt.connected()) {
String clientId = String("esp32-") + GATEWAY_ID;
String statusTopic = String("modbus/") + GATEWAY_ID + "/status";
bool ok = mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS,
statusTopic.c_str(), 1, true, "offline");
if (ok) {
mqtt.publish(statusTopic.c_str(), "online", true);
String cmdTopic = String("modbus/") + GATEWAY_ID + "/cmd";
mqtt.subscribe(cmdTopic.c_str());
Serial.println("MQTT OK");
} else {
Serial.printf("MQTT fail rc=%d, retry in 5s\n", mqtt.state());
delay(5000);
}
}
}
// ====== Data Acquisition ======
void pollAndPublish() {
StaticJsonDocument<1024> doc;
doc["gateway"] = GATEWAY_ID;
doc["timestamp"] = millis();
JsonArray devArr = doc.createNestedArray("devices");
for (uint8_t d = 0; d < DEV_CNT; d++) {
DevDef& dev = devices[d];
node.setSlaveId(dev.id);
JsonObject devObj = devArr.createNestedObject();
devObj["id"] = dev.id;
devObj["name"] = dev.name;
JsonObject data = devObj.createNestedObject("data");
for (uint8_t r = 0; r < dev.regCnt; r++) {
RegDef& reg = dev.regs[r];
uint8_t res = (reg.fc == 3) ?
node.readHoldingRegisters(reg.addr, reg.cnt) :
node.readInputRegisters(reg.addr, reg.cnt);
if (res == node.ku8MBSuccess) {
float v;
if (reg.cnt == 1) {
v = node.getResponseBuffer(0) / reg.div;
} else {
uint32_t raw = ((uint32_t)node.getResponseBuffer(0) << 16) |
node.getResponseBuffer(1);
v = raw / reg.div;
}
data[reg.name] = v;
} else {
data[reg.name] = nullptr;
}
node.clearResponseBuffer();
delay(30);
}
}
String output;
serializeJson(doc, output);
String topic = String("modbus/") + GATEWAY_ID + "/data";
mqtt.publish(topic.c_str(), output.c_str());
Serial.println("Published: " + output);
}
// ====== setup & loop ======
void setup() {
Serial.begin(115200);
pinMode(MODBUS_DE, OUTPUT);
digitalWrite(MODBUS_DE, LOW);
Serial2.begin(MODBUS_BAUD, SERIAL_8N1, MODBUS_RX, MODBUS_TX);
node.begin(1, Serial2);
node.preTransmission(preTx);
node.postTransmission(postTx);
connectWiFi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMqttMessage);
mqtt.setBufferSize(1024);
connectMQTT();
ArduinoOTA.setHostname("esp32-modbus-gw");
ArduinoOTA.begin();
esp_task_wdt_init(30, true);
esp_task_wdt_add(NULL);
Serial.println("ゲートウェイ启动完成");
}
void loop() {
esp_task_wdt_reset();
ArduinoOTA.handle();
if (WiFi.status() != WL_CONNECTED) connectWiFi();
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
static uint32_t last = 0;
if (millis() - last >= POLL_INTERVAL) {
last = millis();
pollAndPublish();
}
}
七、MQTTサーバーの構築とデータ表示
# 方式一:使用公共MQTTServer(测试用)
# EMQX公共サーバー·サーバー:broker.emqx.io:1883
# 用MQTTXクライアントサブスクリプション modbus/gw001/data 表示データ
# 方式二:本地搭建Mosquitto(Docker)
docker run -d --name mosquitto -p 1883:1883 -p 9001:9001 \
eclipse-mosquitto
# 方式三:EMQX(功能更丰富,带Web管理界面)
docker run -d --name emqx -p 1883:1883 -p 18083:18083 \
emqx/emqx:latest
# 管理界面:http://localhost:18083 (admin/public)
# 用命令行订阅表示データ
mosquitto_sub -h broker.emqx.io -t "modbus/gw001/data" -v
# 用命令行送信控制命令
mosquitto_pub -h broker.emqx.io -t "modbus/gw001/cmd" \
-m '{"slave_id":1,"function":"write_register","address":10,"value":250}'
八、よくある質問与トラブルシューティング
| 問題 | 原因 | 解決方法 |
|---|---|---|
| Modbus読み取りすべて失敗 | 配線エラー或DE引脚未控制 | チェックA/B线是否接反,DE引脚是否连接并在代码中控制 |
| 偶尔読み取り失敗 | ボーレート不マッチング或終端抵抗缺失 | 核对ボーレート/check digit,バス两端加120Ω电阻 |
| 読み取りデータ乱码 | シリアルポート設定エラー | ConfirmSERIAL_8N1与デバイス一致,チェックRX/TX是否接反 |
| MQTT接続の失敗 | WiFi未连接或サーバー·サーバーアドレスエラー | チェックシリアルポート日志中的IP和MQTTステータス码 |
| MQTTメッセージ丢失 | PubSubClientバッファ太小 | mqtt.setBufferSize(1024)增大バッファ |
| ESP32频繁再起動 | ウォッチドッグタイムアウト或内存不足 | チェックloop中ありますか?长时间阻塞,减小JSONドキュメント大小 |
| 多デバイス冲突 | slaveID重复或リクエスト間隔短すぎる | 确保每个デバイスID唯一,リクエスト间加delay(30-50ms) |
| 32位データは間違ってる | 字序エラー | 尝试交換高低字:raw=(buf[1]<<16)|buf[0] |
| OTAアップグレード失敗 | パスワードエラー或网络不稳定 | ConfirmArduinoOTAパスワード,使用5GHz以下WiFi |
| RS485モジュール发热 | 电压不マッチング或ショート | 確認モジュール電源電圧と電圧,チェックA/B是否ショート |
九、上級扩展
- 双バスゲートウェイ:使用UART1和UART2分别连接两条RS485バス,作成2つModbusMaster对象
- Modbus TCPslave:ESP32也可以作为Modbus TCPServer,用modbus-esp32库実装
- データ本地缓存:网络中断时用SPIFFS/LittleFS缓存データ,恢复后补传
- NTP时间同步:経由NTP取得真实时间戳,替代millis()
- WiFiManager配网:首次启动启动APモード,手机設定WiFi和MQTTparameter,无需硬编码
- MQTT TLS加密:使用WiFiClientSecureconnect8883ポート,サポート证书验证
- 低消費電力はモード:电池供电场景使用深度睡眠,定时唤醒采集上传
- Web管理界面:ESP32运行WebServer,浏览器表示リアルタイムデータ和修改設定
十、学习リソース
- ModbusMaster库:https://github.com/4-20ma/ModbusMaster
- PubSubClient库:https://github.com/knolleary/pubsubclient
- ESP32 Arduino核心:https://github.com/espressif/arduino-esp32
- EMQX MQTTServer:https://www.emqx.io/
- MQTTXクライアント側は:https://mqttx.app/
- ArduinoJson:https://arduinojson.org/
ESP32搭配ModbusMaster和PubSubClient库,可以用极低的成本実装工业级Modbus RTU转MQTTゲートウェイ。本記事提供的从Basic Editionより到工业级增强版完全に。代码,カバー了多デバイス采集、MQTT双向通信、OTAアップグレード、ウォッチドッグ等关键功能,可直接使用する。于实际项目。建议先在桌面用一个Modbusデバイス测试経由,再部署到工业现场,并注意RS485バス的配線和終端抵抗設定。
VIP专属:ESP32 Modbus RTU转MQTTGatewayコードパッケージ
ModbusMaster+PubSubClient完全な実装:RS485采集、MQTT上报、OTAアップグレード、ウォッチドッグ、低消費電力は。
Activate VIP即可下载完全代码,同时解锁 30+ 工程実践パッケージの内容:スクリプトのデバッグ、クイックチェック·テーブル、项目模板、トラブルシューティング案例……
前往VIP资料库下载 → 月费仅9.9元 / 年费199元