· Eduardo Vieira · Protocolos Industriales · 16 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.

Alcance, evidencia y entorno
Este artículo explica la semántica de solicitudes y respuestas de la capa de aplicación Modbus: clases de objetos, códigos de función, disposición de bytes, validación y escritura controlada. No certifica el mapa de un PLC, un cable serie, una pasarela, una red ni un procedimiento de producción. Las tramas de ejemplo se ejecutaron el 2026-07-21 con CPython 3.12.3 y solo la biblioteca estándar; no se abrió un socket, puerto serie ni conexión a un controlador, ni se usaron credenciales o clientes de terceros. Por tanto son BENCH-ONLY—NOT VALIDATED HERE. La consulta Context7 devolvió Monthly quota exceeded, por lo que las especificaciones oficiales de Modbus y la documentación oficial de PyModbus y libmodbus son la alternativa de fuente. [M-A-F-005] [M-A-F-006]
Cuando una trama RTU necesita dirección de unidad, los ejemplos usan 0x11. Una PDU comienza con un byte de código de función. La ADU RTU antepone la dirección de unidad y añade CRC de dos bytes; la ADU TCP añade la cabecera MBAP, explicada en la guía de Modbus RTU y TCP. Si faltan bytes, el CRC falla o las respuestas son intermitentes, corresponde investigar la ruta física con la guía de diagnóstico RS-485, no convertir un problema de cableado en una supuesta falla del analizador de códigos de función.
El modelo de datos no es una dirección en la trama
Modbus define cuatro clases lógicas: las bobinas son bits de lectura y escritura; cada entrada discreta es un bit de solo lectura; cada registro de entrada es un registro de 16 bits de solo lectura; y los registros de retención son registros de 16 bits de lectura y escritura. Los manuales suelen usar referencias 0xxxx, 1xxxx, 3xxxx y 4xxxx. Esos prefijos orientan a personas; no viajan en la PDU. Una solicitud transporta una dirección inicial de 16 bits basada en cero y una cantidad. Si el 40001 de un manual significa desplazamiento 0 o es una etiqueta de presentación para otro desplazamiento es una decisión del mapa del dispositivo que debe resolverse antes de integrar. [M-A-F-001]
Esa diferencia explica muchas excepciones de dirección ilegal. Una solicitud que parece válida puede apuntar a la tabla equivocada, aplicar dos veces una conversión de base uno o cruzar el final del rango definido por el proveedor. En el mapa de integración conviene conservar juntos la notación del documento, el desplazamiento normalizado basado en cero, la clase de objeto, el tipo de dato, la escala y la unidad de ingeniería. Una etiqueta 4xxxx tampoco demuestra por sí misma que el registro sea escribible: el equipo puede restringirlo por modo operativo, contraseña, enclavamiento de proceso o manual del fabricante.
Solicitudes de lectura: 0x01, 0x02, 0x03 y 0x04
0x01 Read Coils y 0x02 Read Discrete Inputs emplean la misma PDU de solicitud: función | dirección-alta | dirección-baja | cantidad-alta | cantidad-baja. La cantidad permitida va de 1 a 2.000. La respuesta normal es función | recuento-de-bytes | datos-empaquetados..., donde el recuento es ceil(cantidad / 8). El primer bit solicitado ocupa el bit 0, el menos significativo del primer byte de datos; no ocupa el bit más significativo. La respuesta 01 02 4D 01 para diez bobinas representa como activados los bits 0, 2, 3, 6 y 8. Los bits altos sin usar del último byte son relleno, no objetos adicionales. [M-A-F-002]
0x03 Read Holding Registers y 0x04 Read Input Registers conservan la disposición de solicitud, pero piden de 1 a 125 registros. La respuesta normal es función | recuento-de-bytes | datos-de-registros...; el recuento debe ser exactamente el doble de la cantidad solicitada. Cada registro individual de 16 bits se transmite con byte alto primero. Esta PDU solicita dos registros de retención desde el desplazamiento 100:
03 00 64 00 02La respuesta normal 03 04 00 2A 00 0B contiene cuatro bytes de datos y decodifica las palabras crudas 42 y 11. No determina cómo dos registros forman un entero de 32 bits, un número IEEE-754, una marca temporal o un valor con signo. Modbus fija que el byte alto preceda al byte bajo dentro de cada registro de 16 bits, pero no normaliza el orden de palabras de un valor de varios registros. Primero se guardan las palabras crudas; después se aplica el orden de palabras, signo, escala y unidad indicados por el proveedor. Intercambiar bytes porque un valor «parece raro» puede producir una medición verosímil pero falsa. [M-A-F-001]
Comprobaciones de respuesta antes de decodificar
Un analizador de lectura necesita el contexto de la solicitud. Debe comprobar que la función recibida coincide con la solicitada, detectar una excepción antes de interpretar el recuento y exigir que llegue toda la carga declarada. Para lectura de bits, el recuento esperado es (cantidad + 7) // 8; para registros es cantidad * 2. La respuesta 03 03 00 2A 00 a la solicitud de dos registros es malformada: declara tres bytes de datos cuando se esperaban cuatro. Debe rechazarse; no debe rellenarse con ceros, truncarse ni decodificarse como un registro desplazado.
No es una preferencia de estilo defensivo. Un analizador permisivo puede convertir una respuesta dañada en una medida que llegue a un tablero o cálculo de control. El recibo debe guardar la ADU cruda, contexto de transporte, desplazamiento y cantidad solicitados, y la decisión del analizador. Un tiempo de espera significa que no llegó una respuesta completa; una excepción es una respuesta completa del protocolo; una respuesta malformada no es ni una lectura satisfactoria ni una excepción confiable. Separar esos casos permite que reintentos y diagnósticos conserven evidencia.
Escrituras de un objeto: 0x05 y 0x06
0x05 Write Single Coil usa 05 | dirección-alta | dirección-baja | valor-alto | valor-bajo. Sus únicos valores definidos son FF 00 para activar y 00 00 para desactivar. La respuesta normal repite la PDU de solicitud completa. Cualquier otro valor de dos bytes para bobina es un valor de datos inválido y debe rechazarse localmente antes del transporte. 0x06 Write Single Register tiene la misma forma, con un valor de registro de 16 bits entre 0000 y FFFF; su respuesta normal también repite dirección y valor. [M-A-F-002]
ADVERTENCIA: una trama correcta no autoriza a operar un equipo. Nunca se usan 0x05 ni 0x06 como prueba de conectividad y nunca se envían a producción porque una fixture local haya pasado. Antes de una escritura autorizada se documentan propietario del activo, ventana de mantenimiento, desplazamiento normalizado exacto, intervalo de valores permitido, valor actual, lectura de confirmación prevista, condición de aborto y valor de reversión. También se confirma que el registro no sea un flanco de mando, reinicio, cambio de modo, variable de seguridad ni mitad de un objeto multirregistro. Una escritura sintácticamente válida puede iniciar movimiento, borrar historial, modificar una consigna o dejar un proceso en estado parcial inseguro. [M-A-F-004]
Solo como simulación controlada, 05 00 13 FF 00 solicita activar la bobina de desplazamiento 19 y el recibo normal tiene la misma PDU de cinco bytes. 06 00 64 00 2A solicita que el registro de retención 100 tome el valor 42 y su recibo normal vuelve a ser idéntico. El eco únicamente demuestra que un servidor devolvió la forma normal de escritura; un procedimiento real exige lectura posterior autorizada y un criterio de aceptación del proceso.
Escritura múltiple: 0x0F y 0x10
0x0F Write Multiple Coils añade recuento de bytes y valores de bobinas empaquetados: 0F | dirección inicial | cantidad | recuento-de-bytes | valores empaquetados. Su cantidad va de 1 a 1.968 y el recuento debe ser exactamente ceil(cantidad / 8). Usa el mismo empaquetado de bit menos significativo primero que la lectura de bobinas. Para diez bobinas desde el desplazamiento 19, 0F 00 13 00 0A 02 4D 01 declara dos bytes y puede transportar el mismo patrón de bits del ejemplo de lectura. La respuesta normal es más corta: 0F 00 13 00 0A; solo repite dirección inicial y cantidad. Como no repite los datos empaquetados, la PDU saliente debe quedar en el recibo de cambio. [M-A-F-002]
0x10 Write Multiple Registers usa 10 | dirección inicial | cantidad | recuento-de-bytes | datos-de-registros. Su cantidad va de 1 a 123 y el recuento de bytes debe ser cantidad * 2. Una escritura de dos registros desde el desplazamiento 100 usa 10 00 64 00 02 04 00 2A 00 0B; la respuesta normal es 10 00 64 00 02. Se valida el recuento declarado y la longitud real de PDU antes de cualquier transporte. No se sustituye un valor multirregistro definido por el proveedor por dos escrituras 0x06: un observador podría ver el estado intermedio y algunos equipos aplican la actualización solo al recibir la transacción múltiple documentada.
Límites de código de función y capacidad efectiva
La especificación de aplicación fija máximos de protocolo, pero un servidor puede admitir menos, omitir una función o imponer un rango contiguo menor. El límite práctico es la intersección entre máximo de especificación, manual del proveedor, unidad configurada y operación autorizada. 0x01 y 0x02 permiten hasta 2.000 bits; 0x03 y 0x04, hasta 125 registros; 0x0F, hasta 1.968 bobinas; y 0x10, hasta 123 registros. El cliente debe rechazar cantidad cero y una suma dirección más cantidad que supere el espacio de direcciones de 16 bits antes de transmitir bytes. [M-A-F-002]
Leer y escribir son decisiones distintas. Una lectura puede cargar un extremo frágil o revelar datos de proceso, por lo que los escaneos amplios y la enumeración de unidades requieren autorización. Una escritura cambia estado y necesita controles más fuertes: acceso de red de mínimo privilegio, límites de mantenimiento aislados, lista permitida de combinaciones unidad/función/dirección/valor, límites de tasa, registro de cambio aprobado por operador, lectura posterior cuando sea segura y plan explícito de reversión. Modbus no proporciona autenticación de usuario ni cifrado de mensajes. Debe segmentarse de redes no confiables y restringirse toda pasarela; no se expone un extremo Modbus directamente a Internet. [M-A-F-004]
Lectura y escritura múltiple de registros: 0x17
0x17 Read/Write Multiple Registers reúne en una solicitud un rango de lectura y uno de escritura de registros de retención. La solicitud lleva inicio y cantidad de lectura, inicio y cantidad de escritura, recuento de bytes y datos de escritura; la respuesta normal lleva solo recuento de lectura y registros devueltos. La fixture usa 17 00 64 00 02 00 C8 00 02 04 00 2A 00 0B, valida su respuesta de dos registros y no presenta esa comprobación de bytes como transacción de servidor. Un servidor puede no admitir 0x17, y admitirlo no autoriza su parte de escritura. [M-A-F-002]
Las formas de API siguientes son ejemplos de documentación no conectados, no código de conexión ejecutable. PyModbus 3.11.0 documenta client.readwrite_registers(read_address=100, read_count=2, write_address=200, values=[42, 11]); libmodbus 3.2.0 documenta modbus_write_and_read_registers(ctx, 200, 2, src, 100, 2, dest), que escribe dos registros fuente en la dirección 200 y lee dos registros desde la dirección 100 hacia dest. El estado devuelto, la excepción o el código de error forman parte de la operación: no se decodifican registros devueltos ni se supone que hubo escritura hasta que la biblioteca informa éxito. Se confirman límites documentados de 0x17, mapa de direcciones y semántica de atomicidad del servidor, sin proyectar esta trama ilustrativa de dos registros sobre otro equipo. Se usan únicamente contra un servidor explícitamente admitido y autorizado, comprobando errores, con mantenimiento aprobado y valor de reversión. Ningún fragmento abre una conexión aquí ni valida comportamiento del dispositivo. [M-A-F-005] [M-A-F-006]
Respuestas normales y respuestas de excepción
En una respuesta normal el byte de función es el solicitado. En una respuesta de excepción se activa el bit 7 y sigue exactamente un código de excepción. Por ejemplo, 83 02 indica que una solicitud 0x03 recibió 0x02 Illegal Data Address; la función original se obtiene con 0x83 & 0x7F = 0x03. Los códigos más útiles para diagnóstico son 01 Illegal Function, 02 Illegal Data Address, 03 Illegal Data Value y 04 Server Device Failure. [M-A-F-003]
Una excepción es evidencia, no invitación a reintentar sin límite. El código 01 apunta a capacidad no admitida; el 02 a tabla, desplazamiento o rango; el 03 a cantidad, codificación o valor inaceptable; y el 04 a una falla de servidor cuyo diagnóstico depende del proveedor. La política de reintentos debe ser acotada y específica para transporte y operación. Repetir una escritura rechazada puede multiplicar su impacto operacional, y etiquetar una excepción como tiempo de espera elimina la mejor pista de diagnóstico del intercambio.
Vectores deterministas y recibo de validación
El siguiente comando exacto de biblioteca estándar valida localmente los nueve vectores enfocados. No realiza importación de terceros ni acceso a archivo, red, dispositivo, subproceso o credencial.
python3 - <<'PY'
def crc16(data):
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8): crc = (crc >> 1) ^ (0xA001 if crc & 1 else 0)
return crc
def read(fc, request, response, bits=False):
req, rsp = bytes.fromhex(request), bytes.fromhex(response)
qty = int.from_bytes(req[3:5], 'big')
expected = (qty + 7) // 8 if bits else qty * 2
assert req[0] == rsp[0] == fc and rsp[1] == expected == len(rsp[2:])
return rsp
read(1, '01 00 13 00 0A', '01 02 4D 01', True)
assert read(2, '02 00 13 00 0A', '02 02 4D 01', True)[2:] == bytes.fromhex('4D 01')
for fc in (3, 4):
rsp = read(fc, f'{fc:02X} 00 64 00 02', f'{fc:02X} 04 00 2A 00 0B')
assert [int.from_bytes(rsp[i:i+2], 'big') for i in (2, 4)] == [42, 11]
for fc, frame in ((5, '05 00 13 FF 00'), (6, '06 00 64 00 2A')):
req, rsp = bytes.fromhex(frame), bytes.fromhex(frame)
assert req[0] == fc and rsp == req
w15, r15 = bytes.fromhex('0F 00 13 00 0A 02 4D 01'), bytes.fromhex('0F 00 13 00 0A')
q15 = int.from_bytes(w15[3:5], 'big')
assert w15[5] == (q15 + 7)//8 == len(w15[6:]) and r15 == w15[:5]
w16, r16 = bytes.fromhex('10 00 64 00 02 04 00 2A 00 0B'), bytes.fromhex('10 00 64 00 02')
q16 = int.from_bytes(w16[3:5], 'big')
assert w16[5] == q16*2 == len(w16[6:]) and r16 == w16[:5]
w17 = bytes.fromhex('17 00 64 00 02 00 C8 00 02 04 00 2A 00 0B')
r17 = bytes.fromhex('17 04 00 2A 00 0B')
rq, wq = int.from_bytes(w17[3:5], 'big'), int.from_bytes(w17[7:9], 'big')
assert w17[0] == r17[0] == 0x17 and w17[9] == wq*2 == len(w17[10:])
assert r17[1] == rq*2 == len(r17[2:])
assert [int.from_bytes(r17[i:i+2], 'big') for i in (2, 4)] == [42, 11]
exc, malformed = bytes.fromhex('83 02'), bytes.fromhex('03 03 00 2A 00')
assert exc[0] & 0x80 and (exc[0] & 0x7F, exc[1]) == (3, 2)
assert malformed[1] != 4 or len(malformed[2:]) != malformed[1]
raw = bytes.fromhex('11 03 00 64 00 02')
good = raw + crc16(raw).to_bytes(2, 'little')
bad = good[:-1] + bytes([good[-1] ^ 1])
assert crc16(raw) == 0x4487 == int.from_bytes(good[-2:], 'little')
assert crc16(bad[:-2]) != int.from_bytes(bad[-2:], 'little')
valid = lambda start, qty, limit: 1 <= qty <= limit and start + qty <= 0x10000
assert valid(0, 125, 125) and not valid(0, 0, 125)
assert not valid(0, 126, 125) and not valid(0xFFFF, 2, 125)
print('V-A-F-001 PASS: FC=01,02,03,04,05,06,0F,10,17; failures=exception,malformed-length,bad-CRC,invalid-range; CRC=0x4487')
PYResultado esperado y observado en CPython 3.12.3, 2026-07-21: salida 0 y la línea literal impresa arriba. Esa línea deriva de las aserciones: 0x01/0x02 comprueban bits empaquetados; 0x03/0x04 decodifican [42, 11]; 0x05/0x06 comprueban ecos; 0x0F/0x10 comprueban formas de solicitud y respuesta; y 0x17 comprueba ambos rangos, recuento de escritura y palabras devueltas. Las aserciones de excepción, longitud malformada, CRC alterado, cantidad cero/sobre el límite y desborde de dirección rechazan los casos inválidos. Este recibo prueba solo aritmética de codificador y analizador, no respuesta de servidor, interoperabilidad ni escritura segura en dispositivo. [V-A-F-001]
Diagnóstico por clase de fallo
Se comienza por la capa más pequeña que pueda explicar la evidencia. Si falla un CRC RTU, se conserva la trama cruda y se investiga la integridad de captura, el encuadre y la ruta RS-485; no se cambia primero el mapa de direcciones. Si una respuesta TCP tiene identificador de transacción o longitud MBAP incorrectos, se descarta como respuesta a la solicitud actual y se revisa multiplexación de conexión o comportamiento de pasarela en la guía de protocolo. Si la función es correcta pero el recuento de bytes no lo es, se rechaza la carga y se capturan solicitud y respuesta.
Para 83 02, se verifica tabla de objetos y desplazamiento normalizado contra el mapa del proveedor antes de cambiar código. Para 83 03, se revisan cantidad, codificación de escritura, recuento de bytes y valor permitido. Ante un eco 0x05 o 0x06, o respuesta normal 0x0F o 0x10, se comprueba que dirección y cantidad coincidan con la solicitud autorizada y solo entonces se hace la lectura posterior aprobada. Un escaneo amplio o escrituras de ensayo para compensar un mapa incierto crean cambios de estado nuevos y destruyen la evidencia del error original.
Limitaciones, navegación y referencias
Las fixtures cubren únicamente las nueve funciones enfocadas 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x0F, 0x10 y 0x17; quedan fuera funciones opcionales, extensiones de proveedor, diagnósticos y registros de archivo. 0x17 no sustituye autorización, confirmación de capacidad ni contrato de transacción del proveedor. Este artículo no afirma éxito con PLC, variador, pasarela, cable ni proceso reales.
Para encuadre ADU, identificadores de unidad, transacciones y diferencias RTU/TCP, consultá Modbus RTU y TCP. Para terminación, polarización, aislamiento, seguridad de medición y síntomas de ruido, consultá diagnóstico RS-485. Advertencia de seguridad: solo personal cualificado, con procedimientos del fabricante, requisitos LOTO/EPP y plan de mantenimiento aprobado, puede realizar trabajo eléctrico o escrituras de dispositivo autorizadas. Este artículo no implica escaneo, descubrimiento ni escritura en producción.
Identificadores de reclamación y fuentes
- M-A-F-001 — Modelo de datos, campos de dirección PDU basados en cero y orden de bytes de registros de 16 bits. Modbus Organization, Modbus Application Protocol Specification V1.1b3, secciones 4.3–4.5 y 6.1–6.4, consultado 2026-07-21: https://www.modbus.org/file/secure/modbusprotocolspecification.pdf
- M-A-F-002 — Disposiciones de solicitud/respuesta, empaquetado de bits y máximos de cantidad para 0x01/02/03/04/05/06/0F/10/17. Modbus Organization, Modbus Application Protocol Specification V1.1b3, secciones 6.1–6.12 y 6.17, consultado 2026-07-21: https://www.modbus.org/file/secure/modbusprotocolspecification.pdf
- M-A-F-003 — Forma de respuesta de excepción y códigos de excepción. Modbus Organization, Modbus Application Protocol Specification V1.1b3, sección 7, consultado 2026-07-21: https://www.modbus.org/file/secure/modbusprotocolspecification.pdf
- M-A-F-004 — Límites de seguridad de Modbus y orientación de despliegue protegido. Modbus Organization, MODBUS/TCP Security Protocol Specification V3.6, 2021-07-30, consultado 2026-07-21: https://www.modbus.org/file/secure/modbussecurityprotocol.pdf
- M-A-F-005 — API
readwrite_registers(...)de PyModbus 3.11.0 para uso documentado de 0x17. Documentación oficial versionada de PyModbus, consultada 2026-07-21 tras agotar cuota de Context7: https://pymodbus.readthedocs.io/en/v3.11.0/source/client.html - M-A-F-006 — API
modbus_write_and_read_registersde libmodbus 3.2.0 para uso documentado de 0x17. Referencia oficial de libmodbus y versión estable, consultadas 2026-07-21: https://libmodbus.org/reference/modbus_write_and_read_registers y https://github.com/stephane/libmodbus/releases/tag/v3.2.0
Recibo de validación: V-A-F-001. Last verified: 2026-07-21.



