Each task is a small program on its own right. It has an entry point, will normally run forever within an infinite loop. Task are implemented as C functions. the only about them its their prototype which must return a void and take a void. FreeRTOS tasks must not be allowed to return from their implementing function in any way. They must not contain a ‘return’ statement. If no longer required, it should be explicitly deleted, using a FreeRTOS API Function. pointer.
void vTaskFunction(void *pvParmeters)
{
/*init here*/
for(;;)
{
/*your code here*/
}
}
An application can consist of many tasks. If the microcontroller running the application only contains a single core, then only one task can actually be executing at any given time. This implies that a task can exist in of two states, "Running and Not-running"
Task are created using the FreeRTOS API function.
BaseType_t xTaskCreate(
TaskFunction_t pvTaskcode, /*Function top work as a task*/
const char * const pcName, /*name in a string representation*/
uint16_t usStackDepth, /*stack size in words (4 bytes)*/
void * pvParameters, /*pointer to paramters to pass*/
UBaseType_t uxPriority, /*task priority*/
TaskHandle_t * pxCreatedTask /*task handler*/
);
Example program:
...
/*Create first task*/
xTaskCreate(vTask1, "task1", 240, NULL, 1, NULL);
/*Create second task*/
xTaskCreate(vTask2, "task2", 240, NULL, 1, NULL);
...
void vTask1( void *pvParameters){
unsigned long i;
for(;;){
for(i=0;i<100000ul;i++);
SEGGER_SYSVIEW_PrintfHost("Soy Tarea 1");
}
}
void vTask2( void *pvParameters){
unsigned long i;
for(;;){
for(i=0;i<100000ul;i++);
SEGGER_SYSVIEW_PrintfHost("Soy Tarea 2");
}
}
Execution Pattern:
Context
As a task executes it utilizes the processor/microcontroller registers and accesses RAM and ROM just as any other program. These resources together (the processor registers, stack, etc.) comprise the task execution context.
A task is a sequential piece of code - it doesn’t know when it is going to get suspended or resumed by the kernel and doesn’t even know when this has happened. While the task is suspended other tasks will execute and may modify the processor register values. Upon resumption the task will not know that the processor registers have been altered.
The operating system kernel is responsible for ensuring that upon resumption a task has a context identical to that immediately prior to its suspension and does so by saving the context of a task as it is suspended. When the task is resumed its saved context is restored by the operating system kernel prior to its execution.
Each task executes for a “time slice”; it enters the running state at the start of the time slice and exits the running state at the end of the time slice.To be able to select the next task to run, the scheduler itself must execute at the end of each time slice. The “tick interrupt”, is used for this purpose.
Task Priorities
The uxPriority parameter of the xTaskCreate() function assigns an initial priority to the task being created. Low priority values denote low priority tasks. Priority 0 is the lowest possible value. and the maximum is configMAX_PRIORITIES - 1. The scheduler will always ensure that the highest priority task that is able to run is the Running state.
In your project FreeRTOSConfig.h file you will find the following define.
#define configMAX_PRIORITIES ( 5UL )
Change this value to set the maximum priority level for your application. The higher the value the more RAM the kernel will consume, make sure you only set up to the value your application really needs.
To make our tasks actually useful we need to make them, event driven, and that means tasks only has work to perform after the occurrence of the event that triggers it. Using event driven tasks allows create a lot of different priority tasks without the highest priority task starving processing time of the lower priority one.
The Block State
In reality the tasks “not running” state can expanded to another three different states
- Blocked State.- A task that is waiting for an event to occurred.
- Suspend State.- Task in the suspend state are not available to the scheduler.
- Ready State.- Tasks that are in the not-running state but are not Blocked or Suspended are said to be in the ready state.
The tasks created in the last day have been periodic. The delay has been generated very crudely using a null loop. While executing the null loop the task remained in the ready state, starving the other task of any processing time. While polling the task does not really have any work to do, but it still uses maximum processing time and so wastes processor cycles. To avoid this situation we can use the vTaskDelay
. this function place the calling task into the blocked state for a fixed number of "tick" interrupts.
/*in FreeRTOSConfig.h*/
#define INCLUDE_vTaskDelay 1
/*this enable the freertos fcuntion*/
void vTaskDelay(TickType_t xTicksToDelay);
Example program:
...
xTaskCreate( vTask1, "task1", 240, NULL, 1, NULL);
xTaskCreate( vTask2, "task2", 240, NULL, 2, NULL);
...
void vTask1(void *pvParameters){
for(;;){
SEGGER_SYSVIEW_PrintfHost("Hola retardo1");
vTaskDelay( 2000 );
}
}
void vTask2(void *pvParameters){
for(;;){
SEGGER_SYSVIEW_PrintfHost("Hola retardo2");
vTaskDelay( 2000 );
}
}
Execution Pattern:
Selecting Task Priorities
Rate Monotonic Scheduling is a common priority assigned technique that dictates a unique priority be assigned to each task in accordance with the task periodic execution time.
"The highest priority is assigned to the task that has the highest frequency of periodic execution. And the lowest priority is assigned to the task with the lowest frequency of periodic execution."
- Each task is assigned a priority.
- Each task can exist in one of the several states.
- Only one task can exist in the Running state at any one time.
- The scheduler will always select the highest priority Ready state task to enter the Running state.
Task Management
Once a task has been created, its possible to control and modify its behavior. (priority, running state, deleted, etc...). You have to enable each task management function with the following includes in your FreeRTOSConfig.h file
...
#define INCLUDE_vTaskPrioritySet 0
#define INCLUDE_uxTaskPriorityGet 0
#define INCLUDE_vTaskDelete 0
#define INCLUDE_vTaskCleanUpResources 0
#define INCLUDE_vTaskSuspend 0
#define INCLUDE_vTaskDelayUntil 0
#define INCLUDE_vTaskDelay 1
#define INCLUDE_uxTaskGetStackHighWaterMark 0
#define INCLUDE_eTaskGetState 0
...
Delay until
vTaskDelay() will cause a task to block for the specified number of ticks from the time vTaskDelay() is called. It is therefore difficult to use vTaskDelay() by itself to generate a fixed execution frequency as the time between a task unblocking following a call to vTaskDelay() and the next calling vTaskDelay() may not be fixed . The vTaskDelayUntil() specifies the absolute (exact) time at which it wishes to unblock.
void vPeriodic( void * pvParameters )
{
TickType_t xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
SEGGER_SYSVIEW_PrintfHost("Periodic function");
vTaskDelayUntil( &xLastWakeTime, 100 );
}
}
The Idle Task
The task created in example #1 spend most of their time in the blocked state. In this state, they are not able to run and can not be selected by the scheduler. The processor always need something to execute, so there must be at least one task that can enter the Running state. to ensure this is the case, an idle task is automatically created by the scheduler when vTaskStartScheduler() is called.
It is possible to add application specific functionality directly into the idle task through the use of an idle hook function. Common use for the idle task hook include:
- Low priority execution processing
- Measure the amount of processing time
- Placing the processor into low power mode
//set to 1 in FreeRTOSConfig.h
#define configUSE_IDLE_HOOK 1
//Global variable
unsigned long ulCycle = 0ul;
//you can define in your application
void vApplicationIdleHook( void )
{
ulCycle++;
}
Code Snippets
- TASKS: Simple Task to print a messages
- TASKS: Task reuse a function
- TASKS: Task Priority
- TASKS: Idle Hook enabled
- TASKS: Change Task priority
- TASKS: Create one task always active
- TASKS: Create two tasks same priority
- TASKS: Create two tasks from one function
- TASKS: Two task same priority with blocked states
Exercises (print)
- Through a for loop, create 5 tasks using only a simple function “Task template“. Use the parameters function in each task to display a different message. How many Tasks can be created? What are the limitations? Remember that the function
xTaskCreate
returns a parameter, use this parameter to determine to what extent tasks can be created. - Write a program that uses 3 tasks, each one should send a different message at different times, 1sec, 2sec, and 3sec respectively. Use the function
vTaskDelay
, for each one. - From the last exercise, modify the time of the tick interrupt to be generated each 10ms. The program should continue with the same function, displaying the 3 messages in the times previously required.
- Use only a function “Task template“ to create the 3 tasks of exercise 2, and pass the message and the time through the fourth parameter of the function
xTaskCreate
. Investigate how to insert code from our applications just at the moment that “tick“ of the system occurs. Create a demonstrative program. - Create 2 tasks to count from 1 to 20, the first task should run each second and will count from 1 to 10, and the second task will continue the count each 3 seconds from 10 to 20. Delete and create tasks for this purpose.
- Repeat the last exercise, this time use a loop and a HAL_Delay for the delays of 1000 and 3000 counts (This way tasks will execute continuously) in this occasion use the change of priority to get the tasks run alternately.
- Repeat exercise number 2, this time don't delete the tasks. You should use the delay functions for the times and the functions to suspend and reanude tasks. investigate the last 2 functions.
- Investigate the functions uxTaskGetNumberOfTasks, eTaskGetState, xTaskGetTickCount, y taskYIELD.
Exercises
- Write a task that rotates a turned-off LED on port C at a speed that is perceptible to the human eye and a second task the blink the Nucleo on board led every 500ms.
- Write a program that turns on an LED when a button is pressed and turns it off when the button is released. (The LED will only turn on when the button is pressed.), use a task to read the button a a second task to control the led state, a global variable shall be used to communicate both tasks
- Write a program that rotates an LED on port C, but this time with three speeds and three buttons. Each button will activate a different speed. again one task will control the buttons an second task the leds and a global variable to communicate both tasks
- Modify exercise 2 so that pressing the button once turns on the LED, and pressing it again turns it off.
- Repeat exercise 3, but this time using only one button and four speeds. Each time the button is pressed, the speed will increase, and when it reaches the last speed, it will start over.
- Modify the previous program using two buttons. Pressing one button will rotate the LEDs from left to right, and pressing the other button will rotate them from right to left.
This article was written by Alan Padilla Sigala