Date: marzo 20, 2025
Author: Guillermo Garcia
Categories: RTOS Tags: FreeRTOS
Semaphores In FreeRTOS play a crucial role in synchronizing tasks and managing shared resources. When multiple tasks run concurrently, it is essential to ensure that they execute in an orderly manner without conflicts. Semaphores provide an efficient way to control task synchronization and prevent race conditions. This article will explore how to use semaphores to synchronize tasks in FreeRTOS.
Table of Contents
Semaphores in FreeRTOS are signaling mechanisms that help coordinate task execution. There are two main types:
Binary Semaphores – Used primarily for task synchronization.
Counting Semaphores – Used for resource management where multiple instances of a resource are available.
A Binary Semaphore can be used to unblock a task each time a particular interrupt occurs, effectively synchronizing the task with the interrupt. This allows the majority of the interrupt event processing to be implemented within the synchronized task, with only a very fast and short portion remaining directly in the ISR.
In this interrupt synchronization scenario, the binary semaphore can be considered conceptually as a queue with a length of one. The queue can contain a maximum of one item at any time, so is always either empty or full (hence, binary).
Counting semaphores are typically used for two things:
Counting events: In this scenario, an event handler will ‘give’ a semaphore each time an event occurs causing the semaphore’s count value to be incremented on each ‘give’. A handler task will ‘take’ a semaphore each time it processes an event—causing the semaphore’s count value to be decremented on each take.
Resource management: In this usage scenario, the count value indicates the number of resources available. To obtain control of a resource a task must first obtain a semaphore—decrementing the semaphore’s count value. When the count value reaches zero, there are no free resources. When a task finishes with the resource, it ‘gives’ the semaphore back—incrementing the semaphore’s count value.
Within FreeRTOS we have different APIs that allow us to manipulate this mechanism to configure it to our needs in an application. Let’s see the list of functions.
Let’s see an example of how to create a Binary Semaphore. To control the created Semaphore we need an xSemaphore
handler.
#include "FreeRTOS.h" #include "task.h" #include "semphr.h" SemaphoreHandle_t xSemaphore; void vATask( void * pvParameters ) { /* Attempt to create a semaphore. */ xSemaphore = xSemaphoreCreateBinary(); if( xSemaphore == NULL ) { /* There was insufficient FreeRTOS heap available for the semaphore to be created successfully. */ } else { /* The semaphore can now be used. Its handle is stored in the xSemahore variable. Calling xSemaphoreTake() on the semaphore here will fail until the semaphore has first been given. */ } }
Creating a counter type Semaphore is very similar, we simply change it to the xSemaphoreCreateCounting()
API, taking into account that this function needs parameters referring to the maximum count value that can be reached.
SemaphoreHandle_t xSemaphore; void vATask( void * pvParameters ) { SemaphoreHandle_t xSemaphore = NULL; // Semaphore cannot be used before a call to xSemaphoreCreateCounting(). // The max value to which the semaphore can count should be 10, and the // initial value assigned to the count should be 0. xSemaphore = xSemaphoreCreateCounting( 10, 0 ); if( xSemaphore != NULL ) { // The semaphore was created successfully. // The semaphore can now be used. } }
When we want to access a Semaphores we have to take the Semaphores from some task this is done with the following API.
void vAnotherTask( void * pvParameters ) { // ... Do other things. if( xSemaphore != NULL ) { // See if we can obtain the semaphore. If the semaphore is not available // wait 10 ticks to see if it becomes free. if( xSemaphoreTake( xSemaphore, ( TickType_t ) 10 ) == pdTRUE ) { // We were able to obtain the semaphore and can now access the // shared resource. // ... // We have finished accessing the shared resource. Release the // semaphore. xSemaphoreGive( xSemaphore ); } else { // We could not obtain the semaphore and can therefore not access // the shared resource safely. } } }
For this example we are going to use the button on our STM32F407VG Discovery kit board to generate an interrupt when we press the button. Let’s see how to configure this, particularly how to configure the interrupt taking into account the correct priority. To learn more about interrupts we cover more details in the article Interrupts in FreeRTOS.
Let’s go to our STM32CubeMX configuration file to enable the interrupt and assign a priority.
The button is located in the GPIO_EXTI0
vector, we need to enable that interrupt vector.
We need to assign a priority to this interrupt since we will be calling the FreeRTOS secure APIs from a FromISR interrupt we need to assign a priority higher than 5.
But why is this necessary? Why higher than 5? Of course this is due to our configuration in FreeRTOSConfig.h
/* Cortex-M specific definitions. */ #ifdef __NVIC_PRIO_BITS /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ #define configPRIO_BITS __NVIC_PRIO_BITS #else #define configPRIO_BITS 4 /* 15 priority levels */ #endif /* The highest interrupt priority that can be used by any interrupt service routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER PRIORITY THAN THIS! (higher priorities are lower numeric values. */ #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
We increase the Priority group to 4 bits and assign a priority of 5 to our interrupt and generate the code.
We will create a task that enters blocking mode when it takes the Semaphore waiting for some event to release the Semaphore. This event is generated when we press the button. At that moment an interruption occurs and we give the Semaphore and the task is activated to do the work.
#include "Task_Semaphores.h" SemaphoreHandle_t xSemaphore; void TaskWork(void* NotUsed) { while(1) { /* Use the semaphore to wait for the event. The task blocks indefinitely meaning this function call will only return once the semaphore has been successfully obtained - so there is no need to check the returned value. */ SEGGER_SYSVIEW_Print( "Take xSemaphore\n" ); xSemaphoreTake(xSemaphore, portMAX_DELAY); SEGGER_SYSVIEW_Print( "Press buttom work \n" ); } } void Synchronizing_Tasks(void) { /* Create Semaphore */ xSemaphore = xSemaphoreCreateBinary(); /* Create tasks */ xTaskCreate(TaskWork,"Tarea 1",configMINIMAL_STACK_SIZE, NULL,tskIDLE_PRIORITY+2,NULL); } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { static BaseType_t xHigherPriorityTaskWoken; SEGGER_SYSVIEW_RecordEnterISR(); /* This is Button ISR */ if(GPIO_Pin==B1_Pin) { /* 'Give' the semaphore to unblock the task.*/ SEGGER_SYSVIEW_Print( "Give xSemaphore\n" ); xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken); /* Giving the semaphore may have unblocked a task - if it did and the unblocked task has a priority equal to or above the currently executing task then xHigherPriorityTaskWoken will have been set to pdTRUE and portEND_SWITCHING_ISR() will force a context switch to the newly unblocked higher priority task. */ portEND_SWITCHING_ISR( xHigherPriorityTaskWoken ); } SEGGER_SYSVIEW_RecordExitISR(); }
We press the Button and we can see how the interrupt is entered and xSemaphoreGiveFromISR()
is called.
When the Semaphore is given, the Task is immediately activated to do the corresponding processing work.
Semaphores in FreeRTOS are a powerful tool for synchronizing tasks and ensuring orderly execution. By using binary semaphores, developers can enforce task dependencies efficiently. Counting semaphores enable multiple resource management, ensuring fair access among tasks.
Deja una respuesta