Arduino/ESP32 Modbus完全チュートリアル:eModbus/Modbus Masterの3つのライブラリTCP/RTU通信実戦とゲートウェイ開発

無料無料技术资料

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

ArduinoとESP32は、インダストリアルIoTで最も人気のあるオープンソースハードウェアプラットフォームであり、豊富なペリフェラルインタフェース、強力なWiFi/BLE機能、および非常に低コストを備えたModbusデータ収集、プロトコル変換、およびエッジコンピューティングに広く使用されています。この記事では、Arduino/ESP32エコシステムにおける3つの主要なModbusライブラリ(e Modbus、ModbusMaster、Arduino公式Modbusライブラリ)のインストール設定、APIの使用、TCP/RTU通信実装、データ型処理、サーバーサイド開発、完全なプロジェクトの実践について詳しく説明し、開発者が組込みModbus通信開発をすばやく習得できるようにします。

Arduino/ESP32 Modbusの概要

1.1 なぜか。用ESP32做Modbus

  • デュアルコアプロセッサXtensa LX6デュアルコア240MHz、Modbus通信を実行する1コア、ビジネスロジックを実行する1コア、ノンブロッキング
  • WiFi/BLEネイティブ:内置WiFi和蓝牙,可直接做Modbus TCPサーバー·サーバー或MQTTゲートウェイ
  • リッチシリアルポート3ウェイハードウェアシリアルポート(UART0/1/2)、複数のRS 485バスに接続できます。
  • 超低消費電力:深度休眠モード仅10μA,适合电池供电的野外采集节点
  • 超大型メモリ520KBのSRAM + 4MBのフラッシュ、大量のデバイスデータをキャッシュ
  • 非常に低コスト:ESP32模组仅15-25元,批量成本更低
  • 生態学的成熟度Arduino IDE、PlatformIO、ESP-IDF、複数の開発環境

1.2 3つの主要銀行の比較

库名适用プラットフォーム协议モードサーバー側。特徴は
eModbusESP32专用TCP/RTU非同期(コールバック)サポートする。最も強力なノンブロッキング、マルチリクエストキュー
ModbusMasterのすべてのArduinoRTUは同期(ブロック)サポートなし。最简单,API直观,适合初学者
Arduino ESP 32_ModbusESP 32TCP/RTU同期化サポートESP-IDFの下部組織
Modbus-Master SlaveシングルすべてのArduinoRTUは同步サポートする。主と主従、軽量

e Modbusライブラリの詳細(ESP32推奨)

eModbus是ESP32プラットフォーム最强大的Modbus库,采用异步コールバックスキーマ,サポートTCP和RTU的クライアント側は与サーバー側。,内部维护リクエスト队列,完全非阻塞,是工业级项目的首选。

2.1インストールする。

// 方法1:Arduino库管理器検索 "eModbus" インストール
// 方法2:PlatformIO
// platformio.ini:
// lib_deps =
//   eModbus/eModbus @ ^1.6.0

// 依赖库(TCPモード需要)
// - AsyncTCP(ESP32)
// - Ethernet.h兼容库(有线网络时)

// 包含头文件
#include "ModbusClientTCP.h"   // TCPクライアント側は
#include "ModbusClientRTU.h"   // RTUクライアント側は
#include "ModbusServerTCP.h"   // TCPサーバー側。
#include "ModbusServerRTU.h"   // RTUサーバー側。

2.2 Modbus TCPクライアントの完全な例

#include <WiFi.h>
#include "ModbusClientTCP.h"

// WiFi設定
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";

// Modbus TCPサーバー·サーバーアドレス
const char* modbusServer = "192.168.1.100";
const uint16_t modbusPort = 502;

// 作成TCPクライアント側は实例
ModbusClientTCP MBclient;

// リクエスト令牌(用于区分不同リクエスト,可自定义)
const uint32_t TOKEN_READ_HOLDING = 0x0001;
const uint32_t TOKEN_READ_COILS = 0x0002;
const uint32_t TOKEN_WRITE_REG = 0x0003;

