📜 ⬆️ ⬇️

C ++ wrapper for "all" Real Time Operating Systems for CortexM4

image

I already talked about how you can use FreeRtos for projects written in C ++ in the article STM32, C ++ and FreeRTOS. Development from scratch. Part 1 . Since then, as many as 3 years have passed, I have seriously aged, lost a bunch of neural connections, so I decided to shake up the old days in order to restore these connections and wipe the wrapper for "any" of the popular RTOS. This is certainly a joke, I deliberately took “all” in quotes, but in every joke there is some truth.

So, what is the task and why is it relevant at all? At the moment there are a million different operating systems written in C - I don’t want to choose for every taste, paid, free, small, big ... But for projects in which I participate, all these chips of different operating systems are not enough basic functionality, such as the 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 any special bells and whistles.

In my opinion, the domestic RTOS MAX , written in C ++, is perfect for my projects and is a pleasure to use.
')
But the snag is that our devices must comply with the IEC_61508 standard, one of whose requirements sounds like the E.29 Application of a proven-in-use target library . Well, or in simple terms, if you make a device for compliance with the SIL3 level, then use Higher Recommended and libraries that correspond to this level and are time-tested.

Regarding our task, this means that it is possible to use the MAX RTOS for such devices, but the reliability scores will not increase. Therefore, RTOS vendors make special versions of their operating systems that comply with IEC_61508 standards, for example, FreeRTOS has SafeRTOS clones, and embOs has embOS-Safe clones, of course, manufacturers earn very well on this, because licenses for these OSes cost several thousand or tens one thousand dollars.

By the way, a good example is the IAR compiler, a license for which costs about $ 1,500, but IAR Certified versions already costs about 10,000 bucks, although I checked on several projects - the output file of the version without a certificate and with a certificate are completely identical. Well, you understand that you need to pay for peace of mind.

So, at 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 ready code. In addition, I would like it to look beautiful and simple, so that anyone can understand what is happening in the code, then code support will be a simple job for students and practitioners, and gurus will be able to continue working on innovative devices, rather than understand the noodles . In general, I want this fossilization to take place like this:

image

Well, or here it is ...

image

Therefore, I decided to write a wrapper that would fit both FreeRTOS and, say, embOS well for everyone else too :) and for starters, I determined that I needed to be completely happy:


The wrapper should be SIL3 ideological, and this level imposes a lot of High Recommended things, and if you follow them completely, it will turn out that it is better not to write code at all.

But the fact that the standard regulates a bunch of rules, more precisely recommendations, this does not mean that they cannot be violated - it is possible, but you need to follow as many recommendations as possible in order to get more points. Therefore, I decided on some important limitations:


