Introducción
En el área de automatización de Arduino, el protocolo Modbus RTU es un medio de comunicación que permite el intercambio de datos entre controladores lógicos programables (controlador PLC Arduino) y computadoras.
Los dispositivos electrónicos pueden intercambiar información a través de líneas en serie utilizando el protocolo Modbus.
En este post, vas a aprender cómo funciona Modbus RTU.
Usos
1. ¿Qué es un Modbus RTU?
2. ¿Cómo funciona un Modbus RTU?
3. Estructura general del marco Modbus
4. Código de función Modbus
5. Tipos de objetos Modbus
6. Formato de datos Modbus
6.1- 01 (0x01) Leer bobinas
6.2- 02 (0x02) Leer entradas discretas
6.3- 03 (0x03) Lectura de registros de retención
6.4- 04 (0x04) Leer registro de entrada
6.5- 05 (0x05) Escribir 1 bobina
6.6- 06 (0x06) Escribir 1 registro
6.7- 15 (0x0F) Escribir múltiples bobinas
6.8- 16 (0x10) Escribir múltiples registros
7. Creando nuestro mensaje Modbus RTU
8. Software
1. What is Modbus RTU
Modbus es un protocolo de comunicación situado en los niveles 1, 2 y 7 del Modelo OSI, basado en la arquitectura maestro/esclavo, diseñado en 1979 por Modicon para su gama de PLCs.
Convertido en un protocolo de comunicaciones de facto en la industria, veamos algunas de sus principales características:
Diseñado pensando en su uso en aplicaciones industriales
Es público y gratis
Es fácil de implementar y requiere un poco de desarrollo.
Maneja bloques de datos sin asumir restricciones
Cada uno de los mensajes incluye información redundante que garantiza su integridad en la recepción.
Los comandos básicos de Modbus permiten controlar un dispositivo RTU para modificar el valor de cualquiera de sus registros o solicitar el contenido de los mismos.
2. How does MODBUS RTU work
Modbus RTU es la implementación más común disponible para Modbus.
Modbus RTU se utiliza en la comunicación en serie y hace uso de una representación compacta y binaria de los datos para la comunicación del protocolo.
Los mensajes Modbus se dividen por periodos de inactividad, como se puede ver en la siguiente imagen.
Cada dispositivo en una comunicación Modbus tiene una dirección única.
El Modbus RTU funciona mediante RS-485, que es una red multipunto de un solo cable, solo el nodo asignado como maestro puede iniciar un comando. Todos los demás dispositivos son esclavos y responden a solicitudes y comandos.
Un comando Modbus contiene la dirección Modbus del dispositivo al que va dirigido. Sólo el dispositivo al que va dirigido responderá y actuará sobre el comando, aunque otros dispositivos puedan recibirlo.
Además, es importante decir que todos los comandos Modbus contienen información de suma de comprobación para que el receptor pueda detectar errores de transmisión.
Pongamos un ejemplo. Imaginemos que tenemos una red serial Modbus, donde hay un maestro y hasta 31 esclavos, cada uno con una dirección de esclavo única.
El maestro sólo quiere enviar un mensaje al esclavo número 2 solicitando el valor de 6 registros de entrada.
Así, el maestro enviaría un mensaje y todos los esclavos lo recibirían, pero sólo el esclavo número 2 respondería y actuaría sobre la orden, aunque otros dispositivos podrían recibirla.
Con este ejemplo, vamos a crear un mensaje Modbus RTU a lo largo de este post. Mensaje Modbus por el momento: 02 (dirección del esclavo)
3. Modbus general frame structure
La unidad de datos de aplicación (ADU) de Modbus RTU consta de los elementos mostrados:
De ellos, el código de función y los datos constituyen la unidad de datos de protocolo (PDU)
4. Modbus Function Code
MODBUS es un protocolo de solicitud/respuesta y ofrece servicios especificados por códigos de función. Los códigos de función de MODBUS son elementos de las PDU de solicitud/respuesta de MODBUS.
El campo de código de función de una unidad de datos MODBUS se codifica en un byte. Los códigos válidos están en el rango de 1 a 255 decimales (el rango 128 - 255 está reservado y se utiliza para las respuestas de excepción). Cuando se envía un mensaje desde un Cliente a un dispositivo Servidor, el campo de código de función indica al servidor qué tipo de acción debe realizar. El código de función "0" no es válido. Los códigos de subfunción se añaden a algunos códigos de función para definir múltiples acciones.
A continuación puedes encontrar la lista de códigos de función y sus funciones:
5. Modbus Object Types
En Modbus, los tipos de datos se pueden dividir principalmente en dos tipos: Bobinas y Registros. Las bobinas pueden entenderse como digitales ya que sólo pueden estar en ON (1) o en OFF (0). Algunas bobinas pueden representar entradas y otras salidas.
Los registros son de 16 bits ( 2 bytes) sin signo y por lo tanto pueden tener valores de 0 a 65535 (0 a FFFF). Aunque tiene sus limitaciones como que no puede representar números negativos , números en coma flotante o valores con representación mayor a 65535. La siguiente tabla resume los tipos de objetos.
Las cuatro tablas principales son las siguientes:
TABLAS PRINCIPALES | TIPO DE OBJECTO | TIPO DE | COMENTARIOS |
Entradas discretas. (Entradas) | Un solo bit | Sólo lectura | Este tipo de datos será proporcionado por un sistema de E/S. |
Bobinas (Salidas) | Un solo bit | Lectura-Escritura | Este tipo de datos pueden ser alterados por un programa de aplicación. |
Registros de entrada (Entradas) | Palabra de 16 bits | Sólo lectura | Este tipo de datos puede ser proporcionado por un sistema de E/S. |
Registros de retención (Salidas) | Palabra de 16 bits | Lectura-Escritura | Este tipo de datos pueden ser alterados por un programa de aplicación. |
6. Modbus Data Format
Descripción de los códigos de función
Las peticiones y respuestas de Modbus contienen una Unidad de Datos de Aplicación (ADU) que contiene una Unidad de Datos de Protocolo (PDU).
01. (0x01) Read Coils
Este código de función se utiliza para leer de 1 a 2000 estados contiguos de bobinas en un dispositivo remoto.
Las bobinas en el mensaje de respuesta se empaquetan como una bobina por bit del campo de datos. El estado se indica como 1: ON y 0: OFF. El LSB del primer byte de datos contiene la salida a la que se dirige la consulta. Las demás bobinas siguen hacia el extremo de orden alto de este byte, y de orden bajo a orden alto en los bytes siguientes.
Solicitud
Código de función | 1 Byte | 0x01 |
Dirección inicial | 2 Bytes | 0x0000 a 0xFFFF |
Cantidad de bobinas | 2 Bytes | 1 a 2000 (0x7D0) |
Respuesta
Código de función | 1 Byte | 0x01 |
Cantidad de bytes | 1 Byte | N* |
Estado de la bobina | n Bytes | n = N o N+1 |
*N =Cantidad de salidas / 8, si el resto es diferente de 0 => N = N+1
Ejemplo de solicitud de lectura de las salidas discretas 20 - 38:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 01 | Función | 01 |
Dirección inicial Hi | 00 | Cantidad de bytes | 03 |
Dirección inicial Lo | 13 | Estado de las salidas 27-20 | CD |
Cantidad de salidas Hi | 00 | Estado de las salidas 35-28 | 6B |
Cantidad de salidas Lo | 13 | Estados de las salidas | 05 |
*El CRC debe ser calculado.
02 - (0x02) Leer Entradas Discretas
Este código de función se utiliza para leer de 1 a 2000 estados contiguos de entradas discretas en un dispositivo remoto.
La PDU de solicitud especifica la dirección de inicio, es decir, la dirección de la primera entrada especificada, y el número de entradas. En la PDU, las entradas discretas se direccionan empezando por cero. Por lo tanto, las entradas discretas numeradas de 1 a 16 se direccionan como 0 a 15.
Las entradas discretas en el mensaje de respuesta se empaquetan como una entrada por bit del campo de datos. El estado se indica como 1= ON; 0= OFF. El LSB del primer byte de datos contiene la entrada dirigida en la consulta. Las demás entradas siguen hacia el final de orden alto de este byte, y de orden bajo a orden alto en los bytes siguientes.
Solicitud
Código de función | 1 Byte | 0x02 |
Dirección inicial | 2 Bytes | 0x0000 a 0xFFFF |
Cantidad de entradas | 2 Bytes | 1 a 2000 (0x7D0) |
Respuesta
Código de función | 1 Byte | 0x02 |
Recuento de bytes | 1 Byte | N* |
Estado de las entradas | N* x 1 Byte |
Ejemplo de petición de lectura de las entradas discretas 197 - 218:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 02 | Función | 02 |
Dirección de salida Hi | 00 | Recuento de bytes | 03 |
Dirección de salida Lo | C4 | Estado de las salidas 27-20 | AD |
Cantidad de salidas Hi | 00 | Estado de las salidas 35-28 | DB |
Cantidad de salidas Lo | 16 | Estado de las salidas | 35 |
03 - (0x03) Leer registros de retención
Este código de función se utiliza para leer el contenido de un bloque contiguo de registros de retención en un dispositivo remoto. La PDU de solicitud especifica la dirección de registro inicial y el número de registros. En la PDU los registros se direccionan empezando por cero. Por lo tanto, los registros numerados del 1 al 16 se direccionan como 0-15.
Los datos del registro en el mensaje de respuesta se empaquetan en dos bytes por registro, con el contenido binario justificado a la derecha dentro de cada byte. Para cada registro, el primer byte contiene los bits de orden alto y el segundo contiene los bits de orden bajo.
Solicitud
Código de función | 1 Byte | 0x03 |
Dirección inicial | 2 Bytes | 0x0000 a 0xFFFF |
Cantidad de registros | 2 Bytes | 1 a 125 (0x7D0) |
Respuesta
Código de función | 1 Byte | 0x03 |
Recuento de bytes | 1 Byte | 2 x N* |
Valor de registro | N* x 2 Bytes |
*N = Cantidad de registros
Ejemplo de solicitud de lectura de los registros 108 - 110:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 03 | Función | 03 |
Dirección de salida Hi | 00 | Recuento de bytes | 06 |
Dirección de salida Lo | 6B | Valor del registro Hi (108) Valor del registro Lo (108) | 02 2B |
No. de Registros Hi | 00 | Valor del registro Hi (109) Valor del registro Lo (109) | 00 00 |
No. de Registros Lo | 03 | Valor del registro Hi (110) Valor del registro Lo (110) | 00 64 |
04 - (0x04) Leer registros de entrada
Este código de función se utiliza para leer de 1 a 125 registros de entrada contiguos en un dispositivo remoto. La PDU de solicitud especifica la dirección de registro inicial y el número de registros. En la PDU los registros se direccionan empezando por cero. Por lo tanto, los registros de entrada numerados del 1 al 16 se direccionan como 0-15.
Los datos del registro en el mensaje de respuesta se empaquetan en dos bytes por registro, con el contenido binario justificado a la derecha dentro de cada byte. Para cada registro, el primer byte contiene los bits de orden alto y el segundo contiene los bits de orden bajo.
Solicitud
Código de función | 1 Byte | 0x04 |
Dirección inicial | 2 Bytes | 0x0000 a 0xFFFF |
Cantidad de registros de entrada | 2 Bytes | 0x0001 a 0x007D |
Respuesta
Código de función | 1 Byte | 0x04 |
Recuento de bytes | 1 Byte | 2 x N* |
Registros de entrada | N* x 2 Bytes |
*N = Cantidad de registros de entrada
Ejemplo de solicitud de lectura del registro de entrada 9
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 04 | Función | 04 |
Dirección de salida Hi | 00 | Recuento de bytes | 02 |
Dirección de salida Lo | 08 | Entrada Reg. 9 Hi | 00 |
Cantidad de registros de entrada Hi | 00 | Entrada Reg. 9 Lo | 0A |
Cantidad de registros de entrada Lo | 01 |
05 - (0x05) Escribir una sola bobina
Este código de función se utiliza para escribir una única salida en ON u OFF en un dispositivo remoto.
El estado ON/OFF solicitado se especifica mediante una constante en el campo de datos de solicitud. Un valor de FF00 hex. solicita que la salida esté en ON. Un valor de 00 00 solicita que esté en OFF. Todos los demás valores son ilegales y no afectan a la salida.
La PDU de petición especifica la dirección de la bobina que se va a forzar. Las bobinas se direccionan empezando por el cero. Por lo tanto, la bobina número 1 se direcciona como 0. El estado ON/OFF solicitado se especifica mediante una constante en el campo Valor de la bobina. Un valor de 0XFF00 solicita que la bobina esté en ON. Un valor de 0X0000 solicita que la bobina esté apagada. Todos los demás valores son ilegales y no afectan a la bobina.
La respuesta normal es un eco de la petición, que se devuelve después de escribir el estado de la bobina.
Solicitud
Código de función | 1 Byte | 0x05 |
Dirección de salida | 2 Bytes | 0x0000 a 0xFFFF |
Valor de salida | 2 Bytes | 0x0000 a 0xFF00 |
Respuesta
Código de función | 1 Byte | 0x05 |
Recuento de bytes | 2 Byte | 0x0000 a 0xFFFF |
Registros de entrada | 2 Bytes | 0x0000 o 0xFF00 |
Ejemplo de solicitud de escritura de la bobina 173 ON:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 05 | Función | 05 |
Dirección inicial Hi | 00 | Dirección de salida Hi | 00 |
Dirección inicial Lo | AC | Dirección de salida Lo | AC |
Cantidad de Reg. de Entrada Hi | FF | Valor de salida Hi | FF |
Cantidad de Reg. de Entrada Lo | 00 | Valor de salida Lo | 00 |
06 - (0x06) Escribir un solo registro
Este código de función se utiliza para escribir un único registro de retención en un dispositivo remoto.
La PDU de solicitud especifica la dirección del registro que se va a escribir. Los registros se direccionan empezando por cero. Por lo tanto, el registro número 1 se direcciona como 0.
La respuesta normal es un eco de la solicitud, que se devuelve después de que se haya escrito el contenido del registro.
Solicitud
Código de función | 1 Byte | 0x06 |
Dirección de registro | 2 Bytes | 0x0000 a 0xFFFF |
Valor de registro | 2 Bytes | 0x0000 a 0xFFFF |
Respuesta
Código de función | 1 Byte | 0x06 |
Dirección de registro | 2 Byte | 0x0000 a 0xFFFF |
Valor de registro | 2 Bytes | 0x0000 o 0xFF00 |
Ejemplo de solicitud de escritura del registro 2 a 00 03 hex:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 06 | Función | 06 |
Dirección inicial Hi | 00 | Dirección de salida Hi | 00 |
Dirección inicial Lo | 01 | Dirección de salida Lo | 01 |
Cantidad de Reg. de Entrada Hi | 00 | Valor de salida Hi | 00 |
Cantidad de Reg. de Entrada Lo | 03 | Valor de salida Lo | 03 |
15 - (0x0F)Escribir varias bobinas
Este código de función se utiliza para forzar cada bobina de una secuencia de bobinas a ON u OFF en un dispositivo remoto. La PDU de solicitud especifica las referencias de las bobinas que deben forzarse. Las bobinas se direccionan empezando por el cero. Por lo tanto, la bobina número 1 se direcciona como 0.
Los estados ON/OFF solicitados se especifican mediante el contenido del campo de datos de solicitud. Un '1' lógico en una posición de bit del campo solicita que la salida correspondiente esté en ON. Un '0' lógico solicita que esté en OFF.
La respuesta normal devuelve el código de función, la dirección de inicio y la cantidad de bobinas forzadas.
Solicitud
Código de función | 1 Byte | 0x0F |
Dirección inicial | 2 Bytes | 0x0000 a 0xFFFF |
Cantidad de salidas | 2 Bytes | 0x0001 a 0x07B0 |
Recuento de bytes | 1 Byte | N* |
Valor de las salidas | N* x 1 Byte |
*N = Cantidad de salidas / 8, si el resto es diferente de 0 => N = N+1
Respuesta
Código de función | 1 Byte | 0x0F |
Dirección inicial | 2 Byte | 0x0000 a 0xFFFF |
Cantidad de salidas | 2 Bytes | 0x0001 o 0x07B0 |
Ejemplo de petición de escritura del registro 2 a 00 03 hex:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 0F | Función | 0F |
Dirección de salida Hi | 00 | Dirección de salida Hi | 00 |
Dirección de salida Lo | 13 | Dirección de salida Lo | 13 |
Cantidad de salidas Hi | 00 | Cantidad de salidas Hi | 00 |
Cantidad de salidas Lo | 0A | Cantidad de salidas Lo | 0A |
Recuento de bytes | 02 | ||
Valor de las salidas Hi | CD | ||
Valor de las salidas Lo | 01 |
16 - (0x10) Escribir varios registros
Este código de función se utiliza para escribir un bloque de registros contiguos (de 1 a 123 registros) en un dispositivo remoto.
Los valores escritos solicitados se especifican en el campo de datos de solicitud. Los datos se empaquetan en dos bytes por registro.
La respuesta normal devuelve el código de función, la dirección inicial y la cantidad de registros escritos.
Solicitud
Código de función | 1 Byte | 0x10 |
Dirección inicial | 2 Bytes | 0x0000 a 0xFFFF |
Cantidad de registros | 2 Bytes | 0x0001 a 0x007B |
Recuento de bytes | 1 Byte | 2 x N* |
Valor de los registros | N* x 2 Bytes | valor |
*N = Cantidad de registros
Respuesta
Código de función | 1 Byte | 0x10 |
Dirección inicial | 2 Byte | 0x0000 a 0xFFFF |
Cantidad de registros | 2 Bytes | 0x123 o (0x7B) |
Ejemplo de una solicitud para escribir dos registros que comienzan en 2 a 00 0A y 01 02 hex:
Solicitud | Respuesta | ||
Nombre del campo | Hex | Nombre del campo | Hex |
Función | 10 | Función | 10 |
Dirección de salida Hi | 00 | Dirección de salida Hi | 00 |
Dirección de salida Lo | 01 | Dirección de salida Lo | 01 |
Cantidad de registros Hi | 00 | Cantidad de salidas Hi | 00 |
Cantidad de registros Lo | 02 | Cantidad de salidas Lo | 02 |
Recuento de bytes | 04 | ||
Valor de los registros Hi | 00 | ||
Valor de los registros Lo | 0A | ||
Valor de los registros Hi | 01 | ||
Valor de los registros Lo | 02 |
7-Creando nuestro mensaje Modbus RTU
Ahora que ya sabemos un poco más sobre Modbus RTU y su formato de trama, vamos a terminar nuestro mensaje Modbus del ejemplo que dimos al principio de esta entrada del post.
Queríamos que el maestro enviara un mensaje al esclavo número 2 solicitando el valor de 6 registros de entrada.
Nuestro mensaje Modbus RTU se ve así en este momento: 0204 (02 (Dirección del esclavo) + 04 (Código de función))
Como nuestro código de función es el número 04: Read Input Register, los datos deben contener: Dirección inicial Hi + Dirección inicial Lo + Cantidad de Reg. de Entrada Hi + Cantidad de Reg. de Entrada Lo. Lo + CRC.
Por lo tanto, vamos a llenar la solicitud ADU para obtener todo el mensaje:
Solicitud ADU | |
Nombre del campo | HEX |
Dirección del esclavo | 02 |
Código de función | 04 |
Dirección inicial Hi | 00 |
Dirección inicial Lo | 00 |
Cantidad de Reg. de Entrada Hi | 00 |
Cantidad de Reg. de entrada Lo | 06 |
CRC | - |
CRC | - |
Para calcular el CRC, basta con escribir el mensaje Modbus 020400000006 en este sitio web. Selecciona el tipo de entrada HEX y obtén el número CRC-16 (Modbus).
Como es LSB, lo invertiremos. Si el resultado del CRC es 0x3B70, ahora será: 703B.
Finalmente, así es como queda nuestro mensaje Modbus:
020400000006703B
8- Software
Modbus RTU Master con Arduino IDE
El módulo maestro Modbus RTU implementa las capacidades del Modbus RTU Master. Vamos a trabajar con la función modbusrtumaster.h:
#include <ModbusRTUMaster.h>
Es posible utilizar cualquier secuencia de hardware Serial Arduino:
RS-485
#include <RS485.h> ModbusRTUMaster master(RS485);
RS-232
#include <RS232.h> ModbusRTUMaster master(RS232);
Antes de usarlo, es necesario llamar a la función de inicio en la configuración tanto para la variable serial como para la variable Modbus. Es una buena práctica establecer la velocidad de transmisión (valor predeterminado: 19200 bps) también en la variable Modbus para definir los tiempos de espera internos de Modbus.
RS485.begin(9600, HALFDUPLEX, SERIAL_8E1); master.begin(9600);
Las funciones para leer y escribir los valores de los esclavos son:
readCoils(slave_address, address, quantity); readDiscreteInputs(slave_address, address, quantity); readHoldingRegisters(slave_address, address, quantity); readInputRegisters(slave_address, address, quantity); writeSingleCoil(slave_address, address, value); writeSingleRegister(slave_address, address, value); writeMultipleCoils(slave_address, address, values, quantity); writeMultipleRegisters(slave_address, address, values, quantity);
Donde:
slave_address es la dirección del esclavo Modbus RTU.
- address es la bobina, entrada digital, registro de retención o dirección de registro de entrada. Por lo general, esta dirección es la bobina, la entrada digital, el registro de retención o el número de registro de entrada menos 1: el número de registro de retención 40009 tiene la dirección
8
.quantity es el número de bobinas, digitales, registros de retención o registros de entrada a leer/escribir. value es el valor dado de la bobina o los registros de retención en una operación de escritura. Dependiendo de la función, el tipo de datos cambia. Una bobina está representada por un valor bool y un registro de retención está representado por un valor
uint16_t.
En una función de lectura/escritura múltiple, el argumento de address es la primera dirección. En una función de escritura múltiple, el argumento values es una matriz de valores para escribir.
Es importante decir que estas funciones no son de bloqueo, por lo que no devuelven el valor leído. Devuelven true o false dependiendo del estado actual del módulo. Si hay una petición Modbus pendiente, devuelven false
.
// Read 5 holding registers from address 0x24 of slave with address 0x10 if (master.readHoldingRegisters(0x10, 0x24, 5)) { // OK, the request is being processed } else { // ERROR, the master is not in an IDLE state }
Existe la función vailable() para verificar las respuestas del esclavo
ModbusResponse response = master.available(); if (response) { // Process response }
ModbusResponse implementa algunas funciones para obtener la información de respuesta:
hasError(); getErrorCode(); getSlave(); getFC(); isCoilSet(offset); isDiscreteInputSet(offset); isDiscreteSet(offset); getRegister(offset);
ModbusResponse response = master.available(); if (response) { if (response.hasError()) { // There is an error. You can get the error code with response.getErrorCode() } else { // Response ready: print the read holding registers for (int i = 0; i < 5; ++i) { Serial.println(response.getRegister(i)); } } }
Los posibles códigos de error son:
0x01 ILLEGAL FUNCTION
0x02 ILLEGAL DATA ADDRESS
0x03 ILLEGAL DATA VALUE
0x04 SERVER DEVICE FAILURE