← Índice del curso

Comunicación entre Dispositivos · Tema 26 · Taller

Taller: red RS-485 con Modbus

Cierre del bloque de buses industriales con una red completa: un maestro ESP32 que sondea a tres nodos con sensores por un bus RS-485 de dos hilos, habla Modbus RTU, lleva la cuenta de lo que sale bien y de lo que sale mal, y sigue funcionando cuando un nodo se cae. Vamos a diseñar el mapa de registros, escribir el maestro y el esclavo, probar la red por etapas, verificar las tramas con un analizador y, sobre todo, aprender a leer los síntomas: cada falla típica de una red de campo deja un patrón reconocible en los contadores y en el cable.

01 · Objetivo

Qué vamos a construir

En los temas 22 a 25 recorrimos la pila completa de un enlace industrial: RS-232 como contraste, la señal diferencial, RS-485 como capa eléctrica y Modbus RTU como protocolo. En este taller las unimos en un sistema que se parece a uno real: una red de monitoreo ambiental con un maestro y tres nodos.

  • El maestro, un ESP32 con un transceptor MAX3485, consulta por turnos a cada nodo cada pocos segundos y guarda en memoria el último valor, el estado y las estadísticas de cada uno.
  • Cada nodo esclavo (un ESP32 o una placa similar con otro MAX3485) mide temperatura y humedad, mantiene un mapa de registros Modbus y activa un relé cuando el maestro se lo pide.
  • Todo viaja por un bus RS-485 de dos hilos a 19 200 baudios, con terminadores en los extremos, polarización en el maestro y un hilo de referencia.

El objetivo no es medir bien la temperatura, sino lo que cuesta de verdad en una red de campo: que siga funcionando cuando algo falla y que, cuando falla, se pueda saber por qué. Por eso el taller trata con el mismo cuidado el camino feliz (leer sensores) y los caminos tristes (un nodo apagado, un cable con ruido, una dirección repetida).

02 · Materiales y cableado

Materiales y cableado

Hace falta lo siguiente (el código usa ESP32; con otras placas cambian los pines):

  • Cuatro placas ESP32 (una para el maestro y tres para los nodos), cada una con un módulo MAX3485 (3,3 V) para RS-485. Si se usan módulos de 5 V con MAX485, hay que adaptar los niveles (tema 24).
  • Un sensor de temperatura analógico por nodo (un TMP36, que da 10 mV/°C con 500 mV a 0 °C y funciona con 3,3 V) y un potenciómetro como «humedad» de práctica; o un sensor real (BME280, DS18B20) si ya lo tenés.
  • Un relé o un LED con su resistencia en un pin de salida de cada nodo, para la escritura de bobina.
  • Cable de par trenzado de 120 Ω (o UTP de red), dos resistencias de 120 Ω para los extremos y, para la polarización, dos de 330 Ω (una a 3,3 V y otra a masa; con los dos terminadores dan unos 0,28 V en reposo, como vimos en el tema 24).
  • Opcional: un analizador lógico de 8 canales y un convertidor USB–RS-485 para espiar el bus desde la PC.
Red RS-485 con un maestro y tres esclavos Un maestro ESP32 con transceptor MAX3485 y tres esclavos con sus sensores comparten un par trenzado A y B. Hay una resistencia de 120 ohmios en cada extremo y la polarización en el maestro. Un tercer hilo de referencia de masa une los nodos a través de resistencias de 100 ohmios. A B referencia 120 Ω 120 Ω polarización (un solo punto) Maestro ESP32 + MAX3485 maestro Esclavo 1 MAX3485 + sensor dirección 1 Esclavo 2 MAX3485 + sensor dirección 2 Esclavo 3 MAX3485 + sensor dirección 3 Cadena sin ramas. Terminadores en los dos extremos físicos. Referencia de masa con resistencias de unos 100 Ω.

El cableado repite las reglas del tema 24:

  • Cadena de un nodo al siguiente, con derivaciones de pocos centímetros hasta cada transceptor. Sin estrellas.
  • Un terminador de 120 Ω entre A y B en cada extremo físico del cable: uno en la punta cercana al maestro y otro en la punta del último esclavo. Los nodos del medio no llevan terminador.
  • Polarización en un único punto, en el maestro: una resistencia de A (la línea no inversora) a 3,3 V y otra de B a masa. Con dos terminadores (60 Ω) y 330 Ω da una tensión de reposo de unos 0,28 V; con transceptores a prueba de fallas se puede omitir.
  • Un hilo de referencia (masa) que une todas las placas a través de una resistencia de unos 100 Ω en cada nodo, para mantener el modo común dentro de rango; con las placas alimentadas por el mismo USB o fuente, la masa ya es común.
  • DE y RE de cada transceptor unidos y conectados a un pin del ESP32 (pin 4), con una resistencia de 10 kΩ a masa para que el driver arranque apagado. La UART 2 va en los pines 16 (RX) y 17 (TX).

Dos decisiones de configuración, iguales en toda la red: 19 200 baudios, 8N1 (la norma pide 8E1, pero las dos puntas aceptan lo que se configure y 8N1 es lo más habitual en equipos de campo; lo importante es que sea igual en todos) y una dirección distinta para cada nodo (1, 2 y 3). Con 19 200 baudios el tiempo de silencio entre tramas es de unos 2 ms y una consulta de tres registros dura cerca de 20 ms.

03 · Diseñar antes de programar

El mapa de registros

Lo primero que se hace en una red Modbus es escribir el mapa de registros de un nodo y respetarlo en maestro y esclavos. Es el contrato de la red. El de este taller:

Mapa de registros de cada nodo (direcciones del protocolo, contadas desde 0).
TablaDirecciónContenidoFormato
Registros de entrada (0x04)0TemperaturaEntero con signo, décimas de °C (235 = 23,5 °C)
Registros de entrada (0x04)1HumedadEntero sin signo, décimas de % (480 = 48,0 %)
Registros de entrada (0x04)2EstadoBit 0: relé activado; bit 1: falla del sensor
Registros de retención (0x03 / 0x06)0Período de muestreoSegundos (1 a 3600); se puede escribir
Registros de retención (0x03 / 0x06)1Contador de peticiones válidas atendidasEntero sin signo; se pone a cero escribiendo 0
Registros de retención (0x03 / 0x06)2Contador de peticiones con CRC erróneoEntero sin signo; se pone a cero escribiendo 0
Bobinas (0x05)0Relé0xFF00 = encendido, 0x0000 = apagado

