Cuando un ingeniero novato lee un sensor ruidoso, su primera reacción es hacer un "Promedio Móvil" (Filtro FIR): crea un arreglo de 100 lecturas, hace un bucle for que suma las 100 variables y las divide entre 100. Funciona, pero es un desastre computacional. Ocupa muchísima RAM para guardar el historial y desperdicia cientos de ciclos de reloj sumando arreglos cada vez que entra una nueva muestra.
Los Filtros de Respuesta al Impulso Infinita (IIR), específicamente la Media Móvil Exponencial (EMA), utilizan el poder de la retroalimentación. Solo necesitan recordar UNA variable: el resultado del filtro anterior.
La fórmula mágica es:
Y_actual = (Alpha * X_nueva) + ((1.0 - Alpha) * Y_anterior)
Donde Alpha (α) es el Factor de Suavizado (Tuning), un número entre 0.0 y 1.0:
Ejercicio 1: Encapsularemos nuestro filtro digital en un struct de lenguaje C, de modo que podamos instanciar múltiples filtros (uno para el sensor de temperatura, otro para la batería, otro para la presión). Como nuestro STM32F4 tiene FPU de Hardware (Día 151), las multiplicaciones con flotantes tomarán apenas 1 a 3 ciclos de reloj de procesador, haciendo que este filtro corra a la velocidad de la luz en el Súper Bucle.
/**
* @file iir_filter.c
* @brief Implementación Bare-Metal de Filtro IIR de Primer Orden (EMA)
*/
#include "stm32f401xe.h"
#include <stdbool.h>
// =========================================================================
// ESTRUCTURA DEL FILTRO
// =========================================================================
typedef struct {
float alpha; // Factor de suavizado (0.01 a 1.00)
float valor_previo; // Memoria del estado Z^-1
bool inicializado; // Bandera para evitar arranques bruscos
} IIR_Filter_t;
/**
* @brief Inicializa la estructura del filtro con el coeficiente deseado.
*
* @param f Puntero a la estructura
* @param alpha Factor. Ej: 0.1 significa 10% señal nueva, 90% memoria.
*/
void IIR_Init(IIR_Filter_t *f, float alpha) {
// Si meten un valor absurdo, lo forzamos a límites seguros matemáticos
if (alpha > 1.0f) alpha = 1.0f;
if (alpha <= 0.0f) alpha = 0.01f;
f->alpha = alpha;
f->valor_previo = 0.0f;
f->inicializado = false;
}
/**
* @brief Ejecuta el filtro sobre una nueva muestra capturada del ADC.
* Debe llamarse incesantemente en el bucle principal.
*
* @param f Puntero al filtro
* @param nueva_muestra El valor "sucio" recién extraído del sensor
* @return float El valor filtrado y purificado
*/
float IIR_Update(IIR_Filter_t *f, float nueva_muestra) {
// Problema de Arranque: Si el filtro empieza en 0.0, y la lectura real
// es 120.0, el filtro "subirá" lentamente desde cero engañando al sistema.
// Solución: La primera muestra que entre al filtro en la vida, se acepta
// incondicionalmente como el valor base absoluto.
if (!f->inicializado) {
f->valor_previo = nueva_muestra;
f->inicializado = true;
return nueva_muestra;
}
// Ecuación Mágica del Filtro Digital EMA:
// Y[n] = (Alpha * X[n]) + (1 - Alpha) * Y[n-1]
float valor_filtrado = (f->alpha * nueva_muestra) + ((1.0f - f->alpha) * f->valor_previo);
// Almacenamos el resultado en la memoria Z^-1 para la siguiente iteración
f->valor_previo = valor_filtrado;
return valor_filtrado;
}
// =========================================================================
// IMPLEMENTACIÓN EN EL SÚPER BUCLE INDUSTRIAL
// =========================================================================
// Instanciamos filtros independientes para cada sensor de la máquina
IIR_Filter_t filtro_presion_tanque;
IIR_Filter_t filtro_bateria_principal;
// Dependencia
extern float ADC_LeerPresion_Cruda(void); // Supuesta función que lee el hardware
int main(void) {
// 1. Inicialización.
// Alpha = 0.05 (Súper pesado/estable) para Presión
// Alpha = 0.2 (Intermedio) para Batería
IIR_Init(&filtro_presion_tanque, 0.05f);
IIR_Init(&filtro_bateria_principal, 0.20f);
// Habilitar coprocesador flotante (FPU) para máxima velocidad
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));
while(1) {
// Supongamos que el Timer dicta lectura cada 5ms
if (bandera_timer_adc) {
// 2. Extraer ruido y señal mezclados desde el Hardware
float presion_sucia = ADC_LeerPresion_Cruda();
// 3. Purificar la señal instantáneamente (Costo: O(1) memoria)
float presion_real = IIR_Update(&filtro_presion_tanque, presion_sucia);
// 4. Lógica de Paro de Emergencia protegida contra falsos positivos
// Si no tuviéramos el filtro, un pico EMI cerraría la válvula falsamente.
if (presion_real > 120.0f) { // Límite de explosión del tanque
// BLOQUEO_DE_EMERGENCIA();
// LOG_HARDWARE("ALTA PRESION DETECTADA");
}
bandera_timer_adc = false;
}
}
return 0;
}
Objetivo del día: Inmunidad al Ruido (EMI) e Integridad Sensorial.
El estándar internacional dicta que si un Despachador de Gas LP detecta una sobrepresión o que el sensor fue desconectado, debe bloquear las válvulas inmediatamente. En el mundo real, los motores diésel de las pipas, las chispas de encendido y los grandes contactores eléctricos inyectan "basura" de alta frecuencia en los cables de tus sensores. Si tu código `if(presion > MAX)` lee los datos crudos del Convertidor Analógico a Digital (ADC), tu estación sufrirá docenas de falsas alarmas diarias, enfureciendo a los operadores.
Al implementar el **Filtro IIR de Primer Orden**, has creado un escudo invisible de software. Como este filtro tiene memoria histórica (`Z^-1`), cuando entra un pico de voltaje absurdo de un microsegundo, el filtro matemático dice: *"Espera, esto es un pico. Lo atenuaré al 5% y me quedaré con mi historial al 95%"*. El pico se aplasta en la gráfica. Pero si la presión del tanque *realmente* empieza a subir de forma continua debido a un fallo mecánico, el filtro lo rastreará suavemente hasta cruzar la línea de peligro, activando un paro de emergencia legítimo y 100% justificado. Cero falsas alarmas.
Alpha determina el polo del filtro paso-bajos. Valores cercanos a `0.0` logran un rechazo brutal al ruido de alta frecuencia, a costa de introducir un pequeño retraso de fase (Lag) en la señal. Valores cercanos a `1.0` reducen el lag pero dejan pasar más ruido.0 cuando el sensor está leyendo 100, tardará múltiples ciclos en "escalar" hasta la lectura real. La primera muestra debe asignarse como valor absoluto.