Módulo 7

Priorización de Procesos

Gestión Inteligente de Tareas en FreeRTOS

ESP32 FreeRTOS Prioridades UNAM

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.

5-7

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

3-4

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

2

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

0-1

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:

C++ - Sistema Multiprioridad
// 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:

1
Tarea de Mayor Prioridad Siempre Activa

Si una tarea de prioridad 5 está lista para ejecutarse, interrumpirá cualquier tarea de prioridad menor.

2
Round-Robin para Prioridades Iguales

Tareas con la misma prioridad comparten tiempo de CPU equitativamente usando time slicing.

3
Context Switch Automático

El cambio entre tareas es manejado automáticamente por el kernel, preservando el estado completo.

4
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

1

Análisis de Comportamiento del Scheduler

Principiante 30 min

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
2

Sistema de Control de Invernadero

Intermedio 60 min

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
3

Balanceador de Carga Dinámico

Avanzado 90 min

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

Referencias Avanzadas y Lecturas Recomendadas