How to use Counting Semaphore
This is 4th tutorial in the series of FreeRTOS, and today we are going to see How to use Counting semaphore in FreeRTOS. You can browse other tutorial related to FreeRTOS HERE.
From now onward, I am not going to use the CMSIS API anymore, and instead I will use the FreeRTOS functions directly. This will help you understand the process more clearly, and you can use the same functions across different microcontrollers, that supports FreeRTOS.
Counting semaphore can be used to control the access to the resource. To obtain control of a resource, a task must first obtain a semaphore. Thus decrementing the semaphore 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 and thus incrementing the semaphore count value.
There is nothing much to do in the set up part. Just enable the FreeRTOS, and enable the counting Semaphore.
Some Insight into the CODE
As I mentioned above, I am not going to use the CMSIS anymore, that’s why I have commented it out in the include code below. Also we have to manually include all the FreeRTOS related files. I am also including stdlib.h, and string.h, and you will see their functions as we progress forward.
/* Includes ------------------------------------------------------------------*/
#include "main.h"
//#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#include "stdlib.h"
#include "string.h"
Next we need to create handlers for the Tasks, and the semaphore.
// create task defines
TaskHandle_t HPThandler;
void HPT_TASK (void *pvParameters);
TaskHandle_t MPThandler;
void MPT_TASK (void *pvParameters);
TaskHandle_t LPThandler;
void LPT_TASK (void *pvParameters);
TaskHandle_t VLPThandler;
void VLPT_TASK (void *pvParameters);
// semaphore related
SemaphoreHandle_t CountingSem;
// resource related
int resource[3] = {111,222,333};
int indx = 0;
// uart related
uint8_t rx_data = 0;
I have defined 4 handlers for 4 different Tasks, and CountingSem is the handler for counting semaphore.
CountingSem = xSemaphoreCreateCounting(3,0);
if (CountingSem == NULL) HAL_UART_Transmit(&huart2, (uint8_t *) "Unable to Create Semaphore\n\n", 28, 100);
else HAL_UART_Transmit(&huart2, (uint8_t *) "Counting Semaphore created successfully\n\n", 41, 1000);
- Inside the main function, first of all we need to create the counting semaphore.
- xSemaphoreCreateCounting takes 2 parameters, First is the maximum number of count, second is the initial value. I have created a semaphore with 3 tokens, and initial number of tokens available will be 0.
- If there is some error, and Semaphore couldn’t create, it will return NULL, or else it will return some other value.
// create TASKS
xTaskCreate(HPT_TASK, "HPT", 128, NULL, 3, &HPThandler);
xTaskCreate(MPT_TASK, "MPT", 128, NULL, 2, &MPThandler);
xTaskCreate(LPT_TASK, "LPT", 128, NULL, 1, &LPThandler);
xTaskCreate(VLPT_TASK, "VLPT", 128, NULL, 0, &VLPThandler);
vTaskStartScheduler();
After creating the Semaphore, we have to create the Tasks. As you can see above, FOUR Tasks have been created with different priorities, 3 being Highest. Following are the arguments to the xTaskCreate
- Here HPT_TASK is the function, where the task code is written
- “HPT” is just a name of this task. This name is not used anywhere in the program.
- 128 is the stack size
- NULL indicates that I am not passing any argument to the Task
- 3 is the Priority of the Task
- &HPThandler is the handler of the Task
At the end, Start the Scheduler with vTaskStartScheduler()
void HPT_TASK (void *pvParameters)
{
char sresource[3];
int semcount = 0;
char ssemcount[2];
// Give 3 semaphores at the beginning..
xSemaphoreGive(CountingSem);
xSemaphoreGive(CountingSem);
xSemaphoreGive(CountingSem);
while (1)
{
char str[150];
strcpy(str, "Entered HPT Task\n About to ACQUIRE the Semaphore\n ");
semcount = uxSemaphoreGetCount(CountingSem);
itoa (semcount, ssemcount, 10);
strcat (str, "Tokens available are: ");
strcat (str, ssemcount);
strcat (str, "\n\n");
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
xSemaphoreTake(CountingSem, portMAX_DELAY);
itoa (resource[indx], sresource, 10);
strcpy (str, "Leaving HPT Task\n Data ACCESSED is:: ");
strcat (str, sresource);
strcat (str, "\n Not releasing the Semaphore\n\n\n");
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
indx++;
if (indx>2) indx=0;
vTaskDelay(3000);
// vTaskDelete(NULL);
}
}
- As the control enters the HPT, 3 tokens of the counting semaphore will be released
- The task will acquire the Semaphore, and the token available will decrease by 1
- After completing the execution, the task will go in the suspension, without releasing the Semaphore
We have to write the similar functions for the MPT, LPT, and VLPT Tasks. You can check them after downloading the code at the end.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &rx_data, 1);
if (rx_data == 'r')
{
// release the semaphore here
/* The xHigherPriorityTaskWoken parameter must be initialized to pdFALSE as
it will get set to pdTRUE inside the interrupt safe API function if a
context switch is required. */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(CountingSem, &xHigherPriorityTaskWoken); // ISR SAFE VERSION
xSemaphoreGiveFromISR(CountingSem, &xHigherPriorityTaskWoken); // ISR SAFE VERSION
xSemaphoreGiveFromISR(CountingSem, &xHigherPriorityTaskWoken); // ISR SAFE VERSION
/* Pass the xHigherPriorityTaskWoken value into portEND_SWITCHING_ISR(). If
xHigherPriorityTaskWoken was set to pdTRUE inside xSemaphoreGiveFromISR()
then calling portEND_SWITCHING_ISR() will request a context switch. If
xHigherPriorityTaskWoken is still pdFALSE then calling
portEND_SWITCHING_ISR() will have no effect */
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
}
I have also implemented UART to receive the data from the serial console. On receiving character ‘r’ the following will take place:-
- First, we must define a xHigherPriorityTaskWoken and initialize it to the pdFALSE
- Than, give the semaphore using xSemaphoreGiveFromISR. This takes the xHigherPriorityTaskWoken as the parameter
- If a Higher Priority Task has preempted the Low Priority Task, from which we entered the ISR, than a context switch should be performed, and the interrupt safe API function will set *pxHigherPriorityTaskWoken to pdTRUE
- portEND_SWITCHING_ISR will perform the context switching and the control will go to the High Priority Task.
Result
- As the control enters HPT, three semaphores were released and therefore there are 3 tokens available in the beginning. HPT Task takes the semaphore, accesses the resource, and go into the suspension, without releasing the semaphore.
- The control enters the MPT Task now. There are 2 tokens available now. MPT Task takes the semaphore, accesses the resource, and go into the suspension, without releasing the semaphore.
- The control enters the LPT Task. There is only 1 token available. LPT Task takes the semaphore, accesses the resource, and go into the suspension, without releasing the semaphore.
- The VLPT runs and try to acquire the semaphore. There is no token available, so it waits for the Semaphore to become available. The waiting time is forever.
- LPT comes out from the suspension, and preempts the VLPT, and waits for the semaphore.
- When MPT comes out from the suspension, it will preempt the LPT
- HPT wakes up, and preempts MPT, and waits for the semaphore to become available.
- When the character ‘r’ is received, 3 tokens will be released from the ISR. HPT have the Highest priority among the waiting Tasks and therefore it will get the semaphore first
- MPT will acquire the semaphore next, run and go back in the suspension
- LPT will get the semaphore, and it will also go in the suspension
- At this point, VLPT is still waiting for the semaphore. LPT will wake up, as it has least delay, and it will try to take the semaphore. Token available is 0, so it have to wait forever for the semaphore
- MPT will wake up and it will preempt the LPT, and waits for the semaphore
- HPT will preempt the MPT, and now all three Tasks along with VLPT are waiting for the semaphore to be released.