// ====== データレスポンス回调函数 ======
// 当Modbusサーバー·サーバー普通の反応时调用
void handleData(ModbusMessage response, uint32_t token) {
  Serial.printf("收到レスポンス - サーバー·サーバーID:%d, 機能コード:%d, 令牌:%08X, データ長さ:%dバイトn",
                response.getServerID(), response.getFunctionCode(), token, response.size());

  // 解析レジスタを保持する。読み取りレスポンス(機能コード03)
  if (response.getFunctionCode() == READ_HOLD_REGISTER) {
    // 応答データ格式:[Bytesの数, データハイバイト, データ低バイト数, ...]
    // 第0byteはBytesの数,从第1バイト开始是レジスタのデータ
    uint8_t byteCount = response[0];
    uint8_t regCount = byteCount / 2;

    Serial.printf("読み取り到%dレジスタを保持する。:n", regCount);
    for (uint8_t i = 0; i < regCount; i++) {
      // すべてのレジスタ。占2バイト,ビッグエンディアン序
      uint16_t value = (response[1 + i * 2] << 8) | response[2 + i * 2];
      Serial.printf("  レジスタ%d:%d (0x%04X)n", i, value, value);
    }
  }

  // 解析コイル読み取りレスポンス(機能コード01)
  else if (response.getFunctionCode() == READ_COIL) {
    uint8_t byteCount = response[0];
    Serial.printf("読み取り到コイルの状態:n");
    for (uint8_t byteIdx = 0; byteIdx < byteCount; byteIdx++) {
      uint8_t coilByte = response[1 + byteIdx];
      for (uint8_t bit = 0; bit < 8; bit++) {
        uint16_t coilNum = byteIdx * 8 + bit;
        bool state = (coilByte >> bit) & 0x01;
        Serial.printf("  コイル%d:%sn", coilNum, state ? "ON" : "OFF");
      }
    }
  }

  // 写入成功レスポンス
  else if (response.getFunctionCode() == WRITE_SINGLE_REGISTER) {
    uint16_t addr = (response[0] << 8) | response[1];
    uint16_t value = (response[2] << 8) | response[3];
    Serial.printf("写入成功 - アドレス:%d, 值:%dn", addr, value);
  }
}

// ====== エラーレスポンス回调函数 ======
void handleError(Error error, uint32_t token) {
  ModbusError me(error);
  Serial.printf("Modbusエラー - 令牌:%08X, エラー·コード:%02X, 説明:%sn",
                token, (uint8_t)error, (const char*)me);
}

