← Índice del curso

Comunicación entre Dispositivos · Tema 25 · Modbus

Modbus RTU

RS-485 resuelve cómo viajan los bits por un par de hilos compartido, pero no dice qué significan ni quién habla cuándo. Modbus es la respuesta más difundida de la industria: un protocolo sencillo de maestro y esclavos, nacido en 1979, que hoy hablan variadores de frecuencia, medidores de energía, sensores, PLC, inversores solares y miles de dispositivos más. En este tema vemos su modelo de datos (cuatro tablas de bobinas y registros), la trama RTU con sus silencios y su CRC, las funciones de lectura y escritura, las respuestas de excepción, las direcciones de esclavo y la difusión, el costo en tiempo de cada consulta y cómo se interpretan los valores que devuelven los registros.

01 · Capa de aplicación

Un protocolo de preguntas y respuestas

Hasta ahora estudiamos cómo se mueven bytes: UART, SPI, I²C, 1-Wire, RS-232, RS-485. Modbus está un nivel más arriba: define qué significan esos bytes. Lo creó Modicon (hoy parte de Schneider Electric) para comunicar sus PLC con otros equipos, lo publicó sin restricciones de licencia, y por eso se volvió un estándar de hecho. Sus rasgos:

  • Maestro y esclavos. Un único maestro (en la norma moderna, cliente) inicia toda la comunicación; los esclavos (servidores) solo responden. Dos esclavos nunca hablan a la vez, y no pueden hablar entre sí. Eso resuelve de raíz el problema de la contención de RS-485 (tema 24).
  • Un modelo de datos muy simple: cada esclavo expone cuatro tablas numeradas de bits y de registros de 16 bits. El maestro lee y escribe posiciones de esas tablas; no hay tipos complejos, nombres ni estructuras.
  • Tramas cortas y binarias (variante RTU), con un CRC para detectar errores, o legibles como texto (variante ASCII, casi sin uso). El mismo contenido viaja sobre TCP/IP (Modbus TCP, tema 31 en adelante) sin el CRC.
  • Se monta sobre una capa física serie, casi siempre RS-485 de dos hilos (a veces RS-232 o RS-422). La UART es de 8 bits de datos, con la configuración que acuerden maestro y esclavos.

Su simplicidad es su virtud y su límite: es fácil de implementar en un microcontrolador y de depurar con un analizador, pero no tiene eventos ni notificaciones (el maestro debe preguntar cada dato), ni autenticación, ni descubrimiento de dispositivos. Para un tablero de medición o una red de sensores de campo, resulta justo lo que hace falta.

02 · Qué hay en un esclavo

El modelo de datos: cuatro tablas

Las cuatro tablas de datos de Modbus Bobinas: bits de lectura y escritura. Entradas discretas: bits de solo lectura. Registros de entrada: registros de 16 bits de solo lectura. Registros de retención: registros de 16 bits de lectura y escritura. Cada tabla tiene su propio espacio de direcciones. Bobinas coils lectura y escritura numeración 0xxxx direcciones 0 a 65535 Entradas discretas discrete inputs solo lectura numeración 1xxxx direcciones 0 a 65535 Registros de entrada input registers 16 bits16 bits16 bits solo lectura numeración 3xxxx direcciones 0 a 65535 Registros de retención holding registers 16 bits16 bits16 bits lectura y escritura numeración 4xxxx direcciones 0 a 65535 Cuatro espacios de direcciones independientes: la dirección 0 de cada tabla es un dato distinto.

Un esclavo Modbus no es más que un conjunto de cuatro tablas. Cada tabla tiene un tipo de elemento y un tipo de acceso:

Las cuatro tablas de datos de Modbus.
TablaElementoAccesoNumeración clásicaQué suele contener
Bobinas (coils)1 bitLectura y escritura0xxxx (00001 a 09999)Salidas digitales, relés, marcas de control
Entradas discretas (discrete inputs)1 bitSolo lectura1xxxx (10001 a 19999)Entradas digitales, estados, alarmas
Registros de entrada (input registers)16 bitsSolo lectura3xxxx (30001 a 39999)Mediciones: temperatura, tensión, corriente
Registros de retención (holding registers)16 bitsLectura y escritura4xxxx (40001 a 49999)Parámetros, consignas, configuración (y, a menudo, también mediciones)

Cada tabla tiene su propio espacio de direcciones: la dirección 0 de los registros de entrada y la dirección 0 de los de retención son datos distintos. El protocolo usa direcciones de 16 bits (0 a 65535), contando desde cero.

La numeración «clásica» de las columnas (40001, 30001…) viene de los PLC de Modicon: la primera cifra identifica la tabla, y las otras cuatro (o cinco) el número del dato empezando en 1. Es la fuente de un error constante: el registro «40001» de un manual es la dirección 0 en la trama; el «40108» es la dirección 107. Algunos fabricantes ya dan la dirección del protocolo (empezando en 0), otros la numeración de 1 (empezando en 1), y otros usan hexadecimal. Cuando una lectura devuelve el valor del vecino, casi siempre es un desfase de uno.

Hay dos tablas «de 16 bits» que se parecen mucho. La diferencia entre registros de entrada y de retención es de acceso (solo lectura contra lectura y escritura), pero en la práctica muchos dispositivos publican todo en los de retención, y otros usan los de entrada para las mediciones. El manual del equipo manda: dice qué tabla, qué dirección, qué tipo y qué unidad tiene cada dato.

03 · Bytes y silencios

La trama RTU

Trama Modbus RTU Una trama RTU empieza con un silencio de al menos 3,5 tiempos de carácter, sigue con un byte de dirección, un byte de función, de cero a 252 bytes de datos y dos bytes de CRC, con el byte bajo primero, y termina con otro silencio. Abajo, el ejemplo de una lectura de diez registros del esclavo 1: 01 03 00 00 00 0A C5 CD. Silencio ≥ 3,5 caracteres Dirección 1 byte Función 1 byte Datos 0 a 252 bytes CRC-16 2 bytes, bajo primero Silencio ≥ 3,5 caracteres Ejemplo: leer 10 registros de retención desde la dirección 0 del esclavo 1 01 dirección 03 función 00 inicio alto 00 inicio bajo 00 cantidad alta 0A cantidad baja C5 CRC bajo CD CRC alto el CRC se calcula sobre estos 6 bytes se envía 0xC5 y después 0xCD (CRC = 0xCDC5)

RTU significa Remote Terminal Unit y es la variante binaria. Una trama tiene cuatro campos:

Campos de una trama Modbus RTU.
CampoTamañoContenido
Dirección1 byte0 = difusión (a todos); 1 a 247 = esclavo; 248 a 255 reservadas
Función1 byteQué operación pide el maestro (01 a 127); en una respuesta de error, el mismo valor con el bit 7 en 1
Datos0 a 252 bytesDependen de la función: direcciones, cantidades, valores
CRC2 bytesCRC-16 de todos los bytes anteriores, byte bajo primero

