C ++ Wrapper for all Real-Time Operating Systems for CortexM4 (Part 1)
Want to learn how to implement the C++ wrapper for all real-time operating systems on your CortexM4? Check out part one to get you started!
Join the DZone community and get the full member experience.
Join For FreeSo, what is the task and why is it relevant at all? At the moment, there are a million different operating systems written in C. But, for projects that do not need chips from different operating systems and basic functionalities, such as task, events, task notification, critical section, mutexes, and semaphores (although I try not to use them), queues. And all this is needed in a fairly simple form, without special twists.
In my opinion, the native RTOS MAX, written in C ++, is ideal for my projects, and it is a pleasure to worth with.
But, the catch is that our devices must conform to the IEC_61508 standard. One of the requirements sounds like an E.29 Application of a proven-in-use target library. Well, in simple words, if you make a device for compliance with the SIL3 level, then please use libraries that correspond to this level and are time-tested.
Regarding our task, this means that you can use MAX RTOS for such devices, but balls for reliability will not be added. Therefore, the manufacturers of the RTOS make special versions of their operating systems conforming to IEC_61508 standards, for example, FreeRTOS has a clone SafeRTOS, and embOs has an embOS-Safe clone. Of course, manufacturers earn very well on this because the licenses for these OSes cost several thousand.
By the way, a good example is the IAR compiler. The license costs about $ 1500, but the IAR Certified versions cost about $ 10,000. Although I checked on several projects, the output file of the version without a certificate and the certificate are completely identical. Well, you realized that you have to pay for peace.
So, first, we used one operating system. Then, I started using FreeRTOS for my needs, then we switched to another. In general, we constantly had to rewrite the already ready code. In addition, I would like it to look beautiful and simple, so that anyone could understand the code and what is happening. Then, the code support will be a simple work for students and trainees, and the gurus will be able to continue working on innovative devices and not understand the pile of noodles. In general, I want to go about this kind of fossilization:
Well, or this:
So, I decided to write a wrapper that would fit like FreeRTOS and, say, embOS for all the others, too. To start, I determined that I needed to create the following to be successful:
Tasks
Critical sections
Events and task notifications
Semaphores and Mutexes
Queues
The wrapper should be SIL3 ideological, and this level imposes many highly recommended things, and if they are completely followed, it will come out that the code is better to not write at all.
But, the fact that the standard regulates a bunch of rules, or rather recommendations, does not mean that they can not be violated. You can, but you need to follow as many recommendations as possible to get more points. So, I decided on some important restrictions:
No macros — This is to protect against the double inclusion of header files. Macros are evil if you count how much time is spent searching for errors related to macros. Then, it will come out that the universe is not that old. And, how much good could have been done during this time? It's probably better to simply ban them at the legislative level, as the torrents were banned or taken away with the premium for each written macro.
Do not use pointers — If possible, it would be ideal to try not to use them at all, but there are still places where there is none. In any case, the user of the wrapper, if possible, should not even know what the pointer is, since he heard about them only from his grandfather, because now he works exclusively with links
Do not use dynamic allocation of memory — Everything is clear here — just using the heap leads, first, to the fact that you need to reserve RAM for this heap. Secondly, with frequent use of the heap, it is defragmented and new objects are created on it longer and longer. So, actually, FreeRTOS was configured only with the statically allocated memory, setting
configSUPPORT_STATIC_ALLOCATION 1.
But, if you want to work in the default mode and, by default, FreeRTOS uses dynamically allocated memory to create the elements of the operating system, it's enough to setconfigSUPPORT_STATIC_ALLOCATION 0
and
configSUPPORT_DYNAMIC_ALLOCATION 1
. Do not forget to connect the implementation of your own mallocs and callots from the memory manager, for example, thisFreeRtos/portable/MemMang/heap_1.c file
. But, keep in mind that you will have to allocate a small amount of RAM for RAM since you can not count the exact size of the required RAM. I have all the settings, Idle included. The task of program timers is enabled with my two tasks, queues, queue size for timers 10, and so on. Let's say that it's not exactly the most optimal of settings and is earned when I allocated memory like this:
7 357 bytes of read-only code memory
535 bytes of read-only data memory
6 053 bytes of read-write data memory
Static memory allocation is a little bit more compact:
7 329 bytes of read-only code memory
535 bytes of read-only data memory
3 877 bytes of read-write data memory
Now, we are not interested in the question formulated in the article "I allocated 3KB to the operating system and launched only three tasks with a 128B stack, and for the fourth time, for some reason, there is not enough memory." I did this intentionally, for clarity, to show the difference between the dynamic and static allocation of memory with the same settings.
Do not cast the types — Ghost-types and other types already, in itself, means the fact that something is wrong with the design, but as usual, sometimes for the convenience of use, you have to cast, for example, enums that lead to integer ones. And, sometimes, you can not do so without this, but we must avoid this.
Simplicity and convenience — For the user of the wrapper, all the complexities should be hidden. They do not have a life as an oil, and they do not want to complicate it further. They will have created the task, realized everything that was needed in it, launched it, and went to enjoy life.
Now, we set ourselves a task — to create a task!
Creating a Task
Through long research, British scientists found out that for most RTOS, the task has a name, stack, stack size, "control block," identifier or pointer to the "control unit," and priority that the function performs in the task. Actually, everything could be crammed into the class, but it was correct when we wrote an operating system with you, but we are doing a wrapper, so you do not need to store all these things in the wrapper. All this will be done for you by the SIL3 ideational operating system, which we are wrapping. As a matter of fact, we need only the function that is executed in the task and the structure that stores the "control unit," which is filled when creating the task and task ID.
Therefore, the class of a task, let's call it Thread
, can look very simple:
class Thread {
public:
virtual void Execute() = 0 ;
private:
tTaskHandle taskHandle ;
tTaskContext context ;
} ;
It would be desirable to simply declare a class of my task where I could implement all that is necessary and then pass the pointer on an object of this class to a wrapper which would create, the API of the RTOS task, where I would launch the method Execute ()
:
class MyTask : public Thread {
public:
virtual void Execute() override {
while(true) {
//do something..
}
} ;
using tMyTaskStack = std::array<OsWrapper::tStack,
static_cast<tU16>(OsWrapper::StackDepth::minimal)> ;
inline static tMyTaskStack Stack; //!C++17
} ;
MyTask myDesiredTask
int main() {
Rtos::CreateThread(myTask, MyTask::Stack.data(), "myTask") ;
}
In the "all" RTOS, in order for the task to be created, it is necessary to pass a pointer to the function that will be started by the scheduler. In our case, this is the Execute ()
function, but I cannot pass a pointer to this method since it is not static. Therefore, we look at how the task is created in the API of "all" operating systems and notice that you can create a task by passing a parameter to the task function. For example, in embOS, this will look like:
void OS_TASK_CreateEx( OS_TASK* pTask, const char* pName, OS_PRIO Priority,
void (*pRoutine)(void * pVoid ), void OS_STACKPTR *pStack, OS_UINT StackSize, OS_UINT TimeSlice, void* pContext);
void * pContext — This is the key to the solution. Let's have a static method, to which we will pass as a pointer to the method called by the scheduler, and as a parameter, we will pass a pointer to an object of type Thread
, which can call the Execute ()
method directly. This is just the moment when we are without a pointer and casting to types, but this code will be hidden from the user:
static void Run(void *pContext ) {
static_cast<Thread*>(pContext)->Execute() ;
}
T algorithm of operations such that permit the scheduler to launch the Run
method. A pointer to an object of type Thread
is passed to the Run
method. In the Run
method, the Execute ()
method, a specific object of the Thread
class, is directly called, which is just our implementation of the task.
The problem is almost solved. Now, we need to implement the methods. All OSes have different APIs, so to implement, for example, the function of creating a task for embOS, you need to call the void OS_TASK_CreateEx (..)
method. And, for FreeRTOS in dynamic memory allocation mode, it's xTaskCreate (..)
. Although they have the same thing, the same syntax and parameters are different. We do not want to run files for every new OS and write code for each of the methods of the class. So, you will need to put it in one file and get it in the form of macros. Fine, but STOP! I forbade macros to myself — you need a different approach, too.
The simplest thing that came to my mind is to make a separate file for each OS with inline functions. If we want to use any other OSes, we will simply need to implement each of these functions using the API of this OSes. The following file is the rtosFreeRtos.cpp
:
#include "rtos.hpp"
//For FreeRTOS functions prototypes
#include <FreeRTOS.h>
//For xTaskCreate
#include <task.h>
namespace OsWrapper {
void wCreateThread(Thread & thread, const char * pName,
ThreadPriority prior,const tU16 stackDepth,
tStack *pStack) {
#if (configSUPPORT_STATIC_ALLOCATION == 1)
if (pStack != nullptr) {
thread.handle = xTaskCreateStatic(static_cast<TaskFunction_t>(Rtos::Run),
pName,
stackDepth,
&thread,
static_cast<uint32_t>(prior),
pStack,
&thread.taskControlBlock);
}
#else
thread.handle = (xTaskCreate(static_cast<TaskFunction_t>(Rtos::Run),
pName, stackDepth, &thread, static_cast<uint32_t>(prior),
&thread.handle) == pdTRUE) ? thread.handle : nullptr ;
#endif
}
Similarly, the file for embOS rtosEmbOS.cpp
:
#include "rtos.hpp"
//For embOS functions prototypes
#include <rtos.h>
namespace OsWrapper {
void wCreateThread(Thread &thread, const char * pName,
ThreadPriority prior,const tU16 stackDepth,
tStack *pStack) {
constexpr OS_UINT timeSliceNull = 0 ;
if (pStack != nullptr) {
OS_CreateTaskEx(&(thread.handle),
pName,
static_cast<OS_PRIO>(prior),
Rtos::Run,
pStack,
((stackSize == 0U) ? sizeof(pStack) : stackSize),
timeSliceNull,
&thread) ;
}
}
The types of different operating systems are also different, especially the context structure of the tasks. So, create the rtosdefs.hpp
file with our own wrapper aliases:
#include <FreeRTOS.h> //For TaskHandle_t
namespace OsWrapper {
using tTaskContext = StaticTask_t;
using tTaskHandle = TaskHandle_t;
using tStack = StackType_t ;
}
For EmbOS, this might look like this:
#include <rtos.h> // For OS_TASK
namespace OsWrapper {
using tTaskContext = OS_TASK;
using tTaskHandle = OS_TASK;
using tStack = tU16 // it's generally void, but for our kernel this is tU16;
}
As a result, for the alteration under any other RTOS, it is enough to make changes only in these two files: rtosdefs.cpp
and rtos.cpp
. Now, the Thread
and RTOS classes look like c pictures:
Running the Operating System and Finalizing the Task
For the Cortex M4, "all" operating systems use three interrupts System tick timer. System Service is called via the SWI instruction and the request for the system service, which was actually invented for the RTOS. Some RTOS use other system interrupts, but these will be sufficient for most "all" operating systems. And, if not, then you can add three handlers for these interrupts. To start the RTOS, we will also need a start method:
static void HandleSvcInterrupt() ;
static void HandleSvInterrupt() ;
static void HandleSysTickInterrupt() ;
static void Start() ;
The first thing I needed was — and without of which I can not live — the mechanism for notifying tasks. I generally like event-driven programming, so you need to implement a wrapper to notify the tasks.
Everything turned out to be quite simple. Any OS is able, well, except maybe UC-OS-II and III. In my opinion, the mechanism of events there is generally tricky.
In order to notify a task, you just need to send the event, not to the void, but specifically to the task. For this, the notification method should have a pointer to the context of the task or the task identifier. For this, I just store in class Thread
, with means that the method of the notification should be at class Thread
. There must be a method of waiting for an alert. At the same time, we add the method Sleep (..)
, which suspends the execution of the calling task. Now, both classes look like this:
/*******************************************************************************
* Filename : Rtos.hpp
*
* Details : Rtos class is used to create tasks, work with special Rtos
* functions and also it contains a special static method Run. In this method
* the pointer on Thread should be pass. This method is input point as
* the task of Rtos. In the body of the method, the method of concrete Thread
* will run.
*******************************************************************************/
#ifndef __RTOS_HPP
#define __RTOS_HPP
#include "thread.hpp" // for Thread
#include "../../Common/susudefs.hpp"
#include "FreeRtos/rtosdefs.hpp"
namespace OsWrapper
{
extern void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *) ;
extern void wStart() ;
extern void wHandleSvcInterrupt() ;
extern void wHandleSvInterrupt() ;
extern void wHandleSysTickInterrupt() ;
extern void wEnterCriticalSection();
extern void wLeaveCriticalSection();
class Rtos
{
public:
static void CreateThread(Thread &thread ,
tStack * pStack = nullptr,
const char * pName = nullptr,
ThreadPriority prior = ThreadPriority::normal,
const tU16 stackDepth = static_cast<tU16>(StackDepth::minimal)) ;
static void Start() ;
static void HandleSvcInterrupt() ;
static void HandleSvInterrupt() ;
static void HandleSysTickInterrupt() ;
friend void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *);
private:
//cstat !MISRAC++2008-7-1-2 To prevent reinterpet_cast in the CreateTask
static void Run(void *pContext )
{
static_cast<Thread*>(pContext)->Execute() ;
}
} ;
} ;
#endif // __RTOS_HPP
/*******************************************************************************
* Filename : thread.hpp
*
* Details : Base class for any Taskis which contains the pure virtual
* method Execute(). Any active classes which will have a method for running as
* a task of RTOS should inherit the Thread and override the Execute() method.
* For example:
* class MyTask : public OsWrapper::Thread
* {
* public:
* virtual void Execute() override {
* while(true) {
* //do something..
* }
* } ;
*
*******************************************************************************/
#ifndef __THREAD_HPP
#define __THREAD_HPP
#include "FreeRtos/rtosdefs.hpp"
#include "../../Common/susudefs.hpp"
namespace OsWrapper
{
extern void wSleep(const tTime) ;
extern void wSleepUntil(tTime &, const tTime) ;
extern tTime wGetTicks() ;
extern void wSignal(tTaskHandle const &, const tTaskEventMask) ;
extern tTaskEventMask wWaitForSignal(const tTaskEventMask, tTime) ;
constexpr tTaskEventMask defaultTaskMaskBits = 0b010101010 ;
enum class ThreadPriority
{
clear = 0,
lowest = 10,
belowNormal = 20,
normal = 30,
aboveNormal = 80,
highest = 90,
priorityMax = 255
} ;
enum class StackDepth: tU16
{
minimal = 128U,
medium = 256U,
big = 512U,
biggest = 1024U
};
class Thread
{
public:
virtual void Execute() = 0 ;
inline tTaskHandle GetTaskHanlde() const
{
return handle;
}
static void Sleep(const tTime timeOut = 1000ms)
{
wSleep(timeOut) ;
};
inline void Signal(const tTaskEventMask mask = defaultTaskMaskBits)
{
wSignal(handle, mask);
};
inline tTaskEventMask WaitForSignal(tTime timeOut = 1000ms,
const tTaskEventMask mask = defaultTaskMaskBits)
{
return wWaitForSignal(mask, timeOut) ;
}
friend void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *);
private:
tTaskHandle handle ;
tTaskContext context ;
} ;
} ;
#endif // __THREAD_HPP
Then, we began to implement. Here, I was in for the first sign of trouble. It turns out that "any" OSes differently causes its functions from interrupts. For example, FreeRTOS has special implementations of functions to execute them from interrupts, say, if there is a function xTaskNotify (..).
Then, it can not be called from an interrupt, but it is necessary to call xTaskNotifyFromISR (..)
.
With embOS, if you call any function from an to interrupt, be kind enough to use OS_InInterrupt ()
when entering the interrupt and OS_LeaveInterrupt ()
on exit. I had to make the class InterruptEntry
, which has only a constructor and destructor:
namespace OsWrapper {
extern void wEnterInterrupt() ;
extern void wLeaveInterrupt() ;
class InterruptEntry {
public:
inline InterruptEntry() {
wEnterInterrupt() ;
}
inline ~InterruptEntry() {
wLeaveInterrupt() ;
}
} ;
} ;
You can use it like this:
void Button::HandleInterrupt() {
const OsWrapper::InterruptEntry ie;
EXTI->PR = EXTI_PR_PR13 ;
myDesiredTask.Signal();
}
void myDesiredTask::Execute() {
while(true) {
if (WaitForSignal(100000ms) == defaultTaskMaskBits) {
GPIOC->ODR ^= (1 << 5) ;
}
}
} ;
Obviously, for FreeRTOS, both the constructor and the destructor will be empty. And, for notification, you can use the function xTaskNotifyFromISR (..)
, which — without any difference from where it will be called — is a small overhead, but you will not do that for the sake of universality. You can, of course, create separate methods for calling from interrupts, but for now, I've decided to just do it universally.
The same trick InterruptEntry
can be done with the critical section:
namespace OsWrapper{
class CriticalSection {
public:
inline CriticalSection() {
wEnterCriticalSection() ;
}
inline ~CriticalSection() {
wLeaveCriticalSection() ;
}
} ;
} ;
Now, just add the implementation of the functions with the FreeRtos API to the file and run the test. Although you could not start it, it's clear that it will work.
/*******************************************************************************
* Filename : rtosFreeRtos.cpp
*
* Details : This file containce implementation of functions of concrete
* FreeRTOS to support another RTOS create the same file with the
* same functions but another name< for example rtosEmbOS.cpp and
* implement these functions using EmbOS API.
*
*******************************************************************************/
#include "../thread.hpp"
#include "../mutex.hpp"
#include "../rtos.hpp"
#include "../../../Common/susudefs.hpp"
#include "rtosdefs.hpp"
#include "../event.hpp"
#include <limits>
namespace OsWrapper
{
/*****************************************************************************
* Function Name: wCreateThread
* Description: Creates a new task and passes a parameter to the task. The
* function should call appropriate RTOS API function to create a task.
*
* Assumptions: RTOS API create task function should get a parameter to pass the
* paramete to task.
* Some RTOS does not use pStack pointer so it should be set to nullptr
*
* Parameters: [in] thread - refernce on Thread object
* [in] pName - name of task
* [in] prior - task priority
* [in] stackDepth - size of Stack
* [in] pStack - pointer on task stack
* Returns: No
****************************************************************************/
void wCreateThread(Thread & thread, const char * pName,
ThreadPriority prior, const tU16 stackDepth,
tStack *pStack)
{
#if (configSUPPORT_STATIC_ALLOCATION == 1)
if (pStack != nullptr)
{
thread.handle = xTaskCreateStatic(static_cast<TaskFunction_t>(Rtos::Run),
pName,
stackDepth,
&thread,
static_cast<uint32_t>(prior),
pStack,
&thread.context);
}
#else
thread.handle = (xTaskCreate(static_cast<TaskFunction_t>(Rtos::Run),
pName, stackDepth, &thread, static_cast<uint32_t>(prior),
&thread.handle) == pdTRUE) ? thread.handle : nullptr ;
#endif
}
/*****************************************************************************
* Function Name: wStart()
* Description: Starts the RTOS scheduler
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wStart()
{
vTaskStartScheduler() ;
}
/*****************************************************************************
* Function Name: wHandleSvcInterrupt()
* Description: Handle of SVC Interrupt. The function should call appropriate
* RTOS function to handle the interrupt
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wHandleSvcInterrupt()
{
vPortSVCHandler() ;
}
/*****************************************************************************
* Function Name: wHandleSvInterrupt()
* Description: Handle of SV Interrupt. The function should call appropriate
* RTOS function to handle the interrupt
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wHandleSvInterrupt()
{
xPortPendSVHandler() ;
}
/*****************************************************************************
* Function Name: wHandleSysTickInterrupt()
* Description: Handle of System Timer Interrupt. The function should call
* appropriate RTOS function to handle the interrupt
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wHandleSysTickInterrupt()
{
xPortSysTickHandler() ;
}
/*****************************************************************************
* Function Name: wSleep()
* Description: Suspends the calling task for a specified period of time,
* or waits actively when called from main()
*
* Assumptions: No
* Parameters: [in] timeOut - specifies the time interval in system ticks
* Returns: No
****************************************************************************/
void wSleep(const tTime timeOut)
{
vTaskDelay(timeOut) ;
}
/*****************************************************************************
* Function Name: wEnterCriticalSection()
* Description: Basic critical section implementation that works by simply
* disabling interrupts
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wEnterCriticalSection()
{
taskENTER_CRITICAL() ;
}
/*****************************************************************************
* Function Name: wLeaveCriticalSection()
* Description: Leave critical section implementation that works by simply
* enabling interrupts
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wLeaveCriticalSection()
{
taskEXIT_CRITICAL() ;
}
/****************************************************************************
* Function Name: wEnterInterrupt()
* Description: Some RTOS requires to inform the kernel that interrupt code
* is executing
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wEnterInterrupt()
{
}
/****************************************************************************
* Function Name: wLeaveInterrupt()
* Description: Some RTOS requires to inform that the end of the interrupt r
* outine has been reached; executes task switching within ISR
*
* Assumptions: No
* Parameters: No
* Returns: No
****************************************************************************/
void wLeaveInterrupt()
{
}
/****************************************************************************
* Function Name: wSignal()
* Description: Signals event(s) to a specified task
*
* Assumptions: No
* Parameters: [in] taskHandle - Reference to the task structure
* [in] mask - The event bit mask containing the event bits,
* which shall be signaled.
* Returns: No
****************************************************************************/
void wSignal(tTaskHandle const &taskHandle, const tTaskEventMask mask)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE ;
xTaskNotifyFromISR(taskHandle, mask, eSetBits, &xHigherPriorityTaskWoken) ;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) ;
}
/****************************************************************************
* Function Name: wWaitForSignal()
* Description: Waits for the specified events for a given time, and clears
* the event memory when the function returns
*
* Assumptions: No
* Parameters: [in] mask - The event bit mask containing the event bits,
* which shall be waited for
* [in] timeOut - Maximum time in system ticks waiting for events
* to be signaled.
* Returns: Set bits
****************************************************************************/
tTaskEventMask wWaitForSignal(const tTaskEventMask mask, tTime timeOut)
{
uint32_t ulNotifiedValue = 0U ;
xTaskNotifyWait( 0U,
std::numeric_limits<uint32_t>::max(),
&ulNotifiedValue,
timeOut);
return (ulNotifiedValue & mask) ;
}
/****************************************************************************
* Function Name: wCreateEvent()
* Description: Create an Event object
*
* Assumptions: No
* Parameters: [in] event - reference on tEvent object
*
* Returns: Handle of created Event
****************************************************************************/
tEventHandle wCreateEvent(tEvent &event)
{
#if (configSUPPORT_STATIC_ALLOCATION == 1)
return xEventGroupCreateStatic(&event);
#else
return xEventGroupCreate();
#endif
}
/****************************************************************************
* Function Name: wDeleteEvent()
* Description: Create an Event object
*
* Assumptions: No
* Parameters: [in] eventHandle - reference on tEventHandle object
*
* Returns: No
****************************************************************************/
void wDeleteEvent(tEventHandle &eventHandle)
{
vEventGroupDelete(eventHandle);
}
/****************************************************************************
* Function Name: wSignalEvent()
* Description: Sets an resumes tasks which are waiting at the event object
*
* Assumptions: No
* Parameters: [in] event - reference on eventHandle object
* [in] mask - The event bit mask containing the event bits,
* which shall be signaled
*
* Returns: No
****************************************************************************/
void wSignalEvent(tEventHandle const &eventHandle, const tEventBits mask)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xEventGroupSetBitsFromISR(eventHandle, mask, &xHigherPriorityTaskWoken) ;
portYIELD_FROM_ISR(xHigherPriorityTaskWoken) ;
}
/****************************************************************************
* Function Name: wWaitEvent()
* Description: Waits for an event and suspends the task for a specified time
* or until the event has been signaled.
*
* Assumptions: No
* Parameters: [in] event - Reference on eventHandle object
* [in] mask - The event bit mask containing the event bits,
* which shall be signaled
* [in] timeOut - Maximum time in RTOS system ticks until the
* event must be signaled.
* [in] mode - Indicate mask bit behaviour
*
* Returns: Set bits
****************************************************************************/
tEventBits wWaitEvent(tEventHandle const &eventHandle, const tEventBits mask,
const tTime timeOut, OsWrapper::EventMode mode)
{
BaseType_t xWaitForAllBits = pdFALSE ;
if (mode == OsWrapper::EventMode::waitAnyBits)
{
xWaitForAllBits = pdFALSE;
}
return xEventGroupWaitBits(eventHandle, mask, pdTRUE, xWaitForAllBits, timeOut) ;
}
/****************************************************************************
* Function Name: wCreateMutex()
* Description: Create an mutex. Mutexes are used for managing resources by
* avoiding conflicts caused by simultaneous use of a resource. The resource
* managed can be of any kind: a part of the program that is not reentrant, a
* piece of hardware like the display, a flash prom that can only be written to
* by a single task at a time, a motor in a CNC control that can only be
* controlled by one task at a time, and a lot more.
*
* Assumptions: No
* Parameters: [in] mutex - Reference on tMutex structure
* [in] mode - Indicate mask bit behaviour
*
* Returns: Mutex handle
****************************************************************************/
tMutexHandle wCreateMutex(tMutex &mutex)
{
#if (configSUPPORT_STATIC_ALLOCATION == 1)
return xSemaphoreCreateMutexStatic(&mutex) ;
#else
return xSemaphoreCreateMutex();
#endif
}
/****************************************************************************
* Function Name: wDeleteMutex()
* Description: Delete the mutex.
*
* Assumptions: No
* Parameters: [in] mutex - handle of mutex
*
* Returns: Mutex handle
****************************************************************************/
void wDeleteMutex(tMutexHandle &handle)
{
vSemaphoreDelete(handle) ;
}
/****************************************************************************
* Function Name: wLockMutex()
* Description: Claim the resource
*
* Assumptions: No
* Parameters: [in] handle - handle of mutex
* [in] timeOut - Maximum time until the mutex should be available
*
* Returns: true if resource has been claimed, false if timeout is expired
****************************************************************************/
bool wLockMutex(tMutexHandle const &handle, tTime timeOut)
{
return static_cast<bool>(xSemaphoreTake(handle, timeOut)) ;
}
/****************************************************************************
* Function Name: wUnLockMutex()
* Description: Releases a mutex currently in use by a task
*
* Assumptions: No
* Parameters: [in] handle - handle of mutex
*
* Returns: No
****************************************************************************/
void wUnLockMutex(tMutexHandle const &handle)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE ;
xSemaphoreGiveFromISR(handle, &xHigherPriorityTaskWoken) ;
portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) ;
}
/****************************************************************************
* Function Name: wSleepUntil()
* Description: Suspends the calling task until a specified time, or waits
* actively when called from main()
*
* Assumptions: No
* Parameters: [in] last - Refence to a variable that holds the time at which
* the task was last unblocked. The variable must be initialised
* with the current time prior to its first use
* [in] timeOut - Time to delay until, the task will be unblocked
* at time
*
* Returns: No
****************************************************************************/
void wSleepUntil(tTime & last, const tTime timeOut)
{
vTaskDelayUntil( &last, timeOut) ;
}
/****************************************************************************
* Function Name: wGetTicks()
* Description: Returns the current system time in ticks as a native integer
* value
*
* Assumptions: No
* Parameters: No
*
* Returns: Current system time in ticks
****************************************************************************/
tTime wGetTicks()
{
return xTaskGetTickCount();
}
}
Well, that's it for part one! Stay tuned for tomorrow's installment on how to refine the task in your project!
Opinions expressed by DZone contributors are their own.
Comments