void setup() {
  Serial.begin(115200);
  delay(100);

  // 连接WiFi
  Serial.print("连接WiFi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("nWiFi接続された,IPアドレス:%sn", WiFi.localIP().toString().c_str());

  // 設定Modbus TCPクライアント側は
  MBclient.onDataHandler(&handleData);   // 設定データ回调
  MBclient.onErrorHandler(&handleError); // 設定エラー回调
  MBclient.setTimeout(5000);              // 設定タイムアウト5秒
  MBclient.begin();                       // 启动クライアント側は后台任务

  Serial.println("Modbus TCPクライアント側は已启动");
}

void loop() {
  static unsigned long lastRead = 0;

  // 每5秒読み取り一次
  if (millis() - lastRead > 5000) {
    lastRead = millis();

    // ====== リクエスト1:リードホールドレジスタ(機能コード03)======
    // 参数:令牌, サーバー·サーバーID(Unit ID), 機能コード, 開始アドレス, 数量
    Error err = MBclient.addRequest(
        TOKEN_READ_HOLDING,  // 自定义令牌
        1,                   // 駅からの住所(Unit ID)
        READ_HOLD_REGISTER,  // 機能コード03
        0,                   // 開始アドレス
        10                   // 読み取り101つのレジスタ
    );
    if (err != SUCCESS) {
      ModbusError e(err);
      Serial.printf("作成リクエスト失敗:%02X - %sn", err, (const char*)e);
    }

    // ====== リクエスト2:リードコイル(機能コード01)======
    MBclient.addRequest(TOKEN_READ_COILS, 1, READ_COIL, 0, 8);

    // ====== リクエスト3:写入单1つのレジスタ(機能コード06)======
    MBclient.addRequest(TOKEN_WRITE_REG, 1, WRITE_SINGLE_REGISTER, 100, 1234);

    // 注意:addRequest是异步非阻塞的,立即戻る
    // レスポンス経由handleData/handleError回调処理
  }

  delay(100);
}

2.3 eModbus関数コード定数クイックチェック

関数コード定数十進法機能性はaddRequestパラメータ
READ_COIL01リードコイルを読むtoken
READ_DISCR_INPUT02離散入力の読み取りtoken
_HOLD_03リードホールドレジスタtoken
READ_INPT_REGISTER04読み取り入力レジスタ(token, server, FC, addr, count)
WRITE_COIL05シングルコイルを書く。token
WRITE_REGISTER06単一レジスタの書き込みtoken
WRITE_COILS15複数のコイルを書く。token
WRITE_REGISTERS16複数のレジスタを書く。token
READ_WRITE_REGISTERS23複数のレジスタの読み書き複雑なパラメータ

2.4 Modbus RTUクライアント側は(RS485)

#include "ModbusRTU.h"

// RTUクライアントインスタンス、DE/RE制御ピン(MAX485のDEピン)を指定
//自動送受信RS 485モジュールを使用する場合、ピンは送信されません。
#define RTS_PIN 4 // DE/RE制御ピン
ModbusClient RTU MBrtu RTS_PIN;

//コールバック関数(TCPモードと同じ)
void handleData ModbusMessage response uint32_t token {
  Serial.printf "RTUレスポンス-FC %d、長さ%dn" response.getFunctionCode response.size;

  if.getFunctionCode ==_HOLD_ISTER {
    uint8_t regCount = response[0] / 2;
    for uint8_t i = 0; i regCount; i++{
      uint16_t value = response[1 + i*2]<< 8|応答[2 + i*2]。
      Serial.printf "レジスタ%d %dn" i value;
    }
  }
}

void handleError Error error uint32_t token {
  ModbusError me error modbusError me error;
  Serial.printf "RTUエラー:%sn" const char* me;
}

void setup {
  Serial.begin 115200;

  //シリアル2を設定(ESP32のSerial2、GPIO16=RX、GPIO17=TX)
  // RTUutils prepareHardwareSerialはシリアルバッファを設定します。
  RTUls preparewareSerial2;
  Serial 2.begin(96 00、SERIAL_8 N 1、16、17); // RX=16、TX=17

  //RTUクライアントの設定
  MBr.onDataleData;
  MBr.onleError;
  MBrtu.begin Serial2; //シリアルポートをバインド

  Serial.println "Modbus RTUクライアントが起動しました";
}

void loop {
  static unsigned long lastRead = 0;

  if millis-lastRead 3000 {
    lastRead = millis;

    //スレーブアドレス1の保持レジスタ0-9を読み取る
    MBr.addRequest 0x0001 1_HOLD_ISTER 0 10;

    //スレーブアドレス1のレジスタ100に書き込む
    MBr.addRequest 0x0002 1_ISTER 100 5678;
  }

  delay(100);
}

// ======
// ESP32 GPIO17 TX2--MAX485DI
// ESP32 IO16(RX2)---485RO
//ESP32 GPIO4(RTS)--MAX 485DEおよびRE(ショート接続)
// MAX 485A--RS485バスA
// MAX 485B--RS485バスB
//バスの両端に120Ω終端抵抗を接続

2.5複数のレジスタを書き込む(関数コード16)

// 複数のレジスタを書く。需要先构建ModbusMessage
ModbusMessage request;
request.add((uint8_t)1);              // 駅からの住所
request.add((uint8_t)WRITE_REGISTERS); // 機能コード16 (0x10)
request.add((uint16_t)100);            // 開始アドレス
request.add((uint16_t)5);              // レジスタの数

// 追加データ值(每个uint16_t占2バイト,ビッグエンディアン序自动処理)
request.add((uint16_t)100);
request.add((uint16_t)200);
request.add((uint16_t)300);
request.add((uint16_t)400);
request.add((uint16_t)500);

// リクエストの送信
Error err = MBclient.addRequest(0x0010, request);
if (err != SUCCESS) {
  Serial.printf("リクエスト失敗:%02Xn", err);
}

// 複数のコイルを書く。(機能コード15)
ModbusMessage coilReq;
coilReq.add((uint8_t)1);
coilReq.add((uint8_t)WRITE_COILS);  // 機能コード15 (0x0F)
coilReq.add((uint16_t)0);           // 開始アドレス
coilReq.add((uint16_t)8);           // コイルの数
coilReq.add((uint8_t)1);            // Bytesの数
coilReq.add((uint8_t)0xAA);         // コイルの状態:10101010
MBclient.addRequest(0x0011, coilReq);

ModbusMasterライブラリの詳細(最も簡単な入門)

ModbusMaster(4-20ma)是Arduino生态中最经典的Modbus RTUマスター库,API简单直观,同步阻塞モード,适合初学者和简单项目。サポートすべてのArduinoプラットフォーム(Uno、Mega、ESP32、STM32等)。

3.1インストールと基本的な使用

// Arduino库管理器検索 "ModbusMaster" インストール
// 或 PlatformIO: lib_deps = ModbusMaster

#include <ModbusMaster.h>

// 作成ModbusMaster实例(可作成多个实例对应不同スレーブ)
ModbusMaster node;

void setup() {
  Serial.begin(115200);

  // 初期化:指定駅からの住所和シリアルポート
  // 参数1:駅からの住所(1-247)
  // 参数2:用于通讯的Serial对象
  node.begin(1, Serial2);  // スレーブ1,使用Serial2

  // 設定シリアルパラメータ(必须与スレーブ一致)
  Serial2.begin(9600, SERIAL_8N1);

  // 可选:設定タイムアウト(毫秒)
  node.setTimeout(1000);
}

void loop() {
  // ====== リードホールドレジスタ(機能コード03)======
  // 参数:開始アドレス, 数量
  // 戻る:ステータス码(0=成功)
  uint8_t result = node.readHoldingRegisters(0, 10);

  if (result == node.ku8MBSuccess) {
    Serial.println("読み取り成功:");
    // 从レスポンスバッファ取得データ
    for (uint8_t i = 0; i < 10; i++) {
      uint16_t value = node.getResponseBuffer(i);
      Serial.printf("  レジスタ%d:%dn", i, value);
    }
  } else {
    Serial.printf("読み取り失敗,エラー·コード:%02Xn", result);
  }

  // ====== 写入单1つのレジスタ(機能コード06)======
  result = node.writeSingleRegister(100, 1234);
  if (result == node.ku8MBSuccess) {
    Serial.println("写入レジスタ成功");
  }

  // ====== 写入单1つのコイル(機能コード05)======
  // 注意:コイル値用0xFF00表示ON,0x0000表示OFF
  result = node.writeSingleCoil(0, 0xFF00);  // コイル0置ON
  result = node.writeSingleCoil(1, 0x0000);  // コイル1置OFF

  delay(2000);
}

3.2関数コードの完全な例

#include <ModbusMaster.h>
ModbusMaster node;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1);
  node.begin(1, Serial2);
}