La trama completa mide como máximo 256 bytes. Dentro de ella, todo lo que ocupa más de un byte (direcciones, cantidades, valores de registros) se envía con el byte alto primero (big-endian); la única excepción famosa es el CRC, que va con el byte bajo primero. Es una incoherencia histórica que hay que tener presente al armar y verificar tramas a mano.

Modbus RTU no tiene un carácter de comienzo ni de fin de trama. Los límites los da el tiempo:

  • Entre dos bytes de una misma trama no puede haber un silencio mayor a 1,5 tiempos de carácter (t1,5). Si pasa, el receptor descarta la trama incompleta.
  • Una trama se da por terminada cuando hay un silencio de al menos 3,5 tiempos de carácter (t3,5) en la línea; también se exige ese silencio antes de empezar la siguiente.
  • Un carácter son 11 bits: 1 de inicio, 8 de datos, 1 de paridad y 1 de parada (o 2 de parada sin paridad). La norma recomienda paridad par (8E1) por defecto, y admite 8N2; los equipos reales suelen aceptar también 8N1, que son 10 bits.
  • A velocidades altas (más de 19 200 baudios) los tiempos se fijan en 750 µs para t1,5 y 1750 µs para t3,5, porque tiempos tan chicos son impracticables de medir en un sistema operativo.
Tiempos de carácter y silencios (11 bits por carácter).
VelocidadUn caráctert1,5t3,5
1200 baudios9,17 ms13,75 ms32,08 ms
9600 baudios1,146 ms1,72 ms4,01 ms
19 200 baudios0,573 ms0,86 ms2,01 ms
38 400 baudios o más0,286 ms o menos0,75 ms (fijo)1,75 ms (fijo)

La consecuencia práctica para quien programa: no se puede detectar el fin de una trama por la cantidad de bytes ni por un terminador, hay que medir silencio. Un receptor típico lee bytes hasta que pasan t3,5 sin que llegue ninguno. En un sistema con una tarea que pueda ser interrumpida por más de t1,5 en medio de una trama, hay riesgo de cortar la trama en dos. Y en el emisor, todos los bytes de la trama deben salir de corrido, sin pausas.

04 · Qué se puede pedir

Funciones de lectura y escritura

El byte de función dice qué operación pide el maestro. Las más usadas:

Funciones Modbus de uso común.
CódigoNombreTablaLímite por trama
0x01Leer bobinasBobinas2000 bits
0x02Leer entradas discretasEntradas discretas2000 bits
0x03Leer registros de retenciónRegistros de retención125 registros
0x04Leer registros de entradaRegistros de entrada125 registros
0x05Escribir una bobinaBobinas1 bit (0xFF00 = activar, 0x0000 = desactivar)
0x06Escribir un registroRegistros de retención1 registro
0x0F (15)Escribir varias bobinasBobinas1968 bits
0x10 (16)Escribir varios registrosRegistros de retención123 registros

El formato de las más importantes, con ejemplos reales (los bytes con CRC calculado; el CRC va al final, byte bajo primero):

Leer registros de retención (0x03) y de entrada (0x04)

Petición: dirección, función, dirección inicial (2 bytes), cantidad de registros (2 bytes), CRC.

Respuesta: dirección, función, cantidad de bytes de datos (1 byte, igual a 2 × cantidad), los datos (cada registro en 2 bytes, byte alto primero), CRC.

Leer 2 registros desde la dirección 0 del esclavo 1.
SentidoTramaCómo se lee
Maestro → esclavo01 03 00 00 00 02 C4 0Besclavo 1, función 3, inicio 0, cantidad 2
Esclavo → maestro01 03 04 00 FA 00 7D 1A 23esclavo 1, función 3, 4 bytes de datos: registro 0 = 0x00FA = 250, registro 1 = 0x007D = 125

Escribir un registro (0x06)

La petición lleva la dirección del registro y el valor. La respuesta es un eco exacto de la petición: así el maestro confirma lo que el esclavo escribió. Ejemplo: escribir 3 en el registro 1 del esclavo 1: 01 06 00 01 00 03 98 0B (y vuelve la misma trama).

Escribir varios registros (0x10)

La petición lleva la dirección inicial, la cantidad, la cantidad de bytes y los valores. La respuesta confirma la dirección inicial y la cantidad (no repite los datos). Ejemplo: escribir 10 y 258 desde el registro 1: 01 10 00 01 00 02 04 00 0A 01 02 92 30.

Escribir una bobina (0x05)

Una bobina se activa con el valor FF 00 y se desactiva con 00 00; cualquier otro valor es inválido. Ejemplo: activar la bobina 19 (0x13) del esclavo 1: 01 05 00 13 FF 00 7D FF.

Un criterio de diseño: una lectura de muchos registros contiguos es mucho más eficiente que muchas lecturas de uno, porque cada consulta paga la petición, los silencios y el procesamiento del esclavo. Los fabricantes suelen agrupar los datos de uso frecuente en direcciones contiguas por esa razón.

05 · Detectar errores

El CRC-16

Cada trama termina con un CRC-16 de 16 bits calculado sobre todos los bytes anteriores (dirección, función y datos). Un esclavo que recibe una trama con CRC erróneo la descarta sin contestar; un maestro hace lo mismo con una respuesta. El CRC detecta con certeza todos los errores de uno o dos bits y las ráfagas de hasta 16 bits, y deja pasar uno de cada 65 536 errores aleatorios (tema 44 estudia los CRC en detalle).

La variante que usa Modbus tiene el polinomio x16 + x15 + x2 + 1 (0x8005, que reflejado es 0xA001), valor inicial 0xFFFF y sin inversión final. El algoritmo bit a bit:

  1. Se parte de un registro de 16 bits crc = 0xFFFF.
  2. Para cada byte de la trama: se hace XOR del byte con el byte bajo del CRC, y se repiten 8 veces: si el bit menos significativo vale 1, se desplaza a la derecha y se hace XOR con 0xA001; si vale 0, solo se desplaza.
  3. El resultado es el CRC. Se envía primero su byte bajo y después el alto.

Dos propiedades útiles para verificar el cálculo. Primera: el CRC de la cadena ASCII 123456789 es 0x4B37, el «valor de comprobación» de esta variante. Segunda: si se calcula el CRC sobre la trama completa, incluido el CRC recibido, el resultado es 0 si no hubo errores. Eso simplifica el receptor: se corre el CRC sobre todo lo recibido y se compara con cero.

Un detalle práctico: el CRC de la petición 01 03 00 00 00 0A es 0xCDC5, así que en el cable viaja C5 CD. Si en un analizador se ve CD C5, la implementación envía el CRC con los bytes cambiados.

06 · Cuando algo sale mal

Excepciones y fallas

