Para usar Modbus RTU en un PLC basado en Arduino o ESP32, conecta el puerto RS-485 del PLC al bus (A+ con A+, B- con B-), ábrelo con RS485.begin(baudrate, HALFDUPLEX, SERIAL_8E1) y crea un objeto ModbusRTUMaster o ModbusRTUSlave de la librería Modbus de Industrial Shields. El maestro envía una petición con una función como readHoldingRegisters(slave, address, quantity) y lee la respuesta más tarde con available(), porque la librería no bloquea.
Esta guía sirve para la familia M-Duino, la familia Ardbox con RS-485 por hardware, la familia ESP32 PLC y el ESP32 PLC 14. Explica la conexión, la selección de placa en el Arduino IDE, un sketch de maestro probado, un sketch de esclavo, la referencia de funciones de la librería y los primeros errores que verás. Si lo que necesitas es el protocolo (trama, códigos de función, CRC), lee la guía de la trama y los códigos de función de Modbus RTU antes.
¿Cómo funciona una transacción Modbus RTU en un PLC?
Modbus RTU es un protocolo maestro y esclavo sobre una línea serie, normalmente RS-485. Solo el maestro inicia una transacción. Cada esclavo tiene una dirección de 1 a 247 y solo responde a las peticiones con su dirección.
- Paso 1PeticiónEl maestro envía la dirección del esclavo, un código de función, el primer registro, la cantidad y un CRC.
- Paso 2SilencioUna pausa de al menos 3,5 caracteres marca el final de la trama (1,75 ms por encima de 19200 baudios).
- Paso 3RespuestaEl esclavo comprueba el CRC y responde con los datos o con un código de excepción.
- Paso 4Siguiente esclavoEl maestro lee la respuesta, o la da por perdida tras su timeout, y pregunta al siguiente esclavo.
La librería de Industrial Shields sigue estos pasos por ti. Tu sketch solo envía la petición y comprueba la respuesta en loop().
Hardware y software necesarios
- Un PLC Arduino (M-Duino o Ardbox) o un ESP32 PLC como maestro Modbus.
- Un segundo PLC, o cualquier equipo con Modbus RTU por RS-485 (analizador de energía, variador de frecuencia, transmisor de temperatura), como esclavo.
- Una fuente de alimentación de 12 a 24 Vdc.
- Un cable USB para programar el PLC (tipo B en los PLC Arduino, micro USB en los ESP32 PLC).
- Un cable de par trenzado para RS-485 (dos pares trenzados para full duplex).
- El Arduino IDE, las placas de Industrial Shields y la librería Modbus (también en librerías de Industrial Shields).
Conexión del puerto RS-485 en cada familia de PLC
Alimenta el PLC con 12 a 24 Vdc, conecta el bus y configura los interruptores de comunicación. Las tablas completas de interruptores de cada modelo están en el tutorial de RS-485 para PLC Arduino y ESP32.
| Familia de PLC | Bornes RS-485 | Half duplex (2 hilos) | Full duplex (4 hilos) |
|---|---|---|---|
| M-Duino | A+, B-, Y+, Z- | A+ con A+, B- con B-. Interruptor inferior 1 en OFF | A+ con Y+, B- con Z-, Y+ con A+, Z- con B-. Interruptor inferior 1 en ON |
| Ardbox HF+ con RS-485 por hardware | A+, B-, Y+, Z- | A+ con A+, B- con B- | Igual que el M-Duino. HD/FD con el interruptor superior derecho 1 (Analog) o 2 (Relay) |
| ESP32 PLC | A+, B- | A+ con A+, B- con B-. Interruptor 3 en OFF | No disponible |
| ESP32 PLC 14 | A+, B- | A+ con A+, B- con B-. Sin interruptor | No disponible |
Un bus Modbus RTU es casi siempre half duplex: un par trenzado, todos los equipos en línea y una resistencia de terminación de 120 Ω en cada extremo. Conecta también la masa de señal (GND) de los equipos cuando no tienen una referencia común. Los conceptos básicos de RS-485 explican por qué.
Selección de placa y modelo en el Arduino IDE
| PLC | Herramientas > Placa | Herramientas > Modelo |
|---|---|---|
| M-Duino | Industrial Shields AVR Boards > M-Duino [Extension] family | M-Duino [Module] |
| Ardbox | Industrial Shields AVR Boards > Ardbox [Extension] family | Ardbox [Module] HF+ w/HW RS-485 |
| ESP32 PLC | Industrial Shields ESP32 Boards > ESP32 PLC Family | ESP32 PLC [Module] |
| ESP32 PLC 14 | Industrial Shields ESP32 Boards > 14 IOS PLC Family | Sin menú de modelo |
Los sketches de ejemplo están en Archivo > Ejemplos > Modbus (o en Archivo > Ejemplos > Tools40 > Modbus en instalaciones antiguas). Hay un ejemplo de maestro para cada código de función y un ejemplo de esclavo.
Ejemplo de maestro Modbus RTU: leer holding registers
Este sketch lee 4 holding registers (40001 a 40004) del esclavo 1 cada segundo y los muestra en el monitor serie. Compila sin avisos para un M-Duino 21+ con las placas AVR de Industrial Shields 1.2.1 y la librería Modbus 1.1.0. En un Ardbox HF+ con RS-485 por hardware y en un ESP32 PLC el sketch no cambia: su librería RS485 tiene el mismo begin(baudrate, duplex, config).
// Modbus RTU master: read 4 holding registers from slave 1 every second
// M-Duino, Ardbox HF+ w/HW RS-485 and ESP32 PLC (Industrial Shields Modbus library 1.1.0)
#include <RS485.h>
#include <ModbusRTUMaster.h>
const uint32_t BAUDRATE = 19200UL; // same on master and slaves
const uint8_t SLAVE_ID = 1; // address of the slave (1 to 247)
const uint16_t FIRST_REG = 0; // register 40001 = address 0
const uint16_t NUM_REGS = 4;
ModbusRTUMaster master(RS485);
uint32_t lastRequest = 0;
void setup() {
Serial.begin(115200UL);
RS485.begin(BAUDRATE, HALFDUPLEX, SERIAL_8E1); // 8 data bits, even parity, 1 stop bit
master.begin(BAUDRATE); // sets the internal t3.5 and t1.5 timings
master.setTimeout(200); // response timeout in ms (default 1000)
}
void loop() {
// 1. Send a request only when the master is idle
if (!master.isWaitingResponse() && millis() - lastRequest >= 1000UL) {
if (!master.readHoldingRegisters(SLAVE_ID, FIRST_REG, NUM_REGS)) {
Serial.println("Request not sent");
}
lastRequest = millis();
}
// 2. Poll for the answer: the functions do not block
if (master.isWaitingResponse()) {
ModbusResponse response = master.available();
if (master.hasException()) { // timeout, CRC error, wrong slave...
Serial.print("Communication error: ");
Serial.println(master.getExceptionMessage());
master.clearException();
}
if (response) {
if (response.hasError()) { // the slave answered with an exception code
Serial.print("Slave exception: ");
Serial.println(response.getErrorMessage());
} else {
for (uint16_t i = 0; i < NUM_REGS; i++) {
Serial.print(response.getRegister(i));
if (i < NUM_REGS - 1) Serial.print(", ");
}
Serial.println();
}
}
}
}
Tres detalles que provocan la mayoría de las dudas:
- La velocidad se indica dos veces.
RS485.begin()abre el puerto ymaster.begin()usa el mismo valor para calcular los tiempos de 3,5 y 1,5 caracteres. Si dejasmaster.begin()vacío, supone 19200. - Llama a
setTimeout()después debegin().begin()devuelve el timeout de respuesta a 1000 ms. - Un
falsedereadHoldingRegisters()significa "no enviado". El maestro aún está ocupado con la petición anterior, o la dirección del esclavo es 0 o mayor que 247. Nunca significa que el esclavo haya fallado.
La librería Modbus necesita un puerto serie por hardware. Los modelos M-Duino antiguos sin "+" en el nombre gestionan el RS-485 con un puerto serie por software, así que la librería no lo puede usar. En esos modelos, los ejemplos de la librería usan el puerto RS-232 (ModbusRTUMaster master(RS232);).
Antes de cargar, comprueba que la velocidad, la paridad y los bits de parada son iguales en el maestro y en cada esclavo. 8E1 (paridad par) es el valor por defecto de Modbus. Muchos analizadores y variadores vienen en 8N1 o 8N2, así que consulta su manual. Después carga el sketch con Programa > Subir.
Ejemplo de esclavo Modbus RTU: un PLC que responde al maestro
Cuando el esclavo es otro PLC, comparte cuatro arrays con la librería: coils, discrete inputs, holding registers e input registers. La librería responde al maestro a partir de estos arrays y tu sketch los mantiene al día. Este esclavo usa los nombres de pines de un M-Duino 21+. Cámbialos para tu modelo.
// Modbus RTU slave with address 1: 2 coils, 2 discrete inputs, 2 holding and 2 input registers
#include <RS485.h>
#include <ModbusRTUSlave.h>
const uint32_t BAUDRATE = 19200UL;
const uint8_t SLAVE_ID = 1;
bool coils[2]; // FC 01, 05, 15 -> Q0_0, Q0_1
bool discreteInputs[2]; // FC 02 <- I0_0, I0_1
uint16_t holdingRegisters[2]; // FC 03, 06, 16 -> set points written by the master
uint16_t inputRegisters[2]; // FC 04 <- analog inputs I0_7, I0_8 (0 to 1023)
ModbusRTUSlave slave(RS485, SLAVE_ID);
void setup() {
RS485.begin(BAUDRATE, HALFDUPLEX, SERIAL_8E1);
slave.setCoils(coils, 2);
slave.setDiscreteInputs(discreteInputs, 2);
slave.setHoldingRegisters(holdingRegisters, 2);
slave.setInputRegisters(inputRegisters, 2);
slave.begin(BAUDRATE);
}
void loop() {
discreteInputs[0] = digitalRead(I0_0);
discreteInputs[1] = digitalRead(I0_1);
inputRegisters[0] = analogRead(I0_7);
inputRegisters[1] = analogRead(I0_8);
slave.update(); // answers one request when it arrives, never blocks
digitalWrite(Q0_0, coils[0]);
digitalWrite(Q0_1, coils[1]);
}
El índice 0 de cada array es la dirección 0 para el maestro. En este esclavo, el maestro escribe Q0_0 con writeSingleCoil(1, 0, true) y lee I0_7 con readInputRegisters(1, 0, 1). El ejemplo ModbusRTUSlave de la librería hace lo mismo con todas las entradas y salidas de la primera zona del PLC. Desde la versión 1.1.0 de la librería detecta el modelo de PLC, así que ya no se limita al M-Duino 21+.
¿Quieres un ejemplo completo de M-Duino a ESP32 PLC con los dos sketches? Consulta cómo comunicar un PLC M-Duino y un ESP32 PLC con Modbus RTU.
Referencia de funciones del maestro Modbus RTU
Todas las funciones de lectura y escritura envían una petición y vuelven al momento. Devuelven true si la petición ha quedado en cola y false si el maestro estaba ocupado o un parámetro era incorrecto. Los datos llegan después a través de master.available().
| Función | FC | Qué hace | Respuesta con |
|---|---|---|---|
readCoils(slave, addr, quantity) | 01 | Lee de 1 a 2000 coils (bits de lectura y escritura) | response.isCoilSet(i) |
readDiscreteInputs(slave, addr, quantity) | 02 | Lee de 1 a 2000 discrete inputs (bits de solo lectura) | response.isDiscreteInputSet(i) |
readHoldingRegisters(slave, addr, quantity) | 03 | Lee de 1 a 125 holding registers (16 bits, lectura y escritura) | response.getRegister(i) |
readInputRegisters(slave, addr, quantity) | 04 | Lee de 1 a 125 input registers (16 bits, solo lectura) | response.getRegister(i) |
writeSingleCoil(slave, addr, value) | 05 | Pone una coil a true o false | response.hasError() |
writeSingleRegister(slave, addr, value) | 06 | Escribe un holding register (0 a 65535) | response.hasError() |
writeMultipleCoils(slave, addr, values, quantity) | 15 | Escribe un array bool en coils consecutivas | response.hasError() |
writeMultipleRegisters(slave, addr, values, quantity) | 16 | Escribe un array uint16_t en holding registers consecutivos | response.hasError() |
Otras funciones del maestro: isWaitingResponse(), available(), setTimeout(ms), hasException(), getExceptionMessage() y clearException(). La respuesta también tiene getSlave(), getFC(), getErrorCode() y getErrorMessage().
Las direcciones empiezan en 0. El registro 40001 del manual de un equipo es la dirección 0, y el 40009 es la dirección 8. Si un valor sale desplazado un registro, esta es la causa. El tutorial de escritura de un registro muestra los demás códigos de función con un M-Duino.
Los primeros errores que verás y qué significan
| Qué muestra el sketch | De dónde viene | Qué comprobar |
|---|---|---|
| Excepción de timeout | master.getExceptionMessage(): sin respuesta dentro del timeout | Dirección del esclavo, velocidad y paridad, A y B invertidos, interruptor HD/FD, esclavo alimentado |
| Excepción de CRC | La respuesta ha llegado dañada | Terminación en los dos extremos, longitud del cable, masa común, dos maestros en el bus |
| Función no válida (01) | response.getErrorMessage(): el esclavo no admite ese código de función | Usa el código de función del manual del equipo (03 o 04) |
| Dirección de datos no válida (02) | El registro no existe en el esclavo | Número de registro menos 1, y la cantidad |
| Valor de datos no válido (03) | El esclavo rechaza la cantidad o el valor | Rango del valor, cantidad por petición |
| Petición no enviada | La función de lectura o escritura ha devuelto false | Espera a que isWaitingResponse() sea false; dirección del esclavo de 1 a 247 |
Para fallos intermitentes en un bus que antes funcionaba, sigue la lista de diagnóstico de RS-485.
Modbus RTU en proyectos reales con PLC
En el caso de estudio de la red Modbus de temperatura, PLC M-Duino locales leen termómetros Modbus RTU por RS-485 y un PLC principal recoge las lecturas por Modbus TCP. La PLC Solution Library tiene código para las mismas tareas: un maestro Modbus RTU en un ESP32 PLC con timeouts y valores de 32 bits, control de velocidad de un variador de frecuencia y una pasarela Modbus TCP a RTU en un M-Duino.
Mira cada trama antes de cablear nada. En el simulador de red Modbus, un M-Duino 21 interroga a cuatro esclavos Modbus RTU por RS-485 y sirve los datos por Modbus TCP. Apaga un esclavo, invierte el orden de las palabras o cambia la velocidad, y encuentra el fallo en las tramas.
Abrir el simulador de red ModbusConsulta a un ingeniero sobre tu red Modbus
Preguntas frecuentes sobre Modbus RTU en PLC Arduino y ESP32
¿Puede un PLC ser maestro y esclavo Modbus RTU a la vez?
En el mismo bus no, porque un bus Modbus RTU tiene un solo maestro. Un PLC puede ser maestro en su puerto RS-485 y esclavo en otro puerto serie, o esclavo en Modbus TCP por Ethernet. Así funciona una pasarela.
¿Cuántos esclavos puede interrogar un PLC en un bus RS-485?
Modbus admite direcciones de 1 a 247. RS-485 admite 32 cargas unitarias por segmento sin repetidor, y más con transceptores de 1/4 o 1/8 de carga unitaria. En la práctica lo limita el tiempo de ciclo: cada petición espera su respuesta o su timeout.
¿Qué velocidad debo usar?
La que admitan los esclavos. 9600 y 19200 baudios son las más habituales en analizadores y variadores. Las velocidades más altas acortan el ciclo, pero piden buen cable y buena terminación.
¿Funciona Modbus RTU en el puerto RS-232?
Sí. Crea el maestro con ModbusRTUMaster master(RS232); y abre el puerto con RS232.begin(baudrate, SERIAL_8E1). RS-232 solo conecta dos equipos a poca distancia.
¿Cómo leo un valor de 32 bits o un float?
Lee dos registros consecutivos y únelos. El orden de las palabras depende del equipo: unos envían primero la palabra alta y otros la baja. Si el valor parece incorrecto pero no hay error, intercambia las dos palabras.
¿Qué diferencia hay con Modbus TCP?
Modbus TCP lleva los mismos códigos de función por Ethernet, con una dirección IP y un identificador de unidad en lugar de la dirección de esclavo y el CRC. Consulta maestro Modbus TCP en PLC Arduino y ESP32 y Modbus RTU, Modbus TCP u OPC UA.