FreeRTOS Semaphore

What is a Semaphore?
Semaphore is a technique for synchronizing two/more task competing for the same resources. When a task wants to use a resource, it requests for the semaphore and will be allocated if the semaphore is available. If the semaphore is not available then the requesting task will go to blocked state till the semaphore becomes free.
Consider a situation where there are two persons who want to share a bike. At one time only one person can use the bike. The one who has the bike key will get the chance to use it. And when this person gives the key to the 2nd person, then the 2nd person can use the bike.
Semaphore is just like this Key and the bike is the shared resource. Whenever a task wants access to the shared resource, it must acquire the semaphore first. The task should release the semaphore after it is done with the shared resource. Until this time all other tasks have to wait if they need
access to shared resource as semaphore is not available. Even if the task trying to acquire the semaphore is of higher priority than the task acquiring the semaphore, it will be in the wait state until the semaphore is released by the lower priority task.

Types of Semaphores
There are 3-types of semaphores namely Binary, Counting and Mutex semaphore.

• Binary Semaphore: Binary semaphore is used when there is only one shared resource. Binary semaphore exists in two states ie.Acquired(Take), Released(Give). Binary semaphores have no ownership and can be released by any task or ISR regardless of who performed the last take operation. Because of this binary semaphores are often used to synchronize tasks with external events implemented as ISRs, for example waiting for a packet from a network or waiting for a button is pressed. Because there is no ownership concept a binary semaphore object can be created to be either in the ?taken? or ?not taken? state initially.
Cons:
Priority Inversion : HPT needs to wait for the LPT as LPT is holding the resource required for HPT. In between if MPT task comes then LPT will be in blocked state thereby delaying HPT.
1. Does not support recursion : If a task tries to take the semaphore twice then it gets blocked and will not come out of that state till someone releases that semaphore.
2. No ownership : Anyone can release or delete the semaphore because of which the dependent tasks will always be in the blocked state.

. Counting Semaphore: To handle more than one shared resource of the same type, counting semaphore is used. Counting semaphore will be initialized with the count(N) and it will allocate the resource as long as count becomes zero after which the requesting task will enter blocked
state.

•Mutex Semaphore: Mutex is very much similar to binary semaphore and takes care of priority inversion, ownership, and recursion.

Mutex semaphore

It is like a key that associated with the resource. The task holds the key, will lock the resource, process it then unlock and give back the key so that other tasks can use it. This mechanism is similar to binary semaphore except that the task that take the key have to release the key.
Note: Suppose that we have 2 tasks: the low priority task and the high priority task. These tasks are waiting for the key and the low priority task has chance to hold the key then it will block the high priority task and continue executing.

xTaskHandle task1,task2;
xSemaphoreHandle mutex;

void taskFunction1(void* parameter){
  for(;;){
    xSemaphoreTake(mutex,portMAX_DELAY);
    Serial.println("task 1 take semaphore");
    vTaskDelay(10000);
    xSemaphoreGive(mutex);
    Serial.println("task 1 leave semaphore");
    vTaskDelay(10);//because task 1 has higher priority once it give semaphore it take it again you shoudl to put it in block for alittle period time
  }
  vTaskDelete(&task1);
}

void taskFunction2(void* parameter){
  for(;;){
    xSemaphoreTake(mutex,portMAX_DELAY);
    Serial.println("task 2 take semaphore");
    vTaskDelay(1000);
    xSemaphoreGive(mutex);
    Serial.println("task 2 leave semaphore");
  }
  vTaskDelete(&task2);
}

void setup(){
  Serial.begin(9600);
  mutex=xSemaphoreCreateMutex();
  xTaskCreate(::taskFunction1,"task1",4096,nullptr,tskIDLE_PRIORITY+1,&task1);
  xTaskCreate(::taskFunction2,"task2",4096,nullptr,tskIDLE_PRIORITY,&task2);
}