Una consulta puede fallar de tres maneras, y el maestro tiene que distinguirlas:

  • Sin respuesta (timeout). El esclavo no existe, está apagado, tiene otra dirección o velocidad, la petición llegó dañada (CRC erróneo, y entonces el esclavo calla) o el cable está mal. El maestro espera un tiempo máximo (típicamente entre 100 ms y 1 s) y declara el fallo.
  • Respuesta dañada. Llegó pero el CRC no cierra, o el largo no coincide. Se descarta y se reintenta.
  • Respuesta de excepción. El esclavo entendió la trama (CRC correcto) pero no puede ejecutar el pedido. Contesta con la función con el bit 7 en 1 (función + 0x80) y un código de excepción de un byte. Es una respuesta válida: significa «recibí tu pedido, pero no».
Códigos de excepción de Modbus.
CódigoNombreSignificado y causa habitual
01Función no válidaEl esclavo no implementa esa función (por ejemplo, pedir 0x10 a un dispositivo de solo lectura)
02Dirección de dato no válidaLa dirección (o el rango) pedido no existe: el desfase de uno de la numeración o una tabla equivocada
03Valor de dato no válidoLa cantidad está fuera de rango (0 o más de 125) o el valor a escribir no es aceptable
04Falla del servidorError interno al ejecutar el pedido
05ReconocimientoEl pedido fue aceptado, pero tardará (el maestro no debe reenviarlo)
06Servidor ocupadoEstá procesando otro pedido largo: reintentar más tarde

Ejemplo: el maestro lee un registro de la dirección 100 de un esclavo que solo tiene 16 registros. La respuesta es 01 83 02 C0 F1: esclavo 1, función 0x03 + 0x80 = 0x83, excepción 02 (dirección no válida), y el CRC. El maestro reconoce una excepción porque el bit 7 de la función está en 1: debe revisar eso antes de intentar interpretar datos.

Tres reglas de buen maestro: reintentar ante un timeout o una respuesta dañada (dos o tres veces), pero no ante una excepción (volverá a ocurrir); verificar que la respuesta venga del esclavo y de la función pedidos; y llevar la cuenta de errores por esclavo para saber cuál falla.

07 · Quién es quién

Direcciones de esclavo y difusión

La dirección de esclavo es de 1 byte, con este reparto:

  • 0: difusión (broadcast). El maestro manda un pedido de escritura a todos los esclavos a la vez, y ninguno responde (si respondieran todos juntos habría contención). Sirve para fijar un valor común (una hora, un modo) rápidamente. El maestro debe dejar pasar un tiempo antes del siguiente pedido para que todos terminen de procesar.
  • 1 a 247: los esclavos individuales. Cada dispositivo de la red debe tener una dirección distinta.
  • 248 a 255: reservadas.

La dirección se configura de fábrica o por interruptores, un menú o un registro del propio dispositivo. Como no hay forma de descubrir qué hay en el bus (no existe un barrido nativo), en la puesta en marcha se hace por prueba: un programa que consulta las direcciones 1 a 247 con una lectura sencilla y registra cuáles contestan. Cuidado con los efectos: una escritura accidental sobre un dispositivo desconocido puede cambiar su configuración.

Al armar la red:

  • Todos los equipos con el mismo formato (velocidad, paridad, bits de parada) y direcciones únicas. Dos esclavos con la misma dirección contestan a la vez: las respuestas se pisan y llegan dañadas.
  • Un solo maestro en el bus. Con dos maestros se colisionan las peticiones; si hacen falta dos, uno debe esperar al otro.
  • Pasarelas: un convertidor Modbus TCP–RTU permite que un maestro de la red Ethernet consulte esclavos seriales, usando un campo extra (unit ID) como dirección de esclavo.

08 · El costo en milisegundos

Cuánto tarda una consulta

Una consulta maestro–esclavo El maestro envía una petición. Después de un silencio de 3,5 caracteres el esclavo la da por completa, la procesa y responde. El maestro espera la respuesta hasta un tiempo máximo. Terminada la respuesta, otro silencio de 3,5 caracteres antes de la siguiente petición. Maestro Esclavo petición (8 bytes) respuesta (5 + 2N bytes) t3,5 proceso t3,5 tiempo total de la consulta = petición + t3,5 + procesamiento + respuesta + t3,5 el maestro espera hasta el tiempo máximo

Una consulta es un intercambio de dos tramas, separadas por silencios y por el tiempo que el esclavo tarda en procesar. Para una lectura de N registros (función 0x03 o 0x04):

  • La petición mide 8 bytes (dirección, función, inicio, cantidad, CRC).
  • La respuesta mide 5 + 2 · N bytes (dirección, función, cantidad de bytes, 2 · N de datos, CRC).
  • Entre ambas, el esclavo debe esperar t3,5 para saber que la petición terminó, y procesarla (de 1 a decenas de milisegundos según el equipo).
  • Al terminar la respuesta, el maestro espera otro t3,5 antes de la próxima petición.
Tconsulta = 8 · tcar + t3,5 + tproc + (5 + 2N) · tcar + t3,5

Con 9600 baudios (tcar = 1,146 ms, t3,5 = 4,01 ms) y 5 ms de procesamiento:

Tiempo de una lectura de N registros a 9600 baudios (11 bits por carácter, 5 ms de procesamiento).
Registros leídosPeticiónRespuestaConsulta completaConsultas por segundo
19,17 ms8,02 ms30,2 ms33
109,17 ms28,65 ms50,8 ms19,7
509,17 ms120,3 ms142,5 ms7,0
1259,17 ms292,2 ms314,4 ms3,2

De ahí salen conclusiones de diseño. El sondeo es secuencial: con 10 esclavos a los que se les leen 10 registros cada uno, un ciclo completo son unos 508 ms, y ningún esclavo se consulta más de dos veces por segundo. Los dos remedios: subir la velocidad (a 115 200 baudios la misma lectura de 10 registros cuesta unos 11,7 ms, dominado por el procesamiento del esclavo y los silencios fijos) y pedir solo lo necesario, agrupado en pocas consultas. Y al revés: si un esclavo es lento en responder, los 100 ms de su procesamiento se suman en cada ciclo.

El tiempo máximo de espera del maestro debe ser mayor que el peor caso de una respuesta (petición, procesamiento y respuesta completa), no un valor arbitrario. Con un timeout demasiado corto, el maestro da por perdidas respuestas que todavía están llegando, y la siguiente petición se superpone con ellas.

09 · De registros a magnitudes

Interpretar los registros

Un registro es un entero de 16 bits sin significado propio; el manual del equipo dice cómo leerlo. Los casos más comunes:

  • Entero sin signo. 0 a 65 535, tal cual.
  • Entero con signo. En complemento a dos: el registro 0xFFF6 (65 526 sin signo) es −10 con signo. Si no se tiene en cuenta, una temperatura bajo cero aparece como un número enorme.
  • Escala. Para evitar decimales, el equipo guarda el valor multiplicado por 10, 100 o 1000: el registro 253 es 25,3 °C si el manual dice «décimas de grado».
  • Campos de bits. Un registro puede agrupar banderas de estado: bit 0 = en marcha, bit 1 = alarma, etc.
  • Cadenas y códigos. Texto ASCII (dos caracteres por registro) o códigos de modelo.

