Fundamentos de las Interrupciones
Las interrupciones son mecanismos fundamentales en sistemas embebidos que permiten al procesador responder inmediatamente a eventos críticos, pausando temporalmente la ejecución del programa principal para atender tareas prioritarias de tiempo real.
En el ESP32, las interrupciones son especialmente poderosas debido a su arquitectura dual-core, permitiendo un manejo eficiente de múltiples eventos simultáneos sin comprometer el rendimiento del sistema principal.
Interrupciones Externas
- Activadas por cambios en GPIOs
- Tipos: RISING, FALLING, CHANGE
- Prioridad: Alta
- Latencia: < 1 μs
Interrupciones Internas
- Generadas por timers
- Comunicación: I2C, SPI, UART
- ADC, DAC y watchdog
- Eventos del sistema
Tipos y Modos de Activación
El ESP32 soporta múltiples modos de activación de interrupciones externas, cada uno optimizado para diferentes tipos de sensores y eventos:
Modo | Descripción | Uso Típico | Ejemplo Aplicación |
---|---|---|---|
RISING |
Se activa en flanco ascendente (LOW → HIGH) | Detección de activación | Sensor de proximidad, botón normalmente bajo |
FALLING |
Se activa en flanco descendente (HIGH → LOW) | Detección de desactivación | Botón con pull-up, parada de emergencia |
CHANGE |
Se activa en cualquier cambio de estado | Monitoreo de cambios | Encoder rotativo, switch toggle |
LOW |
Se activa mientras el pin esté en LOW | Estado continuo bajo | Alarma activa, sensor activado |
HIGH |
Se activa mientras el pin esté en HIGH | Estado continuo alto | Sensor activado, señal de habilitación |
Implementación de Interrupciones
La configuración correcta de interrupciones requiere considerar aspectos críticos como la duración de las ISR, el manejo de variables compartidas y la prevención de condiciones de carrera:
// Sistema avanzado de interrupciones múltiples en ESP32
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// Definición de pines para diferentes tipos de sensores
#define EMERGENCY_BUTTON_PIN 21 // Botón de parada de emergencia
#define DOOR_SENSOR_PIN 22 // Sensor de puerta (reed switch)
#define MOTION_SENSOR_PIN 23 // Sensor de movimiento PIR
#define ENCODER_A_PIN 18 // Encoder rotativo canal A
#define ENCODER_B_PIN 19 // Encoder rotativo canal B
// Pines de salida para actuadores
#define ALARM_PIN 2 // Buzzer de alarma
#define EMERGENCY_LED_PIN 4 // LED de emergencia
#define STATUS_LED_PIN 5 // LED de estado
// Variables volátiles para comunicación con ISRs
volatile bool emergencyTriggered = false;
volatile bool doorOpened = false;
volatile bool motionDetected = false;
volatile long encoderPosition = 0;
volatile unsigned long lastInterruptTime = 0;
// Contador de eventos para estadísticas
volatile unsigned long emergencyCount = 0;
volatile unsigned long doorEvents = 0;
volatile unsigned long motionEvents = 0;
volatile unsigned long encoderEvents = 0;
// Queue para comunicación entre ISR y tarea principal
QueueHandle_t interruptQueue;
// Estructura para eventos de interrupción
struct InterruptEvent {
uint8_t source; // Fuente de la interrupción
uint32_t timestamp; // Momento del evento
uint8_t state; // Estado del pin
};
// Códigos de fuente de interrupción
#define INT_EMERGENCY 1
#define INT_DOOR 2
#define INT_MOTION 3
#define INT_ENCODER_A 4
#define INT_ENCODER_B 5
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n=== Sistema de Interrupciones Avanzado ESP32 ===");
// Configuración de pines de entrada
pinMode(EMERGENCY_BUTTON_PIN, INPUT_PULLUP); // Botón normalmente alto
pinMode(DOOR_SENSOR_PIN, INPUT_PULLUP); // Reed switch normalmente cerrado
pinMode(MOTION_SENSOR_PIN, INPUT); // PIR sensor
pinMode(ENCODER_A_PIN, INPUT_PULLUP); // Encoder canal A
pinMode(ENCODER_B_PIN, INPUT_PULLUP); // Encoder canal B
// Configuración de pines de salida
pinMode(ALARM_PIN, OUTPUT);
pinMode(EMERGENCY_LED_PIN, OUTPUT);
pinMode(STATUS_LED_PIN, OUTPUT);
// Estados iniciales
digitalWrite(ALARM_PIN, LOW);
digitalWrite(EMERGENCY_LED_PIN, LOW);
digitalWrite(STATUS_LED_PIN, LOW);
// Crear queue para eventos de interrupción
interruptQueue = xQueueCreate(20, sizeof(InterruptEvent));
// Configurar interrupciones externas
attachInterrupt(digitalPinToInterrupt(EMERGENCY_BUTTON_PIN),
emergencyISR, FALLING);
attachInterrupt(digitalPinToInterrupt(DOOR_SENSOR_PIN),
doorISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(MOTION_SENSOR_PIN),
motionISR, RISING);
attachInterrupt(digitalPinToInterrupt(ENCODER_A_PIN),
encoderISR, CHANGE);
Serial.println("Configuración de interrupciones completada:");
Serial.printf("GPIO %d: Botón emergencia (FALLING)\n", EMERGENCY_BUTTON_PIN);
Serial.printf("GPIO %d: Sensor puerta (CHANGE)\n", DOOR_SENSOR_PIN);
Serial.printf("GPIO %d: Sensor movimiento (RISING)\n", MOTION_SENSOR_PIN);
Serial.printf("GPIO %d: Encoder A (CHANGE)\n", ENCODER_A_PIN);
Serial.println("\nSistema de seguridad activo...\n");
// LED de estado para indicar sistema funcionando
digitalWrite(STATUS_LED_PIN, HIGH);
}
void loop() {
InterruptEvent event;
// Procesar eventos de interrupción desde la queue
if (xQueueReceive(interruptQueue, &event, pdMS_TO_TICKS(100)) == pdTRUE) {
processInterruptEvent(event);
}
// Gestión del sistema de emergencia
if (emergencyTriggered) {
handleEmergencyMode();
}
// Reporte periódico de estadísticas
static unsigned long lastReport = 0;
if (millis() - lastReport > 10000) {
printSystemStatus();
lastReport = millis();
}
// Parpadeo del LED de estado
static unsigned long lastBlink = 0;
if (millis() - lastBlink > 1000) {
digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
lastBlink = millis();
}
// Pequeña pausa para permitir otras tareas
delay(10);
}
// ISR para botón de emergencia - Crítica, debe ser rápida
void IRAM_ATTR emergencyISR() {
unsigned long currentTime = millis();
// Debounce simple
if (currentTime - lastInterruptTime < 50) {
return;
}
lastInterruptTime = currentTime;
emergencyTriggered = true;
emergencyCount++;
// Enviar evento a queue
InterruptEvent event = {INT_EMERGENCY, currentTime, LOW};
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(interruptQueue, &event, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// ISR para sensor de puerta
void IRAM_ATTR doorISR() {
unsigned long currentTime = millis();
uint8_t state = digitalRead(DOOR_SENSOR_PIN);
doorOpened = (state == HIGH); // HIGH = puerta abierta
doorEvents++;
InterruptEvent event = {INT_DOOR, currentTime, state};
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(interruptQueue, &event, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// ISR para sensor de movimiento
void IRAM_ATTR motionISR() {
unsigned long currentTime = millis();
motionDetected = true;
motionEvents++;
InterruptEvent event = {INT_MOTION, currentTime, HIGH};
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(interruptQueue, &event, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// ISR para encoder rotativo
void IRAM_ATTR encoderISR() {
static uint8_t lastEncoded = 0;
uint8_t encoded = (digitalRead(ENCODER_A_PIN) << 1) | digitalRead(ENCODER_B_PIN);
uint8_t sum = (lastEncoded << 2) | encoded;
if (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) {
encoderPosition++;
} else if (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) {
encoderPosition--;
}
lastEncoded = encoded;
encoderEvents++;
InterruptEvent event = {INT_ENCODER_A, millis(), encoded};
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(interruptQueue, &event, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
void processInterruptEvent(InterruptEvent event) {
switch (event.source) {
case INT_EMERGENCY:
Serial.printf("⚠️ EMERGENCIA activada! Timestamp: %lu\n", event.timestamp);
digitalWrite(EMERGENCY_LED_PIN, HIGH);
break;
case INT_DOOR:
Serial.printf("🚪 Puerta %s - Timestamp: %lu\n",
event.state ? "ABIERTA" : "CERRADA", event.timestamp);
break;
case INT_MOTION:
Serial.printf("🏃 Movimiento detectado - Timestamp: %lu\n", event.timestamp);
// Auto-reset del sensor de movimiento después de 5 segundos
static unsigned long motionTimer = 0;
motionTimer = event.timestamp;
break;
case INT_ENCODER_A:
Serial.printf("🔄 Encoder: posición %ld\n", encoderPosition);
break;
}
}
void handleEmergencyMode() {
// Activar alarma
digitalWrite(ALARM_PIN, HIGH);
delay(100);
digitalWrite(ALARM_PIN, LOW);
delay(100);
Serial.println("🚨 MODO EMERGENCIA ACTIVO - Todos los procesos detenidos");
Serial.println("Presione RESET para reiniciar el sistema");
// En una aplicación real, aquí se detendrían motores, se cerrarían válvulas, etc.
// Bloquear hasta reset (en aplicación real, permitir reset por comando autorizado)
while (emergencyTriggered) {
digitalWrite(EMERGENCY_LED_PIN, HIGH);
delay(250);
digitalWrite(EMERGENCY_LED_PIN, LOW);
delay(250);
// Verificar si se ha liberado el botón de emergencia
if (digitalRead(EMERGENCY_BUTTON_PIN) == HIGH) {
delay(2000); // Esperar 2 segundos de estabilidad
if (digitalRead(EMERGENCY_BUTTON_PIN) == HIGH) {
emergencyTriggered = false;
digitalWrite(EMERGENCY_LED_PIN, LOW);
Serial.println("✅ Emergencia desactivada - Sistema restaurado");
}
}
}
}
void printSystemStatus() {
Serial.println("\n═══ ESTADO DEL SISTEMA ═══");
Serial.printf("Tiempo activo: %lu segundos\n", millis() / 1000);
Serial.printf("Estado emergencia: %s\n", emergencyTriggered ? "ACTIVA" : "Normal");
Serial.printf("Puerta: %s\n", doorOpened ? "ABIERTA" : "CERRADA");
Serial.printf("Movimiento: %s\n", motionDetected ? "DETECTADO" : "Sin detectar");
Serial.printf("Posición encoder: %ld\n", encoderPosition);
Serial.println("─── ESTADÍSTICAS ───");
Serial.printf("Eventos emergencia: %lu\n", emergencyCount);
Serial.printf("Eventos puerta: %lu\n", doorEvents);
Serial.printf("Eventos movimiento: %lu\n", motionEvents);
Serial.printf("Eventos encoder: %lu\n", encoderEvents);
Serial.printf("Queue disponible: %d espacios\n",
uxQueueSpacesAvailable(interruptQueue));
Serial.println("════════════════════════\n");
}
Ejercicios Prácticos
Objetivo: Implementar un contador de pulsos usando interrupciones externas con display en tiempo real.
Características:
- Contador incremental en flanco ascendente
- Botón de reset con interrupción separada
- Display del conteo en Serial Monitor
- LED que parpadea con cada evento
- Implementación de debounce en hardware y software
Aplicación: Contadores de piezas, sensor de flujo, RPM
Objetivo: Desarrollar un sistema de alarma que monitoree múltiples sensores con diferentes prioridades.
Funcionalidades:
- Sensor PIR para detección de movimiento
- Sensor magnético para puertas y ventanas
- Botón de pánico de alta prioridad
- Sistema de armado/desarmado
- Log de eventos con timestamp
- Notificaciones diferenciadas por tipo de evento
Aplicación: Seguridad doméstica, control de acceso
Objetivo: Implementar control de posición de motor usando encoder rotativo con interrupciones de alta frecuencia.
Características avanzadas:
- Encoder incremental de 2 canales (A/B)
- Detección de dirección de giro
- Control PID de posición
- Límites de software y hardware
- Compensación de backlash
- Interfaz de configuración por Serial
Aplicación: CNC, robótica, automation
Aplicaciones Críticas en Industria
Sistemas de Tiempo Real
Las interrupciones son esenciales en aplicaciones industriales donde la respuesta inmediata a eventos críticos puede significar la diferencia entre operación segura y accidentes costosos.
Aplicaciones Críticas:
- Paradas de emergencia - Respuesta < 1ms
- Detección de fallos - Protección de equipos
- Control de velocidad - Feedback instantáneo
- Posicionamiento preciso - Control servos
Monitoreo en Tiempo Real:
- Vibración de máquinas - Mantenimiento predictivo
- Temperatura crítica - Protección térmica
- Detección de objetos - Sistemas de visión
- Adquisición de datos - Muestreo sincronizado
Casos de Uso Específicos
Brazo Robótico
Encoders y finales de carrera con respuesta < 100μsLínea de Producción
Sensores de posición y contadores de alta velocidadSistema de Seguridad
Múltiples sensores con prioridades diferenciadasMejores Prácticas y Optimización
Diseño de ISRs Eficientes
Reglas Fundamentales
- Brevedad: ISR máximo 10-50 μs
- Variables volatile: Para datos compartidos
- IRAM_ATTR: Para ISRs críticas en RAM
- Atomic operations: Para variables de 32 bits
- No malloc/free: En rutinas de interrupción
Patrones Recomendados
- Set flag + process in loop()
- FreeRTOS queues para eventos
- Semaphores para sincronización
- Ring buffers para datos continuos
Problemas Comunes
ISR demasiado larga
Síntomas: Sistema lento, watchdog reset, pérdida de interrupciones
Solución: Mover lógica compleja al loop principal
Condiciones de carrera
Causa: Acceso concurrente a variables compartidas
Prevención: Usar portENTER_CRITICAL/portEXIT_CRITICAL
Rebote de contactos
Problema: Múltiples interrupciones por un solo evento
Solución: Debounce por software o hardware
Desbordamiento de stack
Causa: ISRs anidadas o recursivas
Prevención: Limitar profundidad, usar IRAM_ATTR