3.1 Chapter Introduction and Scope
3.1 Chapter Introduction and Scope
3.1 Chapter Introduction and Scope
Scope
How FreeRTOS chooses which task should execute at any given time.
How to change the priority of a task that has already been created.
How to implement periodic processing using a task (software timers are discussed in a
later chapter).
When the idle task will execute and how it can be used.
The concepts presented in this chapter are fundamental to understanding how to use
FreeRTOS, and how FreeRTOS applications behave. This is, therefore, the most detailed
chapter in the book.
45
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Tasks are implemented as C functions. The only thing special about them is their prototype,
which must return void and take a void pointer parameter. The prototype is demonstrated by
Listing 11.
Each task is a small program in its own right. It has an entry point, will normally run forever
within an infinite loop, and will not exit. The structure of a typical task is shown in Listing 12.
FreeRTOS tasks must not be allowed to return from their implementing function in any way—
they must not contain a ‘return’ statement and must not be allowed to execute past the end of
the function. If a task is no longer required, it should instead be explicitly deleted. This is also
demonstrated in Listing 12.
A single task function definition can be used to create any number of tasks—each created task
being a separate execution instance, with its own stack and its own copy of any automatic
(stack) variables defined within the task itself.
46
void ATaskFunction( void *pvParameters )
{
/* Variables can be declared just as per a normal function. Each instance of a task
created using this example function will have its own copy of the lVariableExample
variable. This would not be true if the variable was declared static – in which case
only one copy of the variable would exist, and this copy would be shared by each
created instance of the task. (The prefixes added to variable names are described in
section 1.5, Data Types and Coding Style Guide.) */
int32_t lVariableExample = 0;
/* Should the task implementation ever break out of the above loop, then the task
must be deleted before reaching the end of its implementing function. The NULL
parameter passed to the vTaskDelete() API function indicates that the task to be
deleted is the calling (this) task. The convention used to name API functions is
described in section 0, Projects that use a FreeRTOS version older than V9.0.0
must build one of the heap_n.c files. From FreeRTOS V9.0.0 a heap_n.c file is only
required if configSUPPORT_DYNAMIC_ALLOCATION is set to 1 in FreeRTOSConfig.h or if
configSUPPORT_DYNAMIC_ALLOCATION is left undefined. Refer to Chapter 2, Heap Memory
Management, for more information.
Data Types and Coding Style Guide. */
vTaskDelete( NULL );
}
47
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
An application can consist of many tasks. If the processor running the application contains a
single core, then only one task can be executing at any given time. This implies that a task
can exist in one of two states, Running and Not Running. This simplistic model is considered
first—but keep in mind that it is an over simplification. Later in the chapter it is shown that the
Not Running state actually contains a number of sub-states.
When a task is in the Running state the processor is executing the task’s code. When a task
is in the Not Running state, the task is dormant, its status having been saved ready for it to
resume execution the next time the scheduler decides it should enter the Running state.
When a task resumes execution, it does so from the instruction it was about to execute before
it last left the Running state.
NotRunning
Not
Not Running
Running Running
A task transitioned from the Not Running state to the Running state is said to have been
‘switched in’ or ‘swapped in’. Conversely, a task transitioned from the Running state to the Not
Running state is said to have been ‘switched out’ or ‘swapped out’. The FreeRTOS scheduler
is the only entity that can switch a task in and out.
48
3.4 Creating Tasks
FreeRTOS V9.0.0 also includes the xTaskCreateStatic() function, which allocates the memory required to create a
task statically at compile time: Tasks are created using the FreeRTOS xTaskCreate() API function.
This is probably the most complex of all the API functions, so it is unfortunate that it is the first
encountered, but tasks must be mastered first as they are the most fundamental component of
a multitasking system. All the examples that accompany this book make use of the
xTaskCreate() function, so there are plenty of examples to reference.
Section 1.5, Data Types and Coding Style Guide, describes the data types and naming
conventions used.
Parameter Name/
Description
Returned Value
pvTaskCode Tasks are simply C functions that never exit and, as such, are normally
implemented as an infinite loop. The pvTaskCode parameter is simply a
pointer to the function that implements the task (in effect, just the name
of the function).
49
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
pcName A descriptive name for the task. This is not used by FreeRTOS in any
way. It is included purely as a debugging aid. Identifying a task by a
human readable name is much simpler than attempting to identify it by
its handle.
50
Table 8. xTaskCreate() parameters and return value
Parameter Name/
Description
Returned Value
usStackDepth Each task has its own unique stack that is allocated by the kernel to the
task when the task is created. The usStackDepth value tells the kernel
how large to make the stack.
The value specifies the number of words the stack can hold, not the
number of bytes. For example, if the stack is 32-bits wide and
usStackDepth is passed in as 100, then 400 bytes of stack space will be
allocated (100 * 4 bytes). The stack depth multiplied by the stack width
must not exceed the maximum value that can be contained in a variable
of type uint16_t.
The size of the stack used by the Idle task is defined by the application-
defined constant configMINIMAL_STACK_SIZE1. The value assigned
to this constant in the FreeRTOS demo application for the processor
architecture being used is the minimum recommended for any task. If
your task uses a lot of stack space, then you must assign a larger value.
pvParameters Task functions accept a parameter of type pointer to void ( void* ). The
value assigned to pvParameters is the value passed into the task.
Some examples in this book demonstrate how the parameter can be
used.
1 This is the only way the FreeRTOS source code uses the configMINIMAL_STACK_SIZE setting,
although the constant is also used inside demo applications to help make the demos portable across
multiple processor architectures.
51
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
Parameter Name/
Description
Returned Value
uxPriority Defines the priority at which the task will execute. Priorities can be
assigned from 0, which is the lowest priority, to
(configMAX_PRIORITIES – 1), which is the highest priority.
configMAX_PRIORITIES is a user defined constant that is described in
section 3.5.
pxCreatedTask pxCreatedTask can be used to pass out a handle to the task being
created. This handle can then be used to reference the task in API calls
that, for example, change the task priority or delete the task.
If your application has no use for the task handle, then pxCreatedTask
can be set to NULL.
1. pdPASS
2. pdFAIL
This indicates that the task has not been created because there is
insufficient heap memory available for FreeRTOS to allocate enough
RAM to hold the task data structures and stack.
This example demonstrates the steps needed to create two simple tasks, then start the tasks
executing. The tasks simply print out a string periodically, using a crude null loop to create the
52
period delay. Both tasks are created at the same priority, and are identical except for the
string they print out—see Listing 14 and Listing 15 for their respective implementations.
The main() function creates the tasks before starting the scheduler—see Listing 16 for its
implementation.
53
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
/* Create the other task in exactly the same way and at the same priority. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}
1 The screen shot shows each task printing out its message exactly once before the next task executes.
This is an artificial scenario that results from using the FreeRTOS Windows simulator. The Windows
simulator is not truly real time. Also writing to the Windows console takes a relatively long time and
results in a chain of Windows system calls. Executing the same code on a genuine embedded target
with a fast and non-blocking print function may result in each task printing its string many times before
being switched out to allow the other task to run.
54
Figure 10 shows the two tasks appearing to execute simultaneously; however, as both tasks
are executing on the same processor core, this cannot be the case. In reality, both tasks are
rapidly entering and exiting the Running state. Both tasks are running at the same priority,
and so share time on the same processor core. Their actual execution pattern is shown in
Figure 11.
The arrow along the bottom of Figure 11 shows the passing of time from time t1 onwards. The
colored lines show which task is executing at each point in time—for example, Task 1 is
executing between time t1 and time t2.
Only one task can exist in the Running state at any one time. So, as one task enters the
Running state (the task is switched in), the other enters the Not Running state (the task is
switched out).
Task 1
Task 2
t1 t2 t3 Time
Figure 11. The actual execution pattern of the two Example 1 tasks
Example 1 created both tasks from within main(), prior to starting the scheduler. It is also
possible to create a task from within another task. For example, Task 2 could have been
created from within Task 1, as shown by Listing 17.
55
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
/* If this task code is executing then the scheduler must already have
been started. Create the other task before entering the infinite loop. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );
Listing 17. Creating a task from within another task after the scheduler has started
The two tasks created in Example 1 are almost identical, the only difference between them
being the text string they print out. This duplication can be removed by, instead, creating two
instances of a single task implementation. The task parameter can then be used to pass into
each task the string that it should print out.
Listing 18 contains the code of the single task function (vTaskFunction) used by Example 2.
This single function replaces the two task functions (vTask1 and vTask2) used in Example 1.
Note how the task parameter is cast to a char * to obtain the string the task should print out.
56
void vTaskFunction( void *pvParameters )
{
char *pcTaskName;
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */
/* The string to print out is passed in via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;
Listing 18. The single task function used to create two tasks in Example 2
Even though there is now only one task implementation (vTaskFunction), more than one
instance of the defined task can be created. Each created instance will execute independently
under the control of the FreeRTOS scheduler.
Listing 19 shows how the pvParameters parameter to the xTaskCreate() function is used to
pass the text string into the task.
57
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
/* Define the strings that will be passed in as the task parameters. These are
defined const and not on the stack to ensure they remain valid when the tasks are
executing. */
static const char *pcTextForTask1 = "Task 1 is running\r\n";
static const char *pcTextForTask2 = "Task 2 is running\r\n";
/* Create the other task in exactly the same way. Note this time that multiple
tasks are being created from the SAME task implementation (vTaskFunction). Only
the value passed in the parameter is different. Two instances of the same
task are being created. */
xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );
/* If all is well then main() will never reach here as the scheduler will
now be running the tasks. If main() does reach here then it is likely that
there was insufficient heap memory available for the idle task to be created.
Chapter 2 provides more information on heap memory management. */
for( ;; );
}
The output from Example 2 is exactly as per that shown for example 1 in Figure 10.
58
3.5 Task Priorities
The uxPriority parameter of the xTaskCreate() API function assigns an initial priority to the task
being created. The priority can be changed after the scheduler has been started by using the
vTaskPrioritySet() API function.
The FreeRTOS scheduler can use one of two methods to decide which task will be in the
Running state. The maximum value to which configMAX_PRIORITIES can be set depends on
the method used:
1. Generic Method
The generic method is implemented in C, and can be used with all the FreeRTOS
architecture ports.
When the generic method is used, FreeRTOS does not limit the maximum value to
which configMAX_PRIORITIES can be set. However, it is always advisable to keep
the configMAX_PRIORITIES value at the minimum necessary, because the higher its
value, the more RAM will be consumed, and the longer the worst case execution time
will be.
The architecture optimized method uses a small amount of assembler code, and is
faster than the generic method. The configMAX_PRIORITIES setting does not affect
the worst case execution time.
59
161204 Pre-release for FreeRTOS V8.x.x. See http://www.FreeRTOS.org/FreeRTOS-V9.html for information about FreeRTOS
V9.x.x. See https://www.freertos.org/FreeRTOS-V10.html for information about FreeRTOS V10.x.x.
The FreeRTOS scheduler will always ensure that the highest priority task that is able to run is
the task selected to enter the Running state. Where more than one task of the same priority is
able to run, the scheduler will transition each task into and out of the Running state, in turn.
60