void loop(){
  delay(500);
  Serial.println(xSemaphoreGetMutexHolder(mutex)==task1);// 1 if task 1 is take mutex
}
SemaphoreHandle_t xMutex;
void setup() {
  Serial.begin(112500);
  /* create Mutex */
  xMutex = xSemaphoreCreateMutex();
  
  xTaskCreate(
      lowPriorityTask,           /* Task function. */
      "lowPriorityTask",        /* name of task. */
      1000,                    /* Stack size of task */
      NULL,                     /* parameter of the task */
      1,                        /* priority of the task */
      NULL);                    /* Task handle to keep track of created task */
  delay(500);
  /* let lowPriorityTask run first then create highPriorityTask */
  xTaskCreate(
      highPriorityTask,           /* Task function. */
      "highPriorityTask",        /* name of task. */
      1000,                    /* Stack size of task */
      NULL,                     /* parameter of the task */
      4,                        /* priority of the task */
      NULL);                    /* Task handle to keep track of created task */
}

void loop() {

}
void lowPriorityTask( void * parameter )
{
  Serial.println((char *)parameter);
  for(;;){
    Serial.println("lowPriorityTask gains key");
    xSemaphoreTake( xMutex, portMAX_DELAY );
    /* even low priority task delay high priority 
    still in Block state */
    delay(2000);
    Serial.println("lowPriorityTask releases key");
    xSemaphoreGive( xMutex );
  }
  vTaskDelete( NULL );
}

void highPriorityTask( void * parameter )
{
  Serial.println((char *)parameter);
  for(;;){
    Serial.println("highPriorityTask gains key");
    /* highPriorityTask wait until lowPriorityTask release key */
    xSemaphoreTake( xMutex, portMAX_DELAY );
    Serial.println("highPriorityTask is running");
    Serial.println("highPriorityTask releases key");
    xSemaphoreGive( xMutex );
    /* delay so that lowPriorityTask has chance to run */
    delay(1000);
  }
  vTaskDelete( NULL );
}

xSemaphoreCreateMutex
Mutexes created using this function can be accessed using the xSemaphoreTake() and xSemaphoreGive() macros. The xSemaphoreTakeRecursive() and xSemaphoreGiveRecursive() macros must not be used.
This type of semaphore uses a priority inheritance mechanism so a task ‘taking’ a semaphore MUST ALWAYS ‘give’ the semaphore back once the semaphore it is no longer required.
Mutex type semaphores cannot be used from within interrupt service routines.
See vSemaphoreCreateBinary() for an alternative implementation that can be used for pure synchronisation (where one task or interrupt always ‘gives’ the semaphore and another always ‘takes’ the semaphore) and from within interrupt service routines.

xSemaphoreTake
Return
pdTRUE if the semaphore was obtained. pdFALSE if xBlockTime expired without the semaphore becoming available.
Parameters
xSemaphore: A handle to the semaphore being taken – obtained when the semaphore was created.
xBlockTime: The time in ticks to wait for the semaphore to become available. The macro portTICK_PERIOD_MS can be used to convert this to a real time. A block time of zero can be used to poll the semaphore. A block time of portMAX_DELAY can be used to block indefinitely (provided INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h).

xSemaphoreGive
This macro must not be used from an ISR. See xSemaphoreGiveFromISR () for an alternative which can be used from an ISR.
Return
pdTRUE if the semaphore was released. pdFALSE if an error occurred. Semaphores are implemented using queues. An error can occur if there is no space on the queue to post a message – indicating that the semaphore was not first obtained correctly.
Parameters
xSemaphore: A handle to the semaphore being released. This is the handle returned when the semaphore was created.

Binary semaphore