void loop() {
  uint8_t result;

  // ====== 機能コード01:リードコイルを読む ======
  result = node.readCoils(0, 8);
  if (result == node.ku8MBSuccess) {
    // コイルの状態パッキング在レジスタバッファ中
    // 每个uint16_t包含16个コイルの状態
    uint16_t coilStates = node.getResponseBuffer(0);
    for (int i = 0; i < 8; i++) {
      Serial.printf("コイル%d:%sn", i, (coilStates >> i) & 1 ? "ON" : "OFF");
    }
  }

  // ====== 機能コード02:離散入力の読み取り ======
  result = node.readDiscreteInputs(0, 8);
  if (result == node.ku8MBSuccess) {
    uint16_t inputStates = node.getResponseBuffer(0);
    Serial.printf("ショップ型入力:%04Xn", inputStates);
  }

  // ====== 機能コード03:リードホールドレジスタ ======
  result = node.readHoldingRegisters(0, 10);
  if (result == node.ku8MBSuccess) {
    for (int i = 0; i < 10; i++) {
      Serial.printf("レジスタを保持する。%d:%dn", i, node.getResponseBuffer(i));
    }
  }

  // ====== 機能コード04:読み取り入力レジスタ ======
  result = node.readInputRegisters(0, 4);
  if (result == node.ku8MBSuccess) {
    for (int i = 0; i < 4; i++) {
      Serial.printf("入力レジスタ。%d:%dn", i, node.getResponseBuffer(i));
    }
  }

  // ====== 機能コード05:シングルコイルを書く。 ======
  node.writeSingleCoil(0, 0xFF00);  // ON
  node.writeSingleCoil(0, 0x0000);  // OFF

  // ====== 機能コード06:単一レジスタの書き込み ======
  node.writeSingleRegister(50, 999);

  // ====== 機能コード15:複数のコイルを書く。 ======
  // 先送信バッファの設定
  node.setTransmitBuffer(0, 0x00FF);  // 前8个コイルの状態
  result = node.writeMultipleCoils(0, 8);  // 開始アドレス, 数量

  // ====== 機能コード16:複数のレジスタを書く。 ======
  node.setTransmitBuffer(0, 100);
  node.setTransmitBuffer(1, 200);
  node.setTransmitBuffer(2, 300);
  result = node.writeMultipleRegisters(100, 3);  // 開始アドレス, 数量

  delay(3000);
}

3.3 ModbusMasterエラーコード

エラー·コード定数の名前意味の意味
0x00ku 8 MBサクセス成功した
0 x 0 1ku 8MBILLEGALFunction違法な機能コード
0 x 0 2ku8MBIlllegalDataAddress違法なデータアドレス
0x03ku8MBIllegalDataValue不正なデータ値
0 x 0 4ku8MBSlaveDeviceFailureスレーブ機器の障害
0 x E 0ku8MBInvalidSlaveID無効なスレーブID
0xE1ku8MBInvalid無効な機能コード
0 x E 2ku8MBonseTimedOutレスポンス·タイムアウト
0 x E 3ku8MBInvalidCRCCRCチェックエラー

