La Importancia de las Prioridades en Tiempo Real
En sistemas de tiempo real como el ESP32, no todas las tareas tienen la misma importancia. La priorización de procesos es fundamental para garantizar que las operaciones críticas se ejecuten antes que las menos importantes, asegurando respuestas determinísticas y confiables.
¿Por qué es crucial la priorización?
- Tiempo real crítico: Respuesta garantizada en eventos de emergencia
- Optimización de rendimiento: CPU asignado según importancia
- Estabilidad del sistema: Prevención de bloqueos críticos
- Comportamiento predecible: Sistema determinístico y confiable
Sistema de Prioridades en FreeRTOS
FreeRTOS maneja prioridades numéricas donde valores más altos = mayor prioridad. El sistema garantiza que las tareas de alta prioridad siempre se ejecuten antes que las de baja prioridad.
Crítica (Critical)
Sistemas de seguridad y emergencia
Uso típico: Watchdog, interrupciones críticas, sistema de parada de emergencia
Tiempo de respuesta: < 1ms
Ejemplo: Detección de sobrecalentamiento en motor
Alta (High)
Control en tiempo real
Uso típico: Control PID, lecturas de sensores críticos, comunicación importante
Tiempo de respuesta: 1-10ms
Ejemplo: Control de posición de servo motor
Normal
Operaciones regulares del sistema
Uso típico: Procesamiento de datos, interfaz de usuario, comunicación WiFi
Tiempo de respuesta: 10-100ms
Ejemplo: Actualización de display, envío de telemetría
Baja (Low)
Tareas de mantenimiento y background
Uso típico: Logging, estadísticas, limpieza de memoria, tareas idle
Tiempo de respuesta: >100ms
Ejemplo: Guardado periódico de configuración, diagnósticos
Sistema de Control con Prioridades Diferenciadas
Implementemos un sistema completo que demuestre cómo las prioridades afectan el comportamiento del sistema:
// Variables globales para el sistema
volatile bool emergencyStop = false;
volatile float sensorTemperature = 25.0;
volatile int motorSpeed = 0;
unsigned long lastHeartbeat = 0;
// Handles de tareas para control dinámico
TaskHandle_t criticalTaskHandle = NULL;
TaskHandle_t highTaskHandle = NULL;
TaskHandle_t normalTaskHandle = NULL;
TaskHandle_t lowTaskHandle = NULL;
// TAREA CRÍTICA (Prioridad 6) - Sistema de Emergencia
void criticalEmergencyTask(void *pvParameters) {
pinMode(LED_BUILTIN, OUTPUT);
for (;;) {
// Simular lectura de sensor de emergencia (temperatura crítica)
int emergencyPin = digitalRead(0); // Pin BOOT como botón de emergencia
if (!emergencyPin || sensorTemperature > 80.0) {
emergencyStop = true;
// Parada de emergencia inmediata
Serial.println("🚨 EMERGENCIA ACTIVADA! Sistema detenido");
digitalWrite(LED_BUILTIN, HIGH);
// Detener motor inmediatamente
motorSpeed = 0;
// Suspender tareas no críticas durante emergencia
if (normalTaskHandle) vTaskSuspend(normalTaskHandle);
if (lowTaskHandle) vTaskSuspend(lowTaskHandle);
// Parpadeo rápido de alerta
for (int i = 0; i < 10; i++) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
vTaskDelay(pdMS_TO_TICKS(100));
}
} else if (emergencyStop) {
// Recuperación de emergencia
emergencyStop = false;
digitalWrite(LED_BUILTIN, LOW);
Serial.println("✅ Sistema recuperado de emergencia");
// Reactivar tareas suspendidas
if (normalTaskHandle) vTaskResume(normalTaskHandle);
if (lowTaskHandle) vTaskResume(lowTaskHandle);
}
// Actualizar heartbeat
lastHeartbeat = millis();
vTaskDelay(pdMS_TO_TICKS(50)); // Check cada 50ms
}
}
// TAREA ALTA PRIORIDAD (Prioridad 4) - Control de Motor PID
void highPriorityControlTask(void *pvParameters) {
float targetSpeed = 100.0;
float currentSpeed = 0.0;
float error = 0.0, lastError = 0.0;
float integral = 0.0, derivative = 0.0;
// Constantes PID
const float Kp = 2.0, Ki = 0.1, Kd = 0.05;
for (;;) {
if (!emergencyStop) {
// Simular lectura de velocidad actual
currentSpeed = motorSpeed + random(-5, 6); // Ruido del sensor
// Control PID
error = targetSpeed - currentSpeed;
integral += error;
derivative = error - lastError;
float output = Kp * error + Ki * integral + Kd * derivative;
// Aplicar límites y actualizar velocidad del motor
motorSpeed = constrain(motorSpeed + (int)output, 0, 255);
Serial.printf("Control PID: Target=%.1f Current=%.1f Motor=%d\n",
targetSpeed, currentSpeed, motorSpeed);
lastError = error;
// Cambiar target cada 10 segundos
if (millis() % 10000 < 100) {
targetSpeed = random(50, 200);
Serial.printf("🎯 Nueva velocidad objetivo: %.1f\n", targetSpeed);
}
} else {
// En emergencia, resetear PID
integral = 0;
lastError = 0;
}
vTaskDelay(pdMS_TO_TICKS(100)); // Control a 10Hz
}
}
// TAREA NORMAL (Prioridad 2) - Interfaz y Comunicación
void normalInterfaceTask(void *pvParameters) {
unsigned long lastTelemetry = 0;
for (;;) {
// Simular lectura de sensor de temperatura
sensorTemperature = 25.0 + random(0, 600) / 10.0; // 25-85°C
// Enviar telemetría cada 2 segundos
if (millis() - lastTelemetry > 2000) {
Serial.println("\n📊 === TELEMETRÍA DEL SISTEMA ===");
Serial.printf("Temperatura: %.1f°C\n", sensorTemperature);
Serial.printf("Velocidad Motor: %d/255\n", motorSpeed);
Serial.printf("Estado: %s\n", emergencyStop ? "EMERGENCIA" : "Normal");
Serial.printf("Heartbeat: %lu ms\n", millis() - lastHeartbeat);
Serial.printf("Heap Libre: %d bytes\n", ESP.getFreeHeap());
Serial.println("============================\n");
lastTelemetry = millis();
}
// Simular procesamiento de interfaz de usuario
if (Serial.available()) {
String command = Serial.readString();
command.trim();
if (command == "stop") {
emergencyStop = true;
Serial.println("Stop manual activado por usuario");
} else if (command == "resume") {
emergencyStop = false;
Serial.println("Sistema reanudado por usuario");
}
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// TAREA BAJA PRIORIDAD (Prioridad 1) - Mantenimiento
void lowPriorityMaintenanceTask(void *pvParameters) {
unsigned long lastMaintenance = 0;
int maintenanceCounter = 0;
for (;;) {
// Tareas de mantenimiento cada 10 segundos
if (millis() - lastMaintenance > 10000) {
Serial.printf("🔧 Mantenimiento #%d ejecutándose...\n", ++maintenanceCounter);
// Simular limpieza de memoria
Serial.printf("Memoria antes: %d bytes\n", ESP.getFreeHeap());
// Estadísticas de tareas
Serial.println("📋 Estado de las tareas:");
if (criticalTaskHandle) {
UBaseType_t stackFree = uxTaskGetStackHighWaterMark(criticalTaskHandle);
Serial.printf(" Critical Task - Stack libre: %d bytes\n", stackFree * 4);
}
if (highTaskHandle) {
UBaseType_t stackFree = uxTaskGetStackHighWaterMark(highTaskHandle);
Serial.printf(" Control Task - Stack libre: %d bytes\n", stackFree * 4);
}
if (normalTaskHandle) {
UBaseType_t stackFree = uxTaskGetStackHighWaterMark(normalTaskHandle);
Serial.printf(" Interface Task - Stack libre: %d bytes\n", stackFree * 4);
}
// Simulación de compactación de datos históricos
Serial.println("Compactando logs históricos...");
vTaskDelay(pdMS_TO_TICKS(200)); // Simular trabajo
Serial.println("✅ Mantenimiento completado\n");
lastMaintenance = millis();
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("=== Sistema de Control Multiprioridad ===");
Serial.println("Comandos disponibles: 'stop', 'resume'");
Serial.println("Presiona BOOT (GPIO0) para emergencia física\n");
// Configurar pins
pinMode(0, INPUT_PULLUP); // BOOT button
// Crear tareas con diferentes prioridades
xTaskCreatePinnedToCore(
criticalEmergencyTask, "Critical", 2048, NULL,
6, // ⚠️ PRIORIDAD MUY ALTA
&criticalTaskHandle, 0
);
xTaskCreatePinnedToCore(
highPriorityControlTask, "Control", 3072, NULL,
4, // 🎯 PRIORIDAD ALTA
&highTaskHandle, 1
);
xTaskCreatePinnedToCore(
normalInterfaceTask, "Interface", 3072, NULL,
2, // 📱 PRIORIDAD NORMAL
&normalTaskHandle, 1
);
xTaskCreatePinnedToCore(
lowPriorityMaintenanceTask, "Maintenance", 2048, NULL,
1, // 🔧 PRIORIDAD BAJA
&lowTaskHandle, 0
);
Serial.println("🚀 Sistema iniciado con 4 niveles de prioridad");
Serial.printf("Tareas creadas: Critical(6), Control(4), Interface(2), Maintenance(1)\n\n");
}
void loop() {
// Loop vacío - todo controlado por tareas
vTaskDelete(NULL);
}
Funcionamiento del Scheduler
Algoritmo de Planificación Preemptive
FreeRTOS utiliza un scheduler preemptivo basado en prioridades que garantiza:
Tarea de Mayor Prioridad Siempre Activa
Si una tarea de prioridad 5 está lista para ejecutarse, interrumpirá cualquier tarea de prioridad menor.
Round-Robin para Prioridades Iguales
Tareas con la misma prioridad comparten tiempo de CPU equitativamente usando time slicing.
Context Switch Automático
El cambio entre tareas es manejado automáticamente por el kernel, preservando el estado completo.
Idle Task (Prioridad 0)
Cuando no hay tareas listas, ejecuta la tarea idle que puede activar modo de ahorro de energía.
Ejercicios Prácticos
Objetivo: Crear 4 tareas con diferentes prioridades y observar cómo el scheduler las maneja.
Metodología:
- Tareas con prioridades 1, 2, 3, 4
- Cada tarea imprime su ID y timestamp
- Analizar el orden de ejecución
Objetivo: Implementar un sistema de invernadero con 4 niveles de prioridad:
- Crítica (5): Detección de incendio
- Alta (3): Control de temperatura
- Normal (2): Riego automático
- Baja (1): Logging de datos
Objetivo: Sistema que ajusta prioridades automáticamente según carga de trabajo.
Características:
- Monitoreo de CPU y memoria
- Ajuste dinámico de prioridades
- Prevención de starvation
- Métricas de rendimiento en tiempo real