Algunas decisiones de diseño que vale la pena imitar:

  • Los datos que se leen juntos, juntos. Temperatura, humedad y estado ocupan tres registros contiguos y se leen con una sola consulta (función 0x04, cantidad 3): una consulta de 3 registros cuesta unos 20 ms a 19 200 baudios, mientras que tres de un registro costarían unos 53 ms.
  • Enteros escalados en lugar de flotantes: no hay ambigüedad de orden de palabras (tema 25) y cada registro es un valor completo. La temperatura lleva signo: −1,5 °C se transmite como 0xFFF1.
  • Mediciones en registros de entrada, parámetros en registros de retención. Es la separación de la norma y evita que el maestro escriba por error una medición.
  • Contadores de diagnóstico en el propio esclavo. Cada nodo cuenta cuántas peticiones válidas recibió y cuántas llegaron con CRC erróneo. Esa cuenta, comparada con la del maestro, localiza el problema: si el esclavo ve CRC erróneos, el daño ocurre en el camino de ida (maestro → nodo); si el maestro ve CRC erróneos en las respuestas pero el esclavo no, el daño ocurre en el camino de vuelta.
  • Un bit de estado de falla del sensor: el maestro puede distinguir «el nodo está vivo pero su sensor falló» de «el nodo no contesta».

El mapa se documenta en una tabla como la de arriba, con la numeración usada (aquí, la del protocolo, desde 0). Es el primer documento que se le entrega a quien mantenga la red.

04 · La política de sondeo

El maestro: ciclo de sondeo y estados

Un ciclo de sondeo con un esclavo caído y un enlace con ruido El maestro consulta al esclavo 1, que contesta en unos 20 milisegundos. Después al esclavo 2, que está caído: tres intentos de 105 milisegundos cada uno sin respuesta. Después al esclavo 3, cuya primera respuesta llega con CRC erróneo y la segunda es correcta. El esclavo caído ocupa casi todo el ciclo. OKEsclavo 1sin respuestaEsclavo 2reintento 1reintento 2CRC maloEsclavo 3OK ciclo ≈ 20 + 314 + 40 = 374 ms (a 19 200 baudios, 100 ms de timeout, 2 reintentos) sin el esclavo caído, el mismo ciclo duraría unos 60 ms tiempo →

Un maestro Modbus de verdad no es «un bucle que lee». Es una pequeña máquina de estados que tiene que decidir qué hacer cuando algo no sale. El diseño del taller sigue estas reglas:

  1. Sondeo secuencial. Se consulta a un esclavo, se espera su respuesta (o el tiempo máximo), y recién entonces al siguiente. Nunca dos peticiones en vuelo.
  2. Tiempo máximo (timeout) acotado. 100 ms, más que el peor caso de una respuesta de 3 registros (unos 20 ms) y razonablemente corto.
  3. Reintentos. Ante un timeout o un CRC erróneo, se reintenta hasta 2 veces. Ante una excepción, no se reintenta: se registra y se sigue (el pedido está mal, no el enlace).
  4. Marcar caídos. Después de 3 consultas seguidas fallidas (cada una con sus reintentos), el esclavo pasa a «fuera de línea». Se conserva su último valor, pero se avisa que está desactualizado.
  5. Retroceso (backoff). Un esclavo fuera de línea no se sondea en cada ciclo, sino cada 10 segundos. Si contesta, vuelve a línea.
  6. Estadísticas por esclavo. Consultas intentadas, correctas, timeouts, CRC erróneos y excepciones. Son la herramienta de diagnóstico más valiosa que tiene el maestro.

El punto 5 pesa más de lo que parece. Un esclavo caído cuesta tres intentos de 100 ms por ciclo: en la figura, el ciclo pasa de unos 60 ms a unos 374 ms, y los otros dos esclavos sanos se actualizan seis veces más lento por culpa del tercero. Con el retroceso, el nodo caído cuesta esos 300 ms una vez cada 10 s y el ciclo vuelve a la normalidad. Es la diferencia entre un sistema que «anda lento cuando algo falla» y uno que se degrada con elegancia.

El otro costo oculto es el de los reintentos por ruido: en la figura, el esclavo 3 contesta con CRC erróneo en el primer intento y bien en el segundo, y el ciclo se alarga unos 20 ms. Un enlace con mucho ruido no se cae, pero cada reintento se paga en tiempo; por eso conviene mirar los contadores de CRC y no solo si «anda».

05 · El otro lado

El esclavo: contestar rápido

La regla de oro de un esclavo Modbus es contestar con lo que ya tiene. La petición llega, el esclavo tiene que responder en unos pocos milisegundos, y no es momento de ponerse a medir. El diseño del taller separa dos tareas:

  • La medición, en el loop, de forma periódica y sin bloquear: cada «período de muestreo» lee el sensor y actualiza los registros en memoria. Si la medición demora, lo hace entre peticiones.
  • La atención de la petición, que solo copia valores de memoria a la trama de respuesta. Es instantánea y no depende del sensor.

El resto son las obligaciones que vimos en el tema 25:

  • Detectar el fin de trama por silencio de 3,5 caracteres, no por cantidad de bytes.
  • Ignorar sin contestar lo que no es para él (otra dirección) y lo que llega con CRC erróneo (contándolo en el registro de diagnóstico).
  • Esperar el silencio de 3,5 caracteres antes de contestar, y levantar DE solo durante la transmisión, bajándolo apenas sale el último bit.
  • Contestar excepciones ante funciones no soportadas (01), direcciones inexistentes (02) o valores inválidos (03).
  • Callar ante una difusión (dirección 0) después de aplicarla.
  • Arrancar con DE en bajo (driver apagado) para no pisar el bus mientras el microcontrolador se inicializa.

Un detalle que da trabajo: al encender un nodo, su pin DE debe estar en bajo desde el primer instante. Si arranca en alto o flota, el driver del nodo queda habilitado y pisa a todos los demás hasta que el programa configura el pin. Por eso la resistencia de 10 kΩ a masa en DE.

06 · Poner a andar la red

Plan de pruebas

Una red RS-485 con varios nodos se arma por etapas, de lo más simple a lo más completo. Cada etapa se supera antes de pasar a la siguiente:

