Arduino/ESP32 Modbus完全チュートリアル:eModbus/ModbusMaster三大库TCP/RTU通讯実践与ゲートウェイ开发

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

Arduino和ESP32是産業用IoT领域最流行的开源硬件プラットフォーム,凭借丰富的外设接口、强大的WiFi/BLE能力和极低的成本,被广泛用于ModbusData Acquisition、プロトコル変換和边缘計算。本記事详细介绍Arduino/ESP32生态中三大主流Modbus库——eModbus、ModbusMaster和Arduino官方Modbus库的インストール設定、API使用、TCP/RTU通讯実装、データ型処理、サーバー側。开发以及完全项目実践,帮助開発者たちはスピードマスター。エンベデッド·インModbus通讯开发。

一、Arduino/ESP32 Modbus生态概览

1.1 なぜか。用ESP32做Modbus

  • 双核処理器:Xtensa LX6双核240MHz,一个核跑Modbus通讯,一个核跑业务逻辑,互不阻塞
  • WiFi/BLE原生:内置WiFi和蓝牙,可直接做Modbus TCPサーバー·サーバー或MQTTゲートウェイ
  • 丰富シリアルポート:3路硬件シリアルポート(UART0/1/2),可同时接多个RS485バス
  • 超低消費電力は:深度休眠モード仅10μA,适合电池供电的野外采集节点
  • 超大内存:520KB SRAM + 4MB Flash,可缓存大量設備のデータ
  • 成本极低:ESP32模组仅15-25元,批量成本更低
  • 生态成熟:Arduino IDE、PlatformIO、ESP-IDF多种开发环境可选

1.2 三大主流库对比

库名适用プラットフォームProtocolモードサーバー側。特点
eModbusESP32专用TCP/RTUasynchronous(回调)サポート最强大,非阻塞,多リクエスト队列
ModbusMasterすべてのArduinoRTU同步(阻塞)サポートなし。最简单,API直观,适合初学者
Arduino_ESP32_ ModbusESP32TCP/RTU同步サポート乐鑫官方,ESP-IDF底层
Modbus-Master-SlaveすべてのArduinoRTU同步サポート主从一体,轻量级

二、eModbus库详解(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("收到レスポンス - ServerID:%d, function code:%d, 令牌:%08X, データ長さ:%dbyte\n",
                response.getServerID(), response.getFunctionCode(), token, response.size());

  // 解析レジスタを保持する。読み取りレスポンス(function code03)
  if (response.getFunctionCode() == READ_HOLD_REGISTER) {
    // ResponseData format:[byte count, データハイバイト, データ低バイト数, ...]
    // No.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("  register%d:%d (0x%04X)\n", i, value, value);
    }
  }

  // 解析コイル読み取りレスポンス(function code01)
  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:%s\n", 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("写入成功 - address:%d, 值:%d\n", addr, value);
  }
}

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

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

  // connectWiFi
  Serial.print("connectWiFi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.printf("\nWiFiConnected,IPaddress:%s\n", 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();

    // ====== Request1:Read and hold register(function code03)======
    // parameter:令牌, ServerID(Unit ID), function code, starting address, Quantity
    Error err = MBclient.addRequest(
        TOKEN_READ_HOLDING,  // 自定义令牌
        1,                   // slave address(Unit ID)
        READ_HOLD_REGISTER,  // function code03
        0,                   // starting address
        10                   // read101つのレジスタ
    );
    if (err != SUCCESS) {
      ModbusError e(err);
      Serial.printf("作成リクエスト失敗:%02X - %s\n", err, (const char*)e);
    }

    // ====== Request2:リードコイル(function code01)======
    MBclient.addRequest(TOKEN_READ_COILS, 1, READ_COIL, 0, 8);

    // ====== Request3:Write to a single register(function code06)======
    MBclient.addRequest(TOKEN_WRITE_REG, 1, WRITE_SINGLE_REGISTER, 100, 1234);

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

  delay(100);
}

2.3 eModbus機能コード常量速查

