Si conectas una señal de alta frecuencia a un pin EXTI normal, el hardware dispara una interrupción. El CPU deja lo que está haciendo, salta a la función EXTI_IRQHandler, y adentro tú usas código C para leer el tiempo actual (ej. obtener_milisegundos()). Esto tiene un defecto físico: la latencia. El tiempo que le toma al CPU saltar a la interrupción varía si estaba ejecutando una instrucción compleja. Ese retraso de algunos nanosegundos contamina tu medición.
Los Timers de STM32 (como TIM2) tienen "Canales" (CH1, CH2...). El modo Input Capture conecta el pin físico directamente al contador del timer a través de hardware puro. El timer está corriendo libremente (ej. contando microsegundos). Cuando ocurre un flanco de subida en el pin, el hardware "toma una fotografía" instantánea del registro contador (CNT) y la guarda en un registro especial llamado CCR (Capture/Compare Register), todo sin despertar al procesador.
Segundos o milisegundos después, el procesador puede entrar a la interrupción con calma y leer el registro CCR sabiendo que esa es la marca de tiempo exacta con 0% de latencia de software.
Una vez que el ISR de nuestro programa lee ambos "Snapshots" guardados en el registro CCR1, aplica la fórmula fundamental: $\Delta T = CCR_{new} - CCR_{old}$. Al conocer que nuestro timer hace un "tick" cada microsegundo, sabemos que el periodo del pulso es $\Delta T$ microsegundos. La frecuencia exacta del caudalímetro será entonces: $f = 1,000,000 / \Delta T \text{ Hz}$.
Ejercicio 1: El pin PA0 tiene la Función Alternativa 1 (AF1) que lo conecta directamente al Canal 1 del TIM2. Configura el pin en modo AF, arranca el timer con resolución de 1 microsegundo, y escribe la interrupción que calcula el periodo de la señal entrante.
/**
* @file main.c
* @brief Medición de Frecuencia con TIM2 Input Capture
*/
#include "stm32f401xe.h"
// Variables volátiles para comunicar el hardware con el main
volatile uint32_t captura_anterior = 0;
volatile uint32_t captura_actual = 0;
volatile uint32_t periodo_us = 0;
volatile uint8_t captura_flag = 0; // Estado de la máquina de captura
void inicializar_input_capture(void) {
// --- 1. CONFIGURAR PIN FÍSICO (PA0 -> TIM2_CH1) ---
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// Configurar PA0 como Función Alternativa (MODER = 10)
GPIOA->MODER &= ~GPIO_MODER_MODE0;
GPIOA->MODER |= GPIO_MODER_MODE0_1;
// Seleccionar AF1 (TIM2) en el Alternate Function Register (AFRL)
GPIOA->AFR[0] &= ~GPIO_AFRL_AFSEL0;
GPIOA->AFR[0] |= (1 << 0);
// --- 2. CONFIGURAR TIMER 2 EN MODO CAPTURA ---
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// Prescaler: Bajar los 84MHz a 1MHz (Resolución de 1 microsegundo)
TIM2->PSC = 84 - 1;
// ARR al máximo valor (32 bits) para evitar desbordamientos rápidos
TIM2->ARR = 0xFFFFFFFF;
// CCMR1: Configurar Canal 1 como entrada (Mapeado a TI1)
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;
// CCER: Habilitar la captura en Flanco de Subida (Rising Edge)
TIM2->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP);
TIM2->CCER |= TIM_CCER_CC1E; // Enable Capture
// --- 3. HABILITAR INTERRUPCIÓN ---
TIM2->DIER |= TIM_DIER_CC1IE; // Capture/Compare 1 Interrupt Enable
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);
// Arrancar el Timer
TIM2->CR1 |= TIM_CR1_CEN;
}
// ISR del Timer 2
void TIM2_IRQHandler(void) {
// Revisar si la bandera es de Captura del Canal 1 (CC1IF)
if (TIM2->SR & TIM_SR_CC1IF) {
if (captura_flag == 0) {
// Primer flanco detectado. Guardamos la "Fotografía".
captura_anterior = TIM2->CCR1;
captura_flag = 1;
}
else if (captura_flag == 1) {
// Segundo flanco detectado. Calculamos la diferencia matemática.
captura_actual = TIM2->CCR1;
if (captura_actual >= captura_anterior) {
periodo_us = captura_actual - captura_anterior;
} else {
// Manejo de desbordamiento (el contador llegó a max y reinició)
periodo_us = (0xFFFFFFFF - captura_anterior) + captura_actual;
}
// Reiniciamos la máquina de estados para la próxima lectura
captura_anterior = captura_actual;
// flag se queda en 1 para seguir calculando los siguientes deltas
}
// Limpiamos la bandera (en STM32 se limpia escribiendo un 0 en esta flag específica)
TIM2->SR &= ~TIM_SR_CC1IF;
}
}
int main(void) {
inicializar_input_capture();
while(1) {
// En el main, calculamos la frecuencia y los LPM relajadamente
if (periodo_us != 0) {
// Frecuencia = 1 / Periodo (pero en microsegundos)
float frecuencia_Hz = 1000000.0f / (float)periodo_us;
// LPM = (Frecuencia * 60) / FACTOR_K
float litros_por_minuto = (frecuencia_Hz * 60.0f) / 450.0f;
// (Lógica para actualizar pantalla o abortar por bajo caudal)
}
}
return 0;
}
Objetivo del día: Telemetría instantánea y diagnóstico de cavitación.
Si la bomba mecánica del camión repartidor se queda sin presión, el Gas LP dejará de ser líquido y se convertirá en vapor (cavitación). La turbina girará, pero despachará aire. Esto es catastrófico para la medición. La cavitación produce variaciones de microsegundos en la velocidad de giro de las aspas, algo que un conteo de pulsos genérico no detectaría a tiempo.
Al utilizar el Input Capture en el pin PA0, el Hardware de tu STM32 fotografía la velocidad de cada aspa individual de la turbina con una resolución de 1 microsegundo. La variable periodo_us de tu código contiene el ADN mecánico del fluido. Si tu Firmware detecta que el periodo_us fluctúa salvajemente entre pulsos sucesivos, sabrá matemáticamente que hay burbujas de gas en la línea, permitiéndote cerrar la electroválvula (VALVULA_OFF()) y emitir una alarma antes de cobrarle aire al cliente. Tu despachador es ahora inteligente.
CCRx), evadiendo la latencia del software de las interrupciones.MODER como Función Alternativa (Alternate Function).CCR1.