Plan de pruebas del taller, en orden.
EtapaCómo se haceResultado esperadoSi falla, sospechá de…
1. Lazo del transceptorUnir A con B de un nodo con puentes cortos al receptor y enviar un patrón (tema 23)Vuelve el patrónAlimentación del MAX3485, DE/RE, pines de la UART
2. Un maestro y un esclavo, cortosDos placas, 30 cm de cable, con terminadores y polarizaciónLectura de 3 registros correctaDirección, velocidad, formato, polaridad A/B
3. Mapa de registrosLeer los tres registros de entrada y los tres de retención; escribir el período; accionar el reléValores razonables, eco correcto en las escriturasNumeración (desfase de uno), escala, signo
4. Los tres esclavosAgregar los nodos 2 y 3 en cadena, con el cable definitivoLos tres contestan en cada cicloDirecciones repetidas, terminadores de más
5. Reposo del busMedir A − B con el bus calladoMás de 200 mV (idealmente 300 mV)Falta de polarización, o polarización repartida entre nodos
6. Fallas provocadasApagar un nodo, desconectar un cable, cambiar una direcciónEl maestro marca el nodo caído y el ciclo no se degrada; al volver, se recupera soloRetroceso mal implementado, timeouts demasiado largos
7. Ruido y estrésSubir la velocidad, acercar un motor al cable, dejar correr una horaContadores de CRC en cero o casiTerminación, cable, masa, velocidad excesiva

Las etapas 6 y 7 son las que más se omiten y las que más sirven: una red que solo se probó con todo conectado y funcionando no se sabe cómo se comporta cuando algo falla. Provocar las fallas en el banco es mucho más barato que descubrirlas en la instalación.

07 · Con el microcontrolador

El código completo

El maestro reúne todo lo anterior: sondeo secuencial de una lista de esclavos, reintentos, detección de caídos con retroceso, estadísticas por nodo y una escritura de bobina condicionada a la temperatura. Reutiliza las funciones de CRC y de entrada/salida RS-485 del tema 25.

// MAESTRO: sondea una lista de esclavos Modbus RTU por RS-485
const int PIN_RX = 16, PIN_TX = 17, PIN_DE = 4;
const unsigned long BAUD = 19200;
const unsigned long T35_US = (BAUD > 19200) ? 1750UL : 38500000UL / BAUD;

const byte REINTENTOS = 2;                       // además del primer intento
const unsigned long TIMEOUT_MS = 100;
const byte FALLOS_PARA_CAER = 3;                 // consultas seguidas fallidas (cada una con sus reintentos)
const unsigned long REINTENTO_CAIDO_MS = 10000;  // cada cuánto se vuelve a probar un esclavo caído

struct Esclavo {
    byte id;
    const char *nombre;
    bool enLinea;
    byte fallosSeguidos;
    unsigned long proximoIntento;
    uint16_t intentos, ok, timeouts, crc, excepciones;
    int16_t temperaturaDecimas;
    uint16_t humedadDecimas, estado;
};
Esclavo red[] = { {1, "Sala", true}, {2, "Taller", true}, {3, "Deposito", true} };
const byte N = sizeof(red) / sizeof(red[0]);

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();
    digitalWrite(PIN_DE, LOW);
}

uint16_t rs485Recibir(byte *buf, uint16_t maximo, unsigned long timeoutMs) {
    uint16_t k = 0;
    unsigned long inicio = millis(), 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;
}

// Un intento. Devuelve: cantidad de registros (> 0), 0 = timeout, -1 = CRC, -2 = respuesta inconsistente, -100 - código = excepción
int intento(byte id, byte fc, uint16_t ini, byte cant, uint16_t *dest) {
    byte p[8] = { id, fc, (byte)(ini >> 8), (byte)(ini & 0xFF), 0, cant };
    uint16_t c = crc16(p, 6);
    p[6] = c & 0xFF; p[7] = c >> 8;
    while (Serial2.available()) Serial2.read();              // descarta restos de tramas anteriores
    rs485Enviar(p, 8);
    byte r[256];
    uint16_t n = rs485Recibir(r, sizeof(r), TIMEOUT_MS);
    if (n == 0) return 0;
    if (n < 5 || crc16(r, n) != 0) return -1;
    if (r[0] != id || (r[1] & 0x7F) != fc) return -2;
    if (r[1] & 0x80) return -100 - r[2];
    if (r[2] != 2 * cant || n != 5 + 2 * cant) return -2;
    for (byte i = 0; i < cant; i++) dest[i] = ((uint16_t)r[3 + 2 * i] << 8) | r[4 + 2 * i];
    return cant;
}

// Una consulta completa con reintentos y estadísticas. true si se obtuvieron los datos.
bool consultar(Esclavo &e, byte fc, uint16_t ini, byte cant, uint16_t *dest) {
    for (byte k = 0; k <= REINTENTOS; k++) {
        e.intentos++;
        int r = intento(e.id, fc, ini, cant, dest);
        if (r > 0)        { e.ok++; return true; }
        if (r == 0)       e.timeouts++;
        else if (r == -1) e.crc++;
        else if (r <= -100) { e.excepciones++; return false; }      // una excepción no se reintenta
        else              e.crc++;                                 // respuesta inconsistente: se cuenta como dañada
    }
    return false;
}

bool escribirBobina(Esclavo &e, uint16_t dir, bool valor) {
    byte p[8] = { e.id, 0x05, (byte)(dir >> 8), (byte)(dir & 0xFF), (byte)(valor ? 0xFF : 0x00), 0x00 };
    uint16_t c = crc16(p, 6);
    p[6] = c & 0xFF; p[7] = c >> 8;
    rs485Enviar(p, 8);
    byte r[16];
    uint16_t n = rs485Recibir(r, sizeof(r), TIMEOUT_MS);
    return n == 8 && memcmp(r, p, 8) == 0;                    // la respuesta es un eco exacto
}