– This is the simplest way to control the access of resources that only have 2 states: locked/unlocked or unavailable/available.
– The task that want to gains the resource will call xSemaphoreTake(). There are 2 cases:
+ If it is successful to access the resource it will keep the resource until it call xSemaphoreGive() to release resource so that other tasks can gain it.
+ If it is failed it will wait until the resource is released by another task.
– Binary semaphore will be applied to interrupt (ISR) processing where the ISR callback function will call xSemaphoreGiveFromISR() to delegate the interrupt processing to the task that call xSemaphoreTake() (when xSemaphoreTake() is called, the task will move to Block state and waiting interrupt event).
Note:
– Binary semaphore that we use in this demo is a little bit different comparing to the standard theory above because the task that call xSemaphoreTake() will not release semaphore.
– API functions that are called from ISR callback must have prefix “FromISR” (xSemaphoreGiveFromISR). They are designed for Interrupt safe API functions.

int tpin=13;
xTaskHandle task1,task2;
xSemaphoreHandle binary;
long unsigned int lastTouch;

void taskFunction1(void* parameter){
  for(;;){
    xSemaphoreTake(binary,portMAX_DELAY);
    Serial.println("task 1 take semaphore");
    vTaskDelay(10000);
    xSemaphoreGive(binary);
    Serial.println("task 1 leave semaphore");
    vTaskDelay(10);//because task 1 has higher priority once it give semaphore it take it again you shoudl to put it in block for alittle period time
  }
  vTaskDelete(&task1);
}

void taskFunction2(void* parameter){
  for(;;){
    xSemaphoreTake(binary,portMAX_DELAY);
    Serial.println("task 2 take semaphore");
    vTaskDelay(1000);
    xSemaphoreTake(binary,portMAX_DELAY);
    Serial.println("task 2 take semaphore 2");
    vTaskDelay(1000);
  }
  vTaskDelete(&task2);
}

void touchInterrupt(){
  if(millis()-lastTouch>1000){
    Serial.println("touch");
    lastTouch=millis();
    BaseType_t bt=pdTRUE;
    xSemaphoreGiveFromISR(binary,&bt);
  }
}

void setup(){
  Serial.begin(9600);
  binary=xSemaphoreCreateBinary();
  xTaskCreate(::taskFunction1,"task1",4096,nullptr,tskIDLE_PRIORITY+1,&task1);
  xTaskCreate(::taskFunction2,"task2",4096,nullptr,tskIDLE_PRIORITY+0,&task2);
  touchAttachInterrupt(tpin,touchInterrupt,20);
}
/* LED pin */
byte ledPin = 14;
/* pin that is attached to interrupt */
byte interruptPin = 12;
/* hold the state of LED when toggling */
volatile byte state = LOW;
SemaphoreHandle_t xBinarySemaphore;

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  /* set the interrupt pin as input pullup*/
  pinMode(interruptPin, INPUT_PULLUP);
  /* attach interrupt to the pin
  function blink will be invoked when interrupt occurs
  interrupt occurs whenever the pin change value */
  attachInterrupt(digitalPinToInterrupt(interruptPin), ISRcallback, CHANGE);
  /* initialize binary semaphore */
  xBinarySemaphore = xSemaphoreCreateBinary();
  /* this task will process the interrupt event 
  which is forwarded by interrupt callback function */
  xTaskCreate(
    ISRprocessing,           /* Task function. */
    "ISRprocessing",        /* name of task. */
    1000,                    /* Stack size of task */
    NULL,                     /* parameter of the task */
    4,                        /* priority of the task */
    NULL);  
}

void loop() {
}

/* interrupt function callback */
void ISRcallback() {
  /* */
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  /* un-block the interrupt processing task now */
  xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
}

/* this function will be invoked when additionalTask was created */
void ISRprocessing( void * parameter )
{
  Serial.println((char *)parameter);
  /* loop forever */
  for(;;){
    /* task move to Block state to wait for interrupt event */
    xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
    Serial.println("ISRprocessing is running");
    /* toggle the LED now */
    state = !state;
    digitalWrite(ledPin, state);
  }
  vTaskDelete( NULL );
}
touch
task 1 take semaphore
task 1 leave semaphore
task 2 take semaphore
touch
task 1 take semaphore
task 1 leave semaphore
task 2 take semaphore 2