IV.データ型の処理

Modbusレジスタ是16位的,但工业デバイス经常使用32ビット浮動小数点数、32ビット整数等データ型。以下提供完全的转换工具函数。

// ========= 32ビット浮動小数点解析(ABCDビッグエンディアン、最も一般的)======
float parseFloatABCD uint16_t regHigh uint16_t regLow {
  uint32_t raw = uint32_t regHigh<< 16| regLow;
  浮動小数点値
  memcpy value raw sizeof float;
  リターンの価値;
}

//======== 32ビット浮動小数点数解析(CDABワード交換順序)=======
float parseFloatCDAB uint16_t reg0 uint16_t reg1 {
  // reg0は下位16ビット、reg1は上位16ビットです。
  uint32_t raw = uint32_t reg1<< 16| reg 0;
  浮動小数点値
  memcpy value raw sizeof float;
  リターンの価値;
}

// ========== 32ビット符号付き整数(ビッグエンディアン)=======
int32_t parseInt32ABCD uint16_t regHigh uint16_t regLow {
  return int32_t uint32_t regHigh<< 16| regLow;
}

// =========== 32ビット符号なし整数(ビッグエンディアン)======
uint32_t parseUint32ABCD uint16_t regHigh uint16_t regLow {
  return uint32_t regHigh<< 16| regLow;
}

//====== 16ビット符号付き整数 ======
int16_t parseInt16 uint16_t reg {
  return int16_t;
}

// ======
float parseScaled uint16_t reg float scale {
  reg * scaleを返す。
}

// ====== 使用例ModbusMaster ======
void readTemperature {
  uint8_t result = node.readHoldingRegisters 100 2; // 温度は2つのレジスタを使用します。
  if == n.ku8MBSuess {
    uint16_t reg0 = node.getResponseBuffer 0;
    uint16_t reg1 = node.getResponseBuffer 1;
    float temp = parseFloatABCD reg0 reg1;
    Serial.printf "温度%. 2f °Cn" temp;
  }
}

// ========== 使用例(eModbusコールバック内)=====
void handleData ModbusMessage response uint32_t token {
  if.getFunctionCode ==_HOLD_ISTER {
    // 温度はレジスタ100-101(応答の0-1番目のレジスタ)にあります
    uint16_t reg0 =[1]<< 8|応答[2];
    uint16_t reg1 =[3]<< 8| [4]への応答;
    float temp = parseFloatABCD reg0 reg1;
    Serial.printf "温度%. 2f °Cn" temp;
  }
}

Modbus TCPサーバ(スレーブ)

使用eModbus可以快速実装Modbus TCPスレーブデバイス,让ESP32模拟一个Modbusサーバー·サーバー,供其他マスター·ステーション·リーディングデータ。

#include <WiFi.h>
#include "ModbusServerTCP.h"

ModbusServerTCP MBserver;

// スレーブデータストア
#define MAX_REGISTERS 100
uint16_t holdingRegs[MAX_REGISTERS] = {0};
uint16_t inputRegs[MAX_REGISTERS] = {0};
bool coils[MAX_REGISTERS] = {false};
bool discreteInputs[MAX_REGISTERS] = {false};

// ====== 读ホールド·レジスタ·コールバック ======
ModbusMessage readHoldingRegs(ModbusMessage request) {
  uint16_t addr, count;
  request.get(2, addr);   // 開始アドレス(从第2バイト开始)
  request.get(4, count);  // レジスタの数

  ModbusMessage response;
  response.add(request.getServerID());
  response.add((uint8_t)READ_HOLD_REGISTER);
  response.add((uint8_t)(count * 2));  // Bytesの数

  for (uint16_t i = 0; i < count; i++) {
    if (addr + i < MAX_REGISTERS) {
      response.add(holdingRegs[addr + i]);
    } else {
      response.add((uint16_t)0);
    }
  }
  return response;
}

// ====== 単一レジスタの書き込み回调 ======
ModbusMessage writeSingleReg(ModbusMessage request) {
  uint16_t addr, value;
  request.get(2, addr);
  request.get(4, value);

  if (addr < MAX_REGISTERS) {
    holdingRegs[addr] = value;
    Serial.printf("写入レジスタ%d:%dn", addr, value);
  }

  // レスポンス:回显アドレス和值
  ModbusMessage response;
  response.add(request.getServerID());
  response.add((uint8_t)WRITE_SINGLE_REGISTER);
  response.add(addr);
  response.add(value);
  return response;
}