void sondear(Esclavo &e) {
    if (!e.enLinea && millis() < e.proximoIntento) return;       // retroceso: no se insiste con un caído
    uint16_t reg[3];
    if (consultar(e, 0x04, 0, 3, reg)) {
        e.temperaturaDecimas = (int16_t)reg[0];
        e.humedadDecimas = reg[1];
        e.estado = reg[2];
        if (!e.enLinea) { e.enLinea = true; Serial.print(e.nombre); Serial.println(": vuelve a línea"); }
        e.fallosSeguidos = 0;
        if (e.temperaturaDecimas > 280 && !(e.estado & 1)) escribirBobina(e, 0, true);     // control simple: enciende el relé
        if (e.temperaturaDecimas < 260 &&  (e.estado & 1)) escribirBobina(e, 0, false);
    } else if (++e.fallosSeguidos >= FALLOS_PARA_CAER) {
        if (e.enLinea) { Serial.print(e.nombre); Serial.println(": FUERA DE LÍNEA"); }
        e.enLinea = false;
        e.proximoIntento = millis() + REINTENTO_CAIDO_MS;
    }
}

void imprimirEstado() {
    for (byte i = 0; i < N; i++) {
        Esclavo &e = red[i];
        Serial.print(e.nombre); Serial.print(e.enLinea ? " [en línea] " : " [CAÍDO]    ");
        Serial.print(e.temperaturaDecimas / 10.0, 1); Serial.print(" C  ");
        Serial.print(e.humedadDecimas / 10.0, 1); Serial.print(" %  | intentos "); Serial.print(e.intentos);
        Serial.print(" ok "); Serial.print(e.ok); Serial.print(" timeouts "); Serial.print(e.timeouts);
        Serial.print(" crc "); Serial.print(e.crc); Serial.print(" exc "); Serial.println(e.excepciones);
    }
    Serial.println();
}

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 unsigned long ultimoInforme = 0;
    for (byte i = 0; i < N; i++) sondear(red[i]);
    if (millis() - ultimoInforme > 5000) { ultimoInforme = millis(); imprimirEstado(); }
}

El esclavo mide en el loop de forma periódica y no bloqueante, y atiende las funciones 0x03, 0x04, 0x05 y 0x06 con los registros del mapa. Configurá ID_ESCLAVO en cada placa (1, 2 y 3) y el pin analógico de tu sensor:

// ESCLAVO: nodo de monitoreo con mapa de registros Modbus
const int PIN_RX = 16, PIN_TX = 17, PIN_DE = 4;
const int PIN_RELE = 25, PIN_TEMP = 34, PIN_HUM = 35;
const byte ID_ESCLAVO = 1;                       // 1, 2 o 3 según el nodo
const unsigned long BAUD = 19200;
const unsigned long T35_US = (BAUD > 19200) ? 1750UL : 38500000UL / BAUD;

uint16_t entrada[3];                             // 0: temperatura (décimas, con signo), 1: humedad (décimas), 2: estado
uint16_t retencion[3] = { 5, 0, 0 };             // 0: período de muestreo (s), 1: peticiones válidas, 2: peticiones con CRC erróneo
bool rele = false;

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 responder(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 fc, byte codigo) { byte r[5] = { ID_ESCLAVO, (byte)(fc | 0x80), codigo }; responder(r, 3); }

void atender(const byte *q, uint16_t n) {
    if (n < 4) return;
    if (q[0] != ID_ESCLAVO && q[0] != 0) return;                 // ni para mí ni difusión: ni siquiera se cuenta
    if (crc16(q, n) != 0) { retencion[2]++; return; }               // petición dañada: se cuenta y se ignora
    retencion[1]++;
    bool dif = (q[0] == 0);
    byte fc = q[1];
    uint16_t ini = (n >= 6) ? ((q[2] << 8) | q[3]) : 0, dato = (n >= 6) ? ((q[4] << 8) | q[5]) : 0;
    if ((fc == 0x03 || fc == 0x04) && n == 8) {
        if (dif) return;
        const uint16_t *tabla = (fc == 0x03) ? retencion : entrada;
        const uint16_t tam = 3;
        if (dato < 1 || dato > 125) { excepcion(fc, 3); return; }
        if (ini + dato > tam)       { excepcion(fc, 2); return; }
        byte r[3 + 250 + 2];
        r[0] = ID_ESCLAVO; r[1] = fc; r[2] = dato * 2;
        for (uint16_t i = 0; i < dato; i++) { r[3 + 2 * i] = tabla[ini + i] >> 8; r[4 + 2 * i] = tabla[ini + i] & 0xFF; }
        responder(r, 3 + dato * 2);
    } else if (fc == 0x05 && n == 8) {
        if (ini != 0)                         { if (!dif) excepcion(fc, 2); return; }
        if (dato != 0xFF00 && dato != 0x0000) { if (!dif) excepcion(fc, 3); return; }
        rele = (dato == 0xFF00);
        digitalWrite(PIN_RELE, rele ? HIGH : LOW);
        if (!dif) { byte r[8]; memcpy(r, q, 6); responder(r, 6); }
    } else if (fc == 0x06 && n == 8) {
        if (ini >= 3)                          { if (!dif) excepcion(fc, 2); return; }
        if (ini == 0 && (dato < 1 || dato > 3600)) { if (!dif) excepcion(fc, 3); return; }
        retencion[ini] = dato;
        if (!dif) { byte r[8]; memcpy(r, q, 6); responder(r, 6); }
    } else if (!dif) {
        excepcion(fc, 1);
    }
}

void medir() {                                    // se llama periódicamente desde loop(), nunca desde atender()
    int raw = analogRead(PIN_TEMP);
    float mv = raw * 3300.0 / 4095.0;
    float tempC = (mv - 500.0) / 10.0;            // TMP36: 500 mV a 0 °C, 10 mV por °C
    bool falla = (raw < 20 || raw > 4075);        // sensor desconectado o en cortocircuito
    entrada[0] = (uint16_t)(int16_t)lroundf(tempC * 10);
    entrada[1] = (uint16_t)(analogRead(PIN_HUM) * 1000L / 4095);   // potenciómetro: 0 a 100,0 %
    entrada[2] = (rele ? 1 : 0) | (falla ? 2 : 0);
}

void setup() {
    pinMode(PIN_DE, OUTPUT);
    digitalWrite(PIN_DE, LOW);                   // lo primero: driver apagado
    pinMode(PIN_RELE, OUTPUT);
    Serial.begin(115200);
    Serial2.begin(BAUD, SERIAL_8N1, PIN_RX, PIN_TX);
    medir();
}

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

Cinco decisiones del código. El maestro nunca bloquea más que un timeout por intento y una escritura que falla simplemente se repite en el siguiente ciclo. La lista de esclavos es una tabla: agregar un nodo es agregar una línea. Cada nodo lleva su estado (en línea o caído, fallos seguidos, próximo intento) y sus contadores. El esclavo cuenta lo que ve (peticiones válidas y con CRC erróneo) en registros que el maestro puede leer, para el diagnóstico cruzado. Y la medición no se hace al atender: la respuesta sale de la memoria.

