freertos-delays-vtaskdelay-vtaskdelayuntil

vTaskDelay y vTaskDelayUntil en FreeRTOS

  • 4 min

Un delay en FreeRTOS es una espera que bloquea solo a la tarea actual, dejando libre la CPU para que otras tareas puedan ejecutarse mientras tanto.

Esta idea es fundamental. En Arduino clásico, cuando usamos delay(), pensamos “espera un segundo”. En un RTOS tenemos que pensar de otra forma: esta tarea no tiene nada útil que hacer durante un segundo, así que puede dormirse y dejar trabajar a las demás.

El problema de delay()

En un programa secuencial, delay() detiene el flujo del programa. Si escribimos esto:

void loop() {
  leerSensor();
  delay(1000);
  actualizarPantalla();
}
Copied!

Durante ese segundo no pasa nada más dentro de ese flujo. En Arduino-ESP32, delay() usa internamente una espera de FreeRTOS, por lo que bloquea loopTask, no todo el sistema. Aun así, vTaskDelay() expresa mejor la intención dentro de una tarea y trabaja directamente con ticks.

En FreeRTOS, una tarea debe evitar esperas activas como esta:

while (millis() - inicio < 1000) {
  // Esperando sin hacer nada útil
}
Copied!

Ese código consume CPU aunque no esté haciendo trabajo real. Es como dejar a alguien sentado en una silla dando vueltas a un boli, ocupando la sala entera porque sí.

Comparación rápida

FunciónTipo de esperaUso típico
vTaskDelay()RelativaPausas simples, reintentos, parpadeos
vTaskDelayUntil()Absoluta/periódicaLecturas periódicas, control, muestreo

Si el tiempo exacto no importa demasiado, vTaskDelay() es más simple. Si necesitas un ritmo estable, usa vTaskDelayUntil().

vTaskDelay

La función más sencilla para dormir una tarea es vTaskDelay.

void vTaskDelay(TickType_t xTicksToDelay);
Copied!

Esta función mueve la tarea al estado Blocked durante un número determinado de ticks. Mientras está bloqueada, no consume CPU.

void TareaBlink(void *pvParameters) {
  for(;;) {
    digitalWrite(LED_BUILTIN, HIGH);
    vTaskDelay(pdMS_TO_TICKS(500));

    digitalWrite(LED_BUILTIN, LOW);
    vTaskDelay(pdMS_TO_TICKS(500));
  }
}
Copied!

La macro pdMS_TO_TICKS() convierte milisegundos a ticks según la frecuencia configurada del sistema.

Usa pdMS_TO_TICKS(500) en lugar de poner 500 a pelo. Así el código sigue siendo correcto aunque cambie configTICK_RATE_HZ.

Delay relativo

vTaskDelay() hace una espera relativa. Es decir, espera “a partir de ahora”.

Si una tarea tarda 20ms en ejecutar su trabajo y luego hace:

vTaskDelay(pdMS_TO_TICKS(1000));
Copied!

El periodo real será aproximadamente:

20ms de trabajo + 1000ms de espera = 1020ms
Copied!

Para muchas cosas esto está perfectamente bien. Si solo queremos parpadear un LED, actualizar un estado o espaciar lecturas no críticas, vTaskDelay() es suficiente.

El problema aparece cuando queremos una tarea periódica de verdad, por ejemplo leer un sensor exactamente cada 100ms.

vTaskDelayUntil

Para tareas periódicas, FreeRTOS nos da vTaskDelayUntil.

void vTaskDelayUntil(
  TickType_t *pxPreviousWakeTime,
  TickType_t xTimeIncrement
);
Copied!

Esta función no espera “desde ahora”, sino hasta el siguiente instante planificado. Por eso ayuda a mantener un periodo constante aunque el trabajo de la tarea tarde un poco.

void TareaPeriodica(void *pvParameters) {
  TickType_t xLastWakeTime = xTaskGetTickCount();
  const TickType_t periodo = pdMS_TO_TICKS(100);

  for(;;) {
    leerSensor();
    procesarDato();

    vTaskDelayUntil(&xLastWakeTime, periodo);
  }
}
Copied!

Aquí la tarea intenta ejecutarse cada 100ms, no “100ms después de terminar”. Esa diferencia parece pequeña, pero en sistemas temporizados evita que el periodo vaya derivando poco a poco.

Si el trabajo tarda más que el periodo, vTaskDelayUntil() no puede recuperar el tiempo perdido: devolverá inmediatamente hasta que la referencia temporal vuelva a quedar por delante. No convierte una tarea sobrecargada en puntual.

Ojo con los ticks

FreeRTOS mide el tiempo en ticks, no directamente en milisegundos. Si configTICK_RATE_HZ vale 1000, un tick equivale a 1ms. Si vale 100, un tick equivale a 10ms.

Esto significa que no podemos pedir una resolución mejor que la del tick. Además, la tarea despierta en estado Ready: una tarea más prioritaria puede retrasar su ejecución, así que la resolución no equivale a latencia garantizada.

Un delay en FreeRTOS no es un temporizador de alta precisión. Para señales rápidas, PWM, captura de pulsos o tiempos de microsegundos, normalmente necesitaremos periféricos hardware o interrupciones.