Ir al contenido

Maestro Modbus TCP con PLC basados en Arduino y ESP32

Trama MBAP, Modbus TCP frente a RTU, configuración de red y un sketch de cliente probado para PLC M-Duino y ESP32
25 de marzo de 2020 por
Maestro Modbus TCP con PLC basados en Arduino y ESP32
Bernat Garcia

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.

Panel frontal de un PLC M-Duino con el puerto Ethernet que se usa para Modbus TCP

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

Trama Modbus TCP: la PDU de Modbus dentro de una cabecera MBAP de 7 bytes con identificador de transacción, identificador de protocolo, longitud e identificador de unidad

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.

CampoTamañoContenido
Identificador de transacción2 bytesLo fija el cliente y se copia en la respuesta para emparejar petición y respuesta
Identificador de protocolo2 bytesSiempre 0 para Modbus
Longitud2 bytesNúmero de bytes que siguen (identificador de unidad y PDU)
Identificador de unidad1 byteDirección del esclavo detrás de una pasarela. Los equipos TCP directos suelen ignorarlo
Código de función1 byteLos mismos códigos que en Modbus RTU (01 a 06, 15, 16...)
DatosHasta 252 bytesDirecciones, 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 TCPModbus RTU
Capa físicaEthernetRS-485 o RS-232
DireccionamientoDirección IP y puerto (502), más identificador de unidadDirección de esclavo de 1 a 247
Comprobación de erroresChecksums de TCPCRC-16 en cada trama
Separación de tramasCampo de longitud de la cabecera MBAPSilencio de 3,5 caracteres
Maestros en la redVarios clientes pueden hablar con un servidorUn maestro por bus
Uso típicoPLC con PLC, SCADA, HMI, pasarelasEquipos 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 PLCEthernetMaestro y esclavo Modbus TCPHerramientas > Placa
M-DuinoSí (W5500)SíIndustrial Shields AVR Boards > M-Duino family
ESP32 PLCSíSíIndustrial Shields ESP32 Boards > ESP32 PLC Family
ESP32 PLC 14SíSíIndustrial Shields ESP32 Boards > 14 IOS PLC Family
ArdboxNoNo. 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

  1. Alimenta los PLC con 12 a 24 Vdc.
  2. Conéctalos con cables Ethernet a un switch. Si conectas dos PLC directamente y el enlace no se levanta, usa un cable cruzado.
  3. 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 EthernetClient que 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 false mientras hay una respuesta pendiente, así que comprueba isWaitingResponse() 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ónFCQué hace
readCoils(client, unit, addr, quantity)01Lee coils (bits de lectura y escritura)
readDiscreteInputs(client, unit, addr, quantity)02Lee discrete inputs (bits de solo lectura)
readHoldingRegisters(client, unit, addr, quantity)03Lee holding registers (16 bits, lectura y escritura)
readInputRegisters(client, unit, addr, quantity)04Lee input registers (16 bits, solo lectura)
writeSingleCoil(client, unit, addr, value)05Escribe una coil (bool)
writeSingleRegister(client, unit, addr, value)06Escribe un holding register (uint16_t)
writeMultipleCoils(client, unit, addr, values, quantity)15Escribe un array bool en coils consecutivas
writeMultipleRegisters(client, unit, addr, values, quantity)16Escribe 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íntomaCausa probableQué comprobar
"Connection failed"No hay conexión TCP con el esclavoDirecciones 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á conectadoEnvía solo cuando isWaitingResponse() sea false y connected() sea true
Excepción de timeoutEl esclavo ha aceptado la conexión pero no ha respondidoIdentificador 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 esclavoNúmero de registro menos 1, cantidad, mapa de registros del equipo
Valores desplazados un registroNumeración desde 1 en lugar de 0El 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.

Buscar en nuestro blog

Maestro Modbus TCP con PLC basados en Arduino y ESP32
Bernat Garcia 25 de marzo de 2020
Compartir esta publicación

¿Estás buscando tu Controlador Lógico Programable ideal?

Echa un vistazo a esta comparativa de producto de varios controladores industriales basados en Arduino.

Comparamos entradas, salidas, comunicaciones y otras especificaciones con las de los equipos de otras marcas destacadas.

Comparativa de PLCs