ESP32は、Wi-FiとBluetoothを内蔵した費用対効果の高いIoTマイクロコントローラで、Modbus RTUホストライブラリとMQTTクライアントライブラリと組み合わせて、RS 485デバイスからのデータ収集とMQTTサーバーへのアップロードを容易にします。この記事では、ハードウェア配線、ライブラリインストール、Modbusデータ収集、MQTTパブリッシュサブスクリプション、OTAアップグレード、ブレークダウン再接続、完全な産業用ゲートウェイコードなど、ESP32を使用したModbus RTUからMQTTゲートウェイの構築方法について詳しく説明します。
I.プログラムの概要
1.1 なぜか。用ESP32做Modbusゲートウェイ
- 非常に低コストESP32開発ボードはわずか20-30元、RS 485モジュール5-10元、全体的なコストは商用ゲートウェイよりもはるかに低いです。
- 内蔵WiFi:无需额外网络モジュール,直接连接WiFi上传データ
- 十分なパフォーマンスデュアルコア240MHz、520KB SRAM、Modbus取得とMQTT通信を同時に処理
- Arduinoの生態系:丰富的库サポート,ModbusMaster、PubSubClient等成熟库直接可用
- OTAのサポート:ファームウェアをリモートでアップグレードでき、オンサイトメンテナンス不要
- マルチチャネルシリアルポート:ESP32有3路硬件シリアルポート,可同时连接多条RS485バス
1.2システムアーキテクチャ
�������������
│ │ │ MQTT Brokerサーバ │ │ │
│ │ │ EMQX / Mosquitto /Aliyun IoT │ │ │
└─ ── ── ── ── ── ── ── ── ── ── ── ── ── ┬ ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ── ┘
│ MQTT WiFi
● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ●
│ │ │ ESP32ゲートウェイ │ │ │
│ ― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ― ―
│ │ WIFI管理 ● MQTTクライアント● Modbus RTUマスター │ │ │ │
│ │接続/再接続│ PubSubClient │ ModbusMaster │ │ │ │
│
└─ ── ── ── ── ── ── ── ── ── ── ── ── ── ┼ ─ ── ── ── ── ── ── ── ── ── ── ┼ ── ── ── ── ── ── ┘
│ │ │ │ UART2 2/RX2
│ │ │ ▼ ▼ ▼
│ │ │ ‥ ― ― ― ― ― ― ― ― ― ―
│ │ │ RS 485モジュール │ │ │
│ │ │ MAX485/SP3485
│ │ │ ― ― ― ― ― ― ― ― ― ― ― ― ―
│ │ │ │ A/Bの
│ │ │ ‥ ── ── ── ── ── ── ── ── ── ── ── ──
│ │ │ RS 485のバス │ │ │
│ │ │ │ ‥ ── ── ── ── ── ── ── ── │
│ │ │ │ │ │温湿度 │ │ │ │
│ │ │ │ │ │センサー │ │ │ │
│ │ │ │ ‥ ‥ ‥ ‥ ‥ ‥ ‥ ‥ ‥ │
│ │ │ │ ‥ ── ── ── ── ── ── ── ── │
│ │ │ │ │電気メーター │
│ │ │ │ ‥ ‥ ‥ ‥ ‥ ‥ ‥ ‥ ‥ │
│ │ │ その― ― ― ― ― ― ― ― ― ― ― ― ― ―
2.ハードウェアの準備
2.1 所需材料
- ESP32開発ボード:おすすめESP32-WROOM-32或ESP32-S3
- RS485モジュールMAX485、SP 3485、またはRS 485モジュール絶縁(産業用グレード絶縁推奨)
- デュポン線::いくつかの
- 120Ω終端抵抗:バスの両端に1つずつ(長距離必須)
- Modbus RTUデバイス:温湿度センサー、计器等(试験用)
- 5V/3.3V电源ESP32およびRS 485モジュールへの給電
2.2 配線説明
| ESP32ピン | RS 485モジュール | 説明書は |
|---|---|---|
| GPIO17 TX2 | DIは | ESP32送信→ RS 485入力 |
| GPIO16 RX2 | ROさん | RS 485出力→ESP32受信 |
| GPIO 4より | Re/Re | 方向控制(高=送信,低=受信) |
| 3.3V | VCCは | 電源(モジュール電圧、一部は5 V) |
| GND | GND | コモン·グラウンド |
| - | Aは | RS485 A线(機器の接続A) |
| - | Bさんは | RS485B線(接続装置B) |
注意事項について:
- RS 485モジュール5 V電源の場合、DIピンはレベルシフトが必要です(ESP32は3.3 V)。または3.3 V互換モジュールで直接使用します。
- DEピンとREピンは互いに短く接続され、ESP32 1つのGPIOによって方向が制御されます。
- バスの両端には120Ωの終端抵抗(A-B間)が必要です。
- 産業環境で推奨される絶縁型RS 485モジュールは、グランドループによるESP32の損傷を防止します。
- 複数のバスは、ESP32のUART1およびUART2をそれぞれ使用して接続可能である
C.開発環境とライブラリのインストール
// Arduino IDE中インストールESP32开发板サポート:
// 文件 → 首选项 → 附加开发板管理器网址:
// https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
// 工具 → 开发板 → 开发板管理器 → 検索"ESP32" → インストール
// 需要インストール的库(工具 → 管理库 → 検索):
// 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
基本版: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 // ボーレート
#define MODBUS_SLAVE_ID 1 // スレーブID
#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); // 切换到受信モード
}
// ====== WiFi连接 ======
void connectWiFi() {
Serial.print("连接WiFi: ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi接続された");
Serial.print("IPアドレス: ");
Serial.println(WiFi.localIP());
}
// ====== MQTT连接 ======
void connectMQTT() {
while (!mqtt.connected()) {
Serial.print("连接MQTTサーバー·サーバー...");
// 连接MQTT(带遗嘱メッセージ:オフライン时通知)
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("接続された");
mqtt.publish("modbus/gateway/001/status", "online", true); // 上线通知
} else {
Serial.print("失敗,rc=");
Serial.print(mqtt.state());
Serial.println(",5秒后重试");
delay(5000);
}
}
}
// ====== 読み取りModbus設備のデータ ======
struct SensorData {
float temperature; // 温度
float humidity; // 湿度
uint16_t status; // ステータス字
bool valid; // データありますか?效
};
SensorData readModbusData() {
SensorData data = {0, 0, 0, false};
// リードホールドレジスタ0-2(温度、湿度、ステータス)
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("温度: %.1f°C, 湿度: %.1f%%, ステータス: %dn",
data.temperature, data.humidity, data.status);
} else {
Serial.printf("Modbus読み取り失敗,エラー·コード: %dn", 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: %sn", 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);
// 连接WiFi
connectWiFi();
// 設定MQTT
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setBufferSize(512); // 增大MQTTバッファ
}
// ====== loop ======
void loop() {
// 确保WiFi和MQTT连接
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);
}
}
}
5.工業用拡張版
基本機能は利用可能ですが、産業用シナリオでは、マルチデバイスサポート、マルチレジスタマッピング、構成管理、ウォッチドッグ、OTAなどの機能も必要です。以下は、拡張版の主なモジュールです。
5.1 多デバイス多レジスタ設定
// 定义デバイス和レジスタマップ
struct RegisterConfig {
const char* name; // データ名称(MQTT字段名)
uint16_t address; // レジスター·アドレス
uint8_t functionCode; // 機能コード 3=レジスタを保持する。, 4=入力レジスタ。
uint8_t count; // 読み取り数
float multiplier; // 乘数(转换系数)
float divider; // 除数
};
struct DeviceConfig {
uint8_t slaveId; // スレーブID
const char* name; // デバイス名
RegisterConfig* registers; // レジスタ設定配列
uint8_t regCount; // レジスタの数
};
// デバイス1:温湿度センサー
RegisterConfig sensor1Regs[] = {
{"temperature", 0, 3, 1, 10}, // 温度,アドレス0,除以10
{"humidity", 1, 3, 1, 10}, // 湿度,アドレス1,除以10
{"dew_point", 2, 3, 1, 10}, // 露点,アドレス2,除以10
};
// デバイス2:パワーメーター
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 readToJson dev {
StaticJsonDocument 512 doc;
doc["device"] = dev.name;
doc["slave_id"] = dev.slId;
JsonObject data = doc.createNestedObject "data";
bool anySuccess = false
for uint8_t i = 0; i dev.regCount; i++{
Register = dev.registers [i];
node.setSlaveId dev.slaveId; //スレーブIDの設定
Uint8_t result;
if reg.functionCode == 3 {
result = node.readHoldingRegisters (reg.address, reg.count);
(エルゼ)
result = node.readInputRegisters (reg.address, reg.count);
}
if == n.ku8MBSuess {
浮動小数点値
if.count == 1 {
16ビットの値
uint16_t raw = node.getResponseBuffer 0;
value = raw * reg.multiplier/reg.divider;
(エルゼ)
// 32ビット値(ハイワードが前に来る)
uint32_t raw = uint32_t node.getResponseBuffer 0<< 16|
node.getResponseBuffer 1;
値= raw * reg.multiplier/reg.divider;
}
data[reg.name] = value;
anySuccess = true;
(エルゼ)
data[reg.name] = nullptr; //読み取り失敗はnullでマークされます
}
node.clearResponseBuffer;
delay(50); //バスの競合を避けるためのリクエスト間隔
}
if! anySuccess return "";
文字列出力;
serializeJson doc output;
リターン出力;
}
5.3 MQTTリモートコントロール(下りコマンドのサブスクライブ)
// 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();
// 解析JSON命令
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) {
// 機能コード06:単一レジスタの書き込み
uint8_t result = node.writeSingleRegister(address, value);
Serial.printf("写レジスタ %d=%d, 结果: %sn",
address, value,
result == node.ku8MBSuccess ? "成功" : "失敗");
}
else if (strcmp(function, "write_coil") == 0) {
// 機能コード05:シングルコイルを書く。
uint8_t result = node.writeSingleCoil(address, value ? 0xFF00 : 0x0000);
Serial.printf("写コイル %d=%d, 结果: %sn",
address, value,
result == node.ku8MBSuccess ? "成功" : "失敗");
}
else if (strcmp(function, "read_register") == 0) {
// 機能コード03:读レジスタ并回复
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 Arduino OTA.h>
void initOTA {
arduino.setHostname "32-modbus-gateway";
Arduino OTA.setPassword "admin123"; // OTAパスワード
arduino.onStart []{
Serial.println "OTAアップグレードを開始します。.. ");
});
Arduino OTA.onEnd []{
Serial.println "nアップグレードが完了しました。再起動中です。.. ");
});
arduino.onProgress [] uned int ress uned int total {
Serial.printf "アップグレードの進行状況%u%%r" progress /total / 100;
});
arduino.onError [] ota_error_t error {
Serial.printf "OTAエラー [%u]"、error;
if error == OTA_AUTH_ERROR Serial.println "認証失敗";
else if error == OTA_BEGIN_ERROR Serial.println "開始に失敗";
else if error ==__."接続";
else if error ==_VE_."受信";
else if error ==_END_."终了";
});
Arduino OTA.begin;
Serial.println“OTA準備完了”;
}
//ループ内でOTAを処理する
void loop {
Arduino OTA.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 5sn", mqtt.state());
delay(5000);
}
}
}
// ====== データの収集 ======
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サーバーの構築とデータ表示
方法1:パブリックMQTTサーバーを使用する(テスト用)
# EMQXパブリック·サーバー broker.emqx.io 1883
MQTTXクライアントでmodbus/gw001/dataを購読してデータを表示する
方法2:Mosquitto(Docker)をローカルに構築する
docker run-d--name mosquitto-p 1883 1883-p 9001 9001
eclipse-Mosquito
モード3:EMQX(より豊富な機能、Web管理インターフェイス)
docker run -d --name emqx -p 1883 1883 -p 18083 1808384
emqx/emqx est
# 管理インタフェースht18083 admin/public
#コマンドラインサブスクリプションでデータを表示する
mosquitto_-h broker.emqx.io-t "mod/gw001/data" -v
#コマンドラインから制御コマンドを送信する
mosquitto_pub -h broker.emqx.io-t "mod/gw001/"
-m '{"slave_id" 1 "function""write_register""address" 10 "value" 250}'
8.よくある質問とチェック
| 問題 | 理由は | 解決策は |
|---|---|---|
| Modbusの読み取りに失敗 | 配線エラー或DE引脚未控制 | A/Bラインが反転しているか、DEピンが接続されてコードで制御されているかを確認する |
| 時折の読み取り失敗 | ボーレートの不一致または終端抵抗の欠如 | ボーレート/チェックビット、バス両端に120Ω抵抗をチェック |
| 読み取りデータ文字化け | シリアル構成エラー | SERIAL_8 N 1がデバイスと一致していることを確認し、RX/TXが反転していることを確認します。 |
| MQTT接続の失敗 | WiFi未连接或サーバー·サーバーアドレスエラー | シリアルログでIPおよびMQTTステータスコードを確認する |
| MQTTメッセージの消失 | PubSubClientのバッファが小さすぎる | mqtt.setBufferSize 1024バッファの増大 |
| ESP32頻繁な再起動 | ウォッチドッグタイムアウトまたはメモリ不足 | ループ内の長時間ブロックをチェックし、JSONドキュメントサイズを縮小 |
| 複数のデバイスの競合 | スレーブIDの重複または要求間隔が短すぎる | 各デバイスIDが一意であることを確認し、リクエスト間にdelayを追加する(30-50ms) |
| 32ビットデータが間違っている | 単語順序エラー | 高低の単語を入れ替えてみますraw= buf[1]<< 16|バフ〔0〕 |
| OTAアップグレードの失敗 | パスワードエラー或网络不稳定 | Arduino OTAパスワードを確認し、5 GHz以下のWiFiを使用 |
| RS485モジュールの発熱 | 電圧の不一致または短絡 | モジュールの電源電圧を確認し、A/Bが短絡していないか確認する |
九、上級扩展
- デュアルバスゲートウェイ2つのRS 485バスをUART1とUART2で接続し、2つのModbusMasterオブジェクトを作成する。
- Modbus TCPスレーブ:ESP32はmodbus-esp32ライブラリを使用したModbus TCPサーバとしても動作する。
- データ·ローカル·キャッシュ:ネットワーク障害時にSPIFFS/LittleFSでデータをキャッシュし、復旧後に再送信する
- NTP時刻同期実际のタイムスタンプをNTP経由で取得します。millis
- WiFiManagerネットワーク:最初の起動APモード、携帯電話のWiFiとMQTTパラメータ設定、ハードコードなし
- MQTT TLS暗号化:使用WiFiClientSecure连接8883ポート,サポート证书验证
- 低消費電力モードバッテリー駆動シナリオでは、ディープスリープ、タイムウェイクアップ収集アップロードを使用
- Web管理インターフェイス:ESP32运行Webサーバー·サーバー,浏览器表示リアルタイムデータ和修改設定
X.学習資源
- ModbusMasterライブラリgithub.com/4-20ma/ModbusMaster
- PubSubClientライブラリgithub.com/knolleary/pubsubclient
- ESP32 Arduinoコアgithub.com/espressif/arduino-esp32
- EMQX MQTTサーバー:https://www.emqx.io/
- MQTTXクライアントmqttx.app/
- Arduino Jsonの:https://arduinojson.org/
ModbusMasterおよびPubSubClientライブラリと組み合わせることで、ESP32は産業用グレードのModbus RTUからMQTTゲートウェイを非常に低コストで実装できます。この記事では、マルチデバイス取得、MQTT双方向通信、OTAアップグレード、ウォッチドッグなどの主要機能をカバーし、実際のプロジェクトで直接使用できるベーシックからインダストリアルグレードの拡張までの完全なコードを提供します。デスクトップでModbusデバイスを使用してテストに合格し、産業現場に展開し、RS 485バスの配線と終端抵抗の構成に注意を払うことをお勧めします。
VIP专属:ESP32 Modbus RTU转MQTTGatewayコードパッケージ
ModbusMaster+PubSubClientの完全な実装:RS 485取得、MQTTレポート、OTAアップグレード、ウォッチドッグ、低消費電力。
完全なコードをダウンロードしてロックを解除 30歳以上。 工程実戦パッケージ:デバッグスクリプト、速検表、プロジェクトテンプレート、検査ケース
VIPデータベースからダウンロード 月額9.9ドル/年間199ドル