DÍA 236 DE 365

Fase 5: Diseño Industrial y Proyecto Final

📖 Teoría a Estudiar

El Pecado Mortal: Matemáticas en la ISR

Una ISR (Interrupt Service Routine) es una función que detiene todo el procesador (incluso al RTOS) cuando ocurre un evento de hardware, como el cambio de voltaje de nuestro caudalímetro. En sistemas amateurs, es común ver desarrolladores calculando divisiones con números flotantes (`litros = pulsos / 400.0`) dentro de esta función.

Esto viola MISRA-C y el sentido común. La matemática de punto flotante en procesadores como el Cortex-M4 tarda decenas de ciclos de reloj. Si el procesador está "atorado" calculando decimales dentro de la ISR, y llega otro pulso del caudalímetro en ese instante, el pulso será ignorado (Overrun). La regla de oro es: La ISR solo debe incrementar una variable entera y salir inmediatamente. El cálculo se hace en la Tarea Principal.

Secciones Críticas (Atomicidad) y volatile

Si la ISR suma pulsos++ y la Tarea Principal lee pulsos para multiplicarlo, enfrentamos dos riesgos de seguridad:

  1. El Compilador Ciego: El compilador GCC es muy agresivo. Si ve un bucle while(1) leyendo pulsos y nota que en ese bucle nadie lo modifica, guardará el valor en la caché del procesador y nunca más leerá la memoria RAM. Tu máquina pensará que los pulsos están en 0 para siempre. Por eso, toda variable modificada en una ISR DEBE llevar el calificador volatile.
  2. Lectura Corrupta (Non-Atomic Access): Imagina que tu tarea principal está a la mitad de leer los 32 bits de la variable pulsos, y de repente ocurre la interrupción. La ISR cambia el valor, y tu tarea principal termina de leer la otra mitad. Obtendrás un número corrupto gigante. Para evitarlo, debemos usar Secciones Críticas (taskENTER_CRITICAL()), que apagan temporalmente el caudalímetro justo por los 2 nanosegundos que tardamos en copiar el valor.
REFACTORIZACIÓN DE ISR: LECTURA DE CAUDALÍMETRO AMATEUR (Riesgo Financiero) Pin EXTI ISR_Caudalimetro() pulsos++; litros = pulsos / 400; // División Float! Lento Memoria RAM Compartida (Data Race) float litros; Task_Display (Loop) printf("L: %f", litros); ¡Lee mientras la ISR escribe! (Corrupto) MISRA-C (Seguridad SIL-3) Pin EXTI ISR_Caudalimetro() global_pulsos++; // Salida inmediata Memoria Atómica volatile uint32_t global_pulsos; Task_Metrologia() taskENTER_CRITICAL(); uint32_t local = global_pulsos; taskEXIT_CRITICAL(); litros = (float32_t)local / 400.0F; // Casting Explícito y Precisión Segura

⚙️ Ejercicios Prácticos

Circuito Electrónico (Hardware Debouncing): Antes del software, el hardware debe ser seguro. El caudalímetro de turbina (Hall Effect) o de engranes ovalados genera "rebotes" eléctricos (Ruido). Si conectas el cable directo al STM32, un pulso se contará como tres. Debes intercalar un circuito Filtro RC Pasabajo (Resistencia 10k y Capacitor 0.1uF) seguido de un Optoacoplador (PC817) para aislar galvánicamente los ruidos del motor del microcontrolador.

Ejercicio 1: Refactorizaremos el código fuente caudalimetro.c aplicando las reglas MISRA-C tratadas. Notarás la aparición del sufijo U (Unsigned) y F (Float) en todos los números para prohibirle al compilador hacer conversiones implícitas a sus espaldas.


/**
 * @file caudalimetro.c
 * @brief Adquisición de Volumen Gas LP - Certificado MISRA-C:2012
 */

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

// =========================================================================
// DEFINICIONES DE SEGURIDAD (Reglas 2.1 y 10.4)
// =========================================================================
// Usamos tipos explícitos para Punto Flotante y eliminamos Magic Numbers.
typedef float float32_t;

// La hoja de datos del caudalímetro dice que emite 400 pulsos por cada Litro.
// El sufijo 'U' fuerza a constante entera sin signo. El sufijo 'F' a Flotante puro.
#define CAUDAL_PULSOS_POR_LITRO     (400U)
#define CAUDAL_FACTOR_CONVERSION    (400.0F)