Los datos de 32 bits (contadores de energía, números en coma flotante IEEE 754) ocupan dos registros consecutivos, y aquí Modbus no fija un orden. El bit más significativo puede estar en el primer registro o en el segundo, y los bytes dentro de cada registro también pueden venir invertidos. Las cuatro combinaciones se nombran con las letras ABCD de los bytes (A el más significativo del valor):

Órdenes de palabras y bytes para un valor de 32 bits (registro 1, registro 2).
NombreBytes enviadosSe conoce como
ABCD[A B] [C D]Big-endian: el estándar «natural»
CDAB[C D] [A B]Palabras intercambiadas (word swap): la más frecuente en equipos industriales
BADC[B A] [D C]Bytes intercambiados dentro de cada palabra
DCBA[D C] [B A]Little-endian completo

Por ejemplo, el número real 123,456 (0x42F6E979 en IEEE 754) viaja como los registros 42F6 E979 en el orden ABCD, y como E979 42F6 en el orden CDAB. Si se lee con el orden equivocado no hay error ni aviso: se obtiene un valor absurdo (con el orden CDAB leído como ABCD, −1,88 × 1025). El mejor método: probar con un valor conocido (la temperatura ambiente, una tensión de red) y probar las cuatro combinaciones hasta que sea razonable; y anotar el orden de cada familia de equipos.

10 · Con el microcontrolador

Con el microcontrolador

Los dos programas siguientes implementan un maestro y un esclavo Modbus RTU mínimos sobre el bus RS-485 del tema 24 (MAX3485, DE/RE unidos al pin 4, UART 2 del ESP32). Cubren las funciones 0x03 y 0x06, el CRC, las excepciones y la detección de fin de trama por silencio. En proyectos reales se usan bibliotecas ya probadas (como ModbusMaster o ArduinoModbus); escribirlo una vez a mano es el mejor modo de entender qué hacen.

El maestro arma la petición, la envía respetando la dirección de transmisión, espera la respuesta con un silencio de t3,5 como delimitador, y la valida en orden: que haya algo, que el CRC cierre, que sea del esclavo pedido, que no sea una excepción y que el largo coincida:

// MAESTRO MODBUS RTU
const int PIN_RX = 16, PIN_TX = 17, PIN_DE = 4;
const unsigned long BAUD = 9600;
const unsigned long T35_US = (BAUD > 19200) ? 1750UL : 38500000UL / BAUD;     // 3,5 caracteres de 11 bits

uint16_t crc16(const byte *d, uint16_t n) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < n; i++) {
        crc ^= d[i];
        for (byte b = 0; b < 8; b++) crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
    return crc;
}

void rs485Enviar(const byte *datos, uint16_t n) {
    digitalWrite(PIN_DE, HIGH);
    delayMicroseconds(50);
    Serial2.write(datos, n);
    Serial2.flush();                                    // espera a que salga el último bit
    digitalWrite(PIN_DE, LOW);
}

// Lee bytes hasta que pasan T35_US sin recibir ninguno (fin de trama) o vence el tiempo máximo.
uint16_t rs485Recibir(byte *buf, uint16_t maximo, unsigned long timeoutMs) {
    uint16_t k = 0;
    unsigned long inicio = millis();
    unsigned long ultimo = micros();
    while (millis() - inicio < timeoutMs) {
        if (Serial2.available()) {
            if (k < maximo) buf[k++] = Serial2.read(); else Serial2.read();
            ultimo = micros();
        } else if (k > 0 && micros() - ultimo > T35_US) break;
    }
    return k;
}

// Devuelve la cantidad de registros leídos, o un error negativo:
// -1 sin respuesta, -2 CRC, -3 respuesta de otro esclavo o función, -4 largo incoherente, -100 - código si fue una excepción
int leerRegistros(byte esclavo, uint16_t inicio, byte cantidad, uint16_t *destino) {
    byte p[8] = { esclavo, 0x03, (byte)(inicio >> 8), (byte)(inicio & 0xFF), 0, cantidad };
    uint16_t c = crc16(p, 6);
    p[6] = c & 0xFF;                                    // CRC: byte bajo primero
    p[7] = c >> 8;
    rs485Enviar(p, 8);
    byte r[256];
    uint16_t n = rs485Recibir(r, sizeof(r), 300);
    if (n == 0) return -1;
    if (n < 5 || crc16(r, n) != 0) return -2;           // el CRC calculado sobre todo, incluido el CRC, da 0
    if (r[0] != esclavo) return -3;
    if (r[1] == (0x03 | 0x80)) return -100 - r[2];       // excepción
    if (r[1] != 0x03) return -3;
    if (r[2] != 2 * cantidad || n != 5 + 2 * cantidad) return -4;
    for (byte i = 0; i < cantidad; i++) destino[i] = ((uint16_t)r[3 + 2 * i] << 8) | r[4 + 2 * i];   // byte alto primero
    return cantidad;
}

void setup() {
    Serial.begin(115200);
    Serial2.begin(BAUD, SERIAL_8N1, PIN_RX, PIN_TX);    // el estándar pide 8E1; muchos equipos aceptan 8N1 o se configuran
    pinMode(PIN_DE, OUTPUT);
    digitalWrite(PIN_DE, LOW);
}

void loop() {
    uint16_t reg[2];
    int r = leerRegistros(1, 0, 2, reg);
    if (r > 0) {
        Serial.print("Temperatura: "); Serial.print(reg[0] / 10.0, 1);
        Serial.print(" C, humedad: ");  Serial.print(reg[1] / 10.0, 1); Serial.println(" %");
    } else if (r <= -100) { Serial.print("Excepción Modbus "); Serial.println(-100 - r); }
    else if (r == -1) Serial.println("Sin respuesta");
    else if (r == -2) Serial.println("CRC erróneo");
    else Serial.println("Respuesta inconsistente");
    delay(1000);
}

El esclavo mantiene una tabla de registros de retención, detecta el fin de trama por silencio, descarta en silencio lo que llega con CRC erróneo o para otro esclavo, y atiende las funciones 0x03 y 0x06. Responde excepciones para funciones no soportadas, direcciones inexistentes y cantidades inválidas, y calla ante una difusión:

// ESCLAVO MODBUS RTU (funciones 0x03 y 0x06)
const int PIN_RX = 16, PIN_TX = 17, PIN_DE = 4;
const unsigned long BAUD = 9600;
const unsigned long T35_US = (BAUD > 19200) ? 1750UL : 38500000UL / BAUD;
const byte MI_ID = 1;
const uint16_t N_HOLD = 16;
uint16_t holding[N_HOLD] = { 253, 485 };                // registro 0: temperatura (décimas), registro 1: humedad (décimas)

uint16_t crc16(const byte *d, uint16_t n) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < n; i++) {
        crc ^= d[i];
        for (byte b = 0; b < 8; b++) crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
    return crc;
}

