Resource management is a key factor in designing applications based on real-time operating systems. In RTOS, more than one tasks share resources with each other such as peripherals, data, or external devices. Therefore, FreeRTOS provides a mutex semaphore to share resources between tasks securely and without data corruption. In this tutorial, we will learn to use mutex using FreeRTOS API and Arduino. Let’s start with the introduction of a mutex. After that, we will learn to use FreeRTOS mutex API with Arduino.
Mutexes Introduction
What is mutex?
Mutex is a special type of binary semaphore that is used only for resource management between tasks. Resource management means it provides control access of resources between two or more tasks. The word mutex is also known as mutual exclusion.
Why do we need to use mutex?
In multitasking systems, there is a strong possibility that more than one task uses a single resource to complete their execution. But sharing a single resource between multiple tasks can cause multiple errors such as deadlocks, data corruption, priority inversion etc.
For example, we have two tasks in an RTOS application such as Task A and Task B and both tasks write a string on a liquid crystal display. Also the priority of Task B is higher than the Task A. This means Task B can pre-empt Task A.
For instance, if a task starts to access a resource, but does not complete its access before being transitioned out of the Running state. If the task leaves the resource in an inconsistent state, then access to the same resource by any other task or interrupt could result in data corruption, or other similar issue.
As you can see in this diagram, Task B and Task A both access an external peripheral LCD to display a string. Let’s suppose Task A is executing and starts printing a string text “FreeRTOS Mutex” on the LCD. But, TaskB (high priority task) preempts TaskA and TaskA has just printed “FreeRTOS Mu” on the LCD. TaskB starts to execute and displays “LAB 1” on the LCD. As soon as TaskB finishes in execution, TaskA restarts from the point where it was preempted by TaskB and prints “tex” on the LCD.
If we see the final output on LCD, it will look like this “FreeRTOS MuLAB 1tex”. But this is not a required output. But we can resolve this data corruption issue by using mutex. By using FreeRTOS mutex, we can serialize the access to this shared resource (LCD).
How to use mutex to serialize shared resources?
In the scenario discussed in the above example, we can resolve this issue by using mutex. With mutex, we serialize the use of shared resources between multiple tasks.
We can consider a mutex as a token and assign it to each resource. For instance, the shared resource in this example is an LCD. Any task which wants to access LCD to write string, first it must access the token. In other words, it must become the token (mutex) holder. No other task will be able to access LCD, because it does not hold a token. Once a token holder completes its execution and finishes using the resource, it must release the token by giving it back to the resource. Only when the token has been returned can another task successfully take the token, and then safely access the same shared resource. A task is not permitted to access the shared resource unless it holds the mutex.
Mutex Example
As shown in this picture, Task1 and Task2 must acquire a mutex token before printing string on LCD. If Task1 holds a token, Task2 will not be able to preempt Task1, even if TaskB has a higher priority than Task1. This is because FreeRTOS mutex provides a priority inheritance mechanism.
Priority inheritance means if a lower priority task holds a mutex token and a high priority task tries to preempt a lower priority task, then the priority of the lower priority task that holds a mutex will raise to that of the higher priority task. It will prevent high priority task to preempt lower priority task and also high priority task also enters the blocking state unless the lower priority task gives back the mutex. Priority inheritance is used to minimize the effect of priority inversion.
We will discuss priority inversion and Priority inheritance in detail at the end of the tutorial.
We can also use binary semaphore for resource management. But it suffers from some issues that we will discuss at the end of this tutorial.
FreeRTO Mutex API
Before using a mutex, you must first create it using FreeRTOS mutex API. xSemaphoreCreateMutex() is used to create mutex. As we mentioned earlier, it is a type of semaphore. Therefore, Same FreeRTOS API functions are used to take and give back mutex.
- xSemaphoreCreateGive()
- xSemaphoreCreateTake()
If you don’t know how to use these FreeRTOS semaphores, you can check these two tutorials:
- Tasks Synchronization using FreeRTOS Binary Semaphore
- Resource management using FreeRTOS Counting Semaphore
xSemaphoreCreateMutex() is used to create mutex. We should also define a handle variable of type xSemaphoreHandle that can be used to reference mutex while taking and giving a token using the xSemaphoreTake() and xSemaphoreGive() macros. This is a prototype of mutex macro:
SemaphoreHandle_t xSemaphoreCreateMutex( void );
It does not accept any input argument and only returns Null or non-Null value. If it returns a non-null value that means mutex is created successfully and if heap memory is not available to create mutex, it will return a Null value.
FreeRTOS Mutex Example with Arduino
In this example, we have created two instances of a task “OutputTask”. These two instances display data on an Arduino serial monitor of Arduino using a single printer() function. Hence, only one instance of task can access this printer() function at a time. We use FreeRTOS mutex to provide control access to this resource for both instances of the task.
The two instances of OutputTaskare() created at different priorities, so the lower priority task will sometimes be pre-empted by the higher priority task. As a mutex is used to ensure each task gets mutually exclusive access to the Arduino serial terminal, even when pre-emption occurs, the strings that are displayed will be correct and in no way corrupted. The frequency of pre-emption can be increased by reducing the maximum time the tasks spend in the Blocked state, which is set by the xMaxBlockTimeTicks constant.
Arduino FreeRTOS Mutex Example Code
//We will start by adding header files of FreeRTOS and semaphore
#include <Arduino_FreeRTOS.h>
#include "semphr.h"
//create handle for the mutex. It will be used to reference mutex
SemaphoreHandle_t xMutex;
void setup()
{
// Enable serial module of Arduino with 9600 baud rate
Serial.begin(9600);
// create mutex and assign it a already create handler
xMutex = xSemaphoreCreateMutex();
// create two instances of task "OutputTask" which are used to display string on
// arduino serial monitor. We passed strings as a paramter to these tasks such as ""Task 1 //#####################Task1" and "Task 2 ---------------------Task2". Priority of one //instance is higher than the other
xTaskCreate(OutputTask,"Printer Task 1", 100,"Task 1 #####################Task1 \r\n",1,NULL);
xTaskCreate(OutputTask,"Printer Task 2", 100,"Task 2 ---------------------Task2 \r\n",2,NULL);
}
// this is a definition of tasks
void OutputTask(void *pvParameters)
{
char *pcStringToPrint;
pcStringToPrint = (char *)pvParameters;
while(1)
{
printer(pcStringToPrint);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// this printer task send data to arduino serial monitor
//aslo it is shared resource between both instances of the tasks
void printer(const char* pcString)
{
// take mutex
xSemaphoreTake(xMutex, portMAX_DELAY);
Serial.println(pcString); // send string to serial monitor
xSemaphoreGive(xMutex); // release mutex
}
void loop(){}
Serial Monitor Output
As you can see from the output of the Arduino serial monitor, the output is clear and there is corruption or mixed data for two instances of the task. Task2 being a high priority task executes first and takes semaphore mutex. After that it prints string on Arduino serial monitor. After Task2 releases mutex token, Task1 takes it and prints string on the serial monitor.
Mutex Timing Diagram
This is a timing diagram of mutex according to the above example code. For example, at the start, only the idle task is running. After some time, Task2 enters the running state by taking mutex and start writing string on the serial monitor. But before, it completes writing string on serial monitor, Task1 being a higher priority task preempt Task2. Task1 tries to take a mutex, but it is already held by Task2. Hence, Task1 enters the blocking state and allows Task2 to complete its execution. After that Task2 finishes its execution and release the mutex. Finally, Task1 takes the mutex and writes a string on Arduino serial monitor. After that same execution pattern continues.
Video Demo
Priority Inversion Issue
What is Priority Inversion?
Priority inversion occurs when a higher priority task enters the blocking state due to a low priority task which is a holder of mutex token. As you can see in the above diagram, a higher priority task will not be able to execute because a low priority task holds a resource through a mutex token.
In the example discussed above, the priority inversion does not seem to be a problem, in fact it solves the problem of data corruption by serializing the resource access through mutex.
But priority Inversion becomes a real issue when we have medium priority task also. In such a case, a higher priority task will remain in blocking state for a longer time.
PI Example
In order to better understand priority inversion in case of medium priority task, let’s take an example timing diagram as shown below.
This timing diagram has three task such as low, medium ans high priority task. Both low and higher priority taks use mutex for their execution. But a medium priority task does not require a mutex to execute and it runs independently after a certain interval of time.
In this diagram, a low priority task takes the mutex token and starts execution. Before it completes its execution, a higher priority task preempts it. After that, a higher priority task try to take a mutex which is already held by a low priority task. Hence, a higher priority task enters the blocking state and wait for a low priority task to release mutex taken.
In response, a lower priority task starts to excute again where it was being preemped. But, before a lower priority task completes its execution, a medium priority task enters the running state by preempting it. Because, a MPT task does not require mutex. Therefore, it gets to execute before a LPT and HPT. The reason MPT gets to execute before HPT. Because HPT is in blocking state due to non- availability of mutex token.
After that MPT completes its execution and goes to sleep for a certain period. After that LPT completes its execution and gives back mutex. Finally, HPT takes a mutex token and finishes its execution.
As you can easily observe from the above diagram, the HPT task has to wait longer due to the medium priority task. This is the main issue of the mutex. Because it causes a problem of priority inversion.
FreeRTOS Priority Inheritance
What is Priority Inheritance?
Priority inheritance means if a lower priority task holds a mutex token and a high priority task tries to preempt a lower priority task, then the priority of the lower priority task that holds a mutex will raise to that of the higher priority task. It will prevent high priority task to preempt lower priority task and also high priority task also enters the blocking state unless the lower priority task gives back the mutex. Priority inheritance is used to minimize the effect of priority inversion.
As we have seen in the last section, the use of mutex causes a problem of priority inversion and this problem exaggerates when a medium priority task is also available. But, FreeRTOS mutex API solved this problem using priority Inheritance.
Priority Inheritance only solves the problem when a medium priority task is available. Hence, it only minimizes the effect of priority inversion but does not completely remove it. because, it will still exist for tasks that hold mutex e.g a low priority task in the above example.
FreeRTOS Priority Inheritance Example
In this example, let’s say, LPT takes the mutex semaphore and enters the running state. After some time, HPT preempts the LPT and tries to take mutex. But it will not able to take mutex because it is already held by LPT. Moreover, LPT will inherit the priority of HPT. Hence, it will become a high priority task. Even an MPT will not be able to preempt this LPT now. Therefore, it minimizes the effect of priority inversion by raising the priority. In other words, it will not let the MPT execute before HPT.
After that LPT will complete its execution and return back mutex. HPT will attain the mutex and complete its execution. MPT will execute at the end.
Note: By default, FreeRTOS mutex has a feature of priority inheritance. Therefore, we do not need to enable it separately.
Related Content:
- FreeRTOS Software Timers with Arduino – Create One-shot and Auto-Reload Timer
- FreeRTOS Arduino: How to Create Mailbox with Queues
- GSM module interfacing with Arduino: Send and receive SMS
- FreeRTOS Arduino: Changing the Priority of a Task
- How to use FreeRTOS structure Queue to Receive Data from Multiple Tasks
- FreeRTOS Arduino: How to Delete Tasks with vTaskDelete() API
- FreeRTOS Binary Semaphore – Examples of Tasks Interrupt Synchronization using Arduino
- How to use FreeRTOS with Arduino – Real-time operating system
- INTRODUCTION TO ARDUINO IDE
- VL53L0X LIDAR Distance Sensor Interfacing with Arduino