// =========================================================================
// MEMORIA COMPARTIDA (Regla 8.9 y 2.2)
// =========================================================================
// 'volatile' obliga al compilador a extraer el valor desde la RAM física 
// siempre que se solicite, prohibiendo optimizaciones de caché que ignoran la ISR.
static volatile uint32_t isr_pulsos_acumulados = 0U;

// =========================================================================
// ISR: INTERRUPCIÓN DE HARDWARE (Regla 21.3, 17.2)
// =========================================================================
/**
 * @brief Atrapa el pulso físico (Flanco de subida EXTI)
 *        TIEMPO DE EJECUCIÓN: ~5 ciclos de reloj (Ultra Rápido).
 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
    if (GPIO_Pin == GPIO_PIN_0) { // Supongamos PA0 conectado al Optoacoplador
        
        // Única responsabilidad: Incrementar e irse. 
        // ¡CERO DIVISIONES, CERO FLOATS AQUÍ!
        isr_pulsos_acumulados++; 
    }
}

// =========================================================================
// TAREA PRINCIPAL (RTOS)
// =========================================================================
/**
 * @brief Calcula y totaliza el volumen de Gas LP de forma determinista
 */
void Task_Metrologia_Caudalimetro(void *argument) {
    
    uint32_t pulsos_instantanea_local = 0U;
    float32_t volumen_litros_actual = 0.0F;
    
    for (;;) {
        // 1. SECCIÓN CRÍTICA: Extracción Atómica
        // Congelamos las interrupciones del hardware (Deshabilitamos el EXTI)
        // solo durante el nanosegundo que tardamos en copiar la variable.
        // Esto evita la "Data Race" (Corrupción de Lectura).
        taskENTER_CRITICAL();
        pulsos_instantanea_local = isr_pulsos_acumulados;
        taskEXIT_CRITICAL();
        
        // 2. MATEMÁTICAS SEGURAS (Regla 10.3 y 10.4 MISRA-C)
        // Violación Amateur: litros = pulsos / 400; (Hace división entera y trunca los decimales).
        // Corrección: Casting Explícito de ambos operandos al mismo tipo.
        volumen_litros_actual = (float32_t)pulsos_instantanea_local / CAUDAL_FACTOR_CONVERSION;
        
        // 3. Acción Metrológica...
        // Ejemplo: Si superamos el límite, cerrar válvula.
        // if (volumen_litros_actual >= VOLUMEN_OBJETIVO_F) { Cerrar_Valvula_Segura(); }
        
        // Frecuencia de muestreo (Polling rate interno del sistema lógico)
        osDelay(pdMS_TO_TICKS(50U)); 
    }
}
        

🚀 Avance del Proyecto Tesis: Despachador de Gas LP

Objetivo del día: Certificación Metrológica y Prevención de Data Races Estocásticos.

El error más difícil de cazar en un sistema embebido ocurre "una vez cada cien mil veces". Imagina que usas la variable `float litros` modificada dentro de la interrupción. Si el Despachador vende 10,000 litros al mes, un día, justo en el milisegundo en que la rutina principal lee la variable, entra un pulso. La memoria se trunca, y en lugar de leer 10.50 litros, la pantalla lee 10,500.00 litros. Has arruinado a tu cliente con un solo pulso a destiempo.

Al aplicar el refactor **MISRA-C** a la función metrológica, hemos erradicado los eventos estocásticos (aleatorios). La ISR ahora tarda menos de 1 microsegundo en ejecutarse, garantizando que aunque el fluido de gas vaya a máxima presión (cientos de Hz de frecuencia), **ningún pulso (Oveerrun) se perderá jamás**. Además, aislando el cálculo flotante en la Tarea RTOS dentro de una barrera **`taskENTER_CRITICAL()`**, aseguramos matemáticamente la atomicidad. El uso del sufijo estricto `U` y `F` elimina la posibilidad de que el compilador trunque el volumen (ej. de 1.99 litros a 1 litro) debido a una conversión entera oculta. Tu máquina de estados acaba de cumplir el rigor necesario para pasar una auditoría de la Dirección General de Normas (DGN) y PROFECO en México.

📝 Resumen del Día