Master The Pico WiFi: Installing FreeRTOS |
Written by Harry Fairhead and Mike James | ||||
Monday, 01 September 2025 | ||||
Page 2 of 3
Scheduling and TasksFreeRTOS works in terms of tasks. A task is a function that can be run as if it was a “main” program in its own right. That is, a task is like a function call, but it doesn’t block its creator until it has finished. Tasks never return and are generally written as infinite loops. Tasks can be destroyed via FreeRTOS. Creating a FreeRTOS program is all about creating and managing tasks. The basic FreeRTOS call to create a task is: xTaskCreate(pTaskFunction, pName, StackDepth, Its parameters are:
The function that implements a task looks like an interrupt handler, for example: void TaskFunction(void *arg) The stack size should be set to be large enough to store all of the local variables that are created by the task or by any functions it calls. You can find out how close you are to running out of stack memory using uxTaskGetStackHighWaterMark, which reports the smallest free stack space since the task started running. Memory ManagementThe memory needed for the task is allocated by FreeRTOS. If you want to control this then you can use xTaskCreateStatic() and supply pointers to memory to be used by FreeRTOS. In most situations you don’t need to do this. FreeRTOS maintains a heap to allocate memory dynamically and you have to select what degree of memory management you want to use. At the time of writing it supports five levels of heap management: heap 1 The very simplest, does not permit memory to be freed heap 2 Permits memory to be freed, but does not coalesce adjacent free blocks heap 3 Simply wraps the standard malloc() and free() for thread safety heap 4 Coalesces adjacent free blocks to avoid fragmentation and includes absolute address placement option heap 5 As per heap 4 with the ability to span the heap across multiple non-adjacent memory areas Heap 1 can be avoided by simply using static task creation and not using the heap at all. Heap 2 is more or less superseded by heap 4 which does everything it does plus rearranging memory to produce larger contiguous free areas. Heap 5 is more sophisticated but it is slower and uses more program memory. Overall, heap 4 is the best compromise and it is used by all of the examples in this and subsequent chapters. Heap 4 is selected within the CmakeLists.txt file: FreeRTOS-Kernel-Heap4 Core AffinityBy default FreeRTOS uses both cores. The Kernel and a timer task run on core 0 and core 1 is free for you to use. A task that you create will run on either core and can even swap which core it is running on. In the jargon, the task is said to have no core affinity. To assign a task to a particular core we can use: void vTaskCoreAffinitySet( where the uxCoreAffinityMask has a bit for each core. For example, to set a task to use only core 1: uxCoreAffinityMask = 1 << 1; vTaskCoreAffinitySet( pTaskFunctionpTaskFunction, uxCoreAffinityMask ); You can set more than one core in the mask and then FreeRTOS will use any of the assigned cores to run the task as and when they become available. There is also a vTaskCoreAffinityGet function. For these functions to work, configUSE_CORE_AFFINITY has to be set to 1 in the FreeRTOSConfig.h file, which it is by default in the supplied file. Notice that you can start tasks in the main program before starting the scheduler and you can start new tasks from within running tasks. In this case there is no need to start or stop the scheduler as the task will be started or queued for starting when the task that created it is finished. The Task QueueNot all of the tasks you create can be running at any given time. If you only have two cores, like the Pico, then at most two tasks can be running. A task can be in one of four states: Running, Ready (to run), Blocked or Suspended. The difference between Blocked and Suspended is that a task that is Blocked is waiting on something that the system can supply, such as the time being up for a task that has called vTaskDelay. The system can change the status of a task from Blocked to Running on its own. A task can change its state to Suspended by calling vTaskDelay and also becomes Suspended by another task calling vTaskSuspend in which case it can only be returned to the Ready state by another task calling vTaskResume. On a single-core machine there can be only one Running task rather than two on a dual-core machine. Tasks are stored in a list which the scheduler has access to. The system is configured so that every portTICK_PERIOD_MS milliseconds, 1ms by default, there is a timer interrupt that runs the scheduler. This causes the currently running task to change state to Ready and the scheduler examines the list of Ready task and runs the one with the highest priority. If there are multiple tasks with the same priority then they each get their turn to run in a round-robin fashion. This is a very simple scheduler, but there are a few things to notice. The first is that a task doesn’t have any choice about giving up control if the system selects another task to run. That is, FreeRTOS is a priority-based preemptive scheduler. Also notice that if there are tasks that are ready to run with a higher priority, then lower-priority tasks don’t get a look in. You can protect a task against preemption using:
For these to be available, configUSE_TASK_PREEMPTION_DISABLE must be defined as 1, which it is by default. So how do lower-priority tasks ever get to run? The answer is that tasks, irrespective of priority, are not always in a Ready state. If a task is waiting for input, then it will be Blocked and hence not ready to run. If a task has suspended itself using a vTaskDelay(t / portTICK_PERIOD_MS) then it is not ready to run until the time is up. If it has been suspended by itself or another task then it will not be ready to run until another task causes it to resume. At the time of writing the tick period for the Pico is 1ms. For all these reasons, it may well be that there are no tasks of a given priority in the Ready state. In this case the scheduler looks for the highest-priority task that is ready to run. To summarize:
This is a very simple scheduling algorithm and has the advantage that you can mostly work out what is going to happen. However, the picture is slightly complicated by the fact that tasks can have core affinities. If two high-priority tasks both want to run on the same core, then one of them will run and the other will have to wait while a lower-priority task runs on the other core. Similarly, round-robin selection among tasks of equal priority also has to take account of the tasks’ core affinities. |
||||
Last Updated ( Monday, 01 September 2025 ) |