機能コード常量decimalFunctionaddRequestparameter
READ_COIL01リードコイルを読む(token, server, FC, addr, count)
READ_DISCR_INPUT02read discrete inputs(token, server, FC, addr, count)
READ_HOLD_REGISTER03read holding registers(token, server, FC, addr, count)
READ_INPUT_REGISTER04read input registers(token, server, FC, addr, count)
WRITE_COIL05write single coil(token, server, FC, addr, 0xFF00/0x0000)
WRITE_REGISTER06write single register(token, server, FC, addr, value)
WRITE_COILS15write multiple coils(token, server, FC, addr, count, data...)
WRITE_REGISTERS16write multiple registers(token, server, FC, addr, count, data...)
READ_WRITE_REGISTERS23read and write multiple registers复杂参数

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

#include "ModbusClientRTU.h"

// RTUクライアント側は实例,指定DE/RE控制引脚(MAX485的DE脚)
// もし使用自动收发的RS485モジュール,可不传引脚
#define RTS_PIN 4  // DE/RE控制引脚
ModbusClientRTU MBrtu(RTS_PIN);

// 回调函数(与TCPモード相同)
void handleData(ModbusMessage response, uint32_t token) {
  Serial.printf("RTUResponse - FC:%d, Length:%d\n", response.getFunctionCode(), response.size());

  if (response.getFunctionCode() == READ_HOLD_REGISTER) {
    uint8_t regCount = response[0] / 2;
    for (uint8_t i = 0; i < regCount; i++) {
      uint16_t value = (response[1 + i*2] << 8) | response[2 + i*2];
      Serial.printf("  register%d:%d\n", i, value);
    }
  }
}

void handleError(Error error, uint32_t token) {
  ModbusError me(error);
  Serial.printf("RTUError:%s\n", (const char*)me);
}

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

  // シリアルポートの設定2(ESP32的Serial2,GPIO16=RX, GPIO17=TX)
  // RTUutils::prepareHardwareSerial() 会シリアルポートの設定バッファ
  RTUutils::prepareHardwareSerial(Serial2);
  Serial2.begin(9600, SERIAL_8N1, 16, 17);  // RX=16, TX=17

  // 設定RTUクライアント側は
  MBrtu.onDataHandler(&handleData);
  MBrtu.onErrorHandler(&handleError);
  MBrtu.begin(Serial2);  // 绑定シリアルポート

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

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

  if (millis() - lastRead > 3000) {
    lastRead = millis();

    // 読み取り駅からの住所1保持レジスタです。0-9
    MBrtu.addRequest(0x0001, 1, READ_HOLD_REGISTER, 0, 10);

    // 写入駅からの住所1的レジスタ100
    MBrtu.addRequest(0x0002, 1, WRITE_REGISTER, 100, 5678);
  }

  delay(100);
}

// ====== 硬件配線(MAX485モジュール)======
// ESP32 GPIO17 (TX2) --> MAX485 DI
// ESP32 GPIO16 (RX2) --> MAX485 RO
// ESP32 GPIO4  (RTS) --> MAX485 DE 和 RE(短接在一起)
// MAX485 A --> RS485バスA
// MAX485 B --> RS485バスB
// バス両端に接続。120Ω終端抵抗

2.5 write multiple registers(function code16)

// 複数のレジスタを書く。需要先构建ModbusMessage
ModbusMessage request;
request.add((uint8_t)1);              // slave address
request.add((uint8_t)WRITE_REGISTERS); // function code16 (0x10)
request.add((uint16_t)100);            // starting address
request.add((uint16_t)5);              // number of registers

// 追加データ值(每个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);

// return response
Error err = MBclient.addRequest(0x0010, request);
if (err != SUCCESS) {
  Serial.printf("リクエスト失敗:%02X\n", err);
}

// write multiple coils(function code15)
ModbusMessage coilReq;
coilReq.add((uint8_t)1);
coilReq.add((uint8_t)WRITE_COILS);  // function code15 (0x0F)
coilReq.add((uint16_t)0);           // starting address
coilReq.add((uint16_t)8);           // コイルの数
coilReq.add((uint8_t)1);            // byte count
coilReq.add((uint8_t)0xAA);         // Coil status: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);

  // 初期化:指定駅からの住所和シリアルポート
  // parameter1:From station address (1-247)
  // parameter2:用于通讯的Serial对象
  node.begin(1, Serial2);  // slave1,使用Serial2

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

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

