ESP32 Modbus RTUからMQTTゲートウェイへの実戦:RS 485データ収集とアップロードのリモート制御OTAと産業用安定性完全チュートリアル

無料無料技术资料

这篇内容可直接阅读,适合用于基础学习和検索引流。

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 TX2DIはESP32送信→ RS 485入力
GPIO16 RX2ROさんRS 485出力→ESP32受信
GPIO 4よりRe/Re方向控制(高=送信,低=受信)
3.3VVCCは電源(モジュール電圧、一部は5 V)
GNDGNDコモン·グラウンド
-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バスの配線と終端抵抗の構成に注意を払うことをお勧めします。

CLARiX

VIP专属:ESP32 Modbus RTU转MQTTGatewayコードパッケージ

ModbusMaster+PubSubClientの完全な実装:RS 485取得、MQTTレポート、OTAアップグレード、ウォッチドッグ、低消費電力。

完全なコードをダウンロードしてロックを解除 30歳以上。 工程実戦パッケージ:デバッグスクリプト、速検表、プロジェクトテンプレート、検査ケース

VIPデータベースからダウンロード 月額9.9ドル/年間199ドル
この資料を実際のプロジェクトに活用しませんか?

ツールセンターで電文解析、CRCチェック、デバイスデバッグを行うか、要件を送信して選定と接続のアドバイスを受けましょう。

エンジニア会員

この記事を実行可能なデバッグ資料に

登録すると、高度な電文解析、資料パックのダウンロード、コード例、エンジニアリングケース、優先技術サポートが利用可能に。実際のプロジェクト納品に最適です。

高度なツール無制限
資料パックとコードパック
完全なエンジニアリングケースライブラリ
優先技術サポート窓口