// ====== 读コイルコールバック ======
ModbusMessage readCoils(ModbusMessage request) {
  uint16_t addr, count;
  request.get(2, addr);
  request.get(4, count);

  uint8_t byteCount = (count + 7) / 8;
  ModbusMessage response;
  response.add(request.getServerID());
  response.add((uint8_t)READ_COIL);
  response.add(byteCount);

  for (uint8_t b = 0; b < byteCount; b++) {
    uint8_t coilByte = 0;
    for (uint8_t bit = 0; bit < 8; bit++) {
      uint16_t coilNum = addr + b * 8 + bit;
      if (coilNum < MAX_REGISTERS && coils[coilNum]) {
        coilByte |= (1 << bit);
      }
    }
    response.add(coilByte);
  }
  return response;
}

void setup() {
  Serial.begin(115200);

  // 初期化一些模拟データ
  holdingRegs[0] = 100;   // 温度の設定值
  holdingRegs[1] = 250;   // 湿度设定值
  inputRegs[0] = 253;     // 当前温度(25.3°C)
  inputRegs[1] = 652;     // 当前湿度(65.2%)

  // 连接WiFi
  WiFi.begin("your_ssid", "your_password");
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.printf("IPアドレス:%sn", WiFi.localIP().toString().c_str());

  // 登録機能コード処理函数
  MBserver.registerWorker(1, READ_HOLD_REGISTER, &readHoldingRegs);
  MBserver.registerWorker(1, WRITE_SINGLE_REGISTER, &writeSingleReg);
  MBserver.registerWorker(1, READ_COIL, &readCoils);

  // 启动TCPサーバー側。,监听502ポート
  MBserver.start(502, 2, 2000);  // ポート, 最大连接数, タイムアウト
  Serial.println("Modbus TCPサーバー側。已启动,ポート502");
}

void loop() {
  // 模拟センサーはデータ更新
  static unsigned long lastUpdate = 0;
  if (millis() - lastUpdate > 1000) {
    lastUpdate = millis();
    inputRegs[0] = 250 + random(-5, 5);  // 温度小幅波动
    inputRegs[1] = 650 + random(-10, 10); // 湿度小幅波动
  }
  delay(100);
}

六、485ハードウェアガイド

6.1 RS 485モジュールの比較

モジュールモジュールチップは自动收发絶縁价格適用可能なシーン
MAX 48 5モジュールMAX 485否(需GPIO控制)いいえ、いいえ2-5元さん。簡単な実験、短距離
SP 3485モジュールSP3485いいえ、いいえいいえ、いいえ3-6元さん。3.3 Vシステム、低消費電力
絶縁RS 485モジュールADM 248 3部分的サポートはい(2500V)15-25元さん。産業現場、強い干渉
自動送受信モジュールMAX485+ハードウェアロジックはい。否5-8元さん。GPIO制御なし

6.2 配線の仕様

  • バス·トポロジ:必须使用手をつないで菊花链トポロジー,禁断の星连接。分支线長さ不超过0.3米
  • 終端抵抗:バスの両端に120Ω抵抗があり、中間ノードは接続されません。インピーダンスマッチングにより信号の反射を大幅に低減
  • ケーブルの選択シールドツイストペアA/Bラインは一緒に接続する必要があります。
  • 接地地地シールド層シングルエンド接地(通常はメイン制御側)、グランドループを避ける。
  • コモン·グラウンド:デバイス間のグランド電位差が1 Vを超える場合は、絶縁モジュールまたは共通グランドを使用する必要があります。
  • 距離は9600ボーレートは最大1200メートル、115200ボーレートは最大100メートルです。ボーレートを下げると距離が伸びる
  • プルアップダウンダウン:A线接10kΩ上拉到VCC,B线接10kΩ下拉到GND,确保バス暇なとき电平稳定

完全なプロジェクト:ESP32 Modbusデータ収集ゲートウェイ

ESP32は、RS 485経由で複数の温度·湿度センサーやメーターデータを読み取り、WiFi経由でMQTTサーバーにアップロードし、ローカルSCADA読み取り用のModbus TCPサービスを提供します。

#include <WiFi.h>
#include <PubSubClient.h>
#include "ModbusClientRTU.h"
#include "ModbusServerTCP.h"

// ====== 設定 ======
const char* WIFI_SSID = "your_ssid";
const char* WIFI_PASS = "your_password";
const char* MQTT_SERVER = "192.168.1.200";
const int MQTT_PORT = 1883;
const char* MQTT_TOPIC = "factory/sensor/data";