08 · Ver lo que pasa en el cable

Verificar con el analizador

Un analizador lógico (o un convertidor USB–RS-485 con un monitor serie) conectado a A y B muestra las tramas que se cruzan. Estas son las capturas correctas de cada operación del taller, con los bytes y el CRC (a 19 200 baudios cada byte dura unos 0,57 ms):

Tramas de referencia del taller (esclavo 1; los CRC se envían con el byte bajo primero).
OperaciónPeticiónRespuesta
Leer temperatura, humedad y estado (0x04, 3 registros)01 04 00 00 00 03 B0 0B01 04 06 00 EB 01 E0 00 01 85 4E (23,5 °C, 48,0 %, relé activado)
Pedir más registros de los que hay01 04 00 00 00 04 F1 C901 84 02 C2 C1 (excepción 02)
Registro de retención inexistente01 03 00 05 00 01 94 0B01 83 02 C0 F1 (excepción 02)
Encender el relé (0x05, bobina 0)01 05 00 00 FF 00 8C 3Ael mismo eco de 8 bytes

Cuatro cosas para revisar en la captura, en este orden: 1) que cada petición sea seguida de su respuesta tras un silencio corto (de 2 ms a 19 200 baudios, más el procesamiento) y que no haya bytes sueltos en los silencios; 2) que los CRC cierren; 3) que el esclavo que contesta sea el consultado, con la función correcta; 4) que la línea DE de cada nodo suba y baje sin pisar la petición siguiente. Un decodificador de Modbus RTU (el de la mayoría de los analizadores) hace las tres primeras comprobaciones por vos y marca en rojo las tramas dañadas.

Y un truco de diagnóstico propio de este diseño: comparar los contadores del esclavo con los del maestro. Si el maestro lee en el registro de retención 2 de un nodo una cuenta de CRC erróneos que crece, las peticiones llegan dañadas (problema en el camino de ida o en el receptor del nodo); si ese contador queda en cero pero el maestro cuenta CRC erróneos en las respuestas, el problema está en el camino de vuelta (el transmisor del nodo, su DE, la terminación vista desde el maestro).

09 · Analogía con programación

El sondeo con fallas en Python

La política de sondeo se puede probar sin ningún hardware. El modelo siguiente implementa tres esclavos (uno sano, uno caído y uno con ruido), el bus, el maestro con reintentos, retroceso y estadísticas, y el cálculo del tiempo que consume cada política. Con él se ve el efecto de un esclavo caído sobre todo el ciclo y la ganancia del retroceso.

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

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])


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])


class Esclavo:
    def __init__(self, direccion, entrada, retencion, vivo=True, ruido_cada=0):
        self.dir, self.entrada, self.retencion = direccion, dict(entrada), dict(retencion)
        self.vivo, self.ruido_cada, self.enviadas = vivo, ruido_cada, 0

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

    def atender(self, trama):
        if not self.vivo or len(trama) < 4 or crc16(trama) != 0 or trama[0] != self.dir:
            return None
        fc = trama[1]
        if fc in (3, 4):
            tabla = self.retencion if fc == 3 else self.entrada
            inicio, cant = (trama[2] << 8) | trama[3], (trama[4] << 8) | trama[5]
            if not 1 <= cant <= 125:
                return self.excepcion(fc, 3)
            if any(inicio + i not in tabla for i in range(cant)):
                return self.excepcion(fc, 2)
            datos = b"".join(tabla[inicio + i].to_bytes(2, "big") for i in range(cant))
            respuesta = con_crc([self.dir, fc, len(datos)] + list(datos))
            self.enviadas += 1
            if self.ruido_cada and self.enviadas % self.ruido_cada == 0:
                respuesta = respuesta[:-1] + bytes([respuesta[-1] ^ 0xFF])      # se daña el último byte del CRC
            return respuesta
        return self.excepcion(fc, 1)


class Bus:
    def __init__(self, esclavos):
        self.esclavos = esclavos

    def consulta(self, trama):
        for e in self.esclavos:
            r = e.atender(trama)
            if r is not None:
                return r
        return None                                                           # nadie contestó: timeout


def crear_red():
    return Bus([
        Esclavo(1, {0: 235, 1: 480, 2: 1}, {0: 60, 1: 0, 2: 0}),                  # sano
        Esclavo(2, {0: 198, 1: 520, 2: 1}, {0: 60}, vivo=False),                  # apagado
        Esclavo(3, {0: 0xFFF1, 1: 655, 2: 0}, {0: 60}, ruido_cada=3),             # una de cada tres respuestas llega dañada
    ])


def sondeo(bus, ids, ciclos, reintentos=2, retroceso=False):
    estado = {i: dict(intentos=0, ok=0, timeout=0, crc=0, exc=0, fallos=0, proximo=0) for i in ids}
    for ciclo in range(ciclos):
        for i in ids:
            e = estado[i]
            if retroceso and ciclo < e["proximo"]:
                continue                                                      # caído: no se insiste
            pedido = pedido_lectura(i, 4, 0, 3)
            conseguido = False
            for _ in range(reintentos + 1):
                e["intentos"] += 1
                r = bus.consulta(pedido)
                if r is None:
                    e["timeout"] += 1
                    continue
                if crc16(r) != 0:
                    e["crc"] += 1
                    continue
                if r[1] & 0x80:
                    e["exc"] += 1
                    break                                                     # una excepción no se reintenta
                e["ok"] += 1
                conseguido = True
                break
            if conseguido:
                e["fallos"] = 0
            else:
                e["fallos"] += 1
                if retroceso and e["fallos"] >= 3:
                    e["proximo"] = ciclo + 10
    return estado


def consulta_ms(baud, n, proc_ms=5):
    car = 11 / baud * 1000
    t35 = 1.75 if baud > 19200 else 3.5 * car
    return 8 * car + t35 + proc_ms + (5 + 2 * n) * car + t35


def timeout_ms(baud, espera=100):
    return 8 * 11 / baud * 1000 + espera


def tiempo_total(estado, baud=19200):
    return sum((e["ok"] + e["crc"] + e["exc"]) * consulta_ms(baud, 3) + e["timeout"] * timeout_ms(baud) for e in estado.values())


