/* * FreeRTOS-Cpp * Copyright (C) 2021 Jon Enz. All Rights Reserved. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * https://github.com/jonenz/FreeRTOS-Cpp */ #ifndef FREERTOS_SEMAPHORE_HPP #define FREERTOS_SEMAPHORE_HPP #include "FreeRTOS.h" #include "semphr.h" namespace FreeRTOS { /** * @class SemaphoreBase Semaphore.hpp * * @brief Base class that provides the standard semaphore interface to * FreeRTOS::BinarySemaphore, FreeRTOS::StaticBinarySemaphore, * FreeRTOS::CountingSemaphore, and FreeRTOS::StaticCountingSemaphore. * * @note This class is not intended to be instantiated by the user. Use * FreeRTOS::BinarySemaphore, FreeRTOS::StaticBinarySemaphore, * FreeRTOS::CountingSemaphore, or FreeRTOS::StaticCountingSemaphore. */ class SemaphoreBase { public: friend class BinarySemaphore; friend class StaticBinarySemaphore; friend class CountingSemaphore; friend class StaticCountingSemaphore; SemaphoreBase(const SemaphoreBase&) = delete; SemaphoreBase& operator=(const SemaphoreBase&) = delete; static void* operator new(size_t, void* ptr) { return ptr; } static void* operator new[](size_t, void* ptr) { return ptr; } static void* operator new(size_t) = delete; static void* operator new[](size_t) = delete; /** * Semaphore.hpp * * @brief Function that checks if the underlying semaphore handle is not NULL. * This should be used to ensure a semaphore has been created correctly. * * @retval true the handle is not NULL. * @retval false the handle is NULL. */ inline bool isValid() const { return (handle != NULL); } /** * Semaphore.hpp * * @brief Function that calls UBaseType_t uxSemaphoreGetCount( * SemaphoreHandle_t xSemaphore ) * * @see * * Returns the count of a semaphore. * * @return UBaseType_t If the semaphore is a counting semaphore then the * semaphores current count value is returned. If the semaphore is a binary * semaphore then 1 is returned if the semaphore is available, and 0 is * returned if the semaphore is not available. */ inline UBaseType_t getCount() const { return uxSemaphoreGetCount(handle); } /** * Semaphore.hpp * * @brief Function that calls xSemaphoreTake( SemaphoreHandle_t * xSemaphore, TickType_t xTicksToWait ) * * @see * * Function to obtain a semaphore. * * This macro must not be called from an ISR. takeFromISR() can be used to * take a semaphore from within an interrupt if required, although this would * not be a normal operation. Semaphores use queues as their underlying * mechanism, so functions are to some extent interoperable. * * @param ticksToWait 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. * @retval true If the semaphore was obtained. * @retval false If xTicksToWait expired without the semaphore becoming * available. * * Example Usage * @include Semaphore/take.cpp */ inline bool take(const TickType_t ticksToWait = portMAX_DELAY) const { return (xSemaphoreTake(handle, ticksToWait) == pdTRUE); } /** * Semaphore.hpp * * @brief Function that calls xSemaphoreTakeFromISR( SemaphoreHandle_t * xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken) * * @see * * A version of take() that can be called from an ISR. Unlike take(), * takeFromISR() does not permit a block time to be specified. * * @param higherPriorityTaskWoken It is possible (although unlikely, and * dependent on the semaphore type) that a semaphore will have one or more * tasks blocked on it waiting to give the semaphore. Calling takeFromISR() * will make a task that was blocked waiting to give the semaphore leave the * Blocked state. If calling the API function causes a task to leave the * Blocked state, and the unblocked task has a priority equal to or higher * than the currently executing task (the task that was interrupted), then, * internally, the API function will set higherPriorityTaskWoken to true. If * takeFromISR() sets higherPriorityTaskWoken to true, then a context switch * should be performed before the interrupt is exited. This will ensure that * the interrupt returns directly to the highest priority Ready state task. * The mechanism is identical to that used in the FreeRTOS::receiveFromISR() * function, and readers are referred to the FreeRTOS::receiveFromISR() * documentation for further explanation. * @retval true If the semaphore was successfully taken. * @retval false If the semaphore was not successfully taken because it was * not available. */ inline bool takeFromISR(bool& higherPriorityTaskWoken) const { BaseType_t taskWoken = pdFALSE; bool result = (xSemaphoreTakeFromISR(handle, &taskWoken) == pdTRUE); if (taskWoken == pdTRUE) { higherPriorityTaskWoken = true; } return result; } /** * Semaphore.hpp * * @brief Function that calls xSemaphoreTakeFromISR( SemaphoreHandle_t * xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken) * * @see * * @overload */ inline bool takeFromISR() const { return (xSemaphoreTakeFromISR(handle, NULL) == pdTRUE); } /** * Semaphore.hpp * * @brief Function that calls xSemaphoreGive( SemaphoreHandle_t xSemaphore * ) * * @see * * Function to release a semaphore. * * This must not be used from an ISR. See giveFromISR() for an alternative * which can be used from an ISR. * * @retval true If the semaphore was released. * @retval false 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. * * Example Usage * @include Semaphore/give.cpp */ inline bool give() const { return (xSemaphoreGive(handle) == pdTRUE); } /** * Semaphore.hpp * * @brief Function that calls xSemaphoreGiveFromISR( SemaphoreHandle_t * xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken ) * * @see * * Function to release a semaphore. * * This macro can be used from an ISR. * * @param higherPriorityTaskWoken giveFromISR() will set * higherPriorityTaskWoken to true if giving the semaphore caused a task to * unblock, and the unblocked task has a priority higher than the currently * running task. If giveFromISR() sets this value to true then a context * switch should be requested before the interrupt is exited. * @retval true If the semaphore was successfully given. * @retval false Otherwise. * * Example Usage * @include Semaphore/giveFromISR.cpp */ inline bool giveFromISR(bool& higherPriorityTaskWoken) const { BaseType_t taskWoken = pdFALSE; bool result = (xSemaphoreGiveFromISR(handle, &taskWoken) == pdTRUE); if (taskWoken == pdTRUE) { higherPriorityTaskWoken = true; } return result; } /** * Semaphore.hpp * * @brief Function that calls xSemaphoreGiveFromISR( SemaphoreHandle_t * xSemaphore, signed BaseType_t *pxHigherPriorityTaskWoken ) * * @see * * @overload */ inline bool giveFromISR() const { return (xSemaphoreGiveFromISR(handle, NULL) == pdTRUE); } private: SemaphoreBase() = default; /** * Semaphore.hpp * * @brief Destroy the SemaphoreBase object by calling void * vSemaphoreDelete( SemaphoreHandle_t xSemaphore ) * * @see * * @note Do not delete a semaphore that has tasks blocked on it (tasks that * are in the Blocked state waiting for the semaphore to become available). */ ~SemaphoreBase() { vSemaphoreDelete(this->handle); } SemaphoreBase(SemaphoreBase&&) noexcept = default; SemaphoreBase& operator=(SemaphoreBase&&) noexcept = default; /** * @brief Handle used to refer to the semaphore when using the FreeRTOS * interface. */ SemaphoreHandle_t handle = NULL; }; #if (configSUPPORT_DYNAMIC_ALLOCATION == 1) /** * @class BinarySemaphore Semaphore.hpp * * @brief Class that encapsulates the functionality of a FreeRTOS binary * semaphore. * * Each binary semaphore require a small amount of RAM that is used to hold the * semaphore's state. If a binary semaphore is created using * FreeRTOS::BinarySemaphore then the required RAM is automatically allocated * from the FreeRTOS heap. If a binary semaphore is created using * FreeRTOS::StaticBinarySemaphore then the RAM is provided by the application * writer as part of the class and allows the RAM to be statically allocated at * compile time. See the Static Vs Dynamic allocation page for more information. * * The semaphore is created in the 'empty' state, meaning the semaphore must * first be given using the give() API function before it can subsequently be * taken (obtained) using the take() function. * * 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. * * A binary semaphore need not be given back once obtained, so task * synchronisation can be implemented by one task/interrupt continuously * 'giving' the semaphore while another continuously 'takes' the semaphore. This * is demonstrated by the sample code on the giveFromISR() documentation page. * Note the same functionality can often be achieved in a more efficient way * using a direct to task notification. * * The priority of a task that 'takes' a mutex can potentially be raised if * another task of higher priority attempts to obtain the same mutex. The task * that owns the mutex 'inherits' the priority of the task attempting to 'take' * the same mutex. This means the mutex must always be 'given' back - otherwise * the higher priority task will never be able to obtain the mutex, and the * lower priority task will never 'disinherit' the priority. An example of a * mutex being used to implement mutual exclusion is provided on the take() * documentation page. */ class BinarySemaphore : public SemaphoreBase { public: /** * Semaphore.hpp * * @brief Construct a new BinarySemaphore object by calling * SemaphoreHandle_t xSemaphoreCreateBinary( void ) * * @see * * @warning The user should call isValid() on this object to verify that the * binary semaphore was created successfully in case the memory required to * create the queue could not be allocated. * * Example Usage * @include Semaphore/binarySemaphore.cpp */ BinarySemaphore() { this->handle = xSemaphoreCreateBinary(); } ~BinarySemaphore() = default; BinarySemaphore(const BinarySemaphore&) = delete; BinarySemaphore& operator=(const BinarySemaphore&) = delete; BinarySemaphore(BinarySemaphore&&) noexcept = default; BinarySemaphore& operator=(BinarySemaphore&&) noexcept = default; }; /** * @class CountingSemaphore Semaphore.hpp * * @brief Class that encapsulates the functionality of a FreeRTOS counting * semaphore. * * Each counting semaphore require a small amount of RAM that is used to hold * the semaphore's state. If a counting semaphore is created using * FreeRTOS::CountingSemaphore then the required RAM is automatically allocated * from the FreeRTOS heap. If a counting semaphore is created using * FreeRTOS::StaticCountingSemaphore then the RAM is provided by the application * writer as part of the class and allows the RAM to be statically allocated at * compile time. See the Static Vs Dynamic allocation page for more information. * * Counting semaphores are typically used for two things: * 1. Counting Events: * In this usage scenario an event handler will 'give' a semaphore each time an * event occurs (incrementing the semaphore count value), and a handler task * will 'take' a semaphore each time it processes an event (decrementing the * semaphore count value). The count value is therefore the difference between * the number of events that have occurred and the number that have been * processed. In this case it is desirable for the initial count value to be * zero. Note the same functionality can often be achieved in a more efficient * way using a direct to task notification. * * 2. Resource Management: * In this usage scenario the count value indicates the number of resources * available. To obtain control of a resource a task must first obtain a * semaphore - 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 - incrementing the semaphore count * value. In this case it is desirable for the initial count value to be equal * to the maximum count value, indicating that all resources are free. */ class CountingSemaphore : public SemaphoreBase { public: /** * Semaphore.hpp * * @brief Construct a new CountingSemaphore by calling * SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount, * UBaseType_t uxInitialCount) * * @warning The user should call isValid() on this object to verify that the * binary semaphore was created successfully in case the memory required to * create the queue could not be allocated. * * @param maxCount The maximum count value that can be reached. When the * semaphore reaches this value it can no longer be 'given'. * @param initialCount The count value assigned to the semaphore when * it is created. */ explicit CountingSemaphore(const UBaseType_t maxCount, const UBaseType_t initialCount = 0) { this->handle = xSemaphoreCreateCounting(maxCount, initialCount); } ~CountingSemaphore() = default; CountingSemaphore(const CountingSemaphore&) = delete; CountingSemaphore& operator=(const CountingSemaphore&) = delete; CountingSemaphore(CountingSemaphore&&) noexcept = default; CountingSemaphore& operator=(CountingSemaphore&&) noexcept = default; }; #endif /* configSUPPORT_DYNAMIC_ALLOCATION */ #if (configSUPPORT_STATIC_ALLOCATION == 1) /** * @class StaticBinarySemaphore Semaphore.hpp * * @brief Class that encapsulates the functionality of a FreeRTOS binary * semaphore. * * Each binary semaphore require a small amount of RAM that is used to hold the * semaphore's state. If a binary semaphore is created using * FreeRTOS::BinarySemaphore then the required RAM is automatically allocated * from the FreeRTOS heap. If a binary semaphore is created using * FreeRTOS::StaticBinarySemaphore then the RAM is provided by the application * writer as part of the class and allows the RAM to be statically allocated at * compile time. See the Static Vs Dynamic allocation page for more information. * * The semaphore is created in the 'empty' state, meaning the semaphore must * first be given using the give() API function before it can subsequently be * taken (obtained) using the take() function. * * 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. * * A binary semaphore need not be given back once obtained, so task * synchronisation can be implemented by one task/interrupt continuously * 'giving' the semaphore while another continuously 'takes' the semaphore. This * is demonstrated by the sample code on the giveFromISR() documentation page. * Note the same functionality can often be achieved in a more efficient way * using a direct to task notification. * * The priority of a task that 'takes' a mutex can potentially be raised if * another task of higher priority attempts to obtain the same mutex. The task * that owns the mutex 'inherits' the priority of the task attempting to 'take' * the same mutex. This means the mutex must always be 'given' back - otherwise * the higher priority task will never be able to obtain the mutex, and the * lower priority task will never 'disinherit' the priority. An example of a * mutex being used to implement mutual exclusion is provided on the take() * documentation page. */ class StaticBinarySemaphore : public SemaphoreBase { public: /** * Semaphore.hpp * * @brief Construct a new StaticBinarySemaphore object by calling * SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t * *pxSemaphoreBuffer ) * * @see * * @warning This class contains the storage buffer for the binary semaphore, * so the user should create this object as a global object or with the static * storage specifier so that the object instance is not on the stack. * * Example Usage * @include Semaphore/staticBinarySemaphore.cpp */ StaticBinarySemaphore() { this->handle = xSemaphoreCreateBinaryStatic(&staticBinarySemaphore); } ~StaticBinarySemaphore() = default; StaticBinarySemaphore(const StaticBinarySemaphore&) = delete; StaticBinarySemaphore& operator=(const StaticBinarySemaphore&) = delete; StaticBinarySemaphore(StaticBinarySemaphore&&) noexcept = default; StaticBinarySemaphore& operator=(StaticBinarySemaphore&&) noexcept = default; private: StaticSemaphore_t staticBinarySemaphore; }; class StaticCountingSemaphore : public SemaphoreBase { public: /** * @brief Construct a new StaticCountingSemaphore object by calling * SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t * uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t * *pxSemaphoreBuffer ) * * @see * * @warning This class contains the storage buffer for the counting semaphore, * so the user should create this object as a global object or with the static * storage specifier so that the object instance is not on the stack. * * @param maxCount The maximum count value that can be reached. When the * semaphore reaches this value it can no longer be 'given'. * @param initialCount The count value assigned to the semaphore when it is * created. * * Example Usage * @include Semaphore/staticCountingSemaphore.cpp */ explicit StaticCountingSemaphore(const UBaseType_t maxCount, const UBaseType_t initialCount = 0) { this->handle = xSemaphoreCreateCountingStatic(maxCount, initialCount, &staticCountingSemaphore); } ~StaticCountingSemaphore() = default; StaticCountingSemaphore(const StaticCountingSemaphore&) = delete; StaticCountingSemaphore& operator=(const StaticCountingSemaphore&) = delete; StaticCountingSemaphore(StaticCountingSemaphore&&) noexcept = default; StaticCountingSemaphore& operator=(StaticCountingSemaphore&&) noexcept = default; private: StaticSemaphore_t staticCountingSemaphore; }; #endif /* configSUPPORT_STATIC_ALLOCATION */ } // namespace FreeRTOS #endif // FREERTOS_SEMAPHORE_HPP