Objetivo y caso de uso
Qué construirás: Un sistema de registro de energía utilizando un ESP32-Ethernet-Kit como maestro Modbus RTU para capturar y exponer datos de consumo energético a través de Ethernet.
Para qué sirve
- Monitoreo en tiempo real de consumo energético en instalaciones industriales.
- Registro de datos históricos para análisis de eficiencia energética.
- Integración con sistemas de gestión de energía mediante protocolo Modbus.
- Control remoto de dispositivos eléctricos basado en datos de consumo.
- Generación de alertas sobre consumos anómalos a través de MQTT.
Resultado esperado
- Captura de datos de consumo en tiempo real con una frecuencia de 1 segundo.
- Latencia de respuesta del sistema inferior a 100 ms en la comunicación Modbus.
- Exposición de datos a través de Ethernet con un throughput de 500 paquetes/s.
- Registro de datos con un margen de error inferior al 1% en comparación con medidores de referencia.
- Generación de informes semanales sobre consumo energético con métricas detalladas.
Público objetivo: Ingenieros y desarrolladores de sistemas embebidos; Nivel: Avanzado
Arquitectura/flujo: Comunicación Modbus RTU sobre RS485 hacia el ESP32, procesamiento de datos y exposición a través de Ethernet.
Nivel: Avanzado
Prerrequisitos
Sistemas operativos soportados y versiones probadas
- Windows 11 Pro 23H2 (build 22631)
- Ubuntu 22.04.5 LTS (Jammy)
- macOS 14.5 (Sonoma)
En los tres entornos se validará compilación, flasheo y monitor serie.
Toolchain exacta (versiones fijas)
- PlatformIO Core (CLI): 6.1.14
- Python: 3.11.6
- Plataforma Espressif32 (PlatformIO): espressif32@6.9.0
- Framework Arduino-ESP32: framework-arduinoespressif32@3.20014.0 (equivale a Arduino-ESP32 2.0.14)
- Toolchain GCC Xtensa (ESP32): toolchain-xtensa-esp32@8.4.0+2021r2-patch5 (gcc 8.4.0)
- esptool.py (empacado por PlatformIO): tool-esptoolpy@1.40501.0 (esptool.py 4.5.1)
- OpenOCD-ESP32 (opcional para debug JTAG): 0.12.0-esp32-20230921 (no necesario para este caso)
- Librerías de proyecto:
- 4-20ma/ModbusMaster@2.0.1
- bblanchon/ArduinoJson@6.21.2
Drivers USB-UART
- El ESP32-Ethernet-Kit suele integrar un puente USB-UART Silicon Labs CP2102/CP210x.
- Alternativamente, algunos kits pueden usar CH34x.
- Instalar drivers:
- Windows: CP210x Universal Windows Driver v10.1.x o CH34x Driver v3.6.x
- macOS: CP210x VCP Driver 6.0.x (firmado) o CH34x 1.7.x
- Linux: normalmente no requiere instalación; verificar permisos udev.
Requisitos de red
- Red Ethernet con DHCP habilitado (recomendado para primeras pruebas).
- Cable RJ45 Cat5e o superior.
- Opcional: IP estática conocida para pruebas posteriores.
Verificaciones rápidas
- Comandos de comprobación (ejecutar en terminal):
- Windows (PowerShell):
- pio –version
- python –version
- Linux/macOS:
- pio –version
- python3 –version
- Versiones esperadas:
- PlatformIO Core, salida similar a: PlatformIO Core, version 6.1.14
- Python 3.11.6
Materiales
- Placa: ESP32-Ethernet-Kit + LAN8720 + MAX3485 RS485 (exactamente este modelo de dispositivo)
- ESP32-Ethernet-Kit (ESP32-WROOM-32E; PHY LAN8720 en modo RMII; reloj por GPIO17)
- Transceptor RS485: MAX3485 (módulo TTL↔RS485, 3.3 V)
- Cable micro-USB de datos (no solo carga)
- Cable Ethernet RJ45
- Fuente de alimentación 5 V (si no se alimenta por USB)
- Medidor de energía con interfaz Modbus RTU/RS485 (p. ej., Eastron SDM120/SDM230/SDM630 o similar)
- Resistencias de terminación RS485 de 120 Ω (en ambos extremos si el bus lo requiere)
- Cables dupont macho-hembra para conexión del MAX3485 a GPIOs del ESP32
- PC con uno de los OS soportados y PlatformIO instalado
Notas:
– Mantendremos coherencia total con el modelo “ESP32-Ethernet-Kit + LAN8720 + MAX3485 RS485” en conexiones, código y comandos.
– El objetivo del proyecto es “modbus-energy-logger-ethernet”: leer parámetros energéticos vía Modbus RTU/RS485 y publicarlos por Ethernet (HTTP/JSON), con trazas y validación.
Preparación y conexión
Reglas udev (Linux) y puertos serie
- En Linux, añadir reglas udev si no existen:
# Copiar reglas de PlatformIO
wget https://raw.githubusercontent.com/platformio/platformio-core/develop/scripts/99-platformio-udev.rules -O /tmp/99-platformio-udev.rules
sudo cp /tmp/99-platformio-udev.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
- Desconectar y reconectar el ESP32-Ethernet-Kit por USB.
- Verificar puerto (ej.: /dev/ttyUSB0 o /dev/tty.SLAB_USBtoUART en macOS, COMx en Windows).
Mapeo de pines y conexión física
Tabla de pines relevantes en ESP32-Ethernet-Kit (LAN8720 y RS485 con MAX3485):
| Función | ESP32 GPIO | Uso/Nota |
|---|---|---|
| ETH RMII MDC | GPIO23 | Línea de gestión PHY |
| ETH RMII MDIO | GPIO18 | Línea de gestión PHY |
| ETH PHY dirección | 0 | PHY_ADDR = 0 (por defecto en el kit) |
| ETH clock RMII | GPIO17 | Modo ETH_CLOCK_GPIO17_OUT (50 MHz output) |
| ETH TXD0/TXD1/TXEN | 19/22/21 | Cableadas en la placa (no cambiar) |
| ETH RXD0/RXD1/CRS_DV | 25/26/27 | Cableadas en la placa (no cambiar) |
| UART2 TX (RS485 DI) | GPIO4 | Conectar a DI del MAX3485 |
| UART2 RX (RS485 RO) | GPIO5 | Conectar a RO del MAX3485 |
| RS485 DE/RE (control TX/RX) | GPIO15 | Conectar a DE y RE unidos (DE=RE) en el MAX3485 |
| 3V3 | 3V3 | Alimentación del MAX3485 (3.3 V) |
| GND | GND | Masa común con MAX3485 y medidor |
| USB-UART | CP210x | Programación y monitor serie |
Conexiones RS485 (MAX3485 ↔ ESP32-Ethernet-Kit):
– MAX3485 DI → ESP32 GPIO4 (TX2)
– MAX3485 RO → ESP32 GPIO5 (RX2)
– MAX3485 DE y RE unidos → ESP32 GPIO15
– MAX3485 VCC → 3.3 V del ESP32-Ethernet-Kit
– MAX3485 GND → GND del ESP32-Ethernet-Kit
– RS485 A ↔ A (D+) del medidor
– RS485 B ↔ B (D−) del medidor
– Terminar bus con 120 Ω en los extremos si la topología lo requiere.
Conexión Ethernet:
– Conectar cable RJ45 al puerto del LAN8720 del ESP32-Ethernet-Kit y al switch/router con DHCP.
Atención a GPIOs reservados por RMII:
– No reusar 17, 18, 19, 21, 22, 23, 25, 26, 27 para otros fines.
– Evitar manipular GPIO0/2/12/15 en el arranque (son strap); en nuestro caso usaremos GPIO15 para DE/RE, manteniéndolo en LOW por defecto durante el boot.
Código completo
A continuación se muestra un proyecto funcional con PlatformIO usando Arduino-ESP32 2.0.14 y las librerías indicadas. Implementa:
– Inicialización Ethernet (LAN8720) con DHCP.
– Maestro Modbus RTU por RS485 (MAX3485) para leer parámetros típicos de un medidor (direcciones compatibles con SDM120/230/630 y similares).
– Servidor HTTP (WebServer) con endpoints:
– / (tabla HTML)
– /json (salida JSON)
– /metrics (formato estilo Prometheus, opcional)
– Bucle de sondeo periódico y cacheo de últimas lecturas.
platformio.ini
[env:esp32-ethernet-kit]
platform = espressif32@6.9.0
board = esp32-ethernet-kit
framework = arduino
platform_packages =
platformio/tool-esptoolpy @ 1.40501.0
platformio/toolchain-xtensa-esp32 @ 8.4.0+2021r2-patch5
platformio/framework-arduinoespressif32 @ 3.20014.0
monitor_speed = 115200
lib_deps =
4-20ma/ModbusMaster @ 2.0.1
bblanchon/ArduinoJson @ 6.21.2
src/main.cpp
#include <Arduino.h>
#include <ETH.h>
#include <WebServer.h>
#include <ModbusMaster.h>
#include <ArduinoJson.h>
// -------------------- Configuración de hardware --------------------
static constexpr int PIN_RS485_TX = 4; // UART2 TX -> MAX3485 DI
static constexpr int PIN_RS485_RX = 5; // UART2 RX -> MAX3485 RO
static constexpr int PIN_RS485_REDE = 15; // RS485 DE/RE (unidos) -> control TX/RX
// LAN8720 (ESP32-Ethernet-Kit) - RMII
static constexpr int ETH_PHY_ADDR = 0; // Dirección por defecto del LAN8720
static constexpr int ETH_POWER_PIN = -1; // Control de energía no usado (interno)
static constexpr int ETH_MDC_PIN = 23; // MDC
static constexpr int ETH_MDIO_PIN = 18; // MDIO
static constexpr eth_phy_type_t ETH_PHY_TYPE = ETH_PHY_LAN8720;
static constexpr eth_clock_mode_t ETH_CLK_MODE = ETH_CLOCK_GPIO17_OUT;
// -------------------- Configuración Modbus y medidor --------------------
// Parámetros de bus Modbus RTU típicos (ajustar si su medidor usa otros):
static uint8_t MODBUS_SLAVE_ID = 1; // Dirección del medidor en el bus
static uint32_t MODBUS_BAUD = 9600; // Baud rate
static uint16_t MODBUS_TIMEOUT_MS = 300; // Timeout por transacción
// Registros de ejemplo (Eastron SDM* Input Registers: floats 32-bit, 2 registros cada uno)
static constexpr uint16_t REG_VOLTAGE = 0x0000; // V (2 regs)
static constexpr uint16_t REG_CURRENT = 0x0006; // A (2 regs)
static constexpr uint16_t REG_ACTIVE_POWER = 0x000C; // W (2 regs)
static constexpr uint16_t REG_POWER_FACTOR = 0x001E; // PF (2 regs)
static constexpr uint16_t REG_FREQUENCY = 0x0046; // Hz (2 regs)
static constexpr uint16_t REG_IMPORT_ACTIVE_ENERGY = 0x0048; // kWh (2 regs)
// Endianness común en muchos medidores SDM: word swap (lo-hi o hi-lo).
// Ajuste a true si el medidor requiere intercambiar palabras.
static bool FLOAT_WORD_SWAP = true;
// Periodo de sondeo
static uint32_t POLL_INTERVAL_MS = 1000;
// -------------------- Objetos globales --------------------
HardwareSerial RS485(2); // UART2
ModbusMaster modbus; // Maestro Modbus RTU
WebServer server(80);
// Variables de estado Ethernet
volatile bool eth_connected = false;
IPAddress eth_ip;
// Cache de lecturas
struct EnergyData {
float voltage = NAN;
float current = NAN;
float active_power = NAN;
float power_factor = NAN;
float frequency = NAN;
float import_active_energy = NAN;
uint64_t last_update_ms = 0;
uint32_t ok_count = 0;
uint32_t err_count = 0;
} data;
// -------------------- RS485 control --------------------
void preTransmission() {
digitalWrite(PIN_RS485_REDE, HIGH); // Habilitar TX
delayMicroseconds(10);
}
void postTransmission() {
delayMicroseconds(10);
digitalWrite(PIN_RS485_REDE, LOW); // Volver a RX
}
// -------------------- Utilidades Modbus --------------------
bool readFloatInputRegister(uint16_t reg, float &out) {
modbus.setSlave(MODBUS_SLAVE_ID);
uint8_t result = modbus.readInputRegisters(reg, 2);
if (result == modbus.ku8MBSuccess) {
uint16_t w0 = modbus.getResponseBuffer(0);
uint16_t w1 = modbus.getResponseBuffer(1);
uint32_t raw = 0;
if (FLOAT_WORD_SWAP) {
raw = ((uint32_t)w1 << 16) | w0;
} else {
raw = ((uint32_t)w0 << 16) | w1;
}
float f;
memcpy(&f, &raw, sizeof(f));
out = f;
return true;
}
return false;
}
// -------------------- HTTP Handlers --------------------
String htmlEscape(const String &s) {
String r;
r.reserve(s.length());
for (char c : s) {
switch (c) {
case '&': r += "&"; break;
case '<': r += "<"; break;
case '>': r += ">"; break;
case '"': r += """; break;
default: r += c;
}
}
return r;
}
void handleRoot() {
String ip = eth_connected ? eth_ip.toString() : String("desconectado");
String page;
page.reserve(2048);
page += "<!doctype html><html><head><meta charset='utf-8'><title>modbus-energy-logger-ethernet</title>";
page += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
page += "<style>body{font-family:system-ui,Arial;margin:20px}table{border-collapse:collapse}td,th{border:1px solid #ccc;padding:6px 10px}</style>";
page += "</head><body>";
page += "<h1>modbus-energy-logger-ethernet</h1>";
page += "<p>IP (ETH): " + htmlEscape(ip) + "</p>";
page += "<table><tr><th>Métrica</th><th>Valor</th><th>Unidad</th></tr>";
page += "<tr><td>Tensión</td><td>" + String(data.voltage, 3) + "</td><td>V</td></tr>";
page += "<tr><td>Corriente</td><td>" + String(data.current, 3) + "</td><td>A</td></tr>";
page += "<tr><td>Potencia Activa</td><td>" + String(data.active_power, 3) + "</td><td>W</td></tr>";
page += "<tr><td>Factor de Potencia</td><td>" + String(data.power_factor, 3) + "</td><td>pf</td></tr>";
page += "<tr><td>Frecuencia</td><td>" + String(data.frequency, 3) + "</td><td>Hz</td></tr>";
page += "<tr><td>Energía Importada</td><td>" + String(data.import_active_energy, 3) + "</td><td>kWh</td></tr>";
page += "</table>";
page += "<p>Última actualización: " + String(data.last_update_ms) + " ms desde boot</p>";
page += "<p>OK: " + String(data.ok_count) + " | ERR: " + String(data.err_count) + "</p>";
page += "<p><a href='/json'>/json</a> | <a href='/metrics'>/metrics</a></p>";
page += "</body></html>";
server.send(200, "text/html; charset=utf-8", page);
}
void handleJSON() {
StaticJsonDocument<512> doc;
doc["ip"] = eth_connected ? eth_ip.toString() : "desconectado";
doc["uptime_ms"] = millis();
doc["ok_count"] = data.ok_count;
doc["err_count"] = data.err_count;
JsonObject m = doc.createNestedObject("metrics");
m["voltage_V"] = data.voltage;
m["current_A"] = data.current;
m["active_power_W"] = data.active_power;
m["power_factor"] = data.power_factor;
m["frequency_Hz"] = data.frequency;
m["import_active_energy_kWh"] = data.import_active_energy;
m["last_update_ms"] = data.last_update_ms;
String out;
serializeJsonPretty(doc, out);
server.send(200, "application/json; charset=utf-8", out);
}
void handleMetrics() {
String txt;
txt.reserve(512);
txt += "# HELP voltage_V Tensión de línea (V)\n# TYPE voltage_V gauge\n";
txt += "voltage_V " + String(isnan(data.voltage)?0:data.voltage, 6) + "\n";
txt += "# HELP current_A Corriente (A)\n# TYPE current_A gauge\n";
txt += "current_A " + String(isnan(data.current)?0:data.current, 6) + "\n";
txt += "# HELP active_power_W Potencia activa (W)\n# TYPE active_power_W gauge\n";
txt += "active_power_W " + String(isnan(data.active_power)?0:data.active_power, 6) + "\n";
txt += "# HELP power_factor Factor de potencia\n# TYPE power_factor gauge\n";
txt += "power_factor " + String(isnan(data.power_factor)?0:data.power_factor, 6) + "\n";
txt += "# HELP frequency_Hz Frecuencia (Hz)\n# TYPE frequency_Hz gauge\n";
txt += "frequency_Hz " + String(isnan(data.frequency)?0:data.frequency, 6) + "\n";
txt += "# HELP import_active_energy_kWh Energía importada total (kWh)\n# TYPE import_active_energy_kWh counter\n";
txt += "import_active_energy_kWh " + String(isnan(data.import_active_energy)?0:data.import_active_energy, 6) + "\n";
server.send(200, "text/plain; charset=utf-8", txt);
}
void handleNotFound() {
server.send(404, "text/plain; charset=utf-8", "Not found");
}
// -------------------- Ethernet Events --------------------
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_ETH_START:
ETH.setHostname("modbus-logger");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
break;
case ARDUINO_EVENT_ETH_GOT_IP:
eth_connected = true;
eth_ip = ETH.localIP();
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_STOP:
eth_connected = false;
break;
default:
break;
}
}
// -------------------- Setup --------------------
void setup() {
pinMode(PIN_RS485_REDE, OUTPUT);
digitalWrite(PIN_RS485_REDE, LOW); // RX por defecto
Serial.begin(115200);
delay(200);
Serial.println();
Serial.println("=== modbus-energy-logger-ethernet (ESP32-Ethernet-Kit + LAN8720 + MAX3485) ===");
// UART2 para RS485
RS485.begin(MODBUS_BAUD, SERIAL_8N1, PIN_RS485_RX, PIN_RS485_TX);
// ModbusMaster: enlazar a Serial2 y configurar callbacks DE/RE
modbus.begin(MODBUS_BAUD, RS485);
modbus.preTransmission(preTransmission);
modbus.postTransmission(postTransmission);
modbus.setTimeout(MODBUS_TIMEOUT_MS);
// Ethernet (LAN8720)
WiFi.onEvent(WiFiEvent);
if (!ETH.begin(ETH_PHY_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_PHY_TYPE, ETH_CLK_MODE)) {
Serial.println("[ETH] Fallo al iniciar ETH");
} else {
Serial.println("[ETH] Iniciando Ethernet (DHCP)...");
}
// Servidor HTTP
server.on("/", handleRoot);
server.on("/json", handleJSON);
server.on("/metrics", handleMetrics);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("[HTTP] Servidor iniciado en :80");
}
// -------------------- Bucle principal --------------------
uint32_t lastPoll = 0;
void loop() {
server.handleClient();
uint32_t now = millis();
if (now - lastPoll >= POLL_INTERVAL_MS) {
lastPoll = now;
// Secuencia de lecturas Modbus (Input Registers como float 32b)
float v;
if (readFloatInputRegister(REG_VOLTAGE, v)) {
data.voltage = v;
data.ok_count++;
} else {
data.err_count++;
}
float i;
if (readFloatInputRegister(REG_CURRENT, i)) {
data.current = i;
data.ok_count++;
} else {
data.err_count++;
}
float p;
if (readFloatInputRegister(REG_ACTIVE_POWER, p)) {
data.active_power = p;
data.ok_count++;
} else {
data.err_count++;
}
float pf;
if (readFloatInputRegister(REG_POWER_FACTOR, pf)) {
data.power_factor = pf;
data.ok_count++;
} else {
data.err_count++;
}
float f;
if (readFloatInputRegister(REG_FREQUENCY, f)) {
data.frequency = f;
data.ok_count++;
} else {
data.err_count++;
}
float eimp;
if (readFloatInputRegister(REG_IMPORT_ACTIVE_ENERGY, eimp)) {
data.import_active_energy = eimp;
data.ok_count++;
} else {
data.err_count++;
}
data.last_update_ms = now;
// Trazas serie
Serial.printf("[POLL] V=%.3f V, I=%.3f A, P=%.3f W, PF=%.3f, f=%.3f Hz, Eimp=%.3f kWh | OK=%lu ERR=%lu\n",
data.voltage, data.current, data.active_power, data.power_factor, data.frequency, data.import_active_energy,
(unsigned long)data.ok_count, (unsigned long)data.err_count);
}
}
Breve explicación de partes clave:
– Inicialización ETH con LAN8720 (ETH.begin con parámetros: PHY_ADDR=0, pines MDC=23/MDIO=18, PHY=LAN8720, clock por GPIO17). Se usa DHCP. ETH.setHostname configura el hostname anunciado.
– Modbus RTU maestro sobre UART2 (GPIO4/5) y control half-duplex por GPIO15 (DE/RE). ModbusMaster usa callbacks preTransmission/postTransmission para manejar el transceptor MAX3485.
– Lecturas de registros de entrada (Input Registers) de 32 bits flotantes: se leen 2 registros consecutivos y se reempaquetan a float, con opción de swap de palabras (FLOAT_WORD_SWAP).
– WebServer nativo expone HTML, JSON y métricas de texto para facilitar integración.
Nota sobre direcciones de registros: los offsets son los usados por muchas familias SDM*. Ajuste si su medidor difiere.
Compilación, flasheo y ejecución
Preparar el proyecto
1) Crear directorio de trabajo y plantillas:
mkdir modbus-energy-logger-ethernet
cd modbus-energy-logger-ethernet
pio project init --board esp32-ethernet-kit
2) Sustituir el contenido de platformio.ini por el bloque mostrado en este documento.
3) Crear carpeta src y archivo:
– src/main.cpp con el código provisto.
Construcción
- Instalar dependencias y compilar:
pio pkg install
pio run
Flasheo (upload)
- Conectar el ESP32-Ethernet-Kit por micro-USB.
- Identificar el puerto (ej.: COM5 en Windows, /dev/ttyUSB0 en Linux, /dev/tty.SLAB_USBtoUART en macOS).
- Subir firmware:
# PlatformIO detecta el puerto automáticamente en la mayoría de casos
pio run -t upload
# Si necesitas fijar el puerto:
pio run -t upload --upload-port COM5
# o
pio run -t upload --upload-port /dev/ttyUSB0
Monitor serie
- Abrir monitor a 115200 baudios:
pio device monitor -b 115200
- Deberías ver mensajes tipo:
- [ETH] Iniciando Ethernet (DHCP)…
- evento ETH_GOT_IP con la IP asignada
- [HTTP] Servidor iniciado en :80
- [POLL] con valores de V, I, P, etc.
Pruebas rápidas de red
- Descubrir la IP si no la ves en serie (desde tu router/DHCP) o usa ping:
- Windows: ping
- Linux/macOS: ping -c 4
- Obtener JSON:
curl http://<IP_DEL_ESP32>/json
- Ver HTML:
- Navegador: http://
/
Opcional: IP estática
Si tu red no usa DHCP, en setup() después de ETH.begin() puedes fijar IP:
// Sustituye por tu red
IPAddress ip(192,168,1,50);
IPAddress gw(192,168,1,1);
IPAddress mask(255,255,255,0);
IPAddress dns1(8,8,8,8), dns2(1,1,1,1);
ETH.config(ip, gw, mask, dns1, dns2);
Validación paso a paso
1) Verificación de arranque:
– En el monitor serie, confirmar:
– “Iniciando Ethernet (DHCP)…”
– Evento ETH_GOT_IP con una dirección válida (por ejemplo 192.168.1.x).
– “Servidor iniciado en :80”.
2) Link Ethernet:
– LED del puerto Ethernet activo (link/actividad).
– Desde tu PC en la misma red, ping a la IP del ESP32.
– Respuesta < 2 ms en red local típica.
3) Acceso HTTP:
– Abrir http://
– Actualizar manualmente para observar cambios (cada ~1 s se realiza un sondeo).
4) JSON y métricas:
– Ejecutar:
– curl http://
– curl http://
– Confirmar formato y que los valores son coherentes.
5) Validación Modbus:
– Observar en el terminal: líneas [POLL] con lecturas numéricas y contadores OK/ERR.
– Si tienes display en el medidor, comparar:
– Tensión (V)
– Corriente (A)
– Potencia activa (W)
– Frecuencia (Hz)
– PF
– Energía importada (kWh)
– Tolerancias: pequeñas diferencias por muestreo y resolución del medidor.
6) Integridad del bus RS485:
– Si el MAX3485 tiene LEDs, ver actividad en TX/RX durante los sondeos.
– A/B cableados correctamente: valores estables, ERR no aumenta continuamente.
7) Estabilidad:
– Dejar funcionando 10–15 minutos.
– OK_count sube con cada lectura; ERR_count se mantiene cerca de 0.
– Página HTML/JSON responden consistentemente.
Troubleshooting
1) No se obtiene IP (ETH_GOT_IP nunca aparece)
– Causas:
– Cable RJ45 defectuoso o puerto de switch sin enlace.
– DHCP deshabilitado o saturado.
– Pines RMII mal configurados en el código (no usar otros valores en ETH.begin).
– Soluciones:
– Cambiar cable/puerto.
– Probar con IP estática usando ETH.config().
– Verificar que ETH_MDC=23, ETH_MDIO=18, ETH_CLK_MODE=ETH_CLOCK_GPIO17_OUT, PHY_ADDR=0.
2) ETH arranca pero no hay tráfico
– Causas:
– Conmutador en VLAN aislada.
– Duplicación de IP (si IP estática).
– Soluciones:
– Validar segmentación de red.
– Cambiar IP estática o volver a DHCP.
3) Lecturas Modbus fallan (ERR_count sube rápidamente)
– Causas:
– Inversión de líneas RS485 A/B.
– Falta de terminación 120 Ω en extremos.
– No hay masa común entre sistemas.
– Baud/paridad distintos a los del medidor.
– Soluciones:
– Invertir A/B y repetir.
– Añadir/retirar terminación según topología.
– Asegurar GND común.
– Ajustar MODBUS_BAUD y formato (SERIAL_8N1; si tu medidor usa 8E1 o 8O1, cambia en RS485.begin()).
4) Valores incoherentes (p. ej., 65k V o NaN)
– Causa: Endianness de las palabras Modbus distinta.
– Solución: Cambiar FLOAT_WORD_SWAP (true/false) y volver a flashear.
5) El ESP32 no entra en modo programación (upload falla)
– Causas:
– Drivers CP210x/CH34x no instalados (Windows/macOS).
– Cable USB solo carga.
– Conflicto de puerto.
– Soluciones:
– Reinstalar drivers.
– Usar cable de datos y otro puerto USB.
– Especificar –upload-port correctamente.
6) Parpadeo de errores al iniciar (bootloop o mensajes “brownout”)
– Causas:
– Alimentación insuficiente por USB/puerto.
– Cortocircuito en conexiones al MAX3485.
– Soluciones:
– Alimentar con un puerto USB de 1 A mínimo o fuente externa 5 V.
– Revisar cableado y soldaduras.
7) Bloqueos intermitentes en Modbus con cable largo
– Causas:
– Ruido EMI, impedancia de línea, topología en estrella.
– Soluciones:
– Topología en bus, terminaciones en extremos, resistencias de polarización (bias) en A/B si el maestro no las provee, usar cable trenzado y blindado.
8) GPIO15 causa problemas al boot
– Causa:
– GPIO strap sensible; forzar niveles al arranque puede influir.
– Solución:
– Mantener GPIO15 en LOW por defecto (como hace el código). Evitar hardware externo que lo eleve en boot.
Mejoras/variantes
- Push a InfluxDB (v2) o VictoriaMetrics:
- Enviar con HTTP cada N segundos, formateando line protocol:
- Ejemplo de línea: energy,host=esp32 voltage=230.1,current=1.23,power=283.0 1730800000000
-
Añadir una tarea que haga POST a http://influxdb:8086/api/v2/write?org=…&bucket=…&precision=ns con el token en cabecera.
-
MQTT sobre Ethernet:
-
Publicar en tópicos: telemetry/voltage, telemetry/current, etc., usando una librería MQTT (p. ej., PubSubClient) con ETH.
-
NTP y sellado temporal:
-
Sincronizar hora vía NTP (configTime) para registrar timestamps reales y mostrarlos en /json.
-
Configuración en runtime:
-
Exponer UI para cambiar MODBUS_SLAVE_ID, baud, intervalos y endianness desde el navegador y guardar en NVS.
-
Registro persistente:
-
Grabar CSV en SPIFFS/LittleFS con rotación diaria. Exponer descarga por HTTP.
-
Multi-esclavo:
-
Sondear varios medidores en el mismo bus ajustando MODBUS_SLAVE_ID para cada ciclo (añadir lista de esclavos).
-
Seguridad básica:
-
Autenticación HTTP simple para /json y /metrics si se expone fuera de la LAN.
-
IP estática por fallback:
- Intentar DHCP y, si falla, aplicar una IP estática de respaldo.
Checklist de verificación
- [ ] He instalado PlatformIO Core 6.1.14 y Python 3.11.6 (o compatibles) y verifiqué sus versiones.
- [ ] Mi sistema reconoce el USB-UART (CP210x/CH34x) y sé cuál es el puerto.
- [ ] He creado el proyecto con board = esp32-ethernet-kit y usé el platformio.ini provisto con versiones fijas.
- [ ] He cableado el MAX3485 a los pines GPIO4 (TX), GPIO5 (RX) y GPIO15 (DE/RE), con 3V3 y GND.
- [ ] He conectado RS485 A↔A y B↔B entre MAX3485 y medidor, con terminación 120 Ω en los extremos si procede.
- [ ] El cable Ethernet RJ45 está conectado, y el switch/router ofrece DHCP o he configurado IP estática.
- [ ] El firmware compila, se flashea sin errores y el monitor serie muestra ETH_GOT_IP con una IP válida.
- [ ] Puedo abrir http://
/ y ver la tabla con las métricas, y /json y /metrics responden correctamente. - [ ] Los valores leídos son coherentes con el display del medidor, con errores de lectura mínimos (ERR_count ~ 0).
- [ ] Si hay incoherencias en floats, ajusté FLOAT_WORD_SWAP y confirmé la corrección.
Con todo lo anterior, has implementado un “modbus-energy-logger-ethernet” robusto y reproducible usando exactamente el “ESP32-Ethernet-Kit + LAN8720 + MAX3485 RS485” y una toolchain con versiones fijas sobre PlatformIO.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.



