Mar 7 / Wadix Technologies

Mastering Time in FreeRTOS: vTaskDelay(), vTaskDelayUntil(), and the RTOS Tick

Mastering Time in FreeRTOS: vTaskDelay(), vTaskDelayUntil(), and the RTOS Tick

1. Introduction

In real embedded systems, tasks often need to wait. A task may sleep for a while, wait for data with a timeout, or run periodically every fixed interval. At first glance, this looks like ordinary time based behavior. In FreeRTOS, however, time is not a continuous quantity. It is built on a simple and very specific mechanism called the tick.

Understanding what the tick is, how delays work, and how timeouts are handled explains most of the timing behavior you see in a FreeRTOS system. It also explains why tasks sometimes wake up later than expected and why changing the tick rate affects both timing accuracy and CPU load.

This article looks at how FreeRTOS represents time, how delays are implemented, and what this means for system design.


2. The tick as the kernel’s time base:

FreeRTOS does not have a real time clock inside the kernel. Instead, it relies on a periodic interrupt called the tick interrupt. This interrupt is generated by a hardware timer. Each time the interrupt fires, the kernel increments an internal counter called the tick count.

This tick interrupt drives many parts of the kernel. It is used to expire delays, handle timeouts, perform time slicing between tasks of equal priority, and drive software timers.

Conceptually, the hardware timer produces a periodic event, and the kernel advances its notion of time by one step on each event.

Fig 1. A periodic hardware timer interrupt generates the RTOS tick and advances the kernel tick count.

In code, the tick count can be read like this:

TickType_t t = pdMS_TO_TICKS(10); /* Convert 10 ms to ticks */

This value is the only notion of time the kernel has. All delays and timeouts are expressed relative to this counter.


3. What “time” means inside the kernel

Inside FreeRTOS, time is measured in ticks, not in milliseconds or microseconds. The duration of one tick is defined by the tick rate configuration. If the tick rate is 1000 Hz, one tick represents one millisecond. If the tick rate is 100 Hz, one tick represents ten milliseconds.

This means time is quantized. A task can only delay or time out in whole numbers of ticks. There is no concept of half a tick or fractions of a tick inside the kernel.

This also means that a delay of N ticks means “at least N ticks”. The actual wakeup time depends on when the delay was requested relative to the tick interrupt and on what else is running in the system.

For convenience, FreeRTOS provides a macro to convert milliseconds to ticks:

TickType_t t = pdMS_TO_TICKS(10); /* Convert 10 ms to ticks */

Internally, however, the kernel always works in ticks.


4. Delays as a scheduling operation, not a busy wait

A common mistake in bare metal systems is to wait by spinning in a loop and counting time. This wastes CPU cycles and prevents other work from running. In FreeRTOS, a delay is a scheduling operation, not a busy wait.

When a task calls vTaskDelay() or vTaskDelayUntil(), it does not sit in a loop. Instead, the kernel removes the task from the Ready list and places it into a Delayed list with a wakeup time based on the current tick count. The scheduler then selects another Ready task to run.

Fig 2. A task calling vTaskDelay is moved from the Ready list to a Delayed list until its wakeup tick is reached.

A simple example looks like this:

void WorkerTask(void *arg)

{

for (;;)

{

DoWork();


/* Give up the CPU for 50 ms */

vTaskDelay(pdMS_TO_TICKS(50));

}

}

Here, the task is not consuming CPU time during the delay. It is blocked, and other tasks are free to run.


5. vTaskDelay vs vTaskDelayUntil

FreeRTOS provides two main delay functions, and they serve different purposes.

vTaskDelay() specifies a delay relative to the current time. It means “sleep for N ticks from now”. If it is used in a periodic loop, small variations in execution time can accumulate and cause drift.

vTaskDelayUntil() specifies a delay relative to a fixed reference time. It means “sleep until the next multiple of a given period”. This keeps a task aligned to a fixed schedule and avoids long term drift.

A typical periodic task looks like this:

void PeriodicTask(void *arg)

{

  TickType_t lastWake = xTaskGetTickCount();


  for (;;)

  {

    DoPeriodicWork();


    /* Run every 100 ms */
  
    vTaskDelayUntil(&lastWake, pdMS_TO_TICKS(100));

  }

}

The important point is that both functions control when the task becomes Ready again. They do not guarantee when the task will actually run.

Fig 3. Using vTaskDelay Vs vTaskDelayUntil .


6. Timeouts and waiting for events

Many FreeRTOS APIs accept a timeout parameter. For example, a task can wait for data on a queue or for a semaphore with a maximum wait time.

Conceptually, this uses the same mechanism as a delay. The task is placed into a Blocked state, and the kernel records a timeout tick. The task will become Ready again either when the event occurs or when the tick count reaches the timeout value.

For example:

if (xQueueReceive(q, &item, pdMS_TO_TICKS(20)) == pdPASS)

{

  /* Data received */

  }

  else

  {

  /* Timeout expired */

  }

Here, the kernel is still using the tick count. There is no separate timing system for timeouts. Delays and timeouts are two faces of the same mechanism.


7. Accuracy, jitter, and what the tick does not guarantee

Because time is quantized in ticks, timing accuracy is limited by the tick period. A task can only wake up on a tick boundary or later. It cannot wake up in between ticks.

There are also other sources of delay. If a higher priority task is running when a delayed task becomes Ready, the delayed task will not run until the higher priority task blocks or yields. This means a task can wake up later than its nominal time.

Fig 4. A task becomes Ready at its wakeup tick but may start running later due to higher priority work.

It is important to understand what FreeRTOS does not guarantee. The kernel does not schedule tasks by deadlines. It schedules by priority. Time controls when a task becomes Ready. Priority controls which Ready task actually runs.


8. Conclusion

In FreeRTOS, time is built on the tick. Delays and timeouts are scheduling operations that move tasks between states based on the tick count. They are not busy waits and they are not precise timers.

The tick defines when a task becomes Ready. The scheduler, driven by priority, decides when it actually runs. Understanding this separation explains most timing behavior in real systems and helps avoid many common design mistakes.

Choosing the tick rate and choosing how delays and timeouts are used are system architecture decisions, not just API choices.

Created with