← Volver al inicio

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:

Programa Principal (Ejecución Normal)
EVENTO: Ocurre Interrupción
(Ej: Timer Desborda, Botón Pulsado)
CPU termina la instrucción actual
CPU guarda el contexto (registros) en el Stack
Salto a la ISR (Dirección en Vector Table)
Ejecución del Código de la ISR
(¡Limpiar Flag Aquí!)
CPU restaura el contexto desde el Stack
Retorno al Programa Principal

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
}
Recomendación práctica: Para la mayoría de aplicaciones con interrupciones, el Sleep Mode es suficiente. Usa Stop si necesitas conservar batería en dispositivos portátiles con largo tiempo de inactividad. Reserva Standby para aplicaciones que pueden permitirse un reinicio completo al despertar (ej: dataloggers, sensores remotos).