xSemaphoreGiveFromISR
to release a semaphore. The semaphore must have previously been created with a call to vSemaphoreCreateBinary() or xSemaphoreCreateCounting().
Return
pdTRUE if the semaphore was successfully given, otherwise errQUEUE_FULL.
parameters :
xSemaphore: A handle to the semaphore being released. This is the handle returned when the semaphore was created.
[out] pxHigherPriorityTaskWoken: xSemaphoreGiveFromISR() will set *pxHigherPriorityTaskWoken to pdTRUE if giving the semaphore caused a task to unblock, and the unblocked task has a priority higher than the currently running task. If xSemaphoreGiveFromISR() sets this value to pdTRUE then a context switch should be requested before the interrupt is exited.

recursive mutex

SemaphoreHandle_t xMutex = NULL;

// A task that creates a mutex.
void vATask( void * pvParameters )
{
   // Create the mutex to guard a shared resource.
   xMutex = xSemaphoreCreateRecursiveMutex();
}

// A task that uses the mutex.
void vAnotherTask( void * pvParameters )
{
   // ... Do other things.

   if( xMutex != NULL )
   {
       // See if we can obtain the mutex.  If the mutex is not available
       // wait 10 ticks to see if it becomes free.
       if( xSemaphoreTakeRecursive( xSemaphore, ( TickType_t ) 10 ) == pdTRUE )
       {
           // We were able to obtain the mutex and can now access the
           // shared resource.

           // ...
           // For some reason due to the nature of the code further calls to
           // xSemaphoreTakeRecursive() are made on the same mutex.  In real
           // code these would not be just sequential calls as this would make
           // no sense.  Instead the calls are likely to be buried inside
           // a more complex call structure.
           xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );
           xSemaphoreTakeRecursive( xMutex, ( TickType_t ) 10 );

           // The mutex has now been 'taken' three times, so will not be
           // available to another task until it has also been given back
           // three times.  Again it is unlikely that real code would have
           // these calls sequentially, but instead buried in a more complex
           // call structure.  This is just for illustrative purposes.
           xSemaphoreGiveRecursive( xMutex );
           xSemaphoreGiveRecursive( xMutex );
           xSemaphoreGiveRecursive( xMutex );

           // Now the mutex can be taken by other tasks.
       }
       else
       {
           // We could not obtain the mutex and can therefore not access
           // the shared resource safely.
       }
   }
}

Counting Semaphore

– In previous demo, we use binary semaphore to delegate interrupt processing to a task instead of processing it directly in interrupt callback function. In case the task is not finished processing the interrupt event but 2 interrupts occur then we will lose 1 interrupt eventbecausebinary semaphore only store one event (binary semaphore is like a queue with size is 1).- In order to overcome this, we will use counting semaphore where it can store more than 1 events.

/* LED pin */
byte ledPin = 14;
/* pin that is attached to interrupt */
byte interruptPin = 12;
/* hold the state of LED when toggling */
volatile byte state = LOW;
SemaphoreHandle_t xCountingSemaphore;

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  /* set the interrupt pin as input pullup*/
  pinMode(interruptPin, INPUT_PULLUP);
  /* attach interrupt to the pin
  function blink will be invoked when interrupt occurs
  interrupt occurs whenever the pin rising value */
  attachInterrupt(digitalPinToInterrupt(interruptPin), ISRcallback, RISING);
  /* initialize counting semaphore that can store 10 events */
  xCountingSemaphore = xSemaphoreCreateCounting( 10, 0 );
  /* this task will process the interrupt event 
  which is forwarded by interrupt callback function */
  xTaskCreate(
    ISRprocessing,           /* Task function. */
    "ISRprocessing",        /* name of task. */
    1000,                    /* Stack size of task */
    NULL,                     /* parameter of the task */
    4,                        /* priority of the task */
    NULL);  
}

