Para que un PLC basado en Arduino o ESP32 trabaje como maestro (cliente) Modbus TCP, abre una conexión TCP con el esclavo en el puerto 502 con un EthernetClient y envía las peticiones con un objeto ModbusTCPMaster de la librería Modbus de Industrial Shields, por ejemplo master.readHoldingRegisters(client, unitId, address, quantity). La respuesta llega después a través de master.available(). Funciona en la familia M-Duino y en el ESP32 PLC, que tienen Ethernet.
Esta guía explica qué es Modbus TCP, la trama con su cabecera MBAP, las diferencias con Modbus RTU, la configuración de red, un sketch de maestro probado, el lado del esclavo, la referencia de funciones y los errores más habituales.
¿Qué es Modbus TCP?
Modbus TCP (también llamado Modbus TCP/IP) lleva mensajes Modbus por Ethernet dentro de una conexión TCP. Modicon creó Modbus en 1979 como protocolo serie para sus PLC. La versión TCP llegó en 1999 y hoy forma parte de la especificación abierta de la Modbus Organization.

Modbus TCP mantiene el modelo de petición y respuesta de Modbus. El cliente (maestro) es el único equipo que inicia una transacción. El servidor (esclavo) responde con los datos pedidos o ejecuta la acción pedida. Un esclavo es cualquier equipo que tiene datos: un módulo de E/S, un analizador, un variador, otro PLC. Un cliente típico es un PLC, un SCADA o un HMI. Algunos equipos son cliente y servidor a la vez, como una pasarela.
Dicho de otro modo, Modbus TCP combina una red física (Ethernet), un estándar de red (TCP/IP) y una forma estándar de representar los datos (Modbus como protocolo de aplicación): es un mensaje Modbus dentro de un paquete TCP/IP.
Trama Modbus TCP: cabecera MBAP y PDU

