3.1 Chapter Introduction and Scope

Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

3.

1 Chapter Introduction and Scope

Scope

This chapter aims to give readers a good understanding of:

 How FreeRTOS allocates processing time to each task within an application.

 How FreeRTOS chooses which task should execute at any given time.

 How the relative priority of each task affects system behavior.

 The states that a task can exist in.

Readers should also gain a good understanding of:

 How to implement tasks.

 How to create one or more instances of a task.

 How to use the task parameter.

 How to change the priority of a task that has already been created.

 How to delete a task.

 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.

3.2 Task Functions

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.

void ATaskFunction( void *pvParameters );

Listing 11. The task function prototype

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;

/* A task will normally be implemented as an infinite loop. */


for( ;; )
{
/* The code to implement the task functionality will go here. */
}

/* 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 );
}

Listing 12. The structure of a typical task function

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.

3.3 Top Level Task States

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.

All tasks that are Only one task


not currently can be in the
Running are in the Running state at
Not Running state any one time

NotRunning
Not
Not Running
Running Running

Figure 9. Top level task states and transitions

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

The xTaskCreate() API Function

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.

BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,


const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );

Listing 13. The xTaskCreate() API function prototype

Table 8. xTaskCreate() parameters and return value

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.

Table 8. xTaskCreate() parameters and return value

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.

The application-defined constant configMAX_TASK_NAME_LEN


defines the maximum length a task name can take—including the NULL
terminator. Supplying a string longer than this maximum will result in
the string being silently truncated.

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.

There is no easy way to determine the stack space required by a task.


It is possible to calculate, but most users will simply assign what they
think is a reasonable value, then use the features provided by
FreeRTOS to ensure that the space allocated is indeed adequate, and
that RAM is not being wasted unnecessarily. Section 12.3, Stack
Overflow, contains information on how to query the maximum stack
space that has actually been used by a task.

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.

Table 8. xTaskCreate() parameters and return value

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.

Passing a uxPriority value above (configMAX_PRIORITIES – 1) will


result in the priority assigned to the task being capped silently to the
maximum legitimate value.

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.

Returned value There are two possible return values:

1. pdPASS

This indicates that the task has been created successfully.

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.

Chapter 2 provides more information on heap memory


management.

Example 1. Creating tasks

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.

void vTask1( void *pvParameters )


{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */

/* As per most tasks, this task is implemented in an infinite loop. */


for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}

Listing 14. Implementation of the first task used in Example 1

void vTask2( void *pvParameters )


{
const char *pcTaskName = "Task 2 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */

/* As per most tasks, this task is implemented in an infinite loop. */


for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}

Listing 15. Implementation of the second task used in Example 1

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.

int main( void )


{
/* Create one of the two tasks. Note that a real application should check
the return value of the xTaskCreate() call to ensure the task was created
successfully. */
xTaskCreate( vTask1, /* Pointer to the function that implements the task. */
"Task 1",/* Text name for the task. This is to facilitate
debugging only. */
1000, /* Stack depth - small microcontrollers will use much
less stack than this. */
NULL, /* This example does not use the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* This example does not use the task handle. */

/* Create the other task in exactly the same way and at the same priority. */
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );

/* Start the scheduler so the tasks start executing. */


vTaskStartScheduler();

/* 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( ;; );
}

Listing 16. Starting the Example 1 tasks

Executing the example produces the output shown in Figure 10.

Figure 10. The output produced when Example 1 is executed 1

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).

At time t1, Task 1 At time t2 Task 2 enters the Running


enters the Running state and executes until time t3 - at
state and executes which point Task1 re-enters the
until time t2 Running state

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.

void vTask1( void *pvParameters )


{
const char *pcTaskName = "Task 1 is running\r\n";
volatile uint32_t ul; /* volatile to ensure ul is not optimized away. */

/* 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 );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later examples will replace this crude
loop with a proper delay/sleep function. */
}
}
}

Listing 17. Creating a task from within another task after the scheduler has started

Example 2. Using the task parameter

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;

/* As per most tasks, this task is implemented in an infinite loop. */


for( ;; )
{
/* Print out the name of this task. */
vPrintString( pcTaskName );

/* Delay for a period. */


for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{
/* This loop is just a very crude delay implementation. There is
nothing to do in here. Later exercises will replace this crude
loop with a proper delay/sleep function. */
}
}
}

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";

int main( void )


{
/* Create one of the two tasks. */
xTaskCreate( vTaskFunction, /* Pointer to the function that
implements the task. */
"Task 1", /* Text name for the task. This is to
facilitate debugging only. */
1000, /* Stack depth - small microcontrollers
will use much less stack than this. */
(void*)pcTextForTask1, /* Pass the text to be printed into the
task using the task parameter. */
1, /* This task will run at priority 1. */
NULL ); /* The task handle is not used in this
example. */

/* 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 );

/* Start the scheduler so the tasks start executing. */


vTaskStartScheduler();

/* 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( ;; );
}

Listing 19. The main() function for Example 2.

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 maximum number of priorities available is set by the application-defined


configMAX_PRIORITIES compile time configuration constant within FreeRTOSConfig.h. Low
numeric priority values denote low-priority tasks, with priority 0 being the lowest priority
possible. Therefore, the range of available priorities is 0 to (configMAX_PRIORITIES – 1).
Any number of tasks can share the same priority—ensuring maximum design flexibility.

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 generic method will be used if


configUSE_PORT_OPTIMISED_TASK_SELECTION is set to 0 in FreeRTOSConfig.h,
or if configUSE_PORT_OPTIMISED_TASK_SELECTION is left undefined, or if the
generic method is the only method provided for the FreeRTOS port in use.

2. Architecture Optimized Method

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.

If the architecture optimized method is used then configMAX_PRIORITIES cannot be


greater than 32. As with the generic method, it is advisable to keep
configMAX_PRIORITIES at the minimum necessary, as the higher its value, the more
RAM will be consumed.

The architecture optimized method will be used if


configUSE_PORT_OPTIMISED_TASK_SELECTION is set to 1 in FreeRTOSConfig.h.

Not all FreeRTOS ports provide an architecture optimized method.

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

You might also like