用ESP32和Arduino框架搞定Modbus RTU电表数据采集(附完整代码)
2026/4/17 11:03:17 网站建设 项目流程

用ESP32和Arduino框架实现Modbus RTU电表数据采集实战指南

家里电表数据怎么实时监控?最近在工作室搭建能耗监测系统时,发现用ESP32+Arduino框架读取Modbus RTU电表数据特别方便。相比原生SDK,Arduino生态有更丰富的库支持和更简单的开发流程,特别适合快速原型开发。下面分享我的完整实现方案,包含硬件选型、库配置、代码解析和常见问题排查。

1. 硬件准备与连接

做Modbus RTU通信,硬件连接是第一步。我用的核心设备是ESP32开发板(推荐带USB接口的型号,比如ESP32 DevKitC),搭配MAX485模块实现TTL转485。电表端需要确认支持Modbus RTU协议,常见品牌如正泰、德力西都有兼容型号。

必备硬件清单

  • ESP32开发板 ×1
  • MAX485模块 ×1(注意选择3.3V电平版本)
  • 智能电表(Modbus RTU从机) ×1
  • 双绞线(推荐使用屏蔽线)若干米
  • 12V电源适配器(为电表供电)

接线时特别注意:

  1. ESP32的TX接MAX485的DI
  2. ESP32的RX接MAX485的RO
  3. MAX485的A/B端接电表的485+/485-
  4. 共地连接必不可少

提示:实际接线前先用万用表确认线序,485通信对极性敏感,接反会导致通信失败。

2. 开发环境搭建

Arduino IDE需要先安装ESP32支持包。打开首选项→附加开发板管理器网址,添加:

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

然后安装以下关键库:

// 在库管理中搜索安装 #include <ModbusMaster.h> // Modbus主站协议栈 #include <HardwareSerial.h> // 硬件串口控制

库版本建议:

  • ModbusMaster 2.0.1+
  • ESP32 Arduino Core 2.0.6+

3. Modbus通信协议解析

以某品牌电表为例,其Modbus寄存器映射如下:

寄存器地址数据含义数据类型换算公式
0x0008波特率uint16直接读取
0x0010电压值uint32值/100=实际电压(V)
0x0012电流值uint32值/1000=实际电流(A)
0x0014有功功率uint32值/10=实际功率(kW)

典型查询帧结构:

[从机地址][功能码][起始地址高][起始地址低][寄存器数量高][寄存器数量低][CRC低][CRC高]

例如读取0x0010开始的2个寄存器:

uint8_t query[] = {0x01, 0x03, 0x00, 0x10, 0x00, 0x02, 0xC5, 0xCD};

4. 完整代码实现

新建Arduino项目,主要代码结构如下:

#include <ModbusMaster.h> #define MAX485_DE 4 // MAX485 DE/RE控制引脚 #define MAX485_RE_NEG 5 // MAX485 RE_NEG控制引脚 ModbusMaster node; void preTransmission() { digitalWrite(MAX485_DE, HIGH); digitalWrite(MAX485_RE_NEG, HIGH); } void postTransmission() { digitalWrite(MAX485_DE, LOW); digitalWrite(MAX485_RE_NEG, LOW); } void setup() { pinMode(MAX485_DE, OUTPUT); pinMode(MAX485_RE_NEG, OUTPUT); Serial.begin(9600); Serial2.begin(9600, SERIAL_8N2); // 8数据位,无校验,2停止位 node.begin(1, Serial2); // 从机地址1 node.preTransmission(preTransmission); node.postTransmission(postTransmission); } void loop() { uint8_t result; uint16_t data[2]; // 读取电压值(0x0010) result = node.readInputRegisters(0x0010, 2); if (result == node.ku8MBSuccess) { float voltage = (node.getResponseBuffer(0) << 16 | node.getResponseBuffer(1)) / 100.0; Serial.print("Voltage: "); Serial.print(voltage); Serial.println("V"); } delay(3000); }

关键函数说明:

  • readInputRegisters()用于读取输入寄存器
  • getResponseBuffer()获取返回数据缓冲区
  • 32位数据需要组合高低16位寄存器值

5. 数据解析与处理技巧

实际项目中常遇到的数据处理问题:

字节序问题

// 大端转小端 uint32_t value = (data[0] << 16) | data[1];

浮点数处理

// 电表返回的定点数转浮点 float power = value / 10.0; // 0.1kW分辨率

错误处理增强

if (result != node.ku8MBSuccess) { Serial.print("Error: 0x"); Serial.println(result, HEX); if (result == node.ku8MBResponseTimedOut) { Serial.println("设备响应超时"); } }

6. 性能优化与稳定性提升

长期运行中发现几个优化点:

  1. 增加硬件滤波

    • 在MAX485的A/B线间加120Ω终端电阻
    • 并联0.1μF电容减少高频干扰
  2. 软件重试机制

uint8_t retry = 3; while(retry--) { result = node.readInputRegisters(0x0010, 2); if (result == node.ku8MBSuccess) break; delay(100); }
  1. 看门狗配置
#include <esp_task_wdt.h> esp_task_wdt_init(30, true); // 30秒看门狗

7. 数据上传与可视化

本地测试通过后,可以扩展WiFi上传功能。推荐两种方案:

方案A:MQTT上传

#include <WiFi.h> #include <PubSubClient.h> WiFiClient espClient; PubSubClient client(espClient); void sendToMQTT(float value) { char msg[50]; snprintf(msg, 50, "%.2f", value); client.publish("home/power/voltage", msg); }

方案B:HTTP API上报

#include <HTTPClient.h> void postToServer(float value) { HTTPClient http; http.begin("http://yourserver/api/power"); http.addHeader("Content-Type", "application/json"); String payload = "{\"voltage\":" + String(value,2) + "}"; int httpCode = http.POST(payload); if (httpCode != HTTP_CODE_OK) { Serial.printf("HTTP error: %s\n", http.errorToString(httpCode).c_str()); } http.end(); }

实际部署时,建议先写入本地SD卡做缓存,网络恢复后再批量上传,避免数据丢失。

8. 常见问题排查指南

遇到通信失败时,按这个检查流程走:

  1. 物理层检查

    • 用万用表测量A-B间电压(静止时应≈0V,通信时跳变)
    • 检查所有接头是否氧化松动
  2. 协议层诊断

    • 用USB转485适配器接电脑,使用ModScan测试工具验证电表是否响应
    • 对比正常帧和异常帧的Hex dump
  3. 典型错误代码

    • 0xE1:CRC校验错误(检查波特率/停止位设置)
    • 0xE2:从机无响应(检查地址和接线)
    • 0xE3:响应超时(降低波特率测试)
  4. 逻辑分析仪抓包: 如果条件允许,用Saleae逻辑分析仪捕获485信号,直观查看时序问题。

最后分享一个调试技巧:在代码中加入原始数据打印,方便分析:

Serial.print("Raw: "); for(int i=0; i<node.getResponseBufferLength(); i++) { Serial.print(node.getResponseBuffer(i), HEX); Serial.print(" "); } Serial.println();

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询