This article introduces the concept and usage of semaphores in the FreeRTOS operating system. It demonstrates resource sharing between different tasks through button presses and serial communication.
Introduction
A semaphore is a synchronization mechanism used for resource management when multiple tasks access the same resource. Semaphores can be categorized into binary semaphores (similar to “flags”, mainly used for synchronization or event notification), counting semaphores (similar to “counters”, mainly used for shared resource management), and mutexes (a special type of binary semaphore, as introduced in the article <span>FreeRTOS - FatFS File System and SD Card (Part 4)</span>).
Characteristics of the Three Types of Semaphores
Binary Semaphore
- Similar to a “switch”, the counter can only be 0 or 1, mainly used for synchronization or event notification. When the counter is 0, tasks attempting to acquire the semaphore will be blocked.
Counting Semaphore
- The counter can take values from 0 to a user-defined maximum, used for shared resource management. The counter represents the “number of available resources”. When it is 0, tasks attempting to acquire the semaphore will be blocked.
Mutex
- A special type of binary semaphore that additionally supports the priority inheritance mechanism, and it has strict usage permissions, as it can only be released by the task that acquired it.
<span>In the article FreeRTOS - Queue (Part 5)</span>, we introduced queues, which, like semaphores, can cause “blocking waits”. However, queues can pass “data”, while semaphores can only pass “states” and cannot pass specific data. This distinction allows queues to simulate semaphores, but semaphores cannot simulate queues.
“Since mutexes have been introduced previously, this article will mainly focus on binary semaphores and counting semaphores.”
Related APIs
| API | Function |
|---|---|
| xSemaphoreCreateBinary(void) | Dynamically creates a binary semaphore and returns a handle that can reference the semaphore. The initial value is 0, and it must be initialized by calling xSemaphoreGive(). |
| xSemaphoreCreateBinaryStatic(StaticSemaphore_t *pxSemaphoreBuffer) | Statically creates a binary semaphore and returns a handle that can reference the semaphore. The pxSemaphoreBuffer must point to a variable of type StaticSemaphore_t, which will be used to store the semaphore’s state. |
| xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount) | Dynamically creates a counting semaphore and returns a handle that can reference the newly created semaphore. |
| xSemaphoreCreateCountingStatic(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer) | Statically creates a counting semaphore and returns a handle that can reference the newly created semaphore. – uxMaxCount, the maximum count value. – uxInitialCount, the count value assigned to the semaphore when created. – pxSemaphoreBuffer, must point to a variable of type StaticSemaphore_t, which will be used to store the semaphore’s data structure. |
The functions xSemaphoreGive and xSemaphoreTake have been introduced in the article
<span>FreeRTOS - FatFS File System and SD Card (Part 4)</span>, so they will not be elaborated on here.
As with the hardware in the article
<span>FreeRTOS - Queue (Part 5)</span>, this will not be elaborated on here.
Software Objectives
Create three tasks: a button detection task, message printing task 1, and message printing task 2. Create a binary semaphore and a counting semaphore. The button uses interrupts, releasing the binary semaphore each time an interrupt occurs; the button detection task blocks waiting for the binary semaphore, and upon receiving the signal, it prints information. To achieve good experimental results, the maximum count for the counting semaphore is set to 2, with an initial count of 1. A timer is created to release the counting semaphore every 500ms; message printing tasks 1 and 2 acquire the signal every 400ms. Each task occupies the semaphore for 200ms.
Specific Program Implementation
For the timer, we use <span>Timer6</span>, with the clock source being <span>APB1</span>.
Set the prescaler, auto-reload mode, and timer value, and enable interrupts.
Create tasks
<span>In IAR</span>, the code in the <span>main.c</span> file needs to manually enable the interrupt for <span>timer6</span>.
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
/*start timer 6 interrupt */
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
Below is the code automatically generated by <span>STM32CubeMX</span> (regarding tasks and semaphores)
/* Definitions for KeyDetection */
osThreadId_t KeyDetectionHandle;
const osThreadAttr_t KeyDetection_attributes = {
.name = "KeyDetection",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 128 * 4
};
/* Definitions for PrintMessage1 */
osThreadId_t PrintMessage1Handle;
const osThreadAttr_t PrintMessage1_attributes = {
.name = "PrintMessage1",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 128 * 4
};
/* Definitions for PrintMessage2 */
osThreadId_t PrintMessage2Handle;
const osThreadAttr_t PrintMessage2_attributes = {
.name = "PrintMessage2",
.priority = (osPriority_t) osPriorityLow,
.stack_size = 128 * 4
};
/* Definitions for myCountingSem01 */
osSemaphoreId_t myCountingSem01Handle;
const osSemaphoreAttr_t myCountingSem01_attributes = {
.name = "myCountingSem01"
};
/* Definitions for myBinarySem01 */
osSemaphoreId_t myBinarySem01Handle;
const osSemaphoreAttr_t myBinarySem01_attributes = {
.name = "myBinarySem01"
};
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* creation of myBinarySem01 */
myBinarySem01Handle = osSemaphoreNew(1, 1, &myBinarySem01_attributes);
/* creation of myCountingSem01 */
myCountingSem01Handle = osSemaphoreNew(2, 2, &myCountingSem01_attributes);
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of KeyDetection */
KeyDetectionHandle = osThreadNew(StartTaskKeyDet, NULL, &KeyDetection_attributes);
/* creation of PrintMessage1 */
PrintMessage1Handle = osThreadNew(StartTaskPrintMessage1, NULL, &PrintMessage1_attributes);
/* creation of PrintMessage2 */
PrintMessage2Handle = osThreadNew(StartTaskPrintMessage2, NULL, &PrintMessage2_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_EVENTS */
/* add events, ... */
/* USER CODE END RTOS_EVENTS */
}
According to the software objectives, the following tasks acquire the semaphore and print information.
/* USER CODE BEGIN Header_StartTaskKeyDet */
/**
* @brief Function implementing the KeyDetection thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskKeyDet */
void StartTaskKeyDet(void *argument)
{
/* USER CODE BEGIN KeyDetection */
/* Infinite loop */
for(;;)
{
if(osSemaphoreAcquire(myBinarySem01Handle,portMAX_DELAY) == osOK)
{
printf("Key Detection Task got the semaphore.\r\n");
}
osDelay(100);
}
/* USER CODE END KeyDetection */
}
/* USER CODE BEGIN Header_StartTaskPrintMessage1 */
/**
* @brief Function implementing the PrintMessage1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskPrintMessage1 */
void StartTaskPrintMessage1(void *argument)
{
/* USER CODE BEGIN PrintMessage1 */
/* Infinite loop */
for(;;)
{
if(osSemaphoreAcquire(myCountingSem01Handle,portMAX_DELAY) == osOK)
{
printf("Print Message Task1 got the semaphore.\r\n");
HAL_Delay(200);
}
osDelay(400);
}
/* USER CODE END PrintMessage1 */
}
/* USER CODE BEGIN Header_StartTaskPrintMessage2 */
/**
* @brief Function implementing the PrintMessage2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskPrintMessage2 */
void StartTaskPrintMessage2(void *argument)
{
/* USER CODE BEGIN PrintMessage2 */
/* Infinite loop */
for(;;)
{
if(osSemaphoreAcquire(myCountingSem01Handle,portMAX_DELAY) == osOK)
{
printf("Print Message Task2 got the semaphore.\r\n");
HAL_Delay(200);
}
osDelay(400);
}
/* USER CODE END PrintMessage2 */
}
<span>Timer6</span>‘s callback function releases the counting semaphore.
/**
* @brief Period elapsed callback in non-blocking mode
* @note This function is called when TIM7 interrupt took place, inside
* HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
* a global variable "uwTick" used as application time base.
* @param htim : TIM handle
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* USER CODE BEGIN Callback 0 */
/* USER CODE END Callback 0 */
if (htim->Instance == TIM7)
{
HAL_IncTick();
}
/* USER CODE BEGIN Callback 1 */
if (htim->Instance == TIM6)
{
printf("Timer 6 released the semaphore.\r\n");
osSemaphoreRelease(myCountingSem01Handle);
}
/* USER CODE END Callback 1 */
}
Button interrupt callback function
/**
* @brief EXTI line rising detection callback.
* @param GPIO_Pin: Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
/*key is pressed*/
if(GPIO_Pin == WAKEUP_Pin)
{
/*release signal*/
osSemaphoreRelease(myBinarySem01Handle);
}
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Rising_Callback could be implemented in the user file
*/
}
/**
* @brief EXTI line falling detection callback.
* @param GPIO_Pin: Specifies the port pin connected to corresponding EXTI line.
* @retval None
*/
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Falling_Callback could be implemented in the user file
*/
}
Running Results
Effect of triggering the button
Thus, the introduction to semaphores in the <span>FreeRTOS</span> operating system is complete.