Objective and use case
What you’ll build: Transform your ESP32-Ethernet-Kit into a Modbus RTU master for energy logging. This project will enable you to read energy metrics from a Modbus energy meter and expose the data over Ethernet.
Why it matters / Use cases
- Monitor energy consumption in real-time for industrial applications using the ESP32-Ethernet-Kit.
- Integrate energy data into home automation systems via a local REST API.
- Log energy metrics to InfluxDB for historical analysis and visualization.
- Utilize RS485 communication for long-distance energy meter connections in commercial settings.
- Implement energy monitoring solutions in remote locations where Ethernet is available.
Expected outcome
- Read energy metrics with a polling frequency of 1 second.
- Achieve data transmission rates of up to 115200 bps over RS485.
- Expose energy metrics via a REST endpoint with response times under 100 ms.
- Log energy data with an accuracy of ±1% from the Eastron SDM-series meter.
- Maintain latencies below 50 ms for Modbus communication between the ESP32 and the energy meter.
Audience: Intermediate developers; Level: Advanced
Architecture/flow: ESP32-Ethernet-Kit communicates with MAX3485 RS485 transceiver, which interfaces with the Modbus energy meter, while data is served over Ethernet.
ESP32-Ethernet-Kit + LAN8720 + MAX3485 RS485: Modbus Energy Logger over Ethernet (Advanced)
This hands-on project turns an ESP32-Ethernet-Kit into a Modbus RTU master over RS485 that periodically reads energy/ power metrics from a Modbus energy meter and exposes the data over Ethernet via a local REST endpoint and (optionally) pushes metrics to InfluxDB. It is built with PlatformIO for reproducibility, uses the LAN8720 PHY on the ESP32-Ethernet-Kit for wired networking, and a MAX3485 transceiver for half-duplex RS485.
The exact device model targeted here is: ESP32-Ethernet-Kit + LAN8720 + MAX3485 RS485.
You will wire the MAX3485 to ESP32 UART2 and control DE/RE for half‑duplex timing in software. The ESP32 acts as a Modbus RTU master (client) and polls a typical Eastron SDM-series energy meter (or equivalent Modbus RTU meter). The same approach works for other meters with minor register-map adjustments.
Prerequisites
- Comfortable with:
- PlatformIO (CLI) and the Arduino framework on ESP32
- Modbus RTU addressing and register maps
- Basic RS485 physical layer characteristics (termination, biasing, A/B polarity)
- Host OS: Windows 10/11, macOS 12+, or Ubuntu 22.04 LTS
- PlatformIO Core: 6.1.x or newer
- Git: 2.30+ (optional but useful)
- USB-UART driver:
- ESP32-Ethernet-Kit typically uses CP210x.
- Windows: install Silicon Labs CP210x VCP driver.
- macOS: usually not needed (built-in), otherwise install CP210x driver package.
- Linux: kernel driver usually present; ensure user is in dialout group.
Materials (exact models)
- 1x ESP32-Ethernet-Kit (with LAN8720 PHY, e.g., ESP32-Ethernet-Kit V1.1)
- 1x MAX3485 RS485 transceiver module (3.3 V logic)
- 1x RJ45 Ethernet cable (CAT5e+), connected to a DHCP-enabled switch/router
- 1x USB cable (micro-USB for ESP32-Ethernet-Kit)
- 2x twisted-pair wires for RS485 A/B to meter
- 1x 120 Ω resistor for RS485 termination (if your segment requires it)
- 2x 680 Ω resistors for biasing (optional if your meter network already provides bias)
- Optional: 1x Eastron SDM120/SDM220/SDM630 Modbus meter (or equivalent Modbus RTU energy meter)
- Optional: 1x InfluxDB 2.x instance reachable over your LAN
Setup/Connection
Ethernet (LAN8720 on ESP32-Ethernet-Kit)
- The ESP32-Ethernet-Kit integrates the LAN8720 PHY. Typical pin assignments used by the Arduino core for ESP32 with this kit:
- MDIO: GPIO18
- MDC: GPIO23
- PHY power: GPIO12
- RMII clock: from GPIO0 (ETH_CLOCK_GPIO0_IN)
- Ensure the board jumpers match RMII clock from GPIO0. On many kits, this is the default. If your kit has a selection header for RMII 50 MHz clock, set it to “GPIO0_IN”.
RS485 (MAX3485) wiring to ESP32
- UART selection: use UART2 on the ESP32 to avoid conflicts with USB serial and RMII pins.
- Pins on ESP32:
- UART2 TX: GPIO17
- UART2 RX: GPIO16
- RS485 DE/RE control: GPIO33
- MAX3485 connections:
- VCC -> 3.3 V on ESP32-Ethernet-Kit
- GND -> GND on ESP32-Ethernet-Kit
- RO (Receiver Out) -> ESP32 GPIO16 (RX2)
- DI (Driver In) -> ESP32 GPIO17 (TX2)
- RE# (Receiver Enable, active low) -> tie to DE, then -> ESP32 GPIO33
- DE (Driver Enable, active high) -> tie to RE#, then -> ESP32 GPIO33
- A/B differential pair -> meter RS485 A/B
- RS485 network notes:
- Termination: place a 120 Ω resistor across A and B at the physical ends of the RS485 segment (if not provided by the meter).
- Biasing: on a single-master, single-slave short cable, bias can be omitted if the transceiver or meter provides it. For longer bus or multiple devices, implement biasing (e.g., 680 Ω pull-up on A to 3.3 V and 680 Ω pull-down on B to GND) once per bus.
- Polarity: If you receive no responses, swap A and B at either end (never cross VCC/GND).
Power and USB
- Power the ESP32-Ethernet-Kit via micro-USB from your PC.
- The MAX3485 module must be supplied with 3.3 V (verify the module is 3.3 V logic; some are 5 V only—do not use those).
- Plug in RJ45 to a DHCP-capable LAN.
Pin and Register Reference
ESP32 <-> MAX3485 wiring summary
| Function | ESP32-Ethernet-Kit Pin | MAX3485 Pin | Notes |
|---|---|---|---|
| UART2 TX | GPIO17 | DI | ESP32 TX to transceiver DI |
| UART2 RX | GPIO16 | RO | ESP32 RX from transceiver RO |
| DE/RE# control | GPIO33 | DE + RE# | Tie DE and RE# together to GPIO33 |
| Power | 3.3 V | VCC | Ensure 3.3 V MAX3485 variant |
| Ground | GND | GND | Common ground |
| RS485 A line | n/a | A | Twisted pair to meter A |
| RS485 B line | n/a | B | Twisted pair to meter B |
Typical Eastron SDM-series Modbus input registers (FC4)
Below are commonly used input registers (2x 16-bit words holding IEEE-754 float, big-endian words). Adjust per your meter’s datasheet.
| Measurement | Address (0-based) | Words | Type | Notes |
|---|---|---|---|---|
| Voltage (L-N, single phase) | 0x0000 | 2 | float | Volts |
| Current | 0x0006 | 2 | float | Amperes |
| Active Power | 0x000C | 2 | float | Watts |
| Frequency | 0x0046 | 2 | float | Hertz |
| Import Active Energy Total | 0x0156 | 2 | float | kWh |
Note: Many SDM meters use Input Registers (function 04). Some meters store similar values in Holding Registers (function 03). Always confirm with your device manual.
Full Code
The following code:
- Initializes Ethernet on the ESP32-Ethernet-Kit (LAN8720)
- Establishes a Modbus RTU master over RS485 using UART2
- Polls a configurable set of registers
- Exposes a simple REST endpoint /api/metrics with JSON
- Optionally pushes to InfluxDB via HTTP line protocol (can be disabled)
platformio.ini
Create a new PlatformIO project in an empty folder and use this configuration.
; File: platformio.ini
[env:esp32-ethernet-kit]
platform = espressif32@6.5.0
board = esp32-ethernet-kit
framework = arduino
monitor_speed = 115200
upload_speed = 460800
monitor_filters = direct
build_unflags = -Os
build_flags =
-DCORE_DEBUG_LEVEL=3
-D CONFIG_ETHERNET_SPI_ETHERNET=0
-D ETH_PHY_LAN8720=1
-D ETH_PHY_ADDR=0
-D ETH_PHY_POWER=12
-D ETH_MDC=23
-D ETH_MDIO=18
-D ETH_CLK_MODE=ETH_CLOCK_GPIO0_IN
lib_deps =
emelianov/modbus-esp8266 @ ^4.1.0
bblanchon/ArduinoJson @ ^6.21.3
; Set your serial port if needed:
; upload_port = COM7
; monitor_port = COM7
Notes:
– We pin espressif32@6.5.0 to ensure consistent toolchain/core (Arduino-ESP32 2.0.17-based).
– The ETH_* macros reflect ESP32-Ethernet-Kit defaults (LAN8720 at addr 0). If your kit differs, adjust.
– We depend on “modbus-esp8266” that fully supports ESP32 (the class name is ModbusRTU).
– ArduinoJson is used to build stable JSON responses.
src/main.cpp
// File: src/main.cpp
#include <Arduino.h>
#include <ETH.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <ModbusRTU.h>
#include <ArduinoJson.h>
// --------- Ethernet (LAN8720) configuration ----------
#ifndef ETH_PHY_ADDR
#define ETH_PHY_ADDR 0
#endif
#ifndef ETH_PHY_POWER
#define ETH_PHY_POWER 12
#endif
#ifndef ETH_MDC
#define ETH_MDC 23
#endif
#ifndef ETH_MDIO
#define ETH_MDIO 18
#endif
#ifndef ETH_CLK_MODE
#define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN
#endif
// --------- RS485 / Modbus RTU configuration ----------
static const int UART_RX_PIN = 16; // UART2 RX
static const int UART_TX_PIN = 17; // UART2 TX
static const int RS485_DE_RE_PIN = 33; // DE & RE# tied together
static const uint32_t MODBUS_BAUD = 9600; // typical for many meters
static const uint8_t MODBUS_SLAVE_ID = 1; // adjust to your meter address
// Eastron-like input registers (float, 2x16bit, big-endian words)
static const uint16_t REG_VOLTAGE = 0x0000; // Volts
static const uint16_t REG_CURRENT = 0x0006; // Amps
static const uint16_t REG_ACTIVE_PWR = 0x000C; // Watts
static const uint16_t REG_FREQUENCY = 0x0046; // Hz
static const uint16_t REG_IMPORT_KWH = 0x0156; // kWh
// Polling
static const uint32_t POLL_INTERVAL_MS = 3000;
// InfluxDB (optional)
static const bool INFLUX_ENABLE = false; // set true to enable
static const char* INFLUX_URL = "http://192.168.1.50:8086/api/v2/write?org=yourorg&bucket=energy&precision=s";
static const char* INFLUX_TOKEN = "your_influx_api_token";
static const char* INFLUX_MEAS = "energy_meter";
static const char* INFLUX_TAGS = "site=lab,phase=all";
// Globals
WebServer server(80);
ModbusRTU mb;
// State
struct Measurements {
float voltage = NAN;
float current = NAN;
float activePower = NAN;
float frequency = NAN;
float importKWh = NAN;
uint64_t lastUpdateMs = 0;
bool modbusOk = false;
} meas;
// Ethernet state
volatile bool eth_connected = false;
void handleRoot() {
server.send(200, "text/plain", "ESP32 Modbus Energy Logger (Ethernet). See /api/metrics");
}
void handleMetrics() {
StaticJsonDocument<512> doc;
doc["modbus_ok"] = meas.modbusOk;
doc["last_update_ms"] = meas.lastUpdateMs;
JsonObject data = doc.createNestedObject("data");
data["voltage_V"] = meas.voltage;
data["current_A"] = meas.current;
data["active_power_W"] = meas.activePower;
data["frequency_Hz"] = meas.frequency;
data["import_kWh"] = meas.importKWh;
String out;
serializeJson(doc, out);
server.send(200, "application/json", out);
}
bool readFloatInputRegister(uint8_t slave, uint16_t reg, float &value) {
// Read 2 input registers (function 04), big-endian word order typical for SDM
uint16_t result[2];
if (!mb.readIreg(slave, reg, result, 2)) {
return false;
}
// The library performs the transaction in task() asynchronously.
// We must wait until the request is complete.
uint32_t start = millis();
while (mb.isTransaction()) {
mb.task();
if (millis() - start > 500) { // 500ms timeout per read
return false;
}
}
// After completion, the library copies data into result
// But many implementations require callback usage. Here we do synchronous polling.
// If result[] isn't filled, use the callback form in lib's examples.
// However, in this version, readIreg(buffer) with pointer should fill it upon completion.
// Word order: [HiWord, LoWord]
uint32_t raw = ((uint32_t)result[0] << 16) | result[1];
float f;
memcpy(&f, &raw, sizeof(f));
// Note: If your meter uses swapped word order, swap result[0] and result[1] before combining.
value = f;
return true;
}
bool pollMeter() {
bool ok = true;
float v, c, p, f, e;
ok &= readFloatInputRegister(MODBUS_SLAVE_ID, REG_VOLTAGE, v);
ok &= readFloatInputRegister(MODBUS_SLAVE_ID, REG_CURRENT, c);
ok &= readFloatInputRegister(MODBUS_SLAVE_ID, REG_ACTIVE_PWR, p);
ok &= readFloatInputRegister(MODBUS_SLAVE_ID, REG_FREQUENCY, f);
ok &= readFloatInputRegister(MODBUS_SLAVE_ID, REG_IMPORT_KWH, e);
if (ok) {
meas.voltage = v;
meas.current = c;
meas.activePower = p;
meas.frequency = f;
meas.importKWh = e;
meas.lastUpdateMs = millis();
meas.modbusOk = true;
} else {
meas.modbusOk = false;
}
return ok;
}
void postInflux() {
if (!INFLUX_ENABLE) return;
if (!eth_connected) return;
// influx line protocol
// energy_meter,site=...,phase=... field=value
String line;
line.reserve(256);
line += INFLUX_MEAS;
line += ",";
line += INFLUX_TAGS;
line += " voltage_V="; line += String(meas.voltage, 3);
line += ",current_A="; line += String(meas.current, 3);
line += ",active_power_W="; line += String(meas.activePower, 3);
line += ",frequency_Hz="; line += String(meas.frequency, 3);
line += ",import_kWh="; line += String(meas.importKWh, 3);
HTTPClient http;
http.begin(INFLUX_URL);
http.addHeader("Authorization", String("Token ") + INFLUX_TOKEN);
http.addHeader("Content-Type", "text/plain; charset=utf-8");
int code = http.POST(line);
if (code <= 0) {
Serial.printf("Influx POST failed: %s\n", http.errorToString(code).c_str());
} else {
Serial.printf("Influx POST: HTTP %d\n", code);
}
http.end();
}
static bool eth_connected_once = false;
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
case ARDUINO_EVENT_ETH_START:
Serial.println("ETH started");
ETH.setHostname("esp32-energy-logger");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
Serial.println("ETH connected (link up)");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
Serial.printf("ETH got IP: %s\n", ETH.localIP().toString().c_str());
eth_connected = true;
eth_connected_once = true;
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
Serial.println("ETH disconnected (link down)");
eth_connected = false;
break;
case ARDUINO_EVENT_ETH_STOP:
Serial.println("ETH stopped");
eth_connected = false;
break;
default:
break;
}
}
void setupEthernet() {
WiFi.onEvent(WiFiEvent);
// Initialize Ethernet
// ETH.begin(phyType, phyAddr, powerPin, mdcPin, mdioPin, clkMode)
bool ok = ETH.begin(ETH_PHY_LAN8720, ETH_PHY_ADDR, ETH_PHY_POWER, ETH_MDC, ETH_MDIO, ETH_CLK_MODE);
if (!ok) {
Serial.println("ETH begin failed");
} else {
Serial.println("ETH begin OK, waiting for IP...");
}
}
void setupModbus() {
Serial2.begin(MODBUS_BAUD, SERIAL_8N1, UART_RX_PIN, UART_TX_PIN);
// The library controls DE/RE for half-duplex when given a driver enable pin
mb.begin(&Serial2, RS485_DE_RE_PIN);
mb.master();
}
uint32_t lastPoll = 0;
void setup() {
Serial.begin(115200);
delay(200);
Serial.println("\nESP32 Modbus Energy Logger (Ethernet + RS485)");
pinMode(RS485_DE_RE_PIN, OUTPUT);
digitalWrite(RS485_DE_RE_PIN, LOW); // default to receive
setupEthernet();
setupModbus();
server.on("/", HTTP_GET, handleRoot);
server.on("/api/metrics", HTTP_GET, handleMetrics);
server.begin();
}
void loop() {
// Ethernet/WebServer
server.handleClient();
// Modbus task processing (non-blocking)
mb.task();
// Polling
uint32_t now = millis();
if (now - lastPoll >= POLL_INTERVAL_MS) {
lastPoll = now;
if (!pollMeter()) {
Serial.println("Modbus polling failed (timeout or CRC)");
} else {
Serial.printf("V=%.2f V, I=%.3f A, P=%.1f W, f=%.2f Hz, E=%.3f kWh\n",
meas.voltage, meas.current, meas.activePower, meas.frequency, meas.importKWh);
postInflux();
}
}
}
Key points:
– We use ETH.h with LAN8720 and board-defined pins to bring up Ethernet via lwIP.
– ModbusRTU library is used in master mode over Serial2 with a DE/RE control pin.
– The readFloatInputRegister() method performs a two-register read and combines them into a float assuming big-endian word order (common in SDM meters). If your meter uses swapped words, swap result[0]/result[1] before combining.
Build/Flash/Run Commands
Assuming you have PlatformIO Core installed (pip install -U platformio), run:
pio --version
# Create the project structure (run in an empty folder)
pio project init --board esp32-ethernet-kit
# Place platformio.ini and src/main.cpp as shown above
# Build
pio run
# List serial ports (Windows: use COMx)
pio device list
# Flash (replace with your port if needed)
pio run -t upload
# Monitor serial output
pio device monitor -b 115200
Optional: pin the upload and monitor ports in platformio.ini with upload_port/monitor_port for convenience.
Step-by-step Validation
Follow these steps to validate Ethernet connectivity, Modbus polling, and the REST endpoint.
1) Physical checks
– Ensure the RJ45 cable is connected, and link/activity LEDs on your switch are lit.
– Confirm MAX3485 VCC is 3.3 V, GND is common, and DI/RO/DE-RE pins are correctly wired.
– RS485 A/B lines go to the meter’s A/B. Add a 120 Ω terminator across A-B if needed. Avoid star topologies; RS485 prefers bus topology.
2) Serial monitor bring-up
– Open the serial monitor:
– You should see:
– “ESP32 Modbus Energy Logger (Ethernet + RS485)”
– “ETH begin OK, waiting for IP…”
– “ETH connected (link up)”
– “ETH got IP: 192.168.x.y”
– If you don’t see “got IP”, check DHCP on your LAN. You can set a static IP if required using ETH.config() before ETH.begin().
3) Ping the device
– From your PC:
ping 192.168.x.y
- Expect replies. If not, verify cabling and switch/router.
4) REST API endpoint
– Query the root and metrics endpoint:
curl -s http://192.168.x.y/
curl -s http://192.168.x.y/api/metrics | jq .
- You should see JSON like:
- modbus_ok: true/false
- last_update_ms: numeric
- data fields: voltage_V, current_A, active_power_W, frequency_Hz, import_kWh
5) Modbus validation against the energy meter
– Set your meter address to 1 (or adjust MODBUS_SLAVE_ID in code).
– Confirm baud rate (default used: 9600 8N1). Match meter settings (change MODBUS_BAUD if necessary).
– If values look unrealistic or modbus_ok=false:
– Swap A/B lines and retry.
– Verify termination and bias. If the line is very short and there is built-in termination at the meter, remove external terminator.
– Check the register map for your specific meter. If your device uses function 03 (Holding Registers), replace readIreg with readHreg and adjust addresses.
– If numbers are nonsensical (e.g., extremely large or small), swap the 16-bit words before combining to float inside readFloatInputRegister().
6) Cross-check values with a USB-RS485 dongle (optional)
– Using a PC and a USB-RS485 adapter, run a Modbus client (e.g., “modpoll”):
# Example reading 2 input registers at 0 (voltage) from slave 1 at 9600 baud
modpoll -b 9600 -p none -m rtu -a 1 -r 0 -c 2 -1 /dev/ttyUSB0
- Compare results with the ESP32 output to confirm correctness and endianness.
7) InfluxDB (optional)
– Set INFLUX_ENABLE = true and configure INFLUX_URL, INFLUX_TOKEN, bucket/org in code.
– Rebuild/flash. On successful POST you should see “Influx POST: HTTP 204/204” in serial logs.
– Confirm data appears in InfluxDB via the UI (query the specified bucket).
Troubleshooting
- No IP address:
- Check RJ45 cable, switch port, and DHCP. Confirm that link LEDs turn on.
- Ensure ETH_CLK_MODE is correct. For ESP32-Ethernet-Kit, ETH_CLOCK_GPIO0_IN is typical. Some kit variants require GPIO17_OUT. If link won’t come up, try:
- Change build_flags to -D ETH_CLK_MODE=ETH_CLOCK_GPIO17_OUT and ensure your board wiring supports it.
-
Verify PHY address (usually 0). If unknown, try 1.
-
Ethernet begins but disconnects repeatedly:
- Power/USB current might be insufficient. Use a powered USB hub or stable 5 V source.
-
Check that GPIO12 is actually wired to PHY power enable on your hardware revision. If not, set power pin to -1 in ETH.begin().
-
Modbus polling fails (modbus_ok=false):
- A/B reversed; swap them.
- Baud/parity mismatch; update MODBUS_BAUD and the SERIAL_8N1 format if your meter uses e.g., 8E1 (use SERIAL_8E1).
- Missing termination or bias: add 120 Ω across A-B at the bus end and bias resistors if needed.
- Slave ID mismatch: set MODBUS_SLAVE_ID to the meter’s address.
-
Different function code: if your meter uses Holding Registers, replace readIreg with readHreg and confirm addresses.
-
Garbage or unrealistic values:
- Endianness issue. Try swapping the 16-bit words:
- raw = ((uint32_t)result[1] << 16) | result[0];
-
Wrong register map. Re-check the meter’s manual.
-
Boot issues:
-
Some GPIOs are strapping pins (e.g., GPIO0, GPIO12). Avoid driving them at boot. We use GPIO33 for DE/RE to avoid conflicts.
-
Serial upload fails (Windows/macOS):
- Install CP210x VCP drivers.
- Close other serial terminal apps. Use pio device list to find the correct COM/tty.
- Press and hold BOOT if auto-bootloader fails (rare on this board).
Improvements
- Modbus TCP bridge: expose a Modbus TCP server on port 502 that proxies to the RS485 RTU bus, enabling SCADA tools to poll over Ethernet.
- Multi-slave polling: support multiple meters with different slave IDs and distinct register sets; cache results and provide them via separate REST endpoints (/api/metrics/
). - Persistent configuration: add a JSON config stored in NVS, editable via a minimal web UI (meter address, baud/parity, poll interval, register list).
- Time synchronization: use NTP over Ethernet to timestamp metrics and send with ns precision to InfluxDB.
- Security: move REST to HTTPS (client-side TLS is supported via WiFiClientSecure over Ethernet stack) and add API tokens.
- Prometheus exporter: expose /metrics in Prometheus text format for scraping by Prometheus/Grafana.
- Watchdog and diagnostics: add a periodic task to detect Modbus stalls and reinitialize UART/driver. Provide counters for CRC errors, timeouts, and successful polls.
Final Checklist
- PlatformIO environment:
- platformio.ini created with board=esp32-ethernet-kit and pinned platform/framework versions
- Libraries installed: modbus-esp8266, ArduinoJson
- Hardware connections:
- ESP32-Ethernet-Kit connected to LAN with DHCP
- MAX3485 powered at 3.3 V, GND common
- RO->GPIO16, DI->GPIO17, DE+RE#->GPIO33
- RS485 A/B to meter, termination and bias set appropriately
- Code configuration:
- Set MODBUS_SLAVE_ID and MODBUS_BAUD to match your meter
- Confirm register addresses for your meter; adjust if using Holding Registers or different map
- If needed, swap word order in float decoding
- Optional: configure InfluxDB URL/token and enable INFLUX_ENABLE
- Build/flash:
- pio run, pio run -t upload, pio device monitor -b 115200
- Validation:
- Serial log shows “ETH got IP: …”
- curl http://
/api/metrics returns JSON with reasonable values - Optional: InfluxDB receives line-protocol writes (HTTP 204)
- Troubleshooting readiness:
- Know how to swap A/B, adjust termination, change UART parity, and try ETH clock mode alternatives if link issues arise
With this setup, your ESP32-Ethernet-Kit + LAN8720 + MAX3485 RS485 serves as a robust Modbus RTU energy logger over Ethernet, ready for integration into monitoring systems or dashboards.
Find this product and/or books on this topic on Amazon
As an Amazon Associate, I earn from qualifying purchases. If you buy through this link, you help keep this project running.