void loop() {
}

/* interrupt function callback */
void ISRcallback() {
  /* */
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  /* un-block the interrupt processing task now */
  /* each couple is a blinky cycle 
  we bink 3 times then call this 6 times */
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
  xSemaphoreGiveFromISR( xCountingSemaphore, &xHigherPriorityTaskWoken );
}

/* this function will be invoked when additionalTask was created */
void ISRprocessing( void * parameter )
{
  Serial.println((char *)parameter);
  /* loop forever */
  for(;;){
    /* task move to Block state to wait for interrupt event */
    xSemaphoreTake( xCountingSemaphore, portMAX_DELAY );
    Serial.println("ISRprocessing is running");
    /* toggle the LED now */
    state = !state;
    digitalWrite(ledPin, state);
    /* elay here to see LED blinky */
    delay(1000);
  }
  vTaskDelete( NULL );
}

Critical section

– A critical section is a region of code that need to be protected from any concurrent accesses to change it. The critical section must keep be short because in the critical section most of operations are suspended. If this section is long the suspension time is long too. It affect the behavior of the system.
– In order to implement critical section, there are 2 ways:
+ Call taskENTER_CRITICAL() before enter critical section and call taskEXIT_CRITICAL() after leaving it. Using these function we will disable most of interrupts.
+ Call vTaskSuspendAll() before enter critical section and call xTaskResumeAll() after leaving it. Using these functions we still keep interrupts.
Note: in general the watch dog timer willl be feed by RTOS but we disable scheduler so we have to feed it.

#include <Arduino.h>
 volatile int count = 0;
 static portMUX_TYPE my_mutex;
void highPriorityTask( void * parameter );
void lowPriorityTask( void * parameter );

void setup() {
  Serial.begin(9600);
  /* create Mutex */
  xTaskCreatePinnedToCore(
      lowPriorityTask,           /* Task function. */
      "lowPriorityTask",        /* name of task. */
      10000,                    /* Stack size of task */
      NULL,                     /* parameter of the task */
      1,                        /* priority of the task */
      NULL,1);                    /* Task handle to keep track of created task */
  /* let lowPriorityTask run first */ 
  delay(5);
  /* let lowPriorityTask run first then create highPriorityTask */
  xTaskCreatePinnedToCore(
      highPriorityTask,           /* Task function. */
      "highPriorityTask",        /* name of task. */
      10000,                    /* Stack size of task */
      NULL,                     /* parameter of the task */
      4,                        /* priority of the task */
      NULL,0);                    /* Task handle to keep track of created task */
}

void loop() {

}
void lowPriorityTask( void * parameter )
{
  for(;;){

    //Serial.println("lowPriorityTask lock section");
    /* if using shceduler stopping way then 
    un-comment/comments lines below accordingly */
    //vTaskSuspendAll();
	vPortCPUInitializeMutex(&my_mutex);
	portENTER_CRITICAL(&my_mutex);
    /* stop scheduler until this loop is finished */
    for(count=0; count<40000; count++){
    }
    portEXIT_CRITICAL(&my_mutex);
    //Serial.println("end counting");
    /* we resume the scheduler so highPriorityTask will run again */
  //xTaskResumeAll();
    //Serial.println("lowPriorityTask leave section");

  }
  vTaskDelete( NULL );
}

void highPriorityTask( void * parameter )
{
  for(;;){
	  portENTER_CRITICAL(&my_mutex);
    /* highPriorityTask is resumed, we will check the counter should be 40000 */
    Serial.print("highPriorityTask is running and count is ");
    Serial.println(count);
    /* delay so that lowPriorityTask has chance to run */
    portEXIT_CRITICAL(&my_mutex);
    delay(100);
  }
  vTaskDelete( NULL );
}
Tagged :