void enviarConCrc(byte *r, uint16_t n) {
    uint16_t c = crc16(r, n);
    r[n] = c & 0xFF; r[n + 1] = c >> 8;
    delayMicroseconds(T35_US);                          // el maestro necesita su silencio antes de oír la respuesta
    digitalWrite(PIN_DE, HIGH);
    delayMicroseconds(50);
    Serial2.write(r, n + 2);
    Serial2.flush();
    digitalWrite(PIN_DE, LOW);
}

void excepcion(byte funcion, byte codigo) {
    byte r[5] = { MI_ID, (byte)(funcion | 0x80), codigo };
    enviarConCrc(r, 3);
}

void atender(const byte *q, uint16_t n) {
    if (n < 4 || crc16(q, n) != 0) return;             // trama dañada o corta: se ignora, sin contestar
    if (q[0] != MI_ID && q[0] != 0) return;           // ni para mí ni difusión
    bool difusion = (q[0] == 0);
    byte fc = q[1];
    if (fc == 0x03 && n == 8 && !difusion) {
        uint16_t ini = (q[2] << 8) | q[3], cant = (q[4] << 8) | q[5];
        if (cant < 1 || cant > 125) { excepcion(fc, 3); return; }
        if (ini + cant > N_HOLD)      { excepcion(fc, 2); return; }
        byte r[3 + 250 + 2];
        r[0] = MI_ID; r[1] = fc; r[2] = cant * 2;
        for (uint16_t i = 0; i < cant; i++) { r[3 + 2 * i] = holding[ini + i] >> 8; r[4 + 2 * i] = holding[ini + i] & 0xFF; }
        enviarConCrc(r, 3 + cant * 2);
    } else if (fc == 0x06 && n == 8) {
        uint16_t dir = (q[2] << 8) | q[3], valor = (q[4] << 8) | q[5];
        if (dir >= N_HOLD) { if (!difusion) excepcion(fc, 2); return; }
        holding[dir] = valor;
        if (!difusion) { byte r[8]; memcpy(r, q, 6); enviarConCrc(r, 6); }   // el eco de la petición confirma la escritura
    } else if (!difusion) {
        excepcion(fc, 1);                                // función no soportada
    }
}

void setup() {
    Serial.begin(115200);
    Serial2.begin(BAUD, SERIAL_8N1, PIN_RX, PIN_TX);
    pinMode(PIN_DE, OUTPUT);
    digitalWrite(PIN_DE, LOW);
}

void loop() {
    static byte trama[256];
    static uint16_t k = 0;
    static unsigned long ultimo = 0;
    while (Serial2.available()) {
        if (k < sizeof(trama)) trama[k++] = Serial2.read(); else Serial2.read();
        ultimo = micros();
    }
    if (k > 0 && micros() - ultimo > T35_US) {       // silencio de 3,5 caracteres: la trama terminó
        atender(trama, k);
        k = 0;
    }
}

Algunas decisiones del código. El CRC se verifica corriéndolo sobre toda la trama y exigiendo cero. La respuesta se valida de menos a más costoso: existencia, CRC, esclavo y función, excepción, largo. El esclavo calla ante todo lo que no es para él, incluida una trama dañada: contestar con basura a un pedido que no entendió sería peor. El fin de trama es el silencio, no el conteo de bytes. Y el eco de la escritura (0x06) se arma copiando los primeros 6 bytes y recalculando el CRC.

11 · Analogía con programación

Modbus en Python

Un protocolo de este estilo se puede modelar completo en unas decenas de líneas: el CRC, el armado de peticiones, un esclavo con su tabla de registros (con respuestas y excepciones), la decodificación de los valores y los tiempos. Con ese modelo se prueba un maestro sin hardware y se generan las tramas de referencia para comparar con un analizador.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

import struct


def crc16(datos):
    crc = 0xFFFF
    for b in datos:
        crc ^= b
        for _ in range(8):
            crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
    return crc


def con_crc(cuerpo):
    c = crc16(cuerpo)
    return bytes(cuerpo) + bytes([c & 0xFF, c >> 8])           # el byte bajo del CRC va primero


def hexa(trama):
    return " ".join(f"{b:02X}" for b in trama)


def pedido_lectura(esclavo, funcion, inicio, cantidad):
    return con_crc([esclavo, funcion, inicio >> 8, inicio & 0xFF, cantidad >> 8, cantidad & 0xFF])


def pedido_escribir(esclavo, direccion, valor):
    return con_crc([esclavo, 0x06, direccion >> 8, direccion & 0xFF, valor >> 8, valor & 0xFF])


def pedido_escribir_varios(esclavo, inicio, valores):
    cuerpo = [esclavo, 0x10, inicio >> 8, inicio & 0xFF, len(valores) >> 8, len(valores) & 0xFF, 2 * len(valores)]
    for v in valores:
        cuerpo += [v >> 8, v & 0xFF]
    return con_crc(cuerpo)


class Esclavo:
    def __init__(self, direccion, holding):
        self.dir, self.holding = direccion, dict(holding)

    def excepcion(self, funcion, codigo):
        return con_crc([self.dir, funcion | 0x80, codigo])

    def atender(self, trama):
        if len(trama) < 4 or crc16(trama) != 0:
            return None                                           # CRC erróneo: se descarta sin contestar
        if trama[0] not in (self.dir, 0):
            return None                                           # ni para mí ni difusión
        difusion = trama[0] == 0
        fc = trama[1]
        if fc == 0x03:
            inicio, cant = (trama[2] << 8) | trama[3], (trama[4] << 8) | trama[5]
            if not 1 <= cant <= 125:
                respuesta = self.excepcion(fc, 3)
            elif any(inicio + i not in self.holding for i in range(cant)):
                respuesta = self.excepcion(fc, 2)
            else:
                datos = b"".join(self.holding[inicio + i].to_bytes(2, "big") for i in range(cant))
                respuesta = con_crc([self.dir, fc, len(datos)] + list(datos))
        elif fc == 0x06:
            direccion, valor = (trama[2] << 8) | trama[3], (trama[4] << 8) | trama[5]
            if direccion not in self.holding:
                respuesta = self.excepcion(fc, 2)
            else:
                self.holding[direccion] = valor
                respuesta = trama                                 # el eco confirma la escritura
        else:
            respuesta = self.excepcion(fc, 1)
        return None if difusion else respuesta                    # la difusión nunca se contesta


def decodificar(respuesta, escala=1, con_signo=False):
    if crc16(respuesta) != 0:
        return "CRC erróneo"
    if respuesta[1] & 0x80:
        return f"excepción {respuesta[2]:02d}"
    valores = []
    for i in range(respuesta[2] // 2):
        v = (respuesta[3 + 2 * i] << 8) | respuesta[4 + 2 * i]
        if con_signo and v & 0x8000:
            v -= 65536
        valores.append(v / escala if escala != 1 else v)
    return valores


def tiempos(baud, bits=11):
    car = bits / baud * 1000
    return (0.75, 1.75) if baud > 19200 else (1.5 * car, 3.5 * car)       # ms


def consulta_ms(baud, n, proc_ms=5, bits=11):
    car = bits / baud * 1000
    t35 = tiempos(baud, bits)[1]
    return 8 * car + t35 + proc_ms + (5 + 2 * n) * car + t35


def palabras32(r1, r2):
    a, b, c, d = r1 >> 8, r1 & 0xFF, r2 >> 8, r2 & 0xFF
    ordenes = {"ABCD": (a, b, c, d), "CDAB": (c, d, a, b), "BADC": (b, a, d, c), "DCBA": (d, c, b, a)}
    return {n: (int.from_bytes(bytes(v), "big"), struct.unpack(">f", bytes(v))[0]) for n, v in ordenes.items()}


print("CRC de '123456789':", f"{crc16(b'123456789'):#06x}")
print("Escribir varios (10 y 258 desde el registro 1):", hexa(pedido_escribir_varios(1, 1, [10, 258])))

e = Esclavo(1, {0: 250, 1: 125, 2: 0xFFF6})
casos = (("leer 2 registros desde 0", pedido_lectura(1, 3, 0, 2)), ("leer el registro 2", pedido_lectura(1, 3, 2, 1)),
         ("escribir 300 en el registro 1", pedido_escribir(1, 1, 300)), ("leer en la dirección 100", pedido_lectura(1, 3, 100, 1)),
         ("leer 0 registros", pedido_lectura(1, 3, 0, 0)), ("función 0x2B", con_crc([1, 0x2B, 0, 0])))
for nombre, p in casos:
    r = e.atender(p)
    print(f"{nombre}\n   pedido    {hexa(p)}\n   respuesta {hexa(r) if r else 'ninguna'}")

r = e.atender(pedido_lectura(1, 3, 0, 3))
print("\nLectura de 3 registros:", hexa(r))
print("  sin signo:", decodificar(r), "| con signo:", decodificar(r, con_signo=True), "| con signo y /10:", decodificar(r, 10, True))

mala = bytearray(pedido_lectura(1, 3, 0, 2))
mala[7] ^= 0xFF
print("\nCRC dañado:", e.atender(bytes(mala)), "| esclavo 5:", e.atender(pedido_lectura(5, 3, 0, 1)))
print("Difusión (escribir 7 en el registro 0):", e.atender(pedido_escribir(0, 0, 7)), "-> registro 0 =", e.holding[0])

print()
for baud in (1200, 9600, 19200, 115200):
    t15, t35 = tiempos(baud)
    print(f"{baud:6} baudios: carácter {11 / baud * 1000:6.3f} ms, t1,5 = {t15:5.2f} ms, t3,5 = {t35:5.2f} ms, lectura de 10 registros = {consulta_ms(baud, 10):6.2f} ms")
print(f"Ciclo de 10 esclavos a 9600 baudios (10 registros cada uno): {10 * consulta_ms(9600, 10):.1f} ms")

print()
for nombre, (r1, r2) in (("123,456 (float)", (0x42F6, 0xE979)), ("70000 (entero de 32 bits)", (0x0001, 0x1170))):
    print(nombre, "->", {orden: (u if nombre.startswith("70000") else f"{f:.6g}") for orden, (u, f) in palabras32(r1, r2).items()})

Resultado: el CRC de «123456789» es 0x4b37; escribir 10 y 258 desde el registro 1 da 01 10 00 01 00 02 04 00 0A 01 02 92 30. Leer 2 registros desde 0: pedido 01 03 00 00 00 02 C4 0B y respuesta 01 03 04 00 FA 00 7D 1A 23. Leer el registro 2: 01 03 00 02 00 01 25 CA → 01 03 02 FF F6 79 F2. Escribir 300 en el registro 1: pedido 01 06 00 01 01 2C D8 47 y la misma trama como respuesta. Leer en la dirección 100: excepción 01 83 02 C0 F1 (dirección no válida); leer 0 registros: 01 83 03 01 31 (valor no válido); la función 0x2B: 01 AB 01 9E F0 (función no válida). Con el CRC dañado o para otro esclavo, no hay respuesta, y una escritura por difusión no se contesta pero se aplica (el registro 0 pasa a 7). La lectura de 3 registros devuelve 01 03 06 00 FA 01 2C FF F6 F8 E2: sin signo son 250, 300 y 65 526; con signo, 250, 300 y −10; con signo y dividido por 10, 25,0, 30,0 y −1,0. A 9600 baudios el carácter dura 1,146 ms, t1,5 = 1,72 ms y t3,5 = 4,01 ms, y una lectura de 10 registros cuesta 50,83 ms; a 1200 baudios, 9,167 ms de carácter y t3,5 = 32,08 ms; a 19 200, 2,01 ms; y a 115 200 los silencios quedan fijos en 1,75 ms y la lectura en 11,65 ms. El ciclo de 10 esclavos a 9600 baudios es de 508,3 ms. Para los valores de 32 bits: los registros 42F6 E979 dan 123,456 en el orden ABCD y valores absurdos en los otros (−1,88337e+25 en CDAB); los registros 0001 1170 son 70 000 en ABCD y 292 552 705 en CDAB.

Observá cómo el esclavo calla ante lo que no entiende y contesta con una excepción ante lo que entiende pero no puede hacer: son dos fallas distintas con síntomas distintos en el maestro (timeout contra respuesta de excepción).

12 · Exploración interactiva

Laboratorio: tramas, respuestas, tiempos y palabras

El laboratorio tiene cuatro herramientas. La primera arma una petición con su CRC. La segunda decodifica una respuesta (y detecta CRC erróneos y excepciones). La tercera calcula los tiempos de una consulta y el ritmo de sondeo. La cuarta interpreta dos registros como un valor de 32 bits en los cuatro órdenes posibles.

1. Armar una petición

Trama completa (bytes en el cable)—
CRC-16 (se envía el byte bajo primero)—
Largo · tiempo de la petición—
Respuesta esperada—
Veredicto—

Elegí la función y completá los campos.

2. Decodificar una respuesta (funciones 0x03 y 0x04)

Estructura—
CRC—
Valores—
Veredicto—

Pegá o escribí una respuesta.

3. Tiempos de una consulta

Un carácter—
t1,5 · t3,5—
Una consulta completa—
Ciclo de sondeo de todos los esclavos—
Veredicto—

Elegí la velocidad y la cantidad de esclavos.

4. Valores de 32 bits en dos registros

ABCD (palabras en orden)—
CDAB (palabras intercambiadas)—
BADC (bytes intercambiados)—
DCBA (todo invertido)—
Cuál parece razonable—

Escribí los dos registros.

En la herramienta 1, las direcciones se escriben en decimal y la trama se muestra en hexadecimal; la respuesta esperada es la estructura que debería devolver el esclavo. En la 2, se verifica el CRC corriéndolo sobre la trama completa (debe dar 0). En la 3, el sondeo es secuencial y la consulta incluye dos silencios de t3,5; con 10 bits por carácter (8N1) los tiempos se ajustan, aunque la norma pide 11. En la 4, «razonable» significa un flotante finito de magnitud entre 0,001 y 107, o un entero que cabe en un contador usual; es una pista, no una prueba.

Recorrido guiado

  1. Con los valores iniciales (esclavo 1, función 0x03, dirección 0, cantidad 10), la trama es 01 03 00 00 00 0A C5 CD: CRC 0xCDC5 (se envía C5 y después CD), 8 bytes, 9,17 ms a 9600 baudios, respuesta esperada de 25 bytes (5 + 20).
  2. Cambiá la cantidad a 2: 01 03 00 00 00 02 C4 0B. Probá la función 0x04 con cantidad 2: 01 04 00 00 00 02 71 CB. Pedí 200 registros: el laboratorio avisa que el máximo es 125 por trama.
  3. Función 0x06, dirección 1, valor 3: 01 06 00 01 00 03 98 0B; la respuesta esperada es un eco de 8 bytes. Con la función 0x10, dirección 1 y lista «10 258»: 01 10 00 01 00 02 04 00 0A 01 02 92 30.
  4. Función 0x05 con el valor 1 y la dirección 19: 01 05 00 13 FF 00 7D FF (una bobina se activa con FF 00). Con el esclavo 0 en una función de lectura, el laboratorio avisa que una difusión solo sirve para escribir (y que nadie contestaría).
  5. En la herramienta 2, con la respuesta inicial, el CRC es correcto y los registros valen 250 y 125. Con escala ÷ 10: 25,0 y 12,5. Cambiá un byte de los datos (por ejemplo, FA por FB): «CRC erróneo».
  6. Probá la respuesta 01 03 02 FF F6 79 F2: sin signo es 65 526, con signo es −10, y con signo y ÷ 10, −1,0. Pegá 01 83 02 C0 F1: es una excepción 02 («dirección de dato no válida»).
  7. En la herramienta 3, con los valores iniciales (9600 baudios, 11 bits, 10 registros, 5 ms, 10 esclavos): carácter de 1,146 ms, t1,5 = 1,72 ms y t3,5 = 4,01 ms; una consulta de 50,83 ms y un ciclo de 10 esclavos de 508,3 ms.
  8. Subí a 115 200 baudios: los silencios se fijan en 0,75 y 1,75 ms y la consulta baja a unos 11,7 ms. Con 125 registros a 9600 baudios la consulta llega a 314 ms y el ciclo de 10 esclavos, a más de 3 s. Con 100 ms de procesamiento, la lentitud del esclavo domina a cualquier velocidad.
  9. En la herramienta 4, con 42F6 y E979: el orden ABCD da 123,456; los otros dan magnitudes absurdas (−1,88337e+25 en CDAB). Cambialos a E979 y 42F6 y el orden razonable pasa a ser CDAB. Con 0001 y 1170, el entero en ABCD es 70 000 y en CDAB, 292 552 705.
Desafío: cuántos esclavos por segundo

Quiere sondear cada esclavo al menos una vez por segundo, leyendo 10 registros, con un procesamiento de 5 ms por esclavo. A 9600 baudios cada consulta cuesta 50,83 ms: caben 1000 / 50,83 ≈ 19 esclavos por segundo. A 19 200 baudios, la consulta (con 0,573 ms por carácter, t3,5 = 2,01 ms) cuesta 8 · 0,573 + 2,01 + 5 + 25 · 0,573 + 2,01 ≈ 27,9 ms: unos 35 esclavos. A 115 200 baudios, unos 11,7 ms: más de 85 (pero un bus RS-485 admite 32 cargas unitarias sin repetidores, así que el límite práctico lo da el cableado y no el tiempo).

13 · Un procedimiento

Diagnóstico de una red Modbus

Como Modbus está sobre RS-485, hay que descartar primero la capa física (tema 24) y luego la del protocolo. Un recorrido ordenado:

  1. Un maestro, un esclavo, cable corto. Si anda, el problema está en la instalación; si no, en la configuración.
  2. Configuración idéntica en ambos extremos: velocidad, paridad, bits de parada, y la dirección del esclavo (comprobar que sea la que se consulta).
  3. Mirar el bus con un analizador o un convertidor USB-RS485 con un monitor serie: ¿sale la petición? ¿contesta el esclavo? ¿los CRC cierran? Compararlos con las tramas de referencia (este tema trae varias).
  4. Interpretar el resultado: si hay timeout, es dirección, formato, capa física o esclavo apagado; si hay una excepción, el esclavo está vivo y se equivocó el pedido (dirección, cantidad, función); si el CRC falla, ruido o formato; si los valores son raros, escala, signo, orden de palabras o desfase de numeración.
  5. Revisar la numeración: ¿el manual da direcciones de protocolo o numeración de 1? Probar con ±1.
  6. Ajustar los tiempos: timeout del maestro acorde, silencios correctos, sin retardos de más entre peticiones.
Síntomas y causa probable.
SíntomaCausa probableQué hacer
Nunca hay respuesta (timeout)Dirección, velocidad o paridad distintas; polaridad invertida; capa físicaIgualar el formato; cruzar A y B; revisar terminación y polarización
Excepción 02Dirección o cantidad fuera de las tablas del esclavo; desfase de numeraciónConsultar el mapa del manual; probar ±1
Excepción 01Función no implementada o tabla equivocada (registros de entrada contra retención)Probar la 0x04 en lugar de la 0x03, o al revés
Excepción 03Cantidad 0 o mayor al máximo; valor no admitidoRevisar los límites (125 registros por lectura)
Respuestas con CRC erróneo, a vecesRuido, falta de terminación, cable largo; maestro que no espera el silencioBajar la velocidad; terminadores; reintentos
Los valores están desplazados (llega el vecino)Desfase de numeración (0 contra 1)Restar o sumar uno a la dirección
Valores enormes cuando hace fríoRegistro con signo leído sin signoInterpretar en complemento a dos
El flotante de 32 bits da disparatesOrden de palabras equivocadoProbar ABCD, CDAB, BADC y DCBA
Dos equipos contestan juntosDirecciones repetidasCambiar la dirección de uno y reiniciarlo

14 · Comprensión

Confusiones frecuentes

«Los esclavos pueden avisar cuando cambia algo»

No: un esclavo Modbus solo responde. Para enterarse de un cambio, el maestro tiene que sondear. No existen eventos ni notificaciones en el protocolo.

«El registro 40001 es la dirección 40001»

La dirección en la trama es 0. El «40001» es la numeración histórica, con el primer dígito identificando la tabla y la cuenta empezando en 1. Confundirlo da excepción 02 o el valor del registro vecino.

«Una trama termina con un carácter especial o con la cantidad de bytes»

En RTU no hay delimitadores: termina cuando pasan 3,5 tiempos de carácter sin recibir nada. Tanto el emisor (bytes de corrido) como el receptor (medir silencios) deben respetarlo.

«El CRC va con el byte alto primero, como todo lo demás»

Todo en Modbus va con el byte alto primero, salvo el CRC, que va con el bajo primero. Para 01 03 00 00 00 0A el CRC 0xCDC5 viaja como C5 CD.

«Una excepción es una falla de comunicación»

Al contrario: la comunicación funcionó. El esclavo recibió y entendió la trama y respondió que no puede cumplirla. Un timeout o un CRC erróneo sí son fallas de comunicación.

«Reintentar siempre ayuda»

Reintentar sirve ante un timeout o un CRC erróneo, que pueden ser pasajeros. Ante una excepción, el esclavo volverá a contestar lo mismo: hay que corregir el pedido.

«La difusión sirve para leer a todos a la vez»

La difusión (dirección 0) es solo para escritura y no recibe respuesta alguna: leer a todos a la vez produciría contención. Para leer se pregunta de a un esclavo.

«Un valor de 32 bits tiene un único formato»

Modbus no define el orden de las dos palabras ni de los bytes. Cada fabricante elige, y hay cuatro combinaciones posibles. Se determina con el manual y se verifica con un valor conocido.

15 · Práctica guiada

Predecí antes de comprobar

1. Armar una petición

Escribí los 6 bytes (sin CRC) de una petición para leer 3 registros de retención desde la dirección 107 del esclavo 17. ¿Cuántos bytes tiene la respuesta esperada?

Ver solución

Esclavo 17 = 0x11, función 0x03, dirección inicial 107 = 0x006B, cantidad 3 = 0x0003: 11 03 00 6B 00 03 (más 2 bytes de CRC). La respuesta tiene 5 + 2 · 3 = 11 bytes: dirección, función, cantidad de bytes (06), 6 de datos y 2 de CRC.

2. Numeración del manual

Un manual dice que la temperatura está en el registro «40108». ¿Qué dirección va en la trama y con qué función se lee?

Ver solución

El prefijo 4 indica registros de retención; el resto, 0108, es el número del dato contado desde 1: dirección de protocolo = 108 − 1 = 107 (0x006B). Se lee con la función 0x03.

3. Decodificar una respuesta

La respuesta a una lectura es 01 03 04 FF 9C 00 64 xx xx. Los registros son temperatura (décimas de grado, con signo) y humedad (décimas de %). ¿Qué valen?

Ver solución

Registro 0 = 0xFF9C = 65 436 sin signo = −100 con signo (65 436 − 65 536), o sea −10,0 °C. Registro 1 = 0x0064 = 100, o sea 10,0 %.

4. Una excepción

Enviás una lectura y recibís 05 84 02 xx xx. ¿Qué pasó? ¿Qué función habías pedido?

Ver solución

El bit 7 de la función está en 1: 0x84 = 0x04 + 0x80, así que la función pedida fue 0x04 (leer registros de entrada) y el esclavo 5 contestó con la excepción 02: dirección de dato no válida. Es una respuesta válida; hay que corregir la dirección o la cantidad.

5. Tiempos

A 19 200 baudios con 11 bits por carácter, ¿cuánto vale t3,5? ¿Y a 115 200 baudios?

Ver solución

Carácter: 11 / 19 200 = 0,573 ms; t3,5 = 3,5 · 0,573 ≈ 2,01 ms. A 115 200 baudios (más de 19 200) la norma fija 1,75 ms (y 0,75 ms para t1,5).

6. Sondeo

Una red de 20 esclavos a 9600 baudios, leyendo 10 registros de cada uno (50,83 ms por consulta). ¿Cuánto tarda un ciclo y qué podés hacer para acortarlo?

Ver solución

20 · 50,83 ≈ 1017 ms: algo más de un segundo. Para acortarlo: subir la velocidad (a 19 200 baudios la consulta baja a unos 28 ms, o sea unos 558 ms por ciclo), pedir solo los registros necesarios y contiguos, o dividir la red en dos buses con un maestro cada uno.

7. Un flotante extraño

Un medidor entrega la tensión como float de 32 bits en dos registros: 0x0000 y 0x4348. Leído como ABCD da un valor minúsculo. ¿Qué orden es y qué tensión es?

Ver solución

En el orden ABCD, 0x00004348 es un número denormal diminuto (unos 2,4 × 10−41). Con las palabras intercambiadas (CDAB) queda 0x43480000, que en IEEE 754 es 200,0: una tensión de 200 V es razonable. El equipo usa el orden CDAB (el DCBA daría 199 680, que no es una tensión razonable).

16 · Síntesis

Ideas para recordar

  • Modbus es un protocolo de aplicación maestro–esclavo: el maestro pregunta, el esclavo responde, y los esclavos no hablan entre sí ni avisan por iniciativa propia. Resuelve la contención de RS-485 por construcción.
  • Cada esclavo expone cuatro tablas: bobinas (bits R/W), entradas discretas (bits R), registros de entrada (16 bits R) y registros de retención (16 bits R/W). La numeración histórica (40001…) cuenta desde 1; la dirección de la trama cuenta desde 0.
  • Una trama RTU es dirección (1 byte) + función (1) + datos (0 a 252) + CRC (2). Los valores de 16 bits van con el byte alto primero; el CRC, con el bajo primero. Sin delimitadores: la trama termina tras un silencio de 3,5 caracteres, y dentro de ella no puede haber pausas mayores de 1,5.
  • Funciones principales: 0x01/0x02 (leer bits), 0x03/0x04 (leer registros, hasta 125), 0x05 (escribir bobina), 0x06 (escribir registro, con eco), 0x10 (escribir varios, hasta 123).
  • El CRC-16 de Modbus (polinomio 0xA001 reflejado, inicio 0xFFFF) se verifica calculándolo sobre la trama completa: da 0 si no hubo error. Una trama con CRC malo se descarta sin contestar.
  • Tres tipos de falla: timeout (no hubo respuesta), respuesta dañada (CRC o largo) y excepción (función con el bit 7 en 1 más un código: 01 función, 02 dirección, 03 valor). Se reintenta ante las dos primeras, no ante la excepción.
  • Direcciones: 0 es difusión (solo escritura, sin respuesta), 1 a 247 los esclavos, y deben ser únicas. Un bus tiene un solo maestro.
  • El sondeo es secuencial: una consulta cuesta petición + t3,5 + procesamiento + respuesta + t3,5. Se acelera con velocidad y con lecturas agrupadas. Los registros hay que interpretarlos: signo, escala y, para 32 bits, el orden de palabras (ABCD, CDAB, BADC, DCBA).

¿Listo para el siguiente tema?

Antes de avanzar, armá a mano la petición para escribir el valor 500 en el registro 4 del esclavo 3 (sin CRC) y decí qué respuesta esperás; explicá cómo distinguirías un timeout de una excepción en el maestro y qué harías en cada caso; y decidí cuánto debe esperar un maestro como mínimo antes de dar por perdida una respuesta de 10 registros a 9600 baudios. (Para la primera: 03 06 00 04 01 F4 + CRC; la respuesta es un eco.) Si podés justificar las tres respuestas, tenés la base de Modbus RTU.

En el próximo tema armamos un taller de red RS-485 con Modbus: un maestro y varios esclavos leyendo sensores, con diagnóstico de fallas sobre el bus real.