void loop() {
  // ====== Read and hold register(function code03)======
  // parameter:starting address, Quantity
  // 戻る:ステータス码(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("  register%d:%d\n", i, value);
    }
  } else {
    Serial.printf("Read failed,エラー·コード:%02X\n", result);
  }

  // ====== Write to a single register(function code06)======
  result = node.writeSingleRegister(100, 1234);
  if (result == node.ku8MBSuccess) {
    Serial.println("写入レジスタ成功");
  }

  // ====== Write into a single coil(function code05)======
  // 注意:コイル値用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;

  // ====== function code01:リードコイルを読む ======
  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:%s\n", i, (coilStates >> i) & 1 ? "ON" : "OFF");
    }
  }

  // ====== function code02:read discrete inputs ======
  result = node.readDiscreteInputs(0, 8);
  if (result == node.ku8MBSuccess) {
    uint16_t inputStates = node.getResponseBuffer(0);
    Serial.printf("ショップ型入力:%04X\n", inputStates);
  }

  // ====== function code03:read holding registers ======
  result = node.readHoldingRegisters(0, 10);
  if (result == node.ku8MBSuccess) {
    for (int i = 0; i < 10; i++) {
      Serial.printf("レジスタを保持する。%d:%d\n", i, node.getResponseBuffer(i));
    }
  }

  // ====== function code04:read input registers ======
  result = node.readInputRegisters(0, 4);
  if (result == node.ku8MBSuccess) {
    for (int i = 0; i < 4; i++) {
      Serial.printf("入力レジスタ。%d:%d\n", i, node.getResponseBuffer(i));
    }
  }

  // ====== function code05:write single coil ======
  node.writeSingleCoil(0, 0xFF00);  // ON
  node.writeSingleCoil(0, 0x0000);  // OFF

  // ====== function code06:write single register ======
  node.writeSingleRegister(50, 999);

  // ====== function code15:write multiple coils ======
  // 先送信バッファの設定
  node.setTransmitBuffer(0, 0x00FF);  // 前8个コイルの状態
  result = node.writeMultipleCoils(0, 8);  // starting address, Quantity

  // ====== function code16:write multiple registers ======
  node.setTransmitBuffer(0, 100);
  node.setTransmitBuffer(1, 200);
  node.setTransmitBuffer(2, 300);
  result = node.writeMultipleRegisters(100, 3);  // starting address, Quantity

  delay(3000);
}

3.3 ModbusMasterエラー·コード

エラー·コード常量名含义
0x00ku8MBSuccess成功
0x01ku8MBIllegalFunction違法な機能コード
0x02ku8MBIllegalDataAddressIllegal data address
0x03ku8MBIllegalDataValueIllegal data value
0x04ku8MBSlaveDeviceFailureSubstation equipment malfunction
0xE0ku8MBInvalidSlaveID无效スレーブID
0xE1ku8MBInvalidFunction无效機能コード
0xE2ku8MBResponseTimedOutレスポンス·タイムアウト
0xE3ku8MBInvalidCRCCRC checkError

四、データ型処理

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

// ====== 32ビット浮動小数点数解析(ABCDビッグエンディアン序,最も一般的な)======
float parseFloatABCD(uint16_t regHigh, uint16_t regLow) {
  uint32_t raw = ((uint32_t)regHigh << 16) | regLow;
  float value;
  memcpy(&value, &raw, sizeof(float));
  return value;
}

// ====== 32ビット浮動小数点数解析(CDAB言葉の交換序)======
float parseFloatCDAB(uint16_t reg0, uint16_t reg1) {
  // reg0是低16位,reg1是高16位
  uint32_t raw = ((uint32_t)reg1 << 16) | reg0;
  float value;
  memcpy(&value, &raw, sizeof(float));
  return value;
}

