← Maestro Modbus RTU en un PLC ESP32 sobre RS-485
Monitorización textil (tejeduría)ESP32 PLCModbus RTURS485Comunicación
Maestro Modbus RTU en un PLC ESP32 sobre RS-485 — ejemplo completo
Maestro Modbus RTU en un PLC ESP32: lecturas de registros FC3 sobre RS-485 con timeouts, conversión a valores de 32 bits y salida JSON lista para MQTT.
Programa completo y ejecutable para el ESP32 PLC (modbus-rtu-master-32bit.ino): incluye cabecera de conexionado, requisitos y notas de integración.
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales
Vista de solo lectura.
/*
* COMPLETE EXAMPLE — Modbus RTU master with timeout and 32-bit conversion
*
* Hardware: ESP32 PLC (Industrial Shields, with RS-485 port)
* Based on: textile monitoring project, ModbusModule.cpp
*
* Wiring:
* RS-485 A/B Bus towards the Modbus slave (power analyzer, VFD,
* machine controller...) — twisted pair, 120 Ω termination
*
* Architecture:
* 1. modbusRequestFC3(): generic holding-register read with timeout —
* if the slave does not answer, it returns an empty vector and the
* loop keeps running (never blocked by a switched-off device)
* 2. Helpers to combine 2 16-bit registers into 32-bit values,
* signed and unsigned (counters, energies, totalizers)
* 3. Periodic poll every 5 s and JSON packaging ready for MQTT
*
* Works together with other catalog examples:
* - mqtt-events-sd-buffering.ino (JSON output)
* - sd-daily-file-datalogging.ino (offline backup)
*/
#include
#include
const uint32_t BAUDRATE = 9600;
const uint8_t SLAVE_ID = 1; // slave address on the bus
const uint16_t REG_BASE = 0x0000; // first holding register to read
const uint16_t NUM_REGS = 4; // 4 regs = 2 32-bit values
const uint32_t TIMEOUT_MS = 200;
const uint32_t POLL_PERIOD_MS = 5000;
ModbusRTUMaster master(RS485); // RS-485 port of the PLC
uint32_t tPoll = 0;
// ------------------------------------------------- FC3 request with timeout
// Returns the n requested registers, or an empty vector on error/timeout
std::vector modbusRequestFC3(uint8_t slave, uint16_t addr,
uint16_t n, uint32_t timeout) {
std::vector out;
if (!master.readHoldingRegisters(slave, addr, n)) return out;
uint32_t t0 = millis();
while (millis() - t0 < timeout) {
if (master.isWaitingResponse()) {
ModbusResponse r = master.available();
if (r) {
if (!r.hasError())
for (uint16_t i = 0; i < n; i++) out.push_back(r.getRegister(i));
return out; // response (good or with error): exit
}
}
}
return out; // timeout: empty vector
}
// ------------------------------------------------- 16+16 -> 32-bit conversion
// Big-endian word order (high first); swap the order if the slave
// uses little-endian word order
uint32_t regsToU32(uint16_t hi, uint16_t lo) {
return ((uint32_t)hi << 16) | lo;
}
int32_t regsToS32(uint16_t hi, uint16_t lo) {
return (int32_t)regsToU32(hi, lo); // reinterpret as two's complement
}
void setup() {
Serial.begin(115200);
RS485.begin(BAUDRATE, SERIAL_8N1);
master.begin(BAUDRATE);
Serial.println("Modbus RTU master ready at " + String(BAUDRATE) + " bps");
}
void loop() {
if (millis() - tPoll >= POLL_PERIOD_MS) {
tPoll = millis();
std::vector regs =
modbusRequestFC3(SLAVE_ID, REG_BASE, NUM_REGS, TIMEOUT_MS);
if (regs.size() < NUM_REGS) {
Serial.println("Modbus: no response from slave " + String(SLAVE_ID));
return; // the rest of the firmware keeps running
}
// Regs 0-1: unsigned totalizer / Regs 2-3: signed value
uint32_t totalizer = regsToU32(regs[0], regs[1]);
int32_t measurement = regsToS32(regs[2], regs[3]);
String json = "{\"slave\":" + String(SLAVE_ID) +
",\"totalizer\":" + String(totalizer) +
",\"measurement\":" + String(measurement) + "}";
Serial.println(json);
// In production: publishMessage(TOPIC_DATA, json) -> MQTT + SD backup
}
delay(20);
}
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales