[See also Blocking on Multiple RTOS Objects]
The FreeRTOS tutorial book
provides additional information on queues, binary semaphores, mutexes, counting
semaphores and recursive semaphores, along with simple worked examples in a set of accompanying example projects.
FreeRTOS Binary Semaphores
Binary semaphores are used for both mutual exclusion and synchronisation purposes.
Binary semaphores and mutexes are very similar but have some subtle differences: Mutexes include a priority inheritance mechanism, binary
semaphores do not. This makes binary semaphores the better choice for implementing synchronisation (between tasks or between tasks and an
interrupt), and mutexes the better choice for implementing simple mutual exclusion. The description of how a mutex can be used as a mutual
exclusion mechanism holds equally for binary semaphores. This sub section will only describe using binary semaphores for synchronisation.
Semaphore API functions permit a block time to be specified. The block time indicates the maximum number of ‘ticks’ that a task should enter the
Blocked state when attempting to ‘take’ a semaphore, should the semaphore not be immediately available. If
more than one task blocks on the same semaphore then the task with the highest priority will be the task that is unblocked the next time the semaphore becomes
Think of a binary semaphore as a queue that can only hold one item. The queue can therefore only be empty or full (hence binary). Tasks and interrupts
using the queue don’t care what the queue holds – they only want to know if the queue is empty or full. This
mechanism can be exploited to synchronise (for example) a task with an interrupt.
Consider the case where a task is used to
service a peripheral. Polling the peripheral would be wasteful of CPU resources, and prevent other tasks from executing. It is therefore
preferable that the task spends most of its time in the Blocked state (allowing other tasks to execute) and only execute itself when there is
actually something for it to do. This is achieved using a binary semaphore by having the task Block while attempting to ‘take’ the semaphore. An
interrupt routine is then written for the peripheral that just ‘gives’ the semaphore when the peripheral requires servicing. The task
always ‘takes’ the semaphore (reads from the queue to make the queue empty), but never ‘gives’ it. The interrupt always ‘gives’ the semaphore (writes
to the queue to make it full) but never takes it. The source code provided on the xSemaphoreGiveFromISR() documentation page
should make this clearer. Also see RTOS task notifications, which can be used as a faster and lighter
weight binary semaphore alternaitve in some situations.
Task prioritisation can be used to ensure peripherals get services in a timely manner – effectively generating a ‘deferred interrupt’ scheme. (note
FreeRTOS also has a built in deferred interrupt mechanism). An alternative
approach is to use a queue in place of the semaphore. When this is done the interrupt routine can capture the data associated with the peripheral event and send
it on a queue to the task. The task unblocks when data becomes available on the queue, retrieves the data from the queue, then performs any data processing
that is required. This second scheme permits interrupts to remain as short as possible, with all post processing instead occurring within a task.
See the Semaphores/Mutexes section of the user documentation for a list of semaphore related API
functions. Searching the files in the FreeRTOS/Demo/Common/Minimal directory will reveal multiple examples of their usage.
Note that interrupts must NOT use API functions that do not end in “FromISR”.
Using a semaphore to synchronise a task with an interrupt. The interrupt only ever ‘gives’ the
semaphore, while the task only ever ‘takes’ the semaphore.
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.