// ====== 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)reg;
}

// ====== 带缩放的物理量 ======
float parseScaled(uint16_t reg, float scale) {
  return reg * scale;
}

// ====== 使用例の例(ModbusMaster)======
void readTemperature() {
  uint8_t result = node.readHoldingRegisters(100, 2);  // 温度占21つのレジスタ
  if (result == node.ku8MBSuccess) {
    uint16_t reg0 = node.getResponseBuffer(0);
    uint16_t reg1 = node.getResponseBuffer(1);
    float temp = parseFloatABCD(reg0, reg1);
    Serial.printf("temperature:%.2f°C\n", temp);
  }
}

// ====== 使用例の例(eModbus回调中)======
void handleData(ModbusMessage response, uint32_t token) {
  if (response.getFunctionCode() == READ_HOLD_REGISTER) {
    // 温度在レジスタ100-101(レスポンス中的第0-11つのレジスタ)
    uint16_t reg0 = (response[1] << 8) | response[2];
    uint16_t reg1 = (response[3] << 8) | response[4];
    float temp = parseFloatABCD(reg0, reg1);
    Serial.printf("temperature:%.2f°C\n", temp);
  }
}

五、Modbus TCPサーバー側。(slave)

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

#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);   // starting address(从第2バイト开始)
  request.get(4, count);  // number of registers

  ModbusMessage response;
  response.add(request.getServerID());
  response.add((uint8_t)READ_HOLD_REGISTER);
  response.add((uint8_t)(count * 2));  // byte count

  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:%d\n", addr, value);
  }

  // Response:回显アドレス和值
  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%)

  // connectWiFi
  WiFi.begin("your_ssid", "your_password");
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.printf("IPaddress:%s\n", 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);
}

六、RS485硬件设计ガイド

6.1 常用RS485モジュール对比

モジュール芯片自动收发絶縁Price適用可能なシーン
MAX485モジュールMAX485否(需GPIO控制)2-5元简单实验、ショート·ディスタンス
SP3485モジュールSP34853-6元3.3Vsystem、低消費電力は
絶縁RS485モジュールADM2483部分サポート是(2500V)15-25元industrial site、强干渉
自动收发モジュールMAX485+硬件逻辑5-8元无需GPIO控制

6.2 配線の仕様

  • バストポロジー:必须使用手をつないで菊花链トポロジー,禁断の星连接。分支线長さ不超过0.3米
  • 終端抵抗:バス両端に接続。一个120Ω电阻,中间节点不接。阻抗マッチング可大幅减少信号の反射
  • 线缆選択:シールドツイストペアの使用(STP),特性インピーダンス120Ω。A/B线必须绞合在一起
  • 接地:シールドシングルエンド接地(通常在主控端),避免形成地环路
  • 共地:もしデバイス间地電位差超过1V,需要使用絶縁モジュール或加共地线
  • 距离:9600ボーレート最大1200米,115200ボーレート最大100米。ボーレートの低下可延ロング·ディスタンス
  • 上拉下拉:A线接10kΩ上拉到VCC,B线接10kΩ下拉到GND,确保バス暇なとき电平稳定

七、完全项目実践:ESP32 Modbusデータの収集ゲートウェイ

以下是一个完全的工业データの収集ゲートウェイ项目:ESP32経由RS485読み取り多个温湿度センサー和電気メーターデータ,経由WiFi上传到MQTTServer,同时提供Modbus TCP服务供本地SCADAread。

#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=temperature, 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("device%dRead failed:%s\n", devIdx, (const char*)me);
}

// ====== MQTT再接続 ======
void mqttReconnect() {
  while (!mqtt.connected()) {
    if (mqtt.connect("ESP32_Gateway")) {
      Serial.println("MQTTConnected");
    } 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("上传:%s\n", json.c_str());
}

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

  // WiFi
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);
  Serial.printf("WiFiConnected:%s\n", 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);
}

八、よくある質問与トラブルシューティング

