diff --git a/CMakeLists.txt b/CMakeLists.txt index 97f6c4a8..2639ecd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,17 @@ if(NOT CAN_STACK_DISABLE_THREADS AND NOT ARDUINO) endif() endif() +option( + USE_CMSIS_RTOS2_THREADING + "Set to ON to use ARM CMSIS RTOS2 thread syncronization. Replaces std::mutex and implements a CMSIS lock_guard." + OFF) +if(USE_CMSIS_RTOS2_THREADING) + message( + AUTHOR_WARNING + "Using CMSIS RTOS2 threading requires you to implement a hardware timebase (_gettimeofday) for the stack. Make sure you do this using a hardware timer!" + ) +endif() + # A handy function to prepend text to all elements in a list (useful for # subdirectories) function(prepend var prefix) diff --git a/isobus/src/isobus_task_controller_client.cpp b/isobus/src/isobus_task_controller_client.cpp index 9e19140b..9f6f4531 100644 --- a/isobus/src/isobus_task_controller_client.cpp +++ b/isobus/src/isobus_task_controller_client.cpp @@ -60,7 +60,7 @@ namespace isobus if (!initialized) { -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO && !defined USE_CMSIS_RTOS2_THREADING if (spawnThread) { workerThread = new std::thread([this]() { worker_thread_function(); }); diff --git a/isobus/src/isobus_virtual_terminal_client.cpp b/isobus/src/isobus_virtual_terminal_client.cpp index b8af3ec6..e3bdec9e 100644 --- a/isobus/src/isobus_virtual_terminal_client.cpp +++ b/isobus/src/isobus_virtual_terminal_client.cpp @@ -60,7 +60,7 @@ namespace isobus { languageCommandInterface.initialize(); } -#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO +#if !defined CAN_STACK_DISABLE_THREADS && !defined ARDUINO && !defined USE_CMSIS_RTOS2_THREADING if (spawnThread) { workerThread = new std::thread([this]() { worker_thread_function(); }); diff --git a/utility/include/isobus/utility/thread_synchronization.hpp b/utility/include/isobus/utility/thread_synchronization.hpp index c86a87b4..3d8bcdb7 100644 --- a/utility/include/isobus/utility/thread_synchronization.hpp +++ b/utility/include/isobus/utility/thread_synchronization.hpp @@ -25,6 +25,175 @@ namespace isobus /// @brief Disabled LOCK_GUARD macro since threads are disabled. #define LOCK_GUARD(type, x) +#elif defined USE_CMSIS_RTOS2_THREADING + +#include "cmsis_os.h" + +namespace isobus +{ + /// @brief A wrapper around a CMSIS RTOS 2 mutex. + /// @details See definition at https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS.html + class Mutex + { + public: + /// @brief Constructor for the CMSIS RTOS2 mutex wrapper + Mutex() : + handle(nullptr) + { + } + + /// @brief Locks the mutex. Part of BasicLockable requirements. + void lock() + { + if (ready()) + { + osStatus_t osRetVal = osMutexAcquire(handle, osWaitForever); + + if (osOK != osRetVal) + { + while (true) + { + // If your code is stuck in here, that means you did something + // very wrong, like recursively locked this mutex, or tried to + // lock the mutex before the OS was initialized, or called this in + // an interrupt service routine. + // osRetVal may contain more information. + } + } + } + } + + /// @brief Attempts to the mutex, and doesn't wait if it's not available. + /// @returns true if the mutex was successfully locked, false otherwise. + bool try_lock() + { + bool retVal = false; + + if (ready()) + { + osStatus_t osRetVal = osMutexAcquire(handle, 0); + retVal = (osOK == osRetVal); + } + return retVal; + } + + /// @brief Unlocks the mutex. Part of BasicLockable requirements. + void unlock() + { + if (nullptr != handle) + { + osStatus_t osRetVal = osMutexRelease(handle); + + if (osOK != osRetVal) + { + while (true) + { + // If your code is stuck in here, that means you + // either tried to release a mutex which is owned by a different thread, + // or the release failed due to some other OS reason. + // osRetVal may contain more information. + } + } + } + else + { + while (true) + { + // If your code is stuck in here, it's because you tried to unlock a + // mutex which doesn't exist. Don't do that. + // osRetVal may contain more information. + } + } + } + + protected: + /// @brief Checks if the mutex is ready to be used. Initializes the mutex if it's not. + /// @returns true if the mutex is ready to be used, false otherwise. + virtual bool ready() + { + if (nullptr == handle) + { + const osMutexAttr_t attributes = { + nullptr, +#ifdef osCMSIS_FreeRTOS // FreeRTOS doesn't support robust mutexes + osMutexPrioInherit, +#else + osMutexPrioInherit | osMutexRobust, +#endif + nullptr, + 0 + }; + handle = osMutexNew(&attributes); + } + return nullptr != handle; + } + osMutexId_t handle; ///< Mutex ID for reference by other functions or NULL in case of error or not yet initialized + }; + + /// @brief A wrapper around a CMSIS RTOS 2 recursive mutex. + /// @details See definition at https://www.keil.com/pack/doc/CMSIS/RTOS2/html/group__CMSIS__RTOS.html + class RecursiveMutex : public Mutex + { + protected: + /// @brief Checks if the mutex is ready to be used. + /// Initializes the mutex if it's not. + /// @returns true if the mutex is ready to be used, false otherwise. + bool ready() override + { + if (nullptr == handle) + { + const osMutexAttr_t attributes = { + nullptr, +#ifdef osCMSIS_FreeRTOS // FreeRTOS doesn't support robust mutexes + osMutexPrioInherit | osMutexRecursive, +#else + osMutexPrioInherit | osMutexRobust | osMutexRecursive, +#endif + nullptr, + 0 + }; + handle = osMutexNew(&attributes); + } + return nullptr != handle; + } + }; + + template + /// @brief A class to automatically lock and unlock a mutex when the scope ends. + /// Meant for systems with no support for std::lock_guard. + class LockGuard + { + public: + /// @brief Constructor for the LockGuard class. + /// @param mutex The mutex to lock. + /// @details Locks the mutex when the scope starts. + /// Unlocks the mutex when the scope ends. + LockGuard(T *mutex) : + lockable(mutex) + { + lockable->lock(); + } + + /// @brief Destructor for the LockGuard class. + /// @details Unlocks the mutex when the scope ends. + ~LockGuard() + { + lockable->unlock(); + } + + private: + T *lockable; ///< The mutex to lock and unlock. + }; +} // namespace isobus + +namespace std +{ + using mutex = isobus::Mutex; + using recursive_mutex = isobus::RecursiveMutex; +} // namespace std + +#define LOCK_GUARD(type, x) const LockGuard x##Lock(&x) + #else #include