Binary Semaphores

Semaphores are used to signal/synchronize tasks as well as protect resources. 

A binary semaphore can (and should) be used as a means of signaling a task. This signal can come from an interrupt service routine or from another task. A semaphore is an RTOS primitive and is guaranteed to be thread-safe.

Design Pattern

Wake Up On Semaphore

The idea here is to have a task that is almost always waiting/taking on a semaphore and when it is given by an ISR or an other task, the task unblocks, and runs its task. This results in a task that usually sleeping/blocked and not utilizing CPU time unless its been called upon. This could be used to signal an emergency shutdown procedure when a button is triggered, or to trigger a procedure when the state of the system reaches a fault condition. Sample code below:

SemaphoreHandle_t xSemaphore;

void vWaitOnSemaphore( void * pvParameters )
{
    while(1)
    {
        /* Wait here forever until a the semaphore is sent/given */
        if(xSemaphoreTake(xSemaphore, portMAX_DELAY))
        {
            printf("I awaken! Semaphore has been given!");
            /* Do more stuff below ... */
        }
    }
}

void vSemaphoreSupplier( void * pvParameters )
{
    while(1)
    {
        if(checkButtonStatus())
        {
            xSemaphoreGive(xSemaphore);
        }
        /* Do more stuff ... */
    }
}

int main()
{
    constexpr uint32_t STACK_SIZE = 128;
    /* Already in the taken state
     * Needs to be given
     */
    xSemaphore = xSemaphoreCreateBinary();
    /* Create the tasks */
    xTaskCreate(vWaitOnSemaphore, "Waiter", STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);
    xTaskCreate(vSemaphoreSupplier, "Supplier", STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);
    /* Start Scheduler */
    vTaskStartScheduler();
}

Code Block 1. How to use Semaphores and use as a wake up pattern

Semaphore as a flag

The idea of this is to have some looping code that checks the semaphore but does not wait for it, or waits for a short period of time. The task will only react when it notices that the semaphore flag has been given. When your task takes it, it will run an if statement block and continue its looping work.  Keep in mind this will consume your flag. If you want your flag to continue to exist after you check it, a better primitive is EventGroups.

void vWaitOnSemaphore( void * pvParameters )
{
    while(1)
    {
        /* Do more stuff ... */
        /* Check the semaphore if it was set */
        if(xSemaphoreTake(xSemaphore, 0))
        {
            printf("Checked the Semaphore, consumed the flag indicator.");
            /* Do more stuff below ... */
        }
        /* Do more stuff ... */
    }
}

Code Block 2. Semaphores as a consumable flag

Interrupt Signal from ISR

This is useful, because ISRs should be as short as possible as they disrupt the main thread of software or your RTOS. In this case, the ISR can defer work to a task, which means that the ISR is lightning fast. The priority of the task can be change depending on how important that interrupt is.

Do not do this if the ISR is so critical that the time it takes to allow RTOS to run is too much. For example, a power failure interrupt.

void systemInterrupt()
{
    xSemaphoreGiveFromISR(xSemaphore);
}

void vSystemInterruptTask(void * pvParameter)
{
    while(1) 
    {
        if(xSemaphoreTake(xSemaphore, portMAX_DELAY)) 
        {
            // Process the interrupt
        }
    }
}

Code Block 4. Semaphore used within an ISR

NOTICE: The FromISR after the xSemaphoreGive API call? If you are making an RTOS API call from an ISR, you must use the FromISR variant of the API call. Undefined behavior otherwise like freezing the system.

Back to top