Una trama Modbus TCP (ADU) es una cabecera MBAP de 7 bytes seguida de la misma PDU que en Modbus RTU: código de función y datos. No hay CRC, porque TCP ya comprueba los datos.
| Campo | Tamaño | Contenido |
|---|---|---|
| Identificador de transacción | 2 bytes | Lo fija el cliente y se copia en la respuesta para emparejar petición y respuesta |
| Identificador de protocolo | 2 bytes | Siempre 0 para Modbus |
| Longitud | 2 bytes | Número de bytes que siguen (identificador de unidad y PDU) |
| Identificador de unidad | 1 byte | Dirección del esclavo detrás de una pasarela. Los equipos TCP directos suelen ignorarlo |
| Código de función | 1 byte | Los mismos códigos que en Modbus RTU (01 a 06, 15, 16...) |
| Datos | Hasta 252 bytes | Direcciones, cantidades y valores |
Por ejemplo, la petición para leer 4 holding registers desde la dirección 0 de la unidad 1, con la transacción 1, es 00 01 00 00 00 06 01 03 00 00 00 04. La guía de la trama y los códigos de función de Modbus RTU detalla la PDU de cada código de función, que es idéntica en Modbus TCP.
Modbus TCP frente a Modbus RTU
| Modbus TCP | Modbus RTU | |
|---|---|---|
| Capa física | Ethernet | RS-485 o RS-232 |
| Direccionamiento | Dirección IP y puerto (502), más identificador de unidad | Dirección de esclavo de 1 a 247 |
| Comprobación de errores | Checksums de TCP | CRC-16 en cada trama |
| Separación de tramas | Campo de longitud de la cabecera MBAP | Silencio de 3,5 caracteres |
| Maestros en la red | Varios clientes pueden hablar con un servidor | Un maestro por bus |
| Uso típico | PLC con PLC, SCADA, HMI, pasarelas | Equipos de campo: analizadores, variadores, transmisores |
Muchas instalaciones usan los dos: el PLC lee sus equipos de campo por Modbus RTU y comparte los datos con el SCADA por Modbus TCP. Consulta Modbus RTU, Modbus TCP u OPC UA para elegir entre ellos.
¿Qué PLC de Industrial Shields admiten Modbus TCP?
| Familia de PLC | Ethernet | Maestro y esclavo Modbus TCP | Herramientas > Placa |
|---|---|---|---|
| M-Duino | Sí (W5500) | Sí | Industrial Shields AVR Boards > M-Duino family |
| ESP32 PLC | Sí | Sí | Industrial Shields ESP32 Boards > ESP32 PLC Family |
| ESP32 PLC 14 | Sí | Sí | Industrial Shields ESP32 Boards > 14 IOS PLC Family |
| Ardbox | No | No. Usa Modbus RTU por RS-485 |
Para controladores con Linux, consulta la guía de Modbus TCP y RTU para Raspberry PLC, con Python y Node-RED.
Configuración de red Ethernet para Modbus TCP
- Alimenta los PLC con 12 a 24 Vdc.
- Conéctalos con cables Ethernet a un switch. Si conectas dos PLC directamente y el enlace no se levanta, usa un cable cruzado.
- Da a cada equipo una dirección IP fija en la misma subred, por ejemplo 192.168.1.10 para el maestro y 192.168.1.20 para el esclavo, y una dirección MAC distinta.
El puerto Ethernet del M-Duino siempre está conectado al controlador, así que la posición de los interruptores de comunicación no importa para Modbus TCP.
Ejemplo de maestro Modbus TCP para PLC Arduino y ESP32
Este sketch se conecta a un esclavo en 192.168.1.20, lee 4 holding registers 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 ESP32 PLC solo cambia la selección de placa.
// Modbus TCP master (client): read 4 holding registers from a slave every second
// M-Duino family with Ethernet and ESP32 PLC (Industrial Shields Modbus library 1.1.0)
#include <Ethernet.h>
#include <ModbusTCPMaster.h>
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // unique on your network
IPAddress ip(192, 168, 1, 10); // this PLC
IPAddress slaveIp(192, 168, 1, 20); // the Modbus TCP slave (server)
const uint16_t SLAVE_PORT = 502; // standard Modbus TCP port
const uint8_t UNIT_ID = 1; // unit identifier, 1 to 247
const uint16_t FIRST_REG = 0; // register 40001 = address 0
const uint16_t NUM_REGS = 4;
EthernetClient slave; // the TCP connection
ModbusTCPMaster master; // no begin() needed
uint32_t lastRequest = 0;
uint32_t lastConnect = 0;
void setup() {
Serial.begin(9600UL);
Ethernet.begin(mac, ip);
Serial.print("PLC IP: ");
Serial.println(Ethernet.localIP());
master.setTimeout(500); // response timeout in ms (default 1000)
}
void loop() {
// 1. The sketch manages the TCP connection, not the library
if (!slave.connected()) {
if (millis() - lastConnect >= 2000UL) { // retry every 2 s, connect() can block
slave.stop();
Serial.println(slave.connect(slaveIp, SLAVE_PORT) ? "Connected" : "Connection failed");
lastConnect = millis();
}
return;
}
// 2. One request at a time, every second
if (!master.isWaitingResponse() && millis() - lastRequest >= 1000UL) {
if (!master.readHoldingRegisters(slave, UNIT_ID, FIRST_REG, NUM_REGS)) {
Serial.println("Request not sent");
}
lastRequest = millis();
}
// 3. Poll for the answer
if (master.isWaitingResponse()) {
ModbusResponse response = master.available();
if (master.hasException()) { // timeout, wrong transaction id...
Serial.print("Communication error: ");
Serial.println(master.getExceptionMessage());
master.clearException();
}
if (response) {
if (response.hasError()) { // exception code from the slave
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();
}
}
}
}
- El sketch gestiona la conexión. La librería envía y recibe por el
EthernetClientque le pasas, pero no abre ni reabre la conexión. Mantén la conexión abierta entre peticiones: abrir una por petición es lento y algunos equipos limitan el número de conexiones. - El identificador de unidad va donde va la dirección de esclavo en Modbus RTU. Un equipo en la misma red suele aceptar cualquier valor, a menudo 1 o 255. Detrás de una pasarela, selecciona el esclavo RTU.
- Una petición cada vez. Las funciones devuelven
falsemientras hay una respuesta pendiente, así que compruebaisWaitingResponse()antes de enviar la siguiente petición.
Para cargarlo, selecciona la placa y el modelo en Herramientas > Placa y Herramientas > Modelo, el puerto en Herramientas > Puerto y usa Programa > Subir. La librería también tiene un ejemplo de maestro Modbus TCP para cada código de función en Archivo > Ejemplos > Modbus.
El lado del esclavo: otro PLC como servidor Modbus TCP
Para probar el maestro con un segundo PLC, abre Archivo > Ejemplos > Modbus > ModbusTCPSlave, pon su dirección IP en la misma subred y cárgalo. El ejemplo sirve las entradas y salidas de la primera zona del PLC en el puerto 502: coils para las salidas digitales, discrete inputs para las entradas digitales, holding registers para las salidas analógicas e input registers para las entradas analógicas. Desde la versión 1.1.0 de la librería detecta el modelo de PLC, así que no se limita al M-Duino 21+.
Con el esclavo de ejemplo, el maestro puede activar la salida Q0_0 con master.writeSingleCoil(slave, 1, 0, true), leer las entradas analógicas con master.readInputRegisters(slave, 1, 0, 6) y las entradas digitales con master.readDiscreteInputs(slave, 1, 0, 7). El esclavo de la librería responde a cualquier identificador de unidad. Cómo crear tu propio mapa de registros está en cómo usar la librería de esclavo Modbus TCP con un PLC Arduino.
Referencia de funciones del maestro Modbus TCP
Las funciones son las mismas que en Modbus RTU, con el EthernetClient como primer parámetro y el identificador de unidad como segundo. Devuelven true cuando la petición se envía y false si el maestro está ocupado o el cliente no está conectado.
| Función | FC | Qué hace |
|---|---|---|
readCoils(client, unit, addr, quantity) | 01 | Lee coils (bits de lectura y escritura) |
readDiscreteInputs(client, unit, addr, quantity) | 02 | Lee discrete inputs (bits de solo lectura) |
readHoldingRegisters(client, unit, addr, quantity) | 03 | Lee holding registers (16 bits, lectura y escritura) |
readInputRegisters(client, unit, addr, quantity) | 04 | Lee input registers (16 bits, solo lectura) |
writeSingleCoil(client, unit, addr, value) | 05 | Escribe una coil (bool) |
writeSingleRegister(client, unit, addr, value) | 06 | Escribe un holding register (uint16_t) |
writeMultipleCoils(client, unit, addr, values, quantity) | 15 | Escribe un array bool en coils consecutivas |
writeMultipleRegisters(client, unit, addr, values, quantity) | 16 | Escribe un array uint16_t en holding registers consecutivos |
La respuesta llega de master.available() como un ModbusResponse con hasError(), getErrorCode(), getErrorMessage(), getFC(), isCoilSet(i), isDiscreteInputSet(i) y getRegister(i). La dirección es el número de registro menos 1: el holding register 40009 es la dirección 8.
Errores habituales de Modbus TCP
| Síntoma | Causa probable | Qué comprobar |
|---|---|---|
| "Connection failed" | No hay conexión TCP con el esclavo | Direcciones IP en la misma subred, cable y LED de enlace, esclavo alimentado, puerto 502 abierto |
| "Request not sent" | El maestro está ocupado o el cliente no está conectado | Envía solo cuando isWaitingResponse() sea false y connected() sea true |
| Excepción de timeout | El esclavo ha aceptado la conexión pero no ha respondido | Identificador de unidad, código de función admitido por el equipo, timeout demasiado corto |
| Dirección de datos no válida (02) | El registro no existe en el esclavo | Número de registro menos 1, cantidad, mapa de registros del equipo |
| Valores desplazados un registro | Numeración desde 1 en lugar de 0 | El holding register 40001 es la dirección 0 |
Modbus TCP en proyectos reales con PLC
En el caso de estudio de la red Modbus de temperatura, un PLC principal trabaja como cliente Modbus TCP y lee los PLC locales, que recogen los termómetros por Modbus RTU. La PLC Solution Library tiene código para tareas relacionadas: un mapa de registros Modbus TCP para una planta de biorreactores y una pasarela Modbus TCP a RTU en un M-Duino.
Lee un servidor Modbus TCP desde el navegador. En el simulador de red Modbus, un M-Duino 21 sirve los datos de cuatro esclavos RTU como servidor Modbus TCP en el puerto 502. Crea una petición desde el portátil cliente, lee fuera del mapa y mira la excepción, trama a trama.
Abrir el simulador de red ModbusConsulta a un ingeniero sobre tu red Modbus
Preguntas frecuentes sobre Modbus TCP en PLC Arduino y ESP32
¿Qué puerto usa Modbus TCP?
El puerto 502 por defecto. El esclavo de Industrial Shields permite cambiarlo en el constructor, por ejemplo ModbusTCPSlave slave(510);, y el maestro se conecta al puerto que pasas a connect().
¿Qué es el identificador de unidad en Modbus TCP?
El byte que sustituye a la dirección de esclavo de Modbus RTU. Las pasarelas lo usan para reenviar la petición a un esclavo RTU. Los equipos conectados directamente a Ethernet suelen ignorarlo o esperan 1 o 255.
¿Puede un PLC ser servidor Modbus TCP y maestro Modbus RTU a la vez?
Sí. Eso es una pasarela: el PLC interroga a sus esclavos RS-485 como maestro RTU y sirve los valores al SCADA como servidor TCP. El M-Duino tiene Ethernet y RS-485, así que puede hacer las dos cosas.
¿Cuántos clientes pueden leer un servidor Modbus TCP?
Modbus TCP admite varios clientes, pero cada servidor limita sus conexiones abiertas. Los equipos pequeños a menudo solo aceptan unas pocas. Mantén abierta una conexión por cliente en lugar de abrir una nueva en cada petición.
¿Puedo usar DHCP en lugar de una IP fija?
Para el maestro, sí: Ethernet.begin(mac) pide una dirección. El esclavo debería tener una IP fija, o una dirección reservada en el servidor DHCP, para que los clientes siempre lo encuentren.
¿Modbus TCP es lo mismo que Modbus RTU sobre TCP?
No. Modbus RTU sobre TCP envía la trama RTU, con su CRC y sin cabecera MBAP, dentro de una conexión TCP. Algunas pasarelas y analizadores lo usan. La librería de Industrial Shields implementa Modbus TCP estándar.