Un error común es intentar hacer todo dentro del SysTick (el timer de 1ms). Si colocas la función de refrescar la pantalla LCD dentro del SysTick, la pantalla exigirá unos 3 milisegundos para comunicarse por I2C. ¡Tu interrupción tardaría más en ejecutarse que el tiempo límite para la siguiente interrupción! Esto colapsaría la máquina.
La solución en arquitecturas de alto rendimiento (Bare-Metal) es distribuir la carga temporal utilizando diferentes Hardware Timers, cada uno con una frecuencia distinta y una prioridad específica en el NVIC.
Usaremos el TIM3 para nuestra interfaz de usuario. Nuestro reloj base es de $84,000,000\text{ Hz}$. La ecuación es $f = f_{CLK} / ((\text{PSC}+1)(\text{ARR}+1))$.
Ejercicio 1: Configura el TIM3 para que levante una interrupción cada 100 milisegundos (10Hz). Dentro de la Rutina de Servicio de Interrupción (ISR), levanta una "bandera" (flag) para que el `main()` sepa que es el momento exacto de actualizar la Interfaz Humano-Máquina (HMI).
/**
* @file main.c
* @brief Tareas Periódicas de Mediana Frecuencia (10Hz) con TIM3
*/
#include "stm32f401xe.h"
#include <stdbool.h>
// Bandera de sincronización (Hardware -> Software)
volatile bool flag_actualizar_pantalla = false;
void inicializar_timer3_10Hz(void) {
// 1. Habilitar reloj del periférico TIM3 en el bus APB1
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
// 2. Configurar Prescaler (PSC)
// TIM_CLK = 84 MHz. Dividimos entre 8400 para obtener un tick de 10 kHz (0.1ms)
TIM3->PSC = 8400 - 1;
// 3. Configurar Auto-Reload Register (ARR)
// 10,000 Hz / 1000 = 10 Hz (100 ms de periodo)
TIM3->ARR = 1000 - 1;
// 4. Habilitar la interrupción por desbordamiento (Update Event)
TIM3->SR &= ~TIM_SR_UIF; // Limpiar bandera remanente
TIM3->DIER |= TIM_DIER_UIE;
// 5. Configurar el Árbitro (NVIC)
// Le daremos prioridad baja (ej. 12), ya que actualizar una pantalla
// no es tan crítico como cerrar una válvula o atrapar un pulso de gas.
NVIC_SetPriority(TIM3_IRQn, 12);
NVIC_EnableIRQ(TIM3_IRQn);
// 6. Encender el contador
TIM3->CR1 |= TIM_CR1_CEN;
}
// ISR del Timer 3 (Se dispara 10 veces por segundo)
void TIM3_IRQHandler(void) {
if (TIM3->SR & TIM_SR_UIF) {
// En lugar de enviar los datos por I2C aquí mismo (lo cual bloquearía
// el procesador), solo levantamos una bandera.
flag_actualizar_pantalla = true;
// Limpiamos la bandera física de hardware
TIM3->SR &= ~TIM_SR_UIF;
}
}
int main(void) {
// (Asumimos inicialización de RCC a 84MHz y Pantalla LCD)
inicializar_timer3_10Hz();
while(1) {
// El Súper Bucle atiende las tareas cuando el hardware le avisa
if (flag_actualizar_pantalla) {
// Bajamos la bandera inmediatamente
flag_actualizar_pantalla = false;
// Esta función pesada se ejecuta "a demanda" cada 100ms exactos
// lcd_i2c_escribir_dinero(litros_actuales * PRECIO_POR_LITRO);
}
}
return 0;
}
Objetivo del día: Experiencia de Usuario (UX) fluida mediante Hardware.
Piensa en el cliente observando la pantalla de tu Despachador. Si actualizas la cantidad de dinero a pagar muy lento (ej. cada segundo con el TIM2), los números saltarán de forma brusca ($10... $18... $26), generando desconfianza. Si intentas actualizarla miles de veces por segundo, la pantalla LCD I2C se saturará, mostrando caracteres corruptos y bloqueando el CPU.
Al utilizar el TIM3 configurado a 10Hz, logras el balance dorado. Cada 100 milisegundos, el hardware le da un ligero "toque" al software levantando la bandera flag_actualizar_pantalla. El bucle `main` ve la bandera, pinta el nuevo precio en la pantalla, y sigue su camino. El cliente ve cómo los números corren fluidamente como en una gasolinera comercial, y el microcontrolador ni siquiera "suda" con el esfuerzo.
main().