bus = crear_red()
for titulo, p in (("leer temperatura, humedad y estado del esclavo 1", pedido_lectura(1, 4, 0, 3)),
                  ("pedir 4 registros de entrada (hay 3)", pedido_lectura(1, 4, 0, 4)),
                  ("leer el registro de retención 5 (no existe)", pedido_lectura(1, 3, 5, 1))):
    r = bus.consulta(p)
    print(f"{titulo}\n   pedido    {hexa(p)}\n   respuesta {hexa(r)}")
r = bus.consulta(pedido_lectura(1, 4, 0, 3))
temperatura, humedad, estado_nodo = (int.from_bytes(r[3 + 2 * i:5 + 2 * i], "big") for i in range(3))
print(f"   -> {temperatura / 10:.1f} °C, {humedad / 10:.1f} %, relé {'activado' if estado_nodo & 1 else 'apagado'}")
r3 = crear_red().consulta(pedido_lectura(3, 4, 0, 3))
t3 = int.from_bytes(r3[3:5], "big")
print(f"Esclavo 3: temperatura {t3:#06x} = {(t3 - 65536 if t3 & 0x8000 else t3) / 10:.1f} °C (con signo)")

print()
print(f"Una consulta de 3 registros a 19200 baudios: {consulta_ms(19200, 3):.2f} ms; un intento sin respuesta: {timeout_ms(19200):.2f} ms")
for retroceso in (False, True):
    est = sondeo(crear_red(), [1, 2, 3], 10, retroceso=retroceso)
    print(f"\nSondeo de 10 ciclos {'con' if retroceso else 'sin'} retroceso:")
    for i, e in est.items():
        print(f"  esclavo {i}: {e['ok']:2} consultas correctas en {e['intentos']:2} intentos (timeouts {e['timeout']:2}, CRC {e['crc']})")
    total = tiempo_total(est)
    print(f"  tiempo total {total:.1f} ms = {total / 10:.1f} ms por ciclo")
print(f"\nCiclo ideal con los tres esclavos sanos: {3 * consulta_ms(19200, 3):.1f} ms")

Resultado: la lectura de tres registros del esclavo 1 es 01 04 00 00 00 03 B0 0B con respuesta 01 04 06 00 EB 01 E0 00 01 85 4E, que son 23,5 °C, 48,0 % y el relé activado. Pedir 4 registros devuelve la excepción 01 84 02 C2 C1 y el registro de retención 5, 01 83 02 C0 F1. La temperatura del esclavo 3 (0xFFF1) vale −1,5 °C con signo. Una consulta de 3 registros a 19 200 baudios dura 19,90 ms y un intento sin respuesta, 104,58 ms. En 10 ciclos sin retroceso: el esclavo 1 hace 10 consultas correctas en 10 intentos; el 2 (apagado) no contesta nunca y gasta 30 intentos, todos timeouts; el 3 necesita 14 intentos para 10 consultas porque 4 respuestas llegan con CRC erróneo y se reintentan. El tiempo total es de 3615,0 ms, o 361,5 ms por ciclo. Con retroceso, el esclavo 2 se prueba solo 3 veces (9 intentos) antes de darlo por caído, y el total baja a 1418,8 ms, o 141,9 ms por ciclo. Un ciclo con los tres esclavos sanos duraría 59,7 ms.

Observá lo que muestra el modelo: el esclavo caído, que no produce ningún dato, consume la mayor parte del tiempo de bus, y el retroceso lo reduce a una fracción. En 10 ciclos el retroceso se nota poco porque el esclavo 2 todavía se prueba tres veces; en una operación de horas, solo se lo prueba una vez cada 10 s.

10 · Exploración interactiva

Laboratorio: sondeo, fallas, escaneo y estadísticas

El laboratorio tiene cuatro herramientas. La primera calcula el ciclo de sondeo con y sin esclavos caídos, y con o sin retroceso. La segunda es un catálogo de fallas: elegís una y ves qué marcan los contadores del maestro, qué muestra el analizador y cómo se corrige. La tercera estima cuánto tarda un escaneo de direcciones. La cuarta interpreta las estadísticas del maestro y sugiere el diagnóstico.

1. Ciclo de sondeo

Una consulta correcta—
Ciclo con todos sanos—
Ciclo con los caídos—
Parte del ciclo perdida en los caídos—
Actualizaciones por segundo de cada esclavo sano—
Veredicto—

Elegí los parámetros de la red y de la política de sondeo.

2. Catálogo de fallas

Lo que marcan los contadores del maestro—
Lo que muestra el analizador—
Causa—
Cómo se corrige—

Elegí una falla para ver su patrón.

3. Escaneo de direcciones

Una dirección que contesta—
Una dirección vacía (timeout)—
Escaneo completo—
Veredicto—

Elegí el rango y el tiempo máximo.

4. Estadísticas del maestro

Tasa de éxito por intento—
Reparto de las fallas—
Diagnóstico—
Veredicto—

Cargá los contadores del maestro.

En la herramienta 1, un esclavo caído cuesta (reintentos + 1) intentos de 8 bytes de petición más el tiempo máximo; los esclavos sanos, una consulta completa con 5 ms de procesamiento. En la 2, los patrones son los típicos de cada falla (con ruido, los números exactos varían). En la 3, la dirección que contesta cuesta una consulta de 1 registro y la vacía, una petición más el tiempo máximo. En la 4, los porcentajes se calculan sobre los intentos totales.

