Asesoría Agenda una llamada de descubrimiento técnico hoy mismo »

· Eduardo Vieira · Protocolos Industriales  · 3 min de lectura

Códigos de Función Modbus: Guía Técnica con Ejemplos en C y Python

Domina los códigos 0x01, 0x03, 0x06 y 0x10. Ejemplos prácticos de integración con Libmodbus (C) y PyModbus (Python) para sistemas embebidos e industriales.

Domina los códigos 0x01, 0x03, 0x06 y 0x10. Ejemplos prácticos de integración con Libmodbus (C) y PyModbus (Python) para sistemas embebidos e industriales.

Los códigos de función Modbus son el “verbo” en la oración de comunicación industrial. Definen exactamente qué acción debe realizar el esclavo (servidor). En esta guía, no solo veremos qué hacen, sino cómo implementarlos en código real de producción, tanto para Sistemas Embebidos (C) como para Scripting/SCADA (Python).

La Estructura del Byte de Función

El código de función es el segundo byte de la trama Modbus (después de la dirección).

  • Rango: 1 a 127 (0x01 - 0x7F).
  • Excepción: Si el esclavo responde con un error, enciende el bit más significativo (MSB) del código de función (Ej: 0x03 se convierte en 0x83).

1. Lectura de Datos (Read)

0x01: Read Coils (Lectura de Bobinas)

  • Tipo de dato: Bit (R/W)
  • Uso: Leer estado de salidas digitales, relés, habilitaciones.
  • Detalle Técnico: Los bits se empaquetan en bytes en la respuesta. Si pides 10 coils, recibes 2 bytes.

Implementación en C (Libmodbus)

uint8_t bits[10];
int rc = modbus_read_bits(ctx, 0, 10, bits);
if (rc == -1) {
    fprintf(stderr, "Error de lectura: %s\n", modbus_strerror(errno));
} else {
    printf("Coil 0: %d\n", bits[0]);
}

0x03: Read Holding Registers (Lectura de Registros de Retención)

  • Tipo de dato: Word 16-bit (R/W)
  • Uso: La “navaja suiza”. Se usa para leer setpoints, configuraciones y valores analógicos.
  • Uso: Para leer flotantes (32-bit), se solicitan 2 registros consecutivos.

Implementación en Python (PyModbus)

# Leer 2 registros empezando en 40100 (Address 99)
rr = client.read_holding_registers(address=99, count=2, slave=1)
if not rr.isError():
    # Convertir a float (considerando endianness)
    decoder = BinaryPayloadDecoder.fromRegisters(rr.registers, byteorder=Endian.Big, wordorder=Endian.Big)
    temp_celsius = decoder.decode_32bit_float()

2. Escritura de Datos (Write)

0x05: Write Single Coil (Escribir Bobina Única)

  • Uso: Forzar una salida digital (ON/OFF).
  • Curiosidad: En el protocolo crudo, ON es 0xFF00 y OFF es 0x0000. Cualquier otro valor es ilegal, aunque algunas implementaciones laxas toleran 0x0001 como ON.

Implementación Embebida (Zephyr RTOS - Modbus Client)

/* Escribir TRUE a la bobina 10 */
uint16_t coil_val = 0xFF00; // Constante para ON
int err = modbus_write_coil(client_iface, 1, 10, &coil_val);

0x06: Write Single Register (Escribir Registro Único)

  • Uso: Cambiar un parámetro simple de configuración (ej: ID de nodo, Baudrate).
  • Limitación: Solo escribe 16 bits. NO USAR para escribir floats de 32 bits (causa atomicidad rota: escribes la mitad del float, el proceso lee basura, luego escribes la otra mitad).

0x10: Write Multiple Registers (Escribir Registros Múltiples)

  • Uso: La forma segura de escribir datos complejos (Floats, Strings, Long Ints).
  • Ventaja: La escritura es atómica dentro del dispositivo (generalmente).

Implementación Robusta en C

uint16_t tab_reg[2];
float setpoint = 23.5f;

// Serializar float a registros (Big Endian)
modbus_set_float_abcd(setpoint, tab_reg);

// Escribir atómicamente
modbus_write_registers(ctx, 4001, 2, tab_reg);

3. Diagnóstico y Control

0x08: Diagnostics (Diagnóstico Serial)

Rara vez usado hoy en día, pero vital para debug de RS485 puro. El sub-código 0x0000 (Return Query Data) actúa como un Ping: el esclavo devuelve exactamente lo que recibió. Útil para verificar integridad del cableado.


Tabla de Referencia Rápida

CódigoNombreTipo de AccesoÁrea de Memoria
01Read CoilsLectura0xxxx (Salidas)
02Read Discrete InputsLectura1xxxx (Entradas)
03Read Holding RegistersLectura4xxxx (R/W)
04Read Input RegistersLectura3xxxx (Solo Lectura)
05Write Single CoilEscritura (1 bit)0xxxx
06Write Single RegisterEscritura (1 word)4xxxx
15Write Multiple CoilsEscritura (N bits)0xxxx
16Write Multiple RegistersEscritura (N words)4xxxx

Conclusión Industrial

En planta, la fiabilidad no es negociable.

  1. Usa siempre 0x10 en lugar de 0x06 si hay la mínima posibilidad de que el dato crezca a 32 bits en el futuro.
  2. Maneja las excepciones Modbus (0x83, 0x84, etc.) en tu código. Un timeout no es lo mismo que un “Illegal Data Address”.
  3. Valida la atomicidad.

Para implementaciones críticas en tiempo real, revisa mis guías sobre optimización de latencia en RS485.

Volver al blog

Related Posts

View All Posts »