Laboratorio 4: Gestor de Interrupciones
Gestión avanzada de eventos y Modo de Bajo Consumo
1. ¿Qué es una Interrupción?
Una interrupción es un mecanismo que permite detener momentáneamente la ejecución del programa principal (Main Loop) para atender un evento prioritario. Una vez atendido el evento mediante una rutina especial (ISR - Interrupt Service Routine), la CPU retoma la ejecución exactamente donde la dejó.
Es como si estás leyendo un libro y suena el teléfono: detienes la lectura, antiendes la llamada (interrupción) y luego vuelves a leer por la misma línea.
2. Flujo de Ejecución Genérico
Este es el proceso que sigue el microcontrolador cuando se produce un evento externo o interno configurado como interrupción:
(Ej: Timer Desborda, Botón Pulsado)
(¡Limpiar Flag Aquí!)
3. Configuración Paso a Paso: Timer con Interrupción
Vamos a configurar el TIM3 para generar una interrupción cada 250ms. En la rutina de interrupción, conmutaremos el estado de un LED conectado a PC13.
Paso 1: Configurar el GPIO de Salida (PC13)
Primero preparamos el pin que controlaremos desde la interrupción. PC13 suele ser el LED integrado en las placas BluePill/Nucleo.
// 1. Habilitar reloj GPIOC RCC->APB2ENR |= (1 << 4); // 2. Configurar PC13 como Salida Push-Pull (GP Output), 2MHz // PC13 está en CRH (pines 8-15). Bits para pin 13: CRH[23:20] // Modo Output 2MHz (10) | Config Push-Pull (00) -> 0010 (0x2) GPIOC->CRH &= ~(0xF << 20); // Limpiar configuración previa GPIOC->CRH |= (0x2 << 20); // Poner modo 2 (Output 2MHz)
// 1. Habilitar reloj GPIOC usando definiciones CMSIS RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // 2. Configurar PC13 como Salida Push-Pull (GP Output), 2MHz // Usar máscaras CMSIS para mayor claridad GPIOC->CRH &= ~GPIO_CRH_MODE13; // Limpiar MODE13 GPIOC->CRH &= ~GPIO_CRH_CNF13; // Limpiar CNF13 GPIOC->CRH |= GPIO_CRH_MODE13_1; // MODE = 10 (2MHz Output)
CMSIS proporciona nombres simbólicos que hacen el código más legible y portable entre diferentes familias STM32.
Paso 2: Reloj y Base de Tiempos
Configuramos el TIM3 para contar ticks de 0.1ms (10kHz). Con un reloj de 72MHz, PSC = 7199. Para 250ms, ARR = 2499 (2500 pasos).
RCC->APB1ENR |= (1 << 1); // Reloj TIM3 TIM3->PSC = 7200 - 1; // 10kHz TIM3->ARR = 2500 - 1; // 250ms
// Habilitar reloj TIM3 con CMSIS RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // Configurar prescaler y auto-reload TIM3->PSC = 7200 - 1; // 10kHz TIM3->ARR = 2500 - 1; // 250ms
Paso 3: Habilitar Evento en el Timer
Necesitamos decir al Timer que genere una petición de interrupción cuando contador llegue al final (Update Event).
// DIER: DMA/Interrupt Enable Register // Bit 0: UIE (Update Interrupt Enable) TIM3->DIER |= (1 << 0);
// Habilitar interrupción de actualización usando CMSIS TIM3->DIER |= TIM_DIER_UIE;
Esto activa la señal interna, pero aun no llega a la CPU.
Paso 4: Configurar el NVIC (Controlador de Interrupciones)
Debemos habilitar la línea específica IRQ (Interrupt Request) correspondiente al TIM3 en el núcleo Cortex-M3.
// Habilitar la línea global para TIM3 NVIC_EnableIRQ(TIM3_IRQn); // Configurar Prioridad (Opcional pero recomendado) NVIC_SetPriority(TIM3_IRQn, 2); // Prioridad media
Paso 5: La Rutina de Servicio (ISR)
Esta función se ejecutará automáticamente. DEBE tener el nombre exacto definido en el archivo de arranque (startup_stm32f10x_md.s).
void TIM3_IRQHandler(void)
{
// 1. Verificar Flag (¿Es evento de Update?)
if (TIM3->SR & (1 << 0)) {
// 2. Acción: Toggle PC13
GPIOC->ODR ^= (1 << 13);
// 3. LIMPIAR FLAG (Vital para no reentrar)
TIM3->SR &= ~(1 << 0);
}
}
Paso 6: Arrancar el Timer
Lo último que hacemos en el main() es encender el timer. A partir de aquí, todo es automático.
TIM3->CR1 |= (1 << 0); // Enable Counter (CEN)
4. Modos de Bajo Consumo en el STM32F103RB
El STM32F103RB ofrece tres modos de bajo consumo que permiten reducir drásticamente el consumo energético cuando el microcontrolador no está realizando tareas activas. La elección del modo depende del balance entre ahorro de energía y tiempo de despertar.
Comparativa de Modos
Sleep Mode
Consumo: ~8 mA
Wake-up: Instantáneo
Estado: CPU detenida, periféricos funcionando
Stop Mode
Consumo: ~20 μA
Wake-up: ~5 μs
Estado: CPU y relojes detenidos, RAM retenida
Standby Mode
Consumo: ~2 μA
Wake-up: ~50 μs (reset completo)
Estado: Todo apagado excepto dominio Backup
Modo 1: Sleep (Dormir)
En este modo, solo se detiene el reloj de la CPU (Cortex-M3), pero todos los periféricos siguen funcionando. Es ideal para aplicaciones que necesitan despertar rápidamente ante cualquier interrupción.
Configuración: Modo Sleep
No requiere configuración especial de registros. Solo necesitas ejecutar la instrucción
__WFI() o __WFE().
// Instrucción más común __WFI(); // Wait For Interrupt // Alternativa (espera eventos en general) __WFE(); // Wait For Event
La CPU se detiene hasta que llegue cualquier interrupción habilitada en el NVIC.
Modo 2: Stop (Parada Profunda)
Este modo detiene todos los relojes (incluyendo el HSI/HSE) y mantiene sólo el regulador de voltaje en modo de bajo consumo. La RAM y los registros conservan su contenido. El despertar se produce mediante interrupciones EXTI.
Registro PWR_CR (Power Control)
Controla el comportamiento del regulador de voltaje y los modos de bajo consumo.
- Bit 0 (LPDS): Low Power Deepsleep. 1 = Regulador en modo LP.
- Bit 1 (PDDS): Power Down Deepsleep. 0 = Stop, 1 = Standby.
- Bit 2 (CWUF): Clear Wakeup Flag.
Registro SCB->SCR (System Control)
Registro del núcleo Cortex-M3 que define el comportamiento de sleep.
- Bit 2 (SLEEPDEEP): 1 = Entra en Stop/Standby al ejecutar WFI.
- Bit 1 (SLEEPONEXIT): 1 = Sleep al salir de ISR.
Ejemplo: Configurar y Entrar en Stop Mode
// 1. Habilitar reloj del módulo de potencia RCC->APB1ENR |= (1 << 28); // PWR clock enable // 2. Configurar PWR_CR PWR->CR |= (1 << 0); // LPDS = 1: Regulador en Low Power PWR->CR &= ~(1 << 1); // PDDS = 0: Modo STOP (no Standby) // 3. Configurar SCR para Deep Sleep SCB->SCR |= (1 << 2); // SLEEPDEEP = 1 // 4. Ejecutar WFI __WFI(); // 5. Al despertar: Reconfigurar relojes (ver nota abajo) SystemInit(); // Restaurar configuración de reloj
Al salir de Stop, el sistema arranca con el reloj interno HSI (8MHz). Si necesitas volver a 72MHz, debes reconfigurar el PLL.
Modo 3: Standby (Suspensión Total)
El modo más agresivo: apaga casi todo excepto el dominio de Backup (RTC, registros de respaldo). El despertar reinicia completamente el micro, como si fuera un reset. Consumo mínimo: ~2μA.
Ejemplo: Configurar y Entrar en Standby Mode
// 1. Habilitar acceso al dominio Backup RCC->APB1ENR |= (1 << 28); // PWR clock RCC->APB1ENR |= (1 << 27); // BKP clock PWR->CR |= (1 << 8); // DBP = 1: Acceso a Backup // 2. Configurar fuente de despertar (ej: WKUP pin PA0) PWR->CSR |= (1 << 8); // EWUP = 1: Habilitar pin WKUP // 3. Limpiar flag de Wakeup previo PWR->CR |= (1 << 2); // CWUF = 1 // 4. Configurar para Standby PWR->CR |= (1 << 1); // PDDS = 1: Standby mode SCB->SCR |= (1 << 2); // SLEEPDEEP = 1 // 5. Entrar en Standby __WFI(); // Nota: Al despertar, el programa ejecuta desde main() como un reset
Fuentes de Despertar en Standby
- Pin WKUP (PA0) con flanco ascendente
- Alarma RTC
- Reset externo (NRST)
- Reset IWDG (Watchdog)
Detectar Despertar desde Standby
if (PWR->CSR & (1 << 0)) {
// WUF = 1: Despertó de Standby
PWR->CR |= (1 << 2); // Limpiar WUF
}