Recorrido guiado

  1. Con los valores iniciales (19 200 baudios, 3 esclavos, 3 registros, 100 ms, 2 reintentos, 1 caído, sin retroceso): una consulta correcta dura 19,90 ms, el ciclo sano 59,7 ms y el ciclo con el esclavo caído 353,5 ms. El caído se lleva el 89 % del ciclo y los otros dos esclavos pasan de 16,8 a 2,8 actualizaciones por segundo.
  2. Pasá a «Retroceso»: el caído solo cuesta un décimo de lo anterior: ciclo promedio de 71,2 ms y 14,1 actualizaciones por segundo.
  3. Probá 0 reintentos con un caído sin retroceso: el ciclo baja a 144,4 ms (un solo intento de 104,6 ms). Con un timeout de 300 ms y 2 reintentos, un caído cuesta 914 ms y el ciclo llega a 953,5 ms. El timeout y los reintentos se pagan cada vez que algo falla.
  4. Subí a 115 200 baudios con 3 esclavos sanos (0 caídos): la consulta baja a 10,31 ms y el ciclo a 30,9 ms, pero con un caído el ciclo sigue en unos 323 ms: el timeout de 100 ms domina y la velocidad no ayuda con los timeouts.
  5. En la herramienta 2, recorré las fallas. «Esclavo apagado» y «dirección equivocada» marcan lo mismo (timeouts de un solo esclavo): para distinguirlas hay que mirar si el esclavo está alimentado o confirmar su dirección. «Velocidad distinta» y «paridad distinta» producen timeouts o tramas que el decodificador marca como erróneas a la velocidad del maestro.
  6. «A y B invertidos»: el esclavo afectado nunca es entendido (o es entendido al revés): en el analizador se ve la señal negada. «Dos esclavos con la misma dirección»: CRC erróneos constantes en esa dirección y respuestas mezcladas en la captura. «DE liberada tarde»: la falla aparece en el esclavo siguiente del ciclo, no en el que se pasó.
  7. «Registro inexistente»: no es una falla del enlace; el maestro cuenta excepciones, no timeouts. Se corrige el mapa, no el cable.
  8. En la herramienta 3, con los valores iniciales (1 a 247, 3 presentes, 100 ms, 19 200 baudios): una dirección que contesta tarda 17,6 ms, una vacía 104,6 ms y el escaneo completo 25,6 s. Con 30 ms de tiempo máximo, baja a 8,5 s. Con 1 a 32 y 30 ms, 1,1 s.
  9. En la herramienta 4, con los contadores iniciales (1000 intentos, 940 correctos, 40 timeouts, 15 CRC, 5 excepciones): 94 % de éxito y las fallas son sobre todo timeouts (67 %). Cargá 785 correctas, 10 timeouts, 200 CRC y 5 excepciones: la tasa baja a 78,5 %, los CRC son el 93 % de las fallas y el diagnóstico pasa a «ruido»: terminación, polarización, velocidad, direcciones repetidas. Con 1000 timeouts y ninguna correcta, «sin comunicación».
Desafío: cuántos esclavos caben en un ciclo de un segundo

Con 19 200 baudios, 3 registros por esclavo y 5 ms de procesamiento, cada consulta cuesta 19,9 ms. En un ciclo de 1 s caben 1000 / 19,9 ≈ 50 esclavos sanos. Pero con k esclavos caídos sin retroceso, cada uno consume 3 × 104,6 ≈ 314 ms: con 2 caídos ya se van 628 ms, y quedan 372 ms, o sea unos 18 esclavos sanos por ciclo de un segundo. Con retroceso, los caídos cuestan casi nada y vuelven a caber unos 47.

11 · Qué viene después

Ampliaciones y buenas prácticas

El taller deja una red que funciona y se diagnostica. Se puede extender en varias direcciones:

  • Sondeo con prioridades. Los datos críticos (una alarma) se consultan en cada ciclo y los lentos (una temperatura que cambia en minutos) cada varios ciclos. Reduce el tiempo de ciclo sin perder reacción.
  • Registro de eventos. Guardar con fecha cada caída y cada vuelta a línea de un nodo, en una memoria (tema 20), para detectar nodos intermitentes.
  • Pasarela a Ethernet o Wi-Fi. Exponer los valores por Modbus TCP o MQTT desde el maestro, para integrarlo con un panel o un sistema de supervisión (temas siguientes).
  • Escritura confirmada. Comparar el eco de las escrituras y releer el registro después de escribir parámetros importantes.
  • Más funciones. Lectura de bobinas (0x01) y entradas discretas (0x02), escritura de varios registros (0x10), identificación del dispositivo (0x11).
  • Esclavos con perro guardián y reinicio de DE. Un nodo que se cuelga con DE en alto bloquea toda la red; un perro guardián que reinicie la placa y un arranque con DE en bajo lo evitan.
  • Transceptores aislados y protección para nodos en edificios distintos o con maquinaria cercana (tema 24).

Y cuatro hábitos que valen para cualquier red de campo: documentar el mapa de registros y las direcciones, medir y registrar los contadores de errores desde el primer día, probar las fallas en el banco antes de instalar y diseñar para que un nodo caído no arrastre a los demás.

12 · Comprensión

Confusiones frecuentes

«Si no contesta, hay que insistir hasta que conteste»

Insistir sin límite congela el ciclo de todos los demás. Se reintenta un par de veces, se marca el nodo como caído y se lo vuelve a probar de vez en cuando (retroceso).

«Un timeout alto es más seguro»

Un timeout demasiado largo hace que cada falla cueste mucho: con 300 ms y 2 reintentos, un solo esclavo caído suma casi un segundo por ciclo. El timeout debe ser algo mayor que el peor tiempo de respuesta real, no una cifra generosa.

«Si un esclavo responde excepción, el enlace está mal»

Al contrario: la excepción prueba que el enlace funciona. El problema es el pedido (dirección, cantidad, función). No se reintenta y no se cuenta como falla de comunicación.

«Los CRC erróneos son ruido»

Pueden serlo, pero también son síntoma de una dirección repetida, de una terminación ausente, de una velocidad demasiado alta o de una DE liberada tarde. La comparación entre los contadores del maestro y del esclavo ayuda a ubicar de qué lado ocurre.

«Si la falla aparece en el esclavo 3, el problema está en el esclavo 3»

No siempre: una DE liberada tarde del esclavo 2 pisa la petición al 3. El nodo que falla es el siguiente del ciclo. Cuando no se encuentra nada en el nodo que falla, hay que mirar al que se consulta justo antes.

«Medir en el esclavo cuando llega la petición es más fresco»

Mide más lento y retrasa la respuesta, a veces más allá del timeout del maestro. Se mide periódicamente en segundo plano y se contesta con lo que ya está en memoria.

«Con un escaneo de 247 direcciones sé qué hay en la red»

Lo que sabés es qué direcciones contestaron en ese momento, y el escaneo es lento (con 100 ms de timeout, unos 25 s). Además, una escritura a ciegas puede cambiar la configuración de un equipo desconocido: el escaneo debe usar solo lecturas inofensivas.

«El nodo caído no afecta a los demás»

Afecta, y mucho, al sondeo secuencial: cada intento sin respuesta consume el timeout completo. Sin una política de caídos, un solo nodo apagado puede multiplicar por seis el ciclo.

