🔌 ESP32 RS485通信实战:Modbus工业协议从入门到设备互联
📅 2026-06-02 · 📂 硬件编程 · ⏱ 阅读约 9 分钟
📺 B站推荐视频:单片机应用实践篇之RS485通信及MODBUS通信协议 | 金善愚老师主讲
📖 概述
RS485 是工业现场最主流的串行通信标准,采用差分信号传输(A/B双线),抗共模干扰能力极强,最大传输距离可达 1200 米,支持最多 32 个节点挂载在同一条总线上。上至PLC、变频器,下至温湿度传感器、电表,几乎所有的工业设备都配备 RS485 接口。
在 RS485 物理层之上,运行着工业界的事实标准协议——Modbus。它采用简单高效的主从架构:主机发出查询帧(功能码+寄存器地址),从机解析后返回数据。上一讲我们学了 CAN总线——如果说 CAN 是汽车和机器人的「神经系统」,那么 RS485+Modbus 就是工厂自动化和楼宇控制的「循环系统」。
今天我们用 ESP32 外接一颗 MAX485 收发芯片,亲手搭建一个 Modbus 主从通信系统——主机读取从机的温湿度传感器数据,打通从「单片机实验」到「工业设备互联」的关键一步。
💡 为什么学 RS485/Modbus? 国内 90% 以上的工业传感器、PLC、电表、变频器都通过 RS485 Modbus 通信。学会它,你就能让 ESP32 与真实工业设备对话——这是从创客走向工程师的必经之路。
🛒 物料清单
| 物料 | 型号/参数 | 数量 | 参考价格 |
|---|
| 主控板 | ESP32 DevKit V1 (WROOM-32) | 2 | ¥25 ×2 |
| RS485 收发器 | MAX485 模块 (5V供电, SOP-8) | 2 | ¥5 ×2 |
| 温湿度传感器 | DHT22 (AM2302) 精度 ±0.5℃ | 1 | ¥12 |
| OLED显示屏 | SSD1306 0.96寸 128×64 I2C | 1 | ¥10 |
| 120Ω 终端电阻 | 120Ω ±5%, 1/4W 直插 | 2 | ¥0.1 ×2 |
| 双绞线 | 22AWG 双绞信号线(可用网线替代) | ~3m | ¥5 |
| 面包板 + 杜邦线 | 830孔 + 公母各20根 | 2套 | ¥15 ×2 |
| 合计 | 约 ¥112 |
⚡ 选型提醒: MAX485 是 5V 器件,但信号电平与 ESP32 的 3.3V 兼容(逻辑高电平阈值 ≥2.0V)。如果追求完美,可以用 SP3485(3.3V 版本),接线完全一样。另外 必须在 A/B 线两端各并一个 120Ω 终端电阻,否则高速/长距离通信会因信号反射而出错。
🔌 接线图
【主机端 — ESP32 #1 + MAX485 + SSD1306】
──────────────────────────────────────────
ESP32 #1 → MAX485 模块
3.3V → VCC (部分MAX485模块支持3.3V)
5V (VIN) → VCC (若模块仅支持5V)
GND → GND
GPIO 17 (TX2) → DI (驱动输入)
GPIO 16 (RX2) → RO (接收输出)
GPIO 5 → DE + RE (短接,发送/接收使能)
ESP32 #1 → SSD1306 OLED
3.3V → VCC
GND → GND
GPIO 21 (SDA) → SDA
GPIO 22 (SCL) → SCL
【从机端 — ESP32 #2 + MAX485 + DHT22】
──────────────────────────────────────────
ESP32 #2 → MAX485 模块
3.3V/5V → VCC
GND → GND
GPIO 17 (TX2) → DI
GPIO 16 (RX2) → RO
GPIO 5 → DE + RE
ESP32 #2 → DHT22
3.3V → VCC
GND → GND
GPIO 4 → DATA
【RS485 总线连接】
──────────────────────────────────────────
主机 MAX485 A ───┬─── 从机 MAX485 A
│
├── 120Ω 终端电阻 ──┐
│ │
主机 MAX485 B ───┴─── 从机 MAX485 B ─┘ (远端120Ω)
MAX485 模块的 DE(Driver Enable,发送使能)和 RE(Receiver Enable,接收使能)短接后由 GPIO5 控制——高电平=发送模式,低电平=接收模式。RS485 是半双工的,同一时刻只能发送或接收,这在代码中通过切换 DE/RE 引脚来实现。
💻 基础代码:Modbus RTU 主从通信
以下代码使用 ArduinoModbus 库(Arduino IDE 库管理搜索安装),实现主机通过 RS485 读取从机的 DHT22 温湿度数据,并在 OLED 上显示。
从机代码(ESP32 #2 + DHT22)
// ===== Modbus RTU 从机 — ESP32 + MAX485 + DHT22 =====
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <DHT.h>#define DHTPIN 4
#define DHTTYPE DHT22
#define DE_RE_PIN 5
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
dht.begin();
// 初始化 RS485:Serial2, DE/RE引脚, 波特率9600
RS485.begin(9600, 16, 17, DE_RE_PIN);
// 参数: 波特率, RX引脚, TX引脚, 收发使能引脚// 启动 Modbus RTU 从机,地址=1
ModbusRTUServer.begin(1, 9600);
// 配置保持寄存器: 地址0=温度(×10), 地址1=湿度(×10)
ModbusRTUServer.configureHoldingRegisters(0, 2);
Serial.println("Modbus Slave #1 Ready");
}
void loop() {
static unsigned long lastRead = 0;
// 每2秒更新一次寄存器数据if (millis() - lastRead > 2000) {
lastRead = millis();
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
ModbusRTUServer.holdingRegisterWrite(0, (int)(t * 10));
ModbusRTUServer.holdingRegisterWrite(1, (int)(h * 10));
Serial.printf("更新寄存器: T=%.1f℃ H=%.1f%%\n", t, h);
}
}
// 轮询 Modbus 请求(必须频繁调用)
ModbusRTUServer.poll();
}
主机代码(ESP32 #1 + SSD1306 OLED)
// ===== Modbus RTU 主机 — ESP32 + MAX485 + SSD1306 =====
#include <ArduinoRS485.h>
#include <ArduinoModbus.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>#define DE_RE_PIN 5
Adafruit_SSD1306 display(128, 64, &Wire, -1);
void setup() {
Serial.begin(115200);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
RS485.begin(9600, 16, 17, DE_RE_PIN);
ModbusRTUClient.begin(9600);
// 设置超时时间(ms)
ModbusRTUClient.setTimeout(500);
Serial.println("Modbus Master Ready");
}
void loop() {
static unsigned long lastPoll = 0;
if (millis() - lastPoll > 2000) {
lastPoll = millis();
// 读取从机#1的保持寄存器 0-1 (温度×10, 湿度×10)if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0, 2)) {
Serial.print("Modbus 读取失败: ");
Serial.println(ModbusRTUClient.lastError());
displayOLED(0, 0, true);
return;
}
int rawTemp = ModbusRTUClient.read();
int rawHum = ModbusRTUClient.read();
float temp = rawTemp / 10.0;
float hum = rawHum / 10.0;
Serial.printf("从机#1 → 温度: %.1f℃, 湿度: %.1f%%\n", temp, hum);
displayOLED(temp, hum, false);
}
}
void displayOLED(float temp, float hum, bool error) {
display.clearDisplay();
display.setCursor(0, 0);
display.println("Modbus RTU Master");
display.println("----------------");
if (error) {
display.println("Slave #1: OFFLINE!");
} else {
display.printf("Temp: %.1f C\n", temp);
display.printf("Hum : %.1f %%\n", hum);
}
display.display();
}
🔧 代码要点: Modbus 寄存器值用整数传输(避免浮点精度问题),约定温度×10、湿度×10 存入寄存器。主机读取后除以10还原。从机的 poll() 必须在主循环中频繁调用,否则会丢帧。DE/RE 引脚的自动切换由 ArduinoRS485 库内部完成,无需手动操作。
🚀 进阶应用
1. 多从机轮询网络
RS485 总线上最多可挂 32 个节点,每个从机配置不同的 Modbus 地址(1-247)。主机只需改变 requestFrom() 的第一个参数即可切换目标:
// 轮询多个从机
int slaveIDs[] = {1, 2, 3};
for (int id : slaveIDs) {
if (ModbusRTUClient.requestFrom(id, HOLDING_REGISTERS, 0, 2)) {
float t = ModbusRTUClient.read() / 10.0;
float h = ModbusRTUClient.read() / 10.0;
Serial.printf("从机#%d: %.1f℃ %.1f%%\n", id, t, h);
}
delay(50);
}
2. 功能码实战:读写线圈和寄存器
Modbus 定义了标准功能码,最常用的四种:
| 功能码 | 名称 | 作用 | 典型应用 |
|---|
| 01 | 读线圈 | 读取开关量输出 | 读继电器状态 |
| 03 | 读保持寄存器 | 读取16位数据 | 读温度/压力值 |
| 05 | 写单线圈 | 控制单个开关 | 开关继电器 |
| 06 | 写单寄存器 | 写入16位数据 | 设定目标温度 |
// 主机控制从机#1的继电器(线圈地址0)
ModbusRTUClient.coilWrite(1, 0, true); // 闭合继电器
delay(2000);
ModbusRTUClient.coilWrite(1, 0, false); // 断开继电器// 主机写从机#1保持寄存器(设定目标温度 35.5℃ → 寄存器值355)
ModbusRTUClient.holdingRegisterWrite(1, 10, 355);
3. Modbus RTU 帧格式详解
理解底层帧格式有助于用逻辑分析仪调试。一帧 Modbus RTU 报文的结构如下:
主机查询帧(读保持寄存器 0-1,从机地址=1):
┌────────┬──────┬──────────┬──────────┬──────────┬──────────┐
│ 地址 │ 功能码│ 起始地址 │ 寄存器数 │ CRC校验 │ 帧间隔 │
│ 0x01 │ 0x03 │ 0x00 0x00│ 0x00 0x02│ 0xC4 0x0B│ ≥3.5字符 │
└────────┴──────┴──────────┴──────────┴──────────┴──────────┘
从机响应帧(温度=25.0℃=0xFA, 湿度=60.0%=0x258):
┌────────┬──────┬──────┬──────────────┬──────────┬──────────┐
│ 地址 │ 功能码│ 字节数│ 数据 │ CRC校验 │ 帧间隔 │
│ 0x01 │ 0x03 │ 0x04 │0x00 0xFA 0x02│ 0x?? 0x??│ ≥3.5字符 │
│ │ │ │ 0x58 │ │ │
└────────┴──────┴──────┴──────────────┴──────────┴──────────┘
4. WiFi 网关:RS485 → MQTT 上云
结合第5/6篇 WiFi 和第5/22篇 MQTT 知识,将 RS485 网络采集的工业数据推送到云端:
// RS485-MQTT 网关:轮询从机 → 发布到 MQTT
#include <WiFi.h>
#include <PubSubClient.h>
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
void uploadToCloud(int slaveID, float temp, float hum) {
char topic[64], payload[128];
sprintf(topic, "factory/rs485/slave%d", slaveID);
sprintf(payload, "{\"temp\":%.1f,\"hum\":%.1f}", temp, hum);
mqtt.publish(topic, payload);
Serial.printf("MQTT发布: %s → %s\n", topic, payload);
}
🎯 实战场景
场景一:机房环境监控系统
需求: 数据中心机房有 8 个温湿度采集点,需要集中监控。布线距离 50-80 米,I2C/SPI 完全不可行。
方案: 每个采集点用 ESP32 + DHT22 + MAX485 做 Modbus 从机,手拉手串联在 RS485 总线上。主机 ESP32 轮询 8 个从机,OLED 本地显示 + WiFi 推送到云平台。RS485 1200 米的通信距离轻松覆盖整个机房楼层。
物料扩展: 工业场景建议升级为 SHT30(I2C接口,精度 ±0.3℃),配合防水外壳和 24V 供电(加 DC-DC 降压模块)。
联动模块: HC-SR04 超声波做机柜门禁检测,FreeRTOS 多任务分离采集和通信任务,低功耗模式做电池供电节点。
场景二:智能农业大棚控制器
需求: 大棚内分布多个土壤湿度传感器、CO₂传感器和遮阳帘电机,需要总线式集中控制。
方案: RS485 作为大棚内部通信骨干,各传感器节点通过 Modbus 上报数据。主机根据土壤湿度自动通过 Modbus 写线圈控制电磁阀(灌溉)和电机(卷帘)。结合 PID 温控实现温室环境闭环调节。
场景三:工业电表数据采集
需求: 工厂配电柜里多个智能电表通过 RS485 输出电压、电流、功率因数,需要统一采集上传。
方案: ESP32 做 Modbus 主机,轮询各电表的电压(寄存器 0x0000)、电流(0x0008)、有功功率(0x000C)等标准寄存器。配合 HTTP 客户端推送到能耗管理平台。这是典型的工业物联网(IIoT)数据采集架构。
⚠️ 常见问题
❌ 通信完全不通,主机一直超时
先做硬件排查:①万用表量 A/B 线间直流电压——空闲时应为 0.2V~0.3V(差分偏置);②检查 120Ω 终端电阻是否两端都接了;③检查 DE/RE 引脚接线(注意有些 MAX485 模块 DE 和 RE 是分开的,必须短接或分别控制);④确认 A 接 A、B 接 B,不要交叉。如果以上都对,用 USB 转 RS485 工具 + Modbus Poll 软件单独测试从机是否正常。
❌ 偶尔能读到数据,但经常丢包
典型症状:①波特率不匹配——主从必须严格一致(9600 是最稳妥的起点);②没有终端电阻或只接了一端——挂上示波器看 A/B 波形,有振铃/反射就是缺电阻;③总线过长或分支线(stub)太长——RS485 要求菊花链拓扑,分支线不超过 30cm;④双绞线质量差——用网线里的双绞线对比普通杜邦线,效果天差地别。
❌ Modbus 返回错误码 0x02(非法数据地址)
功能码+错误码 0x83 表示请求的寄存器地址超出范围。检查从机的 configureHoldingRegisters() 配置——寄存器起始地址和数量是否覆盖了主机的请求范围。Modbus 从机地址从 0 开始,与 PLC 的 40001 等编号体系不同,注意转换。
❌ 多个从机同时在线时通信混乱
Modbus 是严格的主从协议——从机从不主动发送,只在收到属于自己的查询帧后才响应。如果出现乱码/冲突:①确认所有从机地址唯一(不要有重复 ID);②帧与帧之间必须保持 ≥3.5 字符时间的静默(9600bps 约 3.6ms),ArduinoModbus 库自动处理,但自己写裸协议时必须注意;③检查总线偏置电阻——某些 RS485 中继器或隔离器需要额外偏置。
❌ 刷入代码后 ESP32 不断重启
常见原因:MAX485 模块从 ESP32 的 3.3V 引脚取电导致供电不足(MAX485 峰值电流 ~1mA,但模块上可能有其他元件)。解决方案:从 VIN(5V)或外部电源给 MAX485 供电。另外 DHT22 和 MAX485 共用 GPIO 时要检查引脚冲突。
📊 总结
RS485 + Modbus 是工业通信的「普通话」——学会了它,你的 ESP32 就不再是孤立的开发板,而是一台能与 PLC、变频器、传感器、电表对话的工业级数据终端。
✅ 理解 RS485 差分传输的物理优势(抗干扰、长距离、多节点)
✅ 掌握 Modbus RTU 主从通信编程(ArduinoModbus 库)
✅ 学会硬件报文过滤、多从机轮询、WiFi 网关等进阶用法
✅ 具备排查 RS485 通信故障的调试思路(示波器/逻辑分析仪/ModbusPoll)
下一讲,我们将进入蓝牙 Mesh 组网——用 ESP32 搭建去中心化的低功耗传感器网络,敬请期待!