問題可能原因解決方法
RTU応答なし。A/Bライン·リバース交換A/B线,注意A对A、B对B
RTU応答なし。DE/RE引脚未控制ConfirmRTS_PIN配線正确,または使用自动收发モジュール
CRCErrorボーレート不マッチング確認マスター·マスター駅ボーレート、data bit、stop bit、チェックビット完全一致
CRCErrorシリアルポートバッファ太小使用RTUutils::prepareHardwareSerial()增大バッファ
TCP接続の失敗ポート被占用ESP32作为クライアント側は时確認サーバー·サーバー502ポート开放
TCP接続の失敗ファイアウォール拦截チェックサーバー·サーバーファイアウォール,允许502ポート入站
データ错乱バイトオーダーエラー尝试ABCD/CDAB/DCBA三种浮動小数点数解析方式
偶尔丢包バス干渉加終端抵抗、使用シールド線、ボーレートの低下
eModbusリクエスト不送信队列满减少リクエスト频率,addRequest之间加delay
ESP32再起動ウォッチドッグタイムアウト避免在loop中长时间阻塞,使用异步回调

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

  • バッチ読み取り:一次読み取り连续的多1つのレジスタ,比分多次読み取り快数倍。合理规划レジスター·アドレス表
  • リクエスト間隔:RTUモード下,不同スレーブリクエスト之间至少間隔50-100ms,避免バス冲突
  • 双核分配:ESP32可将Modbus通讯任务放在Core0,业务逻辑放在Core1,使用xTaskCreatePinnedToCore
  • タイムアウトの設定:eModbusデフォルトタイムアウト可能过长,根据实际网络設定2-5秒
  • データ缓存:变化缓慢的データ(如デバイス参数)可缓存,减少Modbusリクエスト频率
  • ボーレート選択:ショート·ディスタンス(<100米)可用115200,ロング·ディスタンス用9600。不要盲目追求高ボーレート
  • 队列深度:eModbusデフォルト队列深度100,高频场景可适当增大
  • 内存优化:大量デバイス时避免使用String类,使用char配列减少内存碎片

十、学习路线与リソース

阶段学习内容おすすめ库目标
入門Arduino基础、シリアルポート通讯、Modbusプロトコル概念ModbusMaster能読み取り单个センサーは
基础すべて機能コード、RS485配線、浮動小数点数処理ModbusMaster能完成常见读書き込み操作。
上級eModbus非同期アーキテクチャ、TCP通讯、多デバイスポーリングeModbus能开发稳定的采集系统
エキスパートModbusサーバー側。、MQTTゲートウェイ、OTAアップグレード、工业级部署eModbus + ESP-IDF能开发工业级Modbusゲートウェイ

10.1 おすすめリソース

  • eModbus官方ドキュメント:https://emodbus.github.io/
  • eModbussource code:https://github.com/eModbus/eModbus
  • ModbusMasterDocument:https://4-20ma.io/ModbusMaster/
  • Modbusプロトコル规范:https://modbus.org/specs.php
  • ESP32官方ドキュメント:https://docs.espressif.com/
  • Arduino ModbusTutorial:https://docs.arduino.cc/tutorials/opta/getting-started-with-modbus-rtu/

Arduino和ESP32以其极低的成本和强大的生态,正在改变工业データの収集的格局。eModbus作为ESP32プラットフォーム最强大的Modbus库,提供了异步非阻塞的TCP/RTUクライアント側は和サーバー側。実装,适合工业级项目;ModbusMaster则以其简单直观的API,是初学者的最佳選択。本記事提供的工具函数和完全ゲートウェイ项目可直接使用する。于实际开发,建议先在PC端用Modbus SlaveSimulator(シミュレータ)测试,再接入真实デバイス。

📦

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

eModbus/ModbusMasterライブラリの完全な例:TCP/RTUマスター·マスター駅、ネットワーク·シャットダウン、WiFi設定,直接燃焼できます。。

Activate VIP即可下载完全代码,同时解锁 30+ 工程実践パッケージの内容:スクリプトのデバッグ、クイックチェック·テーブル、项目模板、トラブルシューティング案例……

前往VIP资料库下载 → 月费仅9.9元 / 年费199元
Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support