#define RTS_PIN 4
#define MODBUS_BAUD 9600

// ====== デバイス定义 ======
struct Device {
  const char* name;
  uint8_t slaveAddr;
  uint16_t tempAddr;
  uint16_t humAddr;
};

Device devices[] = {
  {"车间A", 1, 0, 2},
  {"车间B", 2, 0, 2},
  {"仓库",  3, 0, 1},
};
const int DEVICE_COUNT = sizeof(devices) / sizeof(devices[0]);

// ====== 全局データ ======
struct SensorData {
  float temperature;
  float humidity;
  bool valid;
};
SensorData sensorData[DEVICE_COUNT];

// ====== Modbus RTUクライアント側は ======
ModbusClientRTU MBrtu(RTS_PIN);
WiFiClient espClient;
PubSubClient mqtt(espClient);

// ====== 浮動小数点数解析 ======
float parseFloat(uint16_t high, uint16_t low) {
  uint32_t raw = ((uint32_t)high << 16) | low;
  float f;
  memcpy(&f, &raw, 4);
  return f;
}

// ====== RTUデータ回调 ======
void handleData(ModbusMessage response, uint32_t token) {
  int devIdx = token & 0xFF;  // 令牌低8位存デバイス索引

  if (response.getFunctionCode() == READ_HOLD_REGISTER) {
    uint16_t reg0 = (response[1] << 8) | response[2];
    uint16_t reg1 = (response[3] << 8) | response[4];

    // 判断は、温度まだ湿度(令牌高8位存タイプ:0=温度, 1=湿度)
    uint8_t type = (token >> 8) & 0xFF;
    if (type == 0) {
      sensorData[devIdx].temperature = parseFloat(reg0, reg1);
    } else {
      sensorData[devIdx].humidity = reg0 * 0.1;  // 16ビット整数,分辨率0.1%
    }
    sensorData[devIdx].valid = true;
  }
}

void handleError(Error error, uint32_t token) {
  int devIdx = token & 0xFF;
  sensorData[devIdx].valid = false;
  ModbusError me(error);
  Serial.printf("デバイス%d読み取り失敗:%sn", devIdx, (const char*)me);
}

// ====== MQTT再接続 ======
void mqttReconnect() {
  while (!mqtt.connected()) {
    if (mqtt.connect("ESP32_Gateway")) {
      Serial.println("MQTT接続された");
    } else {
      delay(5000);
    }
  }
}

