· Eduardo Vieira · Protocolos Industriales · 6 min de lectura
Códigos de Función Modbus: La Guía Definitiva (0x01 a 0x17)
Dominá los códigos de función Modbus con ejemplos reales en C (libmodbus) y Python (pymodbus). Guía completa para leer coils, inputs y registers sin errores.

Si alguna vez te quedaste mirando un analizador de tramas preguntándote por qué tu PLC te devuelve un 0x83, esta guía es para vos.
Los códigos de función Modbus son el núcleo del protocolo. Definen qué acción debe realizar el servidor (esclavo). Muchos tutoriales solo cubren el 0x03 (Read Holding Registers), pero en la industria real vas a necesitar dominar todo el espectro para interactuar con I/O remotos, variadores de frecuencia y sensores complejos.
En esta guía definitiva, vamos a desglosar todos los códigos de función estándar, con diagramas de trama bit a bit y código de producción en C (libmodbus) y Python (pymodbus).
El Modelo de Datos Modbus: Las 4 Tablas
Antes de tirar código, entendé dónde estás parado. Modbus divide la memoria en 4 áreas distintas. Confundirlas es la causa #1 de errores “Illegal Data Address”.
| Área | Tipo de Objeto | Acceso | Dirección | Uso Típico |
|---|---|---|---|---|
| Coils | Bit | R/W | 00001 - 09999 | Salidas digitales (Relés, Contactores) |
| Discrete Inputs | Bit | Read Only | 10001 - 19999 | Entradas digitales (Sensores, Pulsadores) |
| Input Registers | 16-bit Word | Read Only | 30001 - 39999 | Mediciones analógicas (ADC, Temperatura) |
| Holding Registers | 16-bit Word | R/W | 40001 - 49999 | Setpoints, Configuración, Valores R/W |
Nota Crítica: En el protocolo (el cable), las direcciones son Zero-Based.
- Holding Register
40001es la dirección0.- Holding Register
40101es la dirección100.Librerías como
pymodbusylibmodbususan direccionamiento Zero-Based (0-65535).
Estructura de la Trama (PDU)
El PDU (Protocol Data Unit) es la parte del mensaje que es independiente del medio físico (TCP o Serial).
+---------------+------------------------+
| Function Code | Data |
+---------------+------------------------+
| 1 Byte | N Bytes |
+---------------+------------------------+- Rango: 0x01 a 0x7F (1-127).
- Excepción: Si el servidor rechaza la solicitud, devuelve el código de función con el MSB en 1 (Ej:
0x03$\to$0x83).
1. Funciones de Lectura (Bit Access)
0x01: Read Coils (Leer Bobinas)
Lee el estado ON/OFF de salidas digitales discreta.
- Request:
[Func Code] [Start Addr HI] [Start Addr LO] [Quantity HI] [Quantity LO] - Response:
[Func Code] [Byte Count] [Coil Status 1] ... [Coil Status N]
Python (pymodbus)
# Leer salidas digitales 0 a 7
rr = client.read_coils(address=0, count=8, slave=1)
if not rr.isError():
print(f"Salidas: {rr.bits}")
else:
print(f"Error: {rr}")C (libmodbus)
uint8_t bits[8];
int rc = modbus_read_bits(ctx, 0, 8, bits);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
}0x02: Read Discrete Inputs (Leer Entradas Discretas)
Igual que el 0x01, pero para leer entradas físicas (sensores, finales de carrera) que no se pueden escribir. A menudo ignorado, pero vital para seguridad.
Python (pymodbus)
# Leer sensores en pines de entrada
rr = client.read_discrete_inputs(address=0, count=16, slave=1)C (libmodbus)
uint8_t inputs[16];
// Notar la función "input_bits"
int rc = modbus_read_input_bits(ctx, 0, 16, inputs);2. Funciones de Lectura (Word Access)
0x03: Read Holding Registers
El “navaja suiza” de Modbus. Lee registros de 16 bits de lectura/escritura. Se usa para todo: configuraciones, valores analógicos, contadores.
- Request:
03 [Addr Hi] [Addr Lo] [Qty Hi] [Qty Lo] - Response:
03 [Byte Count] [Val Hi] [Val Lo] ...
Python (pymodbus) - Manejo de Tipos
Modbus solo mueve bits. pymodbus nos ayuda a decodificarlos en tipos reales (float, int32).
# Leer 2 registros (4 bytes) para un Float 32-bit
rr = client.read_holding_registers(address=100, count=2, slave=1)
if not rr.isError():
# Decodificar float (IEEE 754)
decoder = BinaryPayloadDecoder.fromRegisters(
rr.registers,
byteorder=Endian.Big,
wordorder=Endian.Big
)
temperatura = decoder.decode_32bit_float()C (libmodbus)
uint16_t tab_reg[2];
int rc = modbus_read_registers(ctx, 100, 2, tab_reg);
if (rc != -1) {
// Convertir a float manualmente
float temp = modbus_get_float_abcd(tab_reg);
printf("Temp: %.2f\n", temp);
}0x04: Read Input Registers
Para leer datos analógicos de solo lectura (mediciones de ADC, valores de sensores calibrados). Muchos dispositivos mapean todo a Holding Registers, pero los estrictos usan Input Registers para mediciones.
Python (pymodbus)
rr = client.read_input_registers(address=0, count=1, slave=1)C (libmodbus)
uint16_t val;
modbus_read_input_registers(ctx, 0, 1, &val);3. Funciones de Escritura (Single)
0x05: Write Single Coil
Fuerza una salida a ON u OFF.
- Dato Curioso: En el protocolo,
ONse envía como0xFF00yOFFcomo0x0000. Esto previene activaciones accidentales por ruido (rara vez un byte de ruido es exactamente0xFF).
Python
client.write_coil(address=5, value=True, slave=1)C (libmodbus)
modbus_write_bit(ctx, 5, TRUE);0x06: Write Single Register
Escribe un solo registro de 16 bits.
⚠️ PELIGRO: No uses esto para escribir valores de 32 bits (como Floats o Longs). Rompe la atomicidad. Si tu proceso lee el valor entre las dos escrituras
0x06, leerá basura corrupta. Usá0x10siempre para datos > 16 bits.
Python
client.write_register(address=200, value=1234, slave=1)C (libmodbus)
modbus_write_register(ctx, 200, 1234);4. Funciones de Escritura (Multiple) - ¡Las que debés usar!
0x0F: Write Multiple Coils
Permite setear un banco entero de salidas en una sola transacción atómica. Mucho más eficiente que llamar a write_coil en bucle.
Python
estados = [True, False, True, True, False]
client.write_coils(address=0, values=estados, slave=1)C (libmodbus)
uint8_t bits[] = {1, 0, 1, 1, 0};
modbus_write_bits(ctx, 0, 5, bits);0x10: Write Multiple Registers
El estándar industrial para escribir parámetros. Permite escribir bloques enteros de configuración o valores complejos (Floats, Strings) de forma atómica.
Python
# Escribir un Float (2 registros)
builder = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Big)
builder.add_32bit_float(23.5)
payload = builder.to_registers()
client.write_registers(address=400, values=payload, slave=1)C (libmodbus)
float setpoint = 23.5;
uint16_t raw[2];
modbus_set_float_abcd(setpoint, raw);
modbus_write_registers(ctx, 400, 2, raw);0x17: Read/Write Multiple Registers
La joya oculta. Permite escribir un bloque y leer otro en una sola transacción. Ideal para sincronización de alta velocidad donde querés actualizar setpoints y recibir el estado actual sin latencia de ida y vuelta.
Python (pymodbus)
# Escribir 2 registros en addr 100, Leer 2 de addr 200
rr = client.read_write_multiple_registers(
read_address=200, read_count=2,
write_address=100, write_registers=[10, 20],
slave=1
)C (libmodbus)
uint16_t write_cols[] = {10, 20};
uint16_t read_cols[2];
modbus_write_and_read_registers(ctx, 100, 2, write_cols, 200, 2, read_cols);Códigos de Excepción (Modbus Exceptions)
Cuando algo falla, el esclavo no se queda callado (ni debería). Responde con un código de error. Si tu código no maneja esto, estás programando a ciegas.
| Código | Nombre | Significado Real |
|---|---|---|
| 01 | Illegal Function | Le pediste algo que no sabe hacer (ej: escribir en un Input Register). |
| 02 | Illegal Address | La dirección no existe en el mapa de memoria del dispositivo. |
| 03 | Illegal Data Value | El valor está fuera de rango o la cantidad de registros pedida es inválida. |
| 04 | Server Failure | El esclavo falló internamente (hardware error, etc.). |
| 06 | Server Busy | ”Bancá un toque, estoy ocupado”. Reintentá luego. |
Manejo en Python
from pymodbus.pdu import ExceptionResponse
rr = client.read_holding_registers(0, 10, slave=1)
if rr.isError():
if isinstance(rr, ExceptionResponse):
print(f"Modbus Exception: {rr.exception_code}")
else:
print("Error de comunicación (Timeout/Connection)")Resumen y Mejores Prácticas
- Usá siempre 0x10 (Write Multiple) para valores numéricos complejos (Floats, Int32) para garantizar atomicidad.
- Manejá las excepciones, no solo los timeouts. Un
Illegal Addressrequiere corrección de código, unTimeoutrequiere reintento. - Agrupá lecturas. Es mejor leer 100 registros continuos de una vez (Function 0x03) que hacer 100 peticiones de 1 registro. Modbus es lento por latencia, no por ancho de banda.
- Ojo con el Endianness. Si leés valores “raros” pero consistentes, probablemente tenés los bytes o las palabras invertidas (
ABCDvsCDAB).
Dominar estos códigos te diferencia de un programador que “toca de oído” a un integrador que entiende lo que pasa por el cable.



