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.
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:
Cada paquete en el bus RS485 sigue una estructura (Trama o Frame) matemáticamente estricta, enviada byte por byte (Formato HEX):
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;
}
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.