13 · Práctica guiada

Predecí antes de comprobar

1. Armar las tramas

Escribí los 6 bytes (sin CRC) para (a) leer los tres registros de entrada del esclavo 2 y (b) encender el relé del esclavo 3. ¿Cuántos bytes tiene cada respuesta?

Ver solución

(a) 02 04 00 00 00 03; la respuesta tiene 5 + 2 · 3 = 11 bytes. (b) 03 05 00 00 FF 00; la respuesta es un eco de 8 bytes (con el CRC).

2. Decodificar un nodo

La respuesta de un nodo a la lectura de 3 registros de entrada trae los datos FF E7 01 F4 00 02. ¿Qué dice?

Ver solución

Registro 0 = 0xFFE7 = 65 511, que con signo es −25: −2,5 °C. Registro 1 = 0x01F4 = 500: 50,0 %. Registro 2 = 0x0002: el bit 1 está en 1, es decir, falla del sensor (y el relé apagado). La temperatura no es confiable.

3. El costo de un caído

Con 19 200 baudios (un intento sin respuesta = 104,6 ms), 2 reintentos y 5 esclavos, de los cuales 1 está caído, ¿cuánto dura el ciclo? ¿Y con retroceso (uno de cada 10 ciclos)?

Ver solución

Los sanos: 4 · 19,9 = 79,6 ms. El caído: 3 · 104,58 = 313,7 ms. Ciclo: 393,3 ms. Con retroceso: 79,6 + 313,7 / 10 ≈ 111 ms.

4. Un timeout bien elegido

A 9600 baudios, ¿cuánto tarda en llegar la respuesta de 3 registros desde que termina la petición, con 5 ms de procesamiento? ¿Es razonable un timeout de 50 ms?

Ver solución

tcar = 1,146 ms; t3,5 = 4,01 ms. Respuesta de 11 bytes: 12,6 ms. Desde el fin de la petición hasta el fin de la respuesta: 4,01 + 5 + 12,6 = 21,6 ms. Un timeout de 50 ms deja más del doble de margen; es razonable. Uno de 20 ms sería demasiado justo.

5. Interpretar los contadores

Los contadores del maestro para un esclavo dicen: 500 intentos, 497 correctos, 0 timeouts, 3 CRC, 0 excepciones; y el contador de CRC erróneos que lee del propio esclavo vale 0. ¿Dónde se dañan las tramas?

Ver solución

El maestro recibió tres respuestas dañadas, pero el esclavo nunca recibió una petición dañada (su contador está en cero): el daño ocurre en el camino de vuelta (respuesta del esclavo). Sospechosos: el transmisor o el DE de ese nodo, la terminación vista desde el maestro, ruido localizado cerca del maestro.

6. Una falla en cadena

En un ciclo de cuatro esclavos (1, 2, 3, 4), el esclavo 3 falla siempre por timeout y los demás andan. Revisaste el 3 (alimentación, dirección y formato) y está bien. ¿Qué más sospecharías?

Ver solución

El nodo anterior (el 2): si libera DE tarde, su driver sigue activo cuando el maestro empieza la petición al 3 y la pisa; el 3 nunca la recibe completa. Se mira con el analizador (la línea DE del esclavo 2 respecto de la trama) y se corrige bajando DE justo después del flush.

7. Cuántos escaneos

Querés buscar esclavos en las direcciones 1 a 247 a 19 200 baudios con 25 ms de tiempo máximo y hay 4 presentes. ¿Cuánto tarda?

Ver solución

Una dirección vacía: 4,58 ms (petición) + 25 = 29,6 ms; 243 vacías = 7,2 s. Una presente: 17,6 ms; 4 = 70 ms. Total: 7,3 s. Con 100 ms de tiempo máximo serían 25,5 s. (Un tiempo máximo de 25 ms es suficiente para una respuesta de un registro de un equipo normal, de unos 18 ms.)

14 · Síntesis

Ideas para recordar

  • Una red Modbus RS-485 se diseña desde el mapa de registros: tres registros contiguos para lo que se lee junto, enteros escalados, mediciones en registros de entrada, parámetros en retención, contadores de diagnóstico en el esclavo y un bit de falla del sensor.
  • El maestro es una máquina de estados: sondeo secuencial, tiempo máximo acotado, reintentos ante timeout o CRC (no ante excepciones), marca de «caído» tras varios fallos seguidos, retroceso para los caídos y estadísticas por esclavo.
  • Un esclavo caído cuesta tres timeouts por ciclo: puede multiplicar por seis el ciclo y degradar a todos. El retroceso lo reduce a una fracción.
  • El esclavo contesta con lo que tiene en memoria (la medición se hace en segundo plano), detecta el fin de trama por silencio, espera t3,5 antes de responder, arranca con DE en bajo y calla ante lo que no es para él.
  • La red se prueba por etapas: lazo del transceptor, un maestro y un esclavo, mapa de registros, todos los nodos, reposo del bus, fallas provocadas y estrés.
  • Las tramas de referencia (con su CRC) permiten verificar la red con un analizador: petición y respuesta, silencios, esclavo y función correctos, DE sin pisar.
  • Cada falla deja un patrón: timeout de un solo esclavo (apagado, dirección, formato, polaridad), CRC constantes (dirección repetida), CRC esporádicos (ruido, terminación, velocidad), falla en el esclavo siguiente (DE tarde), excepciones (mapa o numeración).
  • Comparar los contadores del maestro con los del esclavo ubica el daño en el camino de ida o de vuelta.

¿Listo para el siguiente bloque?

Antes de avanzar, calculá el ciclo de sondeo de 8 esclavos a 19 200 baudios leyendo 3 registros cada uno (19,9 ms por consulta) cuando 2 están caídos con 2 reintentos y 100 ms de tiempo máximo; explicá por qué el esclavo no debe medir el sensor al recibir la petición; y decidí qué sospecharías si el esclavo 5 falla siempre pero el 4 y el 6 funcionan y el 5 está bien. (Para la primera: 6 · 19,9 + 2 · 3 · 104,58 = 119,4 + 627,5 = 746,9 ms.) Si podés justificar las tres respuestas, tenés el bloque de buses industriales.

En el próximo tema empezamos con CAN, el bus de los vehículos: señal diferencial, identificadores en lugar de direcciones, arbitraje por prioridad, tramas y filtros.