// ====== 上传データ ======
void publishData() {
  String json = "{";
  for (int i = 0; i < DEVICE_COUNT; i++) {
    if (sensorData[i].valid) {
      if (i > 0) json += ",";
      json += """ + String(devices[i].name) + "":{";
      json += ""temp":" + String(sensorData[i].temperature, 1);
      json += ","hum":" + String(sensorData[i].humidity, 1);
      json += "}";
    }
  }
  json += "}";
  mqtt.publish(MQTT_TOPIC, json.c_str());
  Serial.printf("上传:%sn", json.c_str());
}

void setup() {
  Serial.begin(115200);

  // WiFi
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.printf("WiFi接続された:%sn", WiFi.localIP().toString().c_str());

  // MQTT
  mqtt.setServer(MQTT_SERVER, MQTT_PORT);

  // Modbus RTU
  RTUutils::prepareHardwareSerial(Serial2);
  Serial2.begin(MODBUS_BAUD, SERIAL_8N1, 16, 17);
  MBrtu.onDataHandler(&handleData);
  MBrtu.onErrorHandler(&handleError);
  MBrtu.begin(Serial2);

  Serial.println("ゲートウェイ启动完成");
}

void loop() {
  static unsigned long lastPoll = 0;
  static unsigned long lastPublish = 0;

  if (!mqtt.connected()) mqttReconnect();
  mqtt.loop();

  // 每3秒ポーリング全装備。
  if (millis() - lastPoll > 3000) {
    lastPoll = millis();
    for (int i = 0; i < DEVICE_COUNT; i++) {
      // 温度リクエスト(令牌高8位=0,低8位=デバイス索引)
      MBrtu.addRequest((0 << 8) | i, devices[i].slaveAddr,
                       READ_HOLD_REGISTER, devices[i].tempAddr, 2);
      delay(100);  // RTU需要間隔
      // 湿度リクエスト
      MBrtu.addRequest((1 << 8) | i, devices[i].slaveAddr,
                       READ_HOLD_REGISTER, devices[i].humAddr, 1);
      delay(100);
    }
  }

  // 每30秒上传一次
  if (millis() - lastPublish > 30000) {
    lastPublish = millis();
    publishData();
  }

  delay(100);
}

8.よくある質問とチェック

問題考えられる原因解決策は
RTU応答なしA/Bライン·リバース交換A/B线,注意A对A、B对B
RTU応答なしDE/REピン未制御RTS_PINが正しく配線されていることを確認するか、自動送受信モジュールを使用します。
CRCエラーボーレートの不一致マスタースレーブのボーレート、データビット、停止ビット、検証ビットが完全に一致していることを確認する
CRCエラーシリアルポートバッファ太小RTUutils prepareHardwareSerialを使用してバッファを増大させる
TCP接続の失敗ポートが占有される。ESP32がクライアントである場合、サーバ502ポートの開放を確認する
TCP接続の失敗ファイアウォール傍受サーバのファイアウォールを確認し、ポート502のインバウンドを許可します。
データの乱れバイトオーダーエラーABCD/CDAB/DCBAの3つの浮動小数点解析方法を試す
時折の損失。バス干渉終端抵抗の追加、シールドラインの使用、ボーレートの低減
eModbusリクエスト送信しないキューがいっぱい。リクエストの頻度を減らし、addRequest間の遅延を増やす
ESP32再起動ウォッチドッグタイムアウトループ内の長時間のブロックを回避し、非同期コールバックを使用

九、パフォーマンス最適化建议

  • バッチ読み取り:一次読み取り连续的多1つのレジスタ,比分多次読み取り快数倍。合理规划レジスター·アドレス表
  • リクエスト間隔RTUモードでは、異なるスレーブ要求間に少なくとも50-100ms間隔を置き、バス競合を避ける。
  • デュアルコア割り当てESP32はModbus通信タスクをコア0に、ビジネスロジックをコア1に、xTaskCreatePinnedToCoreを使用して設定できます。
  • タイムアウトの設定eModbusデフォルトのタイムアウトは、実際のネットワーク設定によっては2-5秒長すぎる場合があります。
  • データキャッシュデバイスパラメータなどの遅い変更データをキャッシュし、Modbusリクエスト頻度を低減
  • ボーレートの選択短距離(100 m)は115200、長距離は9600です。盲目的に高いハードルを追求しない
  • キューの深さeModbusデフォルトのキュー深度100、高周波シーンを適切に増やすことができます。
  • メモリ最適化:デバイス数が多い場合はStringクラスを避け、char配列を使用してメモリフラグメントを減らす

十、学习路线与リソース

ステージは学习内容おすすめ库目标
入り口へ。Arduinoの基礎、シリアル通信、Modbusプロトコルの概念ModbusMasterの個々のセンサーを読み取る
ベースは全てのコード、485配、小数点数ModbusMasterの通常の読み取り操作を完了する。
上級eModbus非同期アーキテクチャ、TCP通信、マルチデバイスポーリングeModbus安定した収集システムの開発
上級者。Modbusサーバ、MQTTゲートウェイ、OTAアップグレード、産業用展開eModbus + ESP-IDF産業用Modbusゲートウェイの開発

10.1推奨リソース

  • eModbus公式ドキュメントemodbus.github.io/
  • eModbusソースコードgithub.com/eModbus/eModbus
  • ModbusMasterのドキュメント4-20ma.io/ModbusMaster/
  • Modbusプロトコル仕様modbus.org/specs.php
  • ESP32公式ドキュメントdocs.espressif.com/
  • Arduino Modbusチュートリアルdocs.arduino.cc/tutorials/opta/getting-started-with-modbus-rtu/

ArduinoとESP32は、極めて低コストで強力なエコシステムで、産業データ収集の風景を変えています。eModbusは、ESP32プラットフォーム用の最も強力なModbusライブラリであり、非同期ノンブロッキングTCP/RTUクライアントとサーバの実装を提供し、産業グレードのプロジェクトに適しています。ModbusMasterはシンプルで直感的なAPIを備えているため、初心者に最適です。この記事で提供するツール機能と完全なゲートウェイプロジェクトは、実際の開発に直接使用でき、まずModbus SlaveシミュレータでPCでテストし、実際のデバイスに接続することを推奨します。

📦

VIP专属:Arduino/ESP32 Modbus完全なコードパッケージ

eModbus/ModbusMasterライブラリの完全な例:TCP/RTUマスター/スレーブステーション、ネットワークシャットダウン、WiFi構成、直接書き込み実行可能。

完全なコードをダウンロードしてロックを解除 30歳以上。 工程実践パッケージの内容:スクリプトのデバッグ、クイックチェック·テーブル、项目模板、トラブルシューティング案例……

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

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

エンジニア会員

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

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

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