Embedded Development Board Learning


Semaphores in FreeRTOS #8 Synchronizing Tasks

Date: marzo 20, 2025

Author: Guillermo Garcia

Categories: RTOS Tags:

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.

What Are Semaphores in FreeRTOS?

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.

Characteristics of a Binary Semaphores

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).

Characteristics of a Counting Semaphores

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.

Using a Semaphores

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.

Create a Semaphores

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.
  }
 }

Take and Give a Semaphores

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.
      }
  }
 }

Using a binary semaphore to synchronize a task with an interrupt

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();
}

SystemView Output:

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.

Closing

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.



Card image cap
Guillermo Garcia Thanks for reading! I am one of the editors of this site and I am committed to providing you with the best information possible by sharing my experience in embedded systems.


Comments... There are no comments.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicidad