FreeRTOS is a real-time operating system (RTOS) that is widely used in embedded systems and microcontroller-based applications. One of the key features of FreeRTOS is its scheduler, which allows developers to create and manage tasks in a real-time environment. In this blog post, we will discuss how to use the FreeRTOS scheduler, how to create and manipulate task priorities, and provide examples of how to implement these features in your embedded system.
The FreeRTOS scheduler is a cooperative scheduler that allows tasks to run in parallel. Each task is assigned a priority, and the scheduler runs the task with the highest priority that is ready to run. The scheduler is designed to be simple and efficient, and it requires minimal overhead to run.
How to Create a Task?
To create a task in FreeRTOS, you need to use the xTaskCreate() function. This function takes four parameters: the task function, the task name, the stack size, and the task parameter. The task function is the function that will be executed when the task runs, the task name is a human-readable string that is used to identify the task, the stack size is the amount of memory that will be allocated for the task's stack, and the task parameter is a pointer to any data that the task function needs to access.
The syntax for the xTaskCreate() function in FreeRTOS is as follows:
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,
const char * const pcName,
uint16_t usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask );
The function takes in the following parameters:
pvTaskCode: A pointer to the task function.
pcName: A pointer to a const string that is the name of the task.
usStackDepth: The size, in words, of the task's stack.
pvParameters: A pointer to the task's parameter.
uxPriority: The task's priority, which determines when the task will be scheduled to run.
pxCreatedTask: A pointer to a TaskHandle_t variable, which will be filled with a handle to the created task.
The function returns pdPASS if the task was created successfully, otherwise it returns pdFAIL.
When you create a task, you can also specify the task priority. The priority is an integer value that ranges from 0 (lowest priority) to configMAX_PRIORITIES - 1 (highest priority). The default priority is set to 1.
Once you have created a task, you can use the vTaskPrioritySet() function to change the task's priority at runtime. This function takes two parameters: the task handle and the new priority. The task handle is a pointer to the task control block (TCB) that was returned when the task was created.
Example to create Task(Use Arduino Ide)
In the following example, we will create two tasks, Task1 and Task2, with different priorities. Task1 has a higher priority than Task2.
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
void Task1(void *pvParameters)
{
(void) pvParameters;
while (1)
{
Serial.println("Hello from task1");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void Task2(void *pvParameters)
{
while (1)
{
Serial.println("Hello from task2");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void setup() {
Serial.begin(115200);
// Create the task
//xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL, );
xTaskCreatePinnedToCore(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL,ARDUINO_RUNNING_CORE); //Specific for Arduino IDE
//xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreatePinnedToCore(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 1, NULL,ARDUINO_RUNNING_CORE); //Specific for Arduino IDE
//vTaskStartScheduler(); //not required in Arduino
}
void loop() {
// nothing goes here, all the work is done in the tasks
}
In this example, we create two tasks, Task1 and Task2, and assign them different priorities. Task1 has a priority of 2 and Task2 has a priority of 1. The scheduler will run Task1 before Task2 because it has a higher priority.
Another important aspect of task management is time slicing. FreeRTOS uses a time-slicing mechanism that allows tasks with the same priority to share the CPU time. This ensures that no task is blocked for too long and that all tasks have a chance to run. The time-slicing mechanism is controlled by the configTIMER_TASK_PRIORITY and configTIMER_QUEUE_LENGTH configuration parameters.
It's also worth noting that FreeRTOS also provides several other functions that can be used to manage tasks and their priorities, such as vTaskDelete() which can be used to delete a task, vTaskList() which can be used to get the current status of all tasks and vTaskGetInfo() which can be used to get information about a specific task.
Additionally, FreeRTOS provides a feature called "priority inheritance" which can be used to avoid priority inversion. Priority inversion occurs when a low-priority task holds a resource that a high-priority task needs, causing the high-priority task to be blocked. Priority inheritance can be used to temporarily increase the priority of the low-priority task to the priority of the high-priority task, allowing the high-priority task to run and release the resource.
Example using two tasks with same priorities and with different priority
Here's an example of how you might use two tasks with the same priority and one with a different priority:
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
void Task1(void *pvParameters)
{
(void) pvParameters;
while (1)
{
Serial.println("Hello from task1");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void Task2(void *pvParameters)
{
(void) pvParameters;
while (1)
{
Serial.println("Hello from task2");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void Task3(void *pvParameters)
{
(void) pvParameters;
while (1)
{
Serial.println("Hello from task3");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void setup() {
Serial.begin(115200);
// Create the task
//xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL, ARDUINO_RUNNING_CORE);
xTaskCreatePinnedToCore(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, NULL,ARDUINO_RUNNING_CORE);//Specific to Arduino IDE
//xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL);
xTaskCreatePinnedToCore(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL,ARDUINO_RUNNING_CORE); //Specific to Arduino IDE
//xTaskCreate(Task3, "Task3", configMINIMAL_STACK_SIZE, NULL, 1, NULL);
xTaskCreatePinnedToCore(Task3, "Task3", configMINIMAL_STACK_SIZE, NULL, 1, NULL,ARDUINO_RUNNING_CORE); //Specific to Arduino IDE
//vTaskStartScheduler(); //not required in Arduino
}
void loop() {
// nothing goes here, all the work is done in the tasks
}
In this example, we create three tasks: Task1, Task2, and Task3. Task1 and Task2 have the same priority of 2, while Task3 has a priority of 1.
When the scheduler runs, it will first check if there are any tasks with the highest priority of 1, in this case Task3. If Task3 is ready to run, the scheduler will run it until it is blocked or it finishes.
If Task3 is blocked or finishes, the scheduler will then check for tasks with the next highest priority of 2, in this case, Task1 and Task2. Since both tasks have the same priority, the scheduler will run them in a round-robin fashion, giving each task an equal amount of CPU time.
It's also worth noting that, when there are multiple tasks with the same priority, the order in which they are created can affect the order in which they run.
In this example, the scheduler will run Task3 first, then Task1 and Task2 will be executed in a round-robin fashion.
Keep in mind that the previous example is a basic one, in real-world scenarios, the priorities and the order of execution of tasks can be affected by other factors such as the use of semaphores, message queues, or other synchronization mechanisms.
vTaskStop() and vTaskResume() and vTaskDelete() Functions
vTaskStop() and vTaskResume() are FreeRTOS functions that are used to temporarily stop and resume a task.
vTaskStop() function is used to stop a running task. It suspends the task, preventing it from running. For example:
vTaskStop( TaskHandle_t xTask );
vTaskResume() function is used to resume a suspended task. It will make the task eligible to run again.
vTaskResume( TaskHandle_t xTask );
vTaskDelete() function is used to delete a task. It will remove the task from the FreeRTOS scheduler and free up any memory that was allocated for the task. When a task is deleted, it is no longer eligible to run.
vTaskDelete( TaskHandle_t xTask );
It's important to note that the task should not be deleted if it is currently running, as this may cause memory corruption or system instability.
Here is an example of how these two functions can be used together:
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
TaskHandle_t myTaskHandle1;
TaskHandle_t myTaskHandle2;
TaskHandle_t myTaskHandle3;
void Task1(void *pvParameters)
{
(void) pvParameters;
while (1)
{
Serial.println("Hello from task1");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void Task2(void *pvParameters)
{
(void) pvParameters;
while (1)
{
Serial.println("Hello from task2");
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void Task3(void *pvParameters)
{
(void) pvParameters;
uint8_t cntr = 0;
while (1)
{
Serial.println("Hello from task3");
if (cntr < 16)
{
cntr++;
}
else if (cntr==5)
{
vTaskSuspend( myTaskHandle1 );
Serial.println("Suspend Task1");
}
else if (cntr==10)
{
vTaskResume( myTaskHandle1 );
Serial.println("Resume task1");
}
else if (cntr==15)
{
vTaskDelete( myTaskHandle2 );
Serial.println("Delete task2");
}
vTaskDelay(1000 / portTICK_PERIOD_MS); // delay for 1 second
}
}
void setup() {
Serial.begin(115200);
// Create the task
//xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, myTaskHandle1);
xTaskCreatePinnedToCore(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, 2, &myTaskHandle1,ARDUINO_RUNNING_CORE);//Specific to Arduino IDE
//xTaskCreate(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, myTaskHandle2);
xTaskCreatePinnedToCore(Task2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, &myTaskHandle2,ARDUINO_RUNNING_CORE); //Specific to Arduino IDE
//xTaskCreate(Task3, "Task3", configMINIMAL_STACK_SIZE, NULL, 1, myTaskHandle3);
xTaskCreatePinnedToCore(Task3, "Task3", configMINIMAL_STACK_SIZE, NULL, 1,&myTaskHandle3,ARDUINO_RUNNING_CORE); //Specific to Arduino IDE
//vTaskStartScheduler(); //not required in Arduino IDE
}
void loop() {
// nothing goes here, all the work is done in the tasks
}
In this code,In task3, Task1 is stopped then resumed ,The Task2 is deleted
It's important to note that vTaskDelete() is a blocking function, it will not return until the task has been deleted and the task's resources have been freed. So, you should use it with caution.
Try these code in Arduino IDE and check it out.