FreeRTOS Tutorial #7 -> Using MUTEX
There is no tutorial #6. After no #5 we have #7 directly.
Mutex, which is short for Mutual Exclusion, does what’s it name indicates. It prevents several tasks from accessing a resource mutually. It ensures that at one time, only one task have access to the resource.
In this tutorial, we will see how to use mutex. And I will also explain the difference between a mutex and a binary semaphore. Also we will learn about priority inversion and priority inheritance.
Simple Mutex Operation
SemaphoreHandle_t SimpleMutex;
TaskHandle_t HPT_Handler;
TaskHandle_t MPT_Handler;
void HPT_Task (void *argument);
void MPT_Task (void *argument);
void Send_Uart (uint8_t *str)
{
xSemaphoreTake(SimpleMutex, portMAX_DELAY);
HAL_Delay(2000);
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
xSemaphoreGive(SimpleMutex);
}
- In the code above, I have created a mutex handler (SimpleMutex), two task handlers, and defined the task functions.
- Along with that, there is a function (Send_Uart), which will acquire the mutex first, waits for 2 seconds, sends the data to the UART, and releases the mutex.
Now, let’s write the Task Function
void HPT_Task (void *argument)
{
uint8_t *strtosend = "IN HPT===========================\n";
while (1)
{
char *str = "Entered HPT and About to take MUTEX\n";
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
Send_Uart(strtosend);
char *str2 = "Leaving HPT\n\n";
HAL_UART_Transmit(&huart2, str2, strlen (str2), HAL_MAX_DELAY);
vTaskDelay(2000);
}
}
void MPT_Task (void *argument)
{
uint8_t *strtosend = "IN MPT...........................\n";
while (1)
{
char *str = "Entered MPT and About to take MUTEX\n";
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
Send_Uart(strtosend);
char *str2 = "Leaving MPT\n\n";
HAL_UART_Transmit(&huart2, str2, strlen (str2), HAL_MAX_DELAY);
vTaskDelay(1000);
}
}
Above are the functions for two tasks. Both of them will make a call to the function, Send_Uart. Now we have to see, can the High Priority Task (HPT) preempt the Medium Priority Task (MPT) while the MPT holds the MUTEX.
We will write the following into the Main Function
SimpleMutex = xSemaphoreCreateMutex();
if (SimpleMutex != NULL)
{
HAL_UART_Transmit(&huart2, "Mutex Created\n\n", 15, 1000);
}
/// create tasks
xTaskCreate(HPT_Task, "HPT", 128, NULL, 3, &HPT_Handler);
xTaskCreate(MPT_Task, "MPT", 128, NULL, 2, &MPT_Handler);
vTaskStartScheduler();
We have created the MUTEX, along with two tasks with different priority. And at last, the scheduler will start.
Let’s see the output of the above program.
This was a very simple operation of mutex. And this is exactly what a binary semaphore does too. That’s why we will go a little more deep, and see what’s the difference between the two.
Priority Inversion in Semaphore
For this purpose, I will make some changes in the code again.
SemaphoreHandle_t SimpleMutex;
SemaphoreHandle_t BinSemaphore;
TaskHandle_t HPT_Handler;
TaskHandle_t MPT_Handler;
TaskHandle_t LPT_Handler;
void HPT_Task (void *argument);
void MPT_Task (void *argument);
void LPT_Task (void *argument);
void Send_Uart (uint8_t *str)
{
xSemaphoreTake(BinSemaphore, portMAX_DELAY);
HAL_Delay(5000);
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
xSemaphoreGive(BinSemaphore);
}
This time I have defined a handler for the binary semaphore also (BinSemaphore). Also all the three tasks are being used now.
let’s write the Task Function now
void HPT_Task (void *argument)
{
uint8_t *strtosend = "IN HPT===========================\n";
while (1)
{
char *str = "Entered HPT and About to take Semaphore\n";
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
Send_Uart(strtosend);
char *str2 = "Leaving HPT\n\n";
HAL_UART_Transmit(&huart2, str2, strlen (str2), HAL_MAX_DELAY);
vTaskDelay(750);
}
}
void MPT_Task (void *argument)
{
while (1)
{
char *str = "IN MPT****************************\n\n";
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
vTaskDelay(2000);
}
}
void LPT_Task (void *argument)
{
uint8_t *strtosend = "IN LPT...........................\n";
while (1)
{
char *str = "Entered LPT and About to take Semaphore\n";
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
Send_Uart(strtosend);
char *str2 = "Leaving LPT\n\n";
HAL_UART_Transmit(&huart2, str2, strlen (str2), HAL_MAX_DELAY);
vTaskDelay(1000);
}
}
As you can see above, the HPT, and the LPT will call the function Send_Uart, so they will require the semaphore. But MPT will run independently without any need for the semaphore.
Let’s write the Main Function now
SimpleMutex = xSemaphoreCreateMutex();
if (SimpleMutex != NULL)
{
HAL_UART_Transmit(&huart2, "Mutex Created\n\n", 15, 1000);
}
BinSemaphore = xSemaphoreCreateBinary();
if (BinSemaphore != NULL)
{
HAL_UART_Transmit(&huart2, "Semaphore Created\n\n", 19, 1000);
}
xSemaphoreGive(BinSemaphore);
/// create tasks
xTaskCreate(HPT_Task, "HPT", 128, NULL, 3, &HPT_Handler);
xTaskCreate(MPT_Task, "MPT", 128, NULL, 2, &MPT_Handler);
xTaskCreate(LPT_Task, "LPT", 128, NULL, 1, &LPT_Handler);
vTaskStartScheduler();
Both the mutex and semaphore were created along with three tasks of different priorities. And at last, the scheduler will start.
Let’s see the output of the above
You can see in the above pictures, the MPT can preempt the LPT, and therefore it delays the execution of the HPT also.
HPT even being the Highest priority Task, have to wait for the MPT to finish. This scenario is termed as PRIORITY INVERSION
Now let’s see how can we use mutex to avoid it.
Priority Inheritance using Mutex
I will just make a very small change in the code
void Send_Uart (uint8_t *str)
{
xSemaphoreTake(SimpleMutex, portMAX_DELAY);
HAL_Delay(5000);
HAL_UART_Transmit(&huart2, str, strlen (str), HAL_MAX_DELAY);
xSemaphoreGive(SimpleMutex);
}
In the Send_Uart function, instead of taking the semaphore, now we will take the mutex. The rest of the code will remain exactly as it is.
Let’s see the output now
- As shown above, when the LPT have the Mutex and HPT tries to preempt it, the priority of LPT rises to that of the HPT.
- This is called PRIORITY INHERITANCE as LPT inherits the priority of the highest priority task, that is waiting for the mutex. And in this case that is HPT.
- Now the MPT can not preempt LPT, because the priority of LPT is HIGHER than MPT and the execution goes as planned.