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 | モード | サーバー側。 | 特点 |
|---|---|---|---|---|---|
| eModbus | ESP32专用 | TCP/RTU | asynchronous(回调) | サポート | 最强大,非阻塞,多リクエスト队列 |
| ModbusMaster | すべてのArduino | RTU | 同步(阻塞) | サポートなし。 | 最简单,API直观,适合初学者 |
| Arduino_ESP32_ Modbus | ESP32 | TCP/RTU | 同步 | サポート | 乐鑫官方,ESP-IDF底层 |
| Modbus-Master-Slave | すべてのArduino | RTU | 同步 | サポート | 主从一体,轻量级 |
二、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機能コード常量速查
| 機能コード常量 | decimal | Function | addRequestparameter |
|---|---|---|---|
| READ_COIL | 01 | リードコイルを読む | (token, server, FC, addr, count) |
| READ_DISCR_INPUT | 02 | read discrete inputs | (token, server, FC, addr, count) |
| READ_HOLD_REGISTER | 03 | read holding registers | (token, server, FC, addr, count) |
| READ_INPUT_REGISTER | 04 | read input registers | (token, server, FC, addr, count) |
| WRITE_COIL | 05 | write single coil | (token, server, FC, addr, 0xFF00/0x0000) |
| WRITE_REGISTER | 06 | write single register | (token, server, FC, addr, value) |
| WRITE_COILS | 15 | write multiple coils | (token, server, FC, addr, count, data...) |
| WRITE_REGISTERS | 16 | write multiple registers | (token, server, FC, addr, count, data...) |
| READ_WRITE_REGISTERS | 23 | read 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エラー·コード
| エラー·コード | 常量名 | 含义 |
|---|---|---|
| 0x00 | ku8MBSuccess | 成功 |
| 0x01 | ku8MBIllegalFunction | 違法な機能コード |
| 0x02 | ku8MBIllegalDataAddress | Illegal data address |
| 0x03 | ku8MBIllegalDataValue | Illegal data value |
| 0x04 | ku8MBSlaveDeviceFailure | Substation equipment malfunction |
| 0xE0 | ku8MBInvalidSlaveID | 无效スレーブID |
| 0xE1 | ku8MBInvalidFunction | 无效機能コード |
| 0xE2 | ku8MBResponseTimedOut | レスポンス·タイムアウト |
| 0xE3 | ku8MBInvalidCRC | CRC 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モジュール | SP3485 | 否 | 否 | 3-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元