DÍA 139 DE 365

Fase 3: Comunicaciones y Redes Industriales

📖 Teoría a Estudiar

El Paradigma Maestro / Esclavo

En RS485, si varios equipos hablan al mismo tiempo, el choque eléctrico corrompe los datos. Para evitar esto, Modbus utiliza una arquitectura Maestro / Esclavo estricta. El Servidor Central de la estación de gas es el Maestro. Tu Despachador, junto con otros despachadores, son los Esclavos.

Regla de Oro Modbus: Un esclavo jamás habla a menos que el Maestro le pregunte directamente. El Maestro envía una petición, el esclavo la procesa y responde, e inmediatamente vuelve a guardar silencio bajando el pin `DE/RE` (Día 138) para no estorbar en el cable.

El Mapa de Memoria Modbus

Para simplificar la vida, Modbus divide todas las variables de tu máquina en 4 grandes "Tablas" lógicas (Registros). En nuestro STM32, esto serán simples arreglos (Arrays) globales en lenguaje C:

ANATOMÍA DEL PROTOCOLO MODBUS RTU: TRAMAS Y REGISTROS MASTER "Lee el Precio" TRAMA DE PETICIÓN (Modbus Request Frame) Addr: 01 (1 Byte) Func: 03 (Read Hold Reg) Reg: 0000 (Dir Inicial) Count: 0001 (Cant. de Regs) CRC16 (2 Bytes) STM32 (SLAVE 01) holding_regs[0] 1050 ($10.50 Precio) TRAMA DE RESPUESTA (Modbus Response Frame) Addr: 01 (Yo hablo) Func: 03 (Respondo) Bytes: 02 (Longitud) Data: 04 1A (1050 Decimal) CRC16 (Nuevo CRC) RS485 Bus

La Trama Modbus RTU

Cada paquete en el bus RS485 sigue una estructura (Trama o Frame) matemáticamente estricta, enviada byte por byte (Formato HEX):

  1. ID del Esclavo (1 byte): A quién va dirigido. (0 = Broadcast/A todos).
  2. Código de Función (1 byte): Qué quieres que haga (Ej. `0x03` = Leer Holding Register, `0x06` = Escribir Registro simple).
  3. Payload de Datos (N bytes): Depende de la función. Si es lectura, contiene la dirección de memoria que se quiere leer.
  4. Firma CRC16 (2 bytes): Código de Redundancia Cíclica. Una operación matemática compleja que certifica que los datos no se corrompieron por ruido eléctrico en el viaje.

⚙️ Ejercicios Prácticos

Ejercicio 1: Prepararemos las bases estructurales de la Memoria Modbus en nuestro STM32. Definiremos los arreglos en C que simularán los registros industriales. Analizaremos un paquete que entró silenciosamente a nuestra RAM gracias al **DMA e interrupción IDLE (Día 134)**, y extraeremos el ID, la Función y los Datos mediante aritmética de punteros.


/**
 * @file modbus_core.c
 * @brief Estructura Central de Mapa de Memoria e Intérprete Modbus RTU
 */

#include "stm32f401xe.h"
#include <stdint.h>

// --- ID DE LA MÁQUINA ---
// Si hay 10 bombas en la estación, a esta placa le asignamos la dirección 0x01
#define MI_MODBUS_ID 0x01

// --- CÓDIGOS DE FUNCIÓN OFICIALES MODBUS ---
#define MODBUS_FC_READ_COILS          0x01
#define MODBUS_FC_READ_DISCRETE       0x02
#define MODBUS_FC_READ_HOLDING_REGS   0x03
#define MODBUS_FC_READ_INPUT_REGS     0x04
#define MODBUS_FC_WRITE_SINGLE_COIL   0x05
#define MODBUS_FC_WRITE_SINGLE_REG    0x06

// =================================================================
// EL MAPA DE MEMORIA (El Alma de la Máquina Expendedora)
// En Bare-Metal, son simples arreglos en la memoria SRAM
// =================================================================

// 4x: Holding Registers (Lectura y Escritura Remota por el Servidor)
// [0] = Precio actual por litro (Centavos)
// [1] = Factor K de la turbina (Pulsos x Litro)
// [2] = Comando de reinicio remoto (0=Normal, 1=Resetear Sistema)
uint16_t holding_registers[10] = {1050, 450, 0}; 

