A nivel de arquitectura de software, hemos programado dos mundos que corren en paralelo dentro de nuestro STM32:
main(). Su trabajo es evaluar lo que el mundo asíncrono ha recolectado y tomar decisiones lentas pero críticas: actualizar la pantalla LCD por SPI, abrir válvulas y desencriptar tramas Modbus completas.El archivo main.c no debe contener lógicas matemáticas ni accesos a registros. Debe leerse como un índice de un libro. Inicia llamando al POST (Power-On Self Test) para validar el Hardware y el PCB. Luego arranca el HAL y las variables en RAM (protegidas con TMR). Finalmente, entra a un while(1) donde un gran switch-case gobierna los estados lógicos de la gasolinera: REPOSO, AUTORIZADO, DESPACHANDO, FINALIZADO y BLOQUEO.
Ejercicio 1: El Archivo Maestro. Construiremos el main.c definitivo que consolida los últimos 180 días de aprendizaje. Aplicaremos la compilación condicional para identificar el modelo, el POST para blindar la inicialización física, encapsulamiento para proteger las variables matemáticas, y la Máquina de Estados Finita (FSM) que controla el despacho.
/**
* @file main.c
* @author Arquitecto Embebidos 365
* @date 10 Septiembre 2026 (Cierre Q2)
* @brief Orquestador Maestro (Super Loop FSM) del Despachador de Gas LP
*/
#include "stm32f401xe.h"
#include "system_config.h" // Dia 176: Macros MISRA y Modelos
#include "post_system.h" // Dia 167: Power-On Self Test
#include "hal_gpio.h" // Dia 175: Hardware Abstraction Layer
#include "metrologia.h" // Dia 177: Getters/Setters y Variables TMR
#include "modbus_rtu.h" // Dia 139: Comunicaciones
#include "st7789_driver.h" // Dia 171: UI y Display
#include "valvula_pid.h" // Dia 151: Actuadores
// =========================================================================
// DEFINICIÓN DE ESTADOS DE LA MÁQUINA (FSM)
// =========================================================================
typedef enum {
SYS_STATE_INIT = 0,
SYS_STATE_POST_ERROR,
SYS_STATE_IDLE,
SYS_STATE_AUTHORIZED,
SYS_STATE_DISPENSING,
SYS_STATE_FINISHED
} SystemState_t;
static SystemState_t estado_actual = SYS_STATE_INIT;
// =========================================================================
// SÚPER BUCLE PRINCIPAL
// =========================================================================
int main(void) {
// 1. CONFIGURACIÓN CRÍTICA BARE-METAL
// Configura los PLLs para llevar el CPU a 84 MHz (Día 155)
SystemClock_Config();
// 2. SELF-TEST (Falla Segura)
// Audita I2C EEPROM, Cortos en Barreras ATEX, y Corrupción CRC Flash
if (System_POST_Run() != POST_SUCCESS) {
estado_actual = SYS_STATE_POST_ERROR;
// Inutiliza la máquina, enciende LED rojo, permite Modbus solo para reporte
} else {
estado_actual = SYS_STATE_IDLE;
}
// 3. INICIALIZACIÓN DE MÓDULOS DE APLICACIÓN
Modbus_Init(9600);
Metrologia_Init(); // Arranca variables TMR
Valvula_Init();
#ifdef DISPENSER_MODEL_PREMIUM
ST7789_TFT_Init();
UI_Dibujar_Pantalla_Bienvenida();
#endif
// =========================================================================
// INFINITE SUPER LOOP
// =========================================================================
while(1) {
// A) EJECUCIÓN ASÍNCRONA CONSTANTE
// Revisar si el DMA de recepción tiene paquetes Modbus listos.
// Aquí se descifran tramas XOR, se valida el CRC y se ejecutan Setters.
Modbus_Procesar_RX_Pendiente();
// Revisar Watchdog de Seguridad (Ej. Relé AC pegado - Día 170)
Vigilar_Integridad_Hardware();
// B) MÁQUINA DE ESTADOS FINITA (Lógica Transaccional)
switch(estado_actual) {
case SYS_STATE_IDLE:
// Esperando al cliente. Precio en pantalla girando (TOU Pricing)
#ifdef DISPENSER_MODEL_PREMIUM
UI_Actualizar_Precio_Mostrado( Metro_GetPrecioVigente() );
#endif
// Si el Servidor SCADA mandó comando Modbus de Autorización
if (Metro_VentaEstaAutorizada()) {
estado_actual = SYS_STATE_AUTHORIZED;
}
break;
case SYS_STATE_AUTHORIZED:
// El cliente puede levantar la boquilla
if (Hardware_Boquilla_Levantada()) {
Metro_IniciarVentaNueva(); // Resetea totalizadores (Atómico)
Valvula_SetApertura(100.0f); // Abre bomba al 100%
estado_actual = SYS_STATE_DISPENSING;
}
break;
case SYS_STATE_DISPENSING:
// Las interrupciones EXTI cuentan los litros invisiblemente en el fondo.
// Aquí solo leemos de forma atómica para mostrar en pantalla y calcular precio.
{
float litros_actuales = Metro_GetLitrosTotales();
float importe_actual = litros_actuales * Metro_GetPrecioVigente();
#ifdef DISPENSER_MODEL_PREMIUM
// Renderizado Offline masivo sin parpadeos (Día 172/173)
UI_Dibujar_Numeros_Gigantes(litros_actuales, importe_actual);
#endif
// Si alcanzó el preset de dinero o devolvió la manguera
if (Metro_LimiteVentaAlcanzado() || Hardware_Boquilla_Colgada()) {
Valvula_SetApertura(0.0f); // Cierre suave PID
estado_actual = SYS_STATE_FINISHED;
}
}
break;
case SYS_STATE_FINISHED:
// Guardar la transacción en la EEPROM I2C
Almacenamiento_GuardarVentaFinal();
// Notificar al SCADA mediante la bandera Modbus
Modbus_SetBandera(REG_STATUS, STATUS_VENTA_LISTA);
// Volver a esperar
estado_actual = SYS_STATE_IDLE;
break;
case SYS_STATE_POST_ERROR:
// Estado bloqueante terminal.
// Requiere intervención de Menú Diagnóstico (Día 166)
break;
}
}
return 0; // Inalcanzable
}
Objetivo Alcanzado: Prototipo Industrial Integrado y Functional.
Mira este código, Arquitecto. No hay librerías mágicas de terceros, no hay "cajas negras" comerciales de software, y no dependes de un sistema operativo pesado para gobernar el tiempo. Tú creaste el reloj, tú definiste la memoria, tú escribiste la encriptación de red y tú dominaste los bits que encienden los píxeles de la pantalla.
Al llegar al Día 180, tu Despachador de Gas LP es capaz de iniciar un Auto-Test (POST), aislar fallas físicas, recibir tarifas dinámicas de precios, despachar gas con seguridad ATEX, rendir cuentas en una pantalla gráfica y comunicar su facturación con cifrado XOR, todo dentro de una memoria de 96KB utilizando menos del 15% de procesamiento de la CPU. Has transformado teoría académica en una arquitectura comercial robusta que cumple con las normativas MISRA y SIL. Has cruzado el umbral de aprendiz a desarrollador Bare-Metal Senior.