Hasta hoy, utilizábamos un pin digital (GPIO) configurado como interrupción (EXTI) para leer el caudalímetro. Un pin digital hace una pregunta binaria: "¿El voltaje es mayor a 2.0V?" Si sí, es un 1. Si no, es un 0.
Si el sensor está en reposo y manda 0 Voltios, el microcontrolador lee 0. Si un ladrón corta el cable con unas tijeras, el pin del microcontrolador se queda "al aire" y, con una resistencia Pull-Down interna, leerá 0 Voltios. Para tu código, el ladrón y el reposo pacífico se ven exactamente igual. La máquina es vulnerable.
Para solucionar esto, abandonaremos el puerto digital binario y conectaremos el sensor a un puerto Analógico a Digital (ADC) del STM32. Modificaremos físicamente el cableado agregando dos resistencias de 10 kΩ: una en nuestra tarjeta (Pull-Up) y otra escondida dentro de la carcasa del sensor (Parallel EOL).
Al hacer esto, convertimos un sensor binario en un Sensor de 4 Estados:
Configuración STM32 (DMA + ADC): Ya no usaremos interrupciones EXTI que paralizan el procesador. Configuraremos el ADC multicanal con DMA (Acceso Directo a Memoria) en modo circular continuo (1 kHz). El DMA actualizará mágicamente una variable en la RAM del STM32 con el voltaje del sensor 1000 veces por segundo, sin gastar un solo ciclo de reloj de la CPU.
Ejercicio 1: Escribiremos la tarea de RTOS que evalúa matemáticamente el voltaje dejado en la RAM por el DMA. Aplicaremos MISRA-C con umbrales estrictos (`#define`) para decodificar los 4 estados físicos.
/**
* @file tamper_adc.c
* @brief Decodificación de Sabotaje por ADC (Circuito EOL) - MISRA-C:2012
*/
#include "stm32f401xe.h"
#include "cmsis_os2.h"
#include <stdint.h>
// =========================================================================
// LÍMITES DE VENTANA DE VOLTAJE ADC (12-bits: 0 a 4095)
// =========================================================================
// Zona de Pulso a Tierra (Interruptor Cerrado)
#define ADC_THR_PULSO_MAX (500U)
// Zona de Reposo Normal (Divisor 10k/10k a 3.3V = 1.65V = ~2048)
#define ADC_THR_REPOSO_MIN (1500U)
#define ADC_THR_REPOSO_MAX (2500U)
// Zona de Cable Cortado (Pull-Up sin camino a tierra = 3.3V = 4095)
#define ADC_THR_TAMPER_MIN (3500U)
// =========================================================================
// ESTADOS ESTRUCTURADOS DEL HARDWARE (Regla 10.4)
// =========================================================================
typedef enum {
SENSOR_ESTADO_INDETERMINADO = 0U,
SENSOR_ESTADO_REPOSO = 1U,
SENSOR_ESTADO_PULSO_ACTIVO = 2U,
SENSOR_ESTADO_SABOTAJE = 3U
} EstadoFisicoSensor_t;
// Variable actualizada mágicamente por el Hardware DMA en background
extern volatile uint16_t dma_adc_caudalimetro_raw;
// Acumulador metrológico
extern volatile uint32_t isr_pulsos_acumulados;
// =========================================================================
// TAREA DE PROCESAMIENTO ANALÓGICO (1 kHz / 1ms)
// =========================================================================
void Task_ADC_Tamper_Monitor(void *argument) {
EstadoFisicoSensor_t estado_anterior = SENSOR_ESTADO_INDETERMINADO;
for(;;) {
// 1. Lectura segura (DMA actualiza esta variable de forma atómica en HW)
uint16_t adc_val = dma_adc_caudalimetro_raw;
EstadoFisicoSensor_t estado_actual = SENSOR_ESTADO_INDETERMINADO;
// 2. Clasificación en Ventanas de Voltaje
if (adc_val <= ADC_THR_PULSO_MAX) {
estado_actual = SENSOR_ESTADO_PULSO_ACTIVO;
}
else if ((adc_val >= ADC_THR_REPOSO_MIN) && (adc_val <= ADC_THR_REPOSO_MAX)) {
estado_actual = SENSOR_ESTADO_REPOSO;
}
else if (adc_val >= ADC_THR_TAMPER_MIN) {
estado_actual = SENSOR_ESTADO_SABOTAJE;
}
else {
// El voltaje está transitando o hay ruido electromagnético severo
estado_actual = SENSOR_ESTADO_INDETERMINADO;
}
// 3. Máquina de Transición y Acción
if (estado_actual != estado_anterior) {
if ((estado_anterior == SENSOR_ESTADO_REPOSO) && (estado_actual == SENSOR_ESTADO_PULSO_ACTIVO)) {
// Equivalente al Flanco de Bajada de un EXTI. Sumamos un litro.
isr_pulsos_acumulados++;
}
else if (estado_actual == SENSOR_ESTADO_SABOTAJE) {
// ¡ALERTA MÁXIMA! El ladrón acaba de cortar el cable.
// Disparar_Protocolo_Lockdown_Inmediato(); (Día 239)
// Enviar_Alarma_Nube("CABLE_SENSOR_MUTILADO");
}
estado_anterior = estado_actual;
}
// Polling agresivo para no perder pulsos (1ms)
osDelay(pdMS_TO_TICKS(1U));
}
}
Objetivo del día: Seguridad Perimetral, Anti-Vandalismo (Tamper-Proof) y Ciber-Resiliencia Física.
Los ingenieros de software a menudo olvidan que el hardware existe en el mundo físico, donde las personas tienen herramientas. Un delincuente experimentado en estaciones de servicio sabe que puenteando un cable a GND o cortándolo con un alicate, puede engañar a la computadora de un dispensador comercial barato para que registre "cero litros" mientras llena su tanque. Si el dispensador funciona con pines digitales (EXTI) normales, el robo será invisible hasta el inventario de fin de mes.
Al implementar un **Bucle Analógico Supervisado (EOL Resistor)** y utilizar el **ADC con DMA** del STM32, has sellado esta vulnerabilidad. Tu código ahora vigila no solo *cuántos* pulsos llegan, sino la *salud eléctrica* del cable de cobre en sí mismo. Si el cable se corta, el voltaje sube a 3.3V y el sistema entra en paro de emergencia instantáneo (Lockdown). Si el ladrón intenta puentearlo a GND para anularlo, el voltaje cae a 0V permanentemente y el sistema detona un "Timeout de Pulso Pegado". Tu firmware MISRA-C ha elevado la máquina a un nivel de seguridad militar (Tamper-Proof), garantizando que ni un solo litro de gas fluya sin ser contabilizado o alarmado a la nube AWS.