We will start with this, so we set ourselves the task of creating a task (it turned out, right from the “forbidden to forbid" series).

Task creation


Through long studies, the British scientists ( The whole truth about the RTOS from Colin Walls. Article # 4. Tasks, context switching and interruptions ) (by the way, if you didn’t know, the assembler for ARM was also invented by a British scientist, I was not surprised by anything once :)), so the British scientists found out that for the majority of “all” RTOS, the task has a name , stack , stack size , “control unit” , identifier or pointer to “control unit” , priority , function that is performed in the task . Actually, everything could have been crammed into a class, but it would be correct if we wrote OSes with you, but we make a wrapper, so there is no need to store all these things in a wrapper, SIL3 will do all this for you we wrap up. In essence, we need only the function that is executed in the task and the structure that stores the “control unit” , which is filled when the task is created and the task ID . Therefore, the task class, let's call it Thread, can look very simple:

class Thread { public: virtual void Execute() = 0 ; private: tTaskHandle taskHandle ; tTaskContext context ; } ; 

I want to just declare my task class, where I could implement everything I needed and then pass a pointer to an object of this class to a wrapper, which would create a task using the RTOS API, where I would run the Execute () method:

 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 "all" the RTOS, in order for the task to be created, it is necessary to pass a pointer to the function that will be launched 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 a 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, for embOS these are:

 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 us have a static method, the pointer 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 where you can call the Execute () method directly. This is exactly the moment when there is no pointer and no type conversion, but this code will be hidden from the user:

 static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Execute() ; } 

Those. the algorithm works like this, the scheduler runs the Run method, a pointer to an object of type Thread is passed to the Run method. In the Run method, the Execute () method is directly called, a specific object of the class Thread , 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 task creation function for embOS, you must call the void OS_TASK_CreateEx (..) method, and for FreeRTOS in the dynamic memory allocation mode, this is xTaskCreate (..) and although they have the same essence but the syntax and parameters are different. We don’t want, each time for a new OS to run through the files and write code for each of the class methods, so you need to somehow put it into one file and ... arrange in the form of macros. Great, but stop, I forbid the macros to myself - a different approach is needed.

The simplest thing that occurred to me is to make a separate file for each operating system with inline functions. If we want to use any other OS, we will just have to implement each of these functions using the API of this OS. The following rtosFreeRtos.cpp file is obtained.

 #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 may also look like .

 #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 structures of the tasks, so we will create a 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 //   void,      tU16 ; } 

As a result, for reworking under any other RTOS, it is enough to make changes only in these two rtosdefs.cpp and rtos.cpp files. Now Thread and Rtos classes look like c pictures

image

Running an OS and finalizing the task


For Cortex M4, “all” operating systems use 3 interrupts, System tick timer interrupts, System Service call via SWI instruction , Pendable request for system service , which were actually invented for the RTOS. Some RTOS still use other system interrupts, but these will be enough for most "all" operating systems. And if not, then it will be possible to add, so just define three interrupt handlers and to launch the RTOS we will need another start method:

 static void HandleSvcInterrupt() ; static void HandleSvInterrupt() ; static void HandleSysTickInterrupt() ; static void Start() ; 

The first thing I needed and without which I cannot live, what I dream about is the mechanism of task notification. I generally like Event-driven programming, so I need to implement a wrapper for task notification as soon as possible.

Everything turned out to be quite simple, any operating system can do that, well, except maybe uc-OS-II and III , although maybe I didn’t read well, but, in my opinion, the mechanism of events there is generally tricky, but oh well, “everything” is the rest for sure.

In order to notify a task, you simply need to send an event not to a void, but specifically to a task. For this, the notification method must have a pointer to the context of the task or the task identifier. I just keep these in the Thread class, which means the notification method should be in the Thread class. There should also be an alert waiting method. At the same time, we will add the Sleep (..) method, which suspends the execution of the calling task. Now both classes look like this:

image

rtos.hpp
 /******************************************************************************* * 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 


thread.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 


I started to implement, and here the first trouble lurked me, it turns out that “any” OS causes its functions to be different from interrupts. For example, FreeRTOS has special implementations of functions for executing them from interrupts, say, if there is a function xTaskNotify (..) , then it cannot be called from an interrupt, but it is necessary to call xTaskNotifyFromISR (..) .
In embOS, if you call any function from an interrupt, please use OS_InInterrupt () when entering an interrupt and OS_LeaveInterrupt () when exiting. I had to make the InterruptEntry class, which has only a constructor and a 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 xTaskNotifyFromISR (..) function, which, no matter where it comes from, is a little overhead, but you can't do it for the sake of versatility. Of course, you can create separate methods for calling from interrupts, but for now I have decided to simply make universally.
The same trick as with InterruptEntry can be done with the critical section:

 namespace OsWrapper{ class CriticalSection { public: inline CriticalSection() { wEnterCriticalSection() ; } inline ~CriticalSection() { wLeaveCriticalSection() ; } } ; } ; 

Now we simply add the implementation of functions using the FreeRtos API to the file and run the check, although it could not be started, so it is clear that it will work :)
rtosFreeRtos.cpp
 /******************************************************************************* * 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(); } } 


image

We continue to refine the task


The task now has almost everything needed, we added the Sleep () method. This method pauses the task for a specified time. In most cases, this is enough, but if you need a clearly deterministic time, then Sleep () can cause problems. For example, you want to perform some calculation and blink the LED and do it exactly once in 100 ms

 void MyTask::Execute() { while(true) { DoCalculation(); //It takes about 10ms Led1.Toggle() ; Sleep(100ms) ; } } 

This code will flash the LED once every 110 ms. But you want once in 100ms, you can roughly calculate the calculation time and put Sleep (90ms). But what if the calculation time depends on the input parameters, the blinking will not be deterministic at all. For such cases, there are special methods in "all" operating systems, such as DelayUntil (). It works according to this principle - first you need to memorize the current value of the tick counter of the operating system, then add to this value the number of ticks to which the task should be suspended as soon as the tick counter reaches this value, the task is unlocked. Thus, the task will be blocked exactly at the value you set and your LED will blink exactly every 100ms regardless of the duration of the calculation.
This mechanism is implemented in different ways in different operating systems, but it has one algorithm. As a result, the mechanism, for example, implemented on FreeRTOS, will be simplified to the state shown in the following picture:

image

As you can see, the reading of the initial state of the Tick counter of the operating system occurs before entering the infinite loop, and we need to think of something to realize it. The design pattern Template Method comes to the rescue. , , , , Execute(), , .. . , ( ), .

  class Thread { public: virtual void Execute() = 0 ; friend class Rtos ; private: void Run() { lastWakeTime = wGetTicks() ; Execute(); } ... tTime lastWakeTime = 0ms ; ... } 

Run Rtos, Execute(), Run() Thread. Rtos , Run() Thread.

 static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Run() ; } 

The only restriction for the SleepUntil () method , it cannot be used in conjunction with other methods of blocking the task. Alternatively, to solve the problem of working with other methods blocking the task, you can add a method to update the stored tick counter of the system, and call it before SleepUntil () , but for now just keep this nuance in mind. An extreme variant of the classes look shown in the following picture:
image

thread.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.. * } * } ; * * Author : Sergey Kolody *******************************************************************************/ #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) ; }; void SleepUntil(const tTime timeOut = 1000ms) { wSleepUntil(lastWakeTime, 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 *); friend class Rtos ; private: tTaskHandle handle ; tTaskContext context ; tTime lastWakeTime = 0ms ; void Run() { lastWakeTime = wGetTicks() ; Execute(); } } ; } ; #endif // __THREAD_HPP 


rtos.hpp
 /******************************************************************************* * 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 *); friend class Thread ; private: //cstat !MISRAC++2008-7-1-2 To prevent reinterpet_cast in the CreateTask static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Run() ; } } ; } ; #endif // __RTOS_HPP 



Developments


So the task is created, it can send an event, but I want to realize an event that can be sent not to a specific task, but to any subscriber who decides to expect this event. Roughly speaking, you need to implement a wrapper over the Event.

, , , , , , , . , . , , , , .



:

 OsWrapper::Event event{10000ms, 3}; //  ,    10000ms,    0    1. void SomeTask::Execute() { while(true) { using OsWrapper::operator""ms ; Sleep(1000ms); event.Signal() ; //      0   1. Sleep(1000ms); event.SetMaskBits(4) //    2. event.Signal() ; //      2. } } ; void AnotherTask::Execute() { while(true) { using namespace::OsWrapper ; //,      ,    10000ms if ((event.Wait() & defaultTaskMaskBits) != 0) { GPIOC->ODR ^= (1 << 5) ; } } } ; 


,


Initially, while writing this article, I haven’t implemented them yet, but as promised I developed mutexes and mailboxes, the source code is here: GitHub OsWrapper . An example of mailbox usage is as follows:
 OsWrapper::MailBox<tU32, 10> queue; //    10   int void ReceiveTask::Execute() { tU32 item; while(true) { using OsWrapper::operator""ms ; if (queue.Get(item, 10000ms)) { //    GPIOC->ODR ^= (1 << 9); } } } ; void SendTask::Execute() { tU32 item = 0U; while(true) { queue.Put(item); item ++; SleepUntil(1000ms); } } ; 


How to use all this business


, , , : LedTask , 2 , 2 myTask , 10 , , . 2 . , event . , :)

 using OsWrapper::operator""ms ; OsWrapper::Event event{10000ms, 1}; class MyTask : public OsWrapper::Thread { public: virtual void Execute() override { while(true) { if (event.Wait() != 0) { GPIOC->ODR ^= (1 << 9); } } } using tMyTaskStack = std::array<OsWrapper::tStack, static_cast<tU16>(OsWrapper::StackDepth::minimal)> ; inline static tMyTaskStack Stack; //C++17   IAR 8.30 } ; class LedTask : public OsWrapper::Thread { public: virtual void Execute() override { while(true) { GPIOC->ODR ^= (1 << 5) ; using OsWrapper::operator""ms ; SleepUntil(2000ms); event.Signal() ; } } using tLedStack = std::array<OsWrapper::tStack, static_cast<tU16>(OsWrapper::StackDepth::minimal)> ; inline static tLedStack Stack; //C++17   IAR 8.30 } ; MyTask myTask; LedTask ledTask; int main() { using namespace OsWrapper ; Rtos::CreateThread(myTask, MyTask::Stack.data(), "myTask", ThreadPriority::lowest, MyTask::Stack.size()) ; Rtos::CreateThread(ledTask, LedTask::Stack.data()) ; Rtos::Start(); return 0; } 


Conclusion


. , ++ ++ . ++.
, ++, , , , , , , . , .

But for now, most of us use the traditional Sish OSes, you can use the wrapper as a starting point for the transition to a happy future with C ++ :)

I put together a small test project in Clion . , , IAR toolchain, , , elf , hex , , GDB. — , , , 2 , , , , . , Clion. , IAR toolchain , .

IAR 8.30.1, . : XNUCLEO-F411RE , ST-Link. , , Clion — , :)

image

IAR : IAR 8.30.1 , , github, , , FreeRtos.

ZY GitHub

Source: https://habr.com/ru/post/420467/


All Articles