Over the last few years, I have become a fan of FreeRTOS for a few different reasons:
- It’s free (though a paid versions allow for some protection)
- Portable as it abstracts well from the hardware it runs on
- Provides an easy way to separate sections of code
- Allows for control of when, and how often, tasks are run
It’s the last point that this post will delve into. How does each thread (tasks in FreeRTOS speak) control when they run? It all comes down to the ‘tick,’ the magical trigger that FreeRTOS uses to know when to switch which task is running or give up waiting for a semaphore that will never be given.
What Drives the FreeRTOS Tick
It all comes from the FreeRTOS Tick – the function that is called at a periodic interval. As we will see below, this tick drives the context switch between tasks. But first, what drives this tick?
At the simplest level, the tick is just a timer that has a period set to match the desired tick rate. By default, this is set to 1ms which provides a good balance between task speed and overhead of task switching. The timer interrupt is triggered every time the period is hit. The ISR calls into FreeRTOS’ vTaskSwitchContext() function which we dive into below.
For ARM core processors, there is a special timer designed specifically for the purpose of providing an RTOS its tick. This timer is called the SysTick (System Tick) and provides its own clock configuration, counter value, and interrupt flag. This allows you to set up the SysTick to provide FreeRTOS’ tick and use all the other timers for your program’s needs.
But as we will look at in future posts, we can utilize the flexible nature of what drives the FreeRTOS tick to reduce power.
What Happens at the FreeRTOS Tick
So coming back to the vTaskSwitchContext() function that is called during the timer tick, what happens when the tick happens?
While we won’t focus on the nature of cooperative and preemptive multitasking. You can check out the links provided to get a review of these multitasking models. FreeRTOS allows for both models, but our discussion on the tick makes more sense in the preemptive mode.
In FreeRTOS, you create ‘tasks’ that contain a functional component that you want to run at specific times or in reaction to other events. When the operation system is running, each of these tasks exists in one of four states:
- Running – the process currently doing work
- Ready – is available to run as soon as the OS allows it
- Blocked – waiting for an event to allow it to run
- Suspended – not even checked by the OS, task is shut off
When the tick triggers vTaskSwitchContext() the kernel looks at which tasks are in the Ready state and if there are any higher priority tasks than the current one, it switches context to allow the task time to run.
The diagram below shows a basic example of three tasks switching context either due to priority interrupt or context switching during the tick.
Higher priority tasks (higher number in FreeRTOS) are immediately switched to if they become unblocked due to a semaphore or queue releasing them and the next task takes over if any task goes into the blocked state (waiting for a semaphore or queue).
Tick Drives Round Robin
The FreeRTOS tick also looks for any other tasks that are equal in priority and will give each a chance to run. If multiple tasks are the same priority and are always in the Running or Ready state, the kernel creates a Round Robin model where each gets a full tick before switching to the next.
This Round Robin mode is where the balance between how fast the System Tick should be. A faster tick would allow more tasks to do operations in a given timeframe, but each task will only be able to do a limited amount of work before having to stop. And while the kernel is small and optimized for microcontrollers, it still takes a certain amount of time for it to save the current task’s context and load in the next task.
Ticks can Unblock Tasks
While the FreeRTOS tick function generally is switching context between equal priority tasks, it could also cause tasks to go from the Blocked to the Ready state, potentially moving a higher priority task into the Running state. One way this happens is if a task has called vTaskDelay() which delays a certain number of ticks.
Each call to vTaskSwitchContext() checks if any task is blocked due to the delay function and update’s its state if it has elapsed the desired time.
What if Nothing Needs to Happen
For many systems, especially low power systems, you don’t want tasks constantly running. In part, this happens because tasks are generally setup to react to certain events, events that may not happen often.
Between waiting for the next event, it could be that all of your tasks have nothing to do and have entered the Blocked state.
The Idle Task of Last Resort
When this occurs, FreeRTOS’ idle task runs instead, which is where FreeRTOS does some garbage collection for systems that create and delete tasks during runtime. Otherwise, the idle task is where you do low priority tasks like diagnostics or logging. This code could be called by the idleTaskHook function inside the Idle task, or creating tasks that are set to the idle priority of 0.
Looking at Lower Power
What we will get into later is looking at how long we need to wait for the next task to be serviced or times out waiting. We can use this information to decide to go into lower power states to conserve battery life or improve power efficiency.
At the simplest level, the ARM call to WFI will put the processor on hold until the next interrupt kicks it back into gear. This saves a small amount of current as the core processor clock is disabled until the next FreRTOS tick.
Shortly we will have a post focused strictly on the low power aspect of FreeRTOS. If you want to make sure you notified of these upcoming posts, sign up for our weekly newsletter below.