// 3x: Input Registers (Solo Lectura Remota, el STM32 los escribe)
// [0] = Litros despachados en la venta actual
// [1] = Importe total a cobrar
// [2] = Presión actual del tanque de Gas LP
uint16_t input_registers[10] = {0, 0, 0};

// --- ESTRUCTURA DE LA TRAMA BARE-METAL ---
// Utilizamos punteros crudos (raw pointers) para evitar crear structs
// que puedan sufrir de "Padding" por parte del compilador GCC.

/**
 * @brief Analizador inicial (Parser) de un paquete capturado por DMA
 * @param paquete: Puntero al arreglo de bytes en RAM
 * @param longitud: Cantidad de bytes descargados (Del NDTR del DMA)
 */
void Modbus_Parse_Trama(uint8_t* paquete, uint16_t longitud) {
    
    // 1. FILTRO DE BASURA Y RUIDO
    // Una trama Modbus RTU legítima jamás puede tener menos de 4 bytes 
    // (1 ID + 1 Func + 2 CRC). Si es menor, fue un "glitch" eléctrico.
    if (longitud < 4) return;
    
    // 2. FILTRO DE DESTINATARIO
    // El primer byte de la trama SIEMPRE es el ID del esclavo
    uint8_t esclavo_destino = paquete[0];
    
    if (esclavo_destino != MI_MODBUS_ID && esclavo_destino != 0x00) {
        // La pregunta no es para nosotros. Ignorar y guardar silencio en el bus.
        return; 
    }
    
    // 3. (AQUÍ EN EL DÍA 140 INSERTAREMOS LA VERIFICACIÓN DEL CRC16)
    // if (!Modbus_Validar_CRC(paquete, longitud)) return;
    
    // 4. DECODIFICACIÓN DEL COMANDO
    uint8_t funcion = paquete[1];
    
    // Si llegamos hasta aquí, el Servidor Central nos está hablando a nosotros
    switch (funcion) {
        
        case MODBUS_FC_READ_HOLDING_REGS: // 0x03
            // El servidor quiere saber, por ejemplo, el precio actual.
            // Los bytes 2 y 3 indican desde qué registro empezar (Big-Endian).
            // Los bytes 4 y 5 indican cuántos registros leer.
            // procesar_lectura_holding_regs(paquete);
            break;
            
        case MODBUS_FC_WRITE_SINGLE_REG: // 0x06
            // El servidor administrador nos manda el nuevo Precio del Gas para el día de hoy.
            // procesar_escritura_holding_reg(paquete);
            // holding_registers[direccion] = nuevo_valor;
            // EEPROM_PageWrite(...); // Guardarlo en memoria no volátil
            break;
            
        default:
            // Función no soportada. 
            // Según la norma, debemos responder con una Exception (Error 0x01)
            // enviar_modbus_exception(funcion, 0x01);
            break;
    }
}

int main(void) {
    
    // (Inicializaciones de fase 1, 2 y UART RX DMA del Día 134)
    
    while(1) {
        
        // Súper Bucle Asíncrono
        
        // if (bandera_trama_rs485_lista) {
        //     // El DMA ha capturado un paquete completo (IDLE)
        //     Modbus_Parse_Trama(paquete_comando, longitud_paquete);
        //     bandera_trama_rs485_lista = false;
        // }
    }
    
    return 0;
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Interoperabilidad Estándar e Integración SCADA.

Si inventaras tu propio protocolo serial casero (ej. enviar el texto *"DAME_PRECIO\n"*), tu Despachador de Gas LP sería un sistema cerrado. Cuando intentaras vender tu máquina a una gran empresa gasera, te dirían: *"Nuestro sistema ERP y nuestras pantallas HMI Delta no entienden tu código"*. Tendrías que reprogramar los servidores de la empresa a un costo millonario.

Al dotar a tu firmware de la estructura del protocolo **Modbus RTU**, tu Tesis se convierte mágicamente en equipo industrial *Plug and Play*. Has convertido tus variables estáticas en lenguaje C en una **Memoria de Holding Registers** estandarizada. Mañana, si conectas el par de cables RS485 a un PLC Siemens, un servidor de Ignition SCADA, o un simple adaptador USB a PC, los equipos extraerán el "Registro 40001" y automáticamente verán en sus pantallas gráficas el Precio por Litro del Despachador, sin importar que no tengan idea del código fuente que corre en tu STM32.

📝 Resumen del Día