
我已经在文章
STM32,C ++和FreeRTOS中讨论了如何对用C ++编写的项目使用FreeRtos
。 从头开始发展。 第一部分 从那以后,长达3年的时间,我严重衰老,失去了一堆神经连接,所以我决定改变过去,以恢复这些连接并扫除“任何”流行的RTOS的包装。 我当然是在开玩笑,我故意将“所有人”都用引号引起来,但每个玩笑中都有一些道理。
那么,任务是什么?为什么它与之相关? 目前有上百万种用C语言编写的操作系统-我不想为每种口味选择付费,免费,小型,大型……但是对于我参与的项目,不需要所有操作系统的所有这些芯片,诸如任务之类的基本功能就足够了,事件,任务通知,关键部分,互斥锁和信号灯(尽管我尽量不使用它们),队列。 所有这些都需要以一种非常简单的形式进行,并且没有任何多余的装饰。
我认为,用C ++编写的家用
OSRV MAX非常适合我的项目,使用它是一种乐趣。
但是要注意的是,我们的设备必须符合IEC_61508标准,其中一项要求是
经过验证的目标库的
E.29应用 。 好吧,或者简单地说,如果您制造的设备符合
SIL3级别,那么(建议更高)请使用与该级别相对应且经过时间测试的库。
关于我们的任务,这意味着可以将
MAX MAX RTOS用于此类设备,但不会增加可靠性点。 因此,RTOS制造商会制造符合IEC_61508标准的特殊版本的操作系统,例如,
FreeRTOS具有
SafeRTOS克隆,而
embOs具有
embOS-Safe克隆,制造商当然可以从中赚钱,因为这些OS的许可证需要花费数千甚至数十美元。一千美元。
顺便说一句,一个很好的例子是IAR编译器,其许可证的价格约为1,500美元,但IAR认证的版本已经耗资约10,000美元,尽管我检查了多个项目-没有证书且带有证书的版本的输出文件完全相同。 好吧,您了解您需要为和平付出代价。
因此,首先我们使用
一个操作系统 ,然后根据需要开始使用
FreeRTOS ,然后切换到
另一个 操作系统 ,通常,我们经常不得不重写完成的代码。 另外,我希望它看起来美丽而简单,以便任何人都可以通过代码来理解正在发生的事情,然后代码支持对于学生和从业者来说将是一件简单的工作,大师将能够继续在创新设备上工作,而不是去理解面条。 总的来说,我希望看到这样的僵化现象:

好吧,这样的...

因此,我决定写一个适合
FreeRTOS的包装,并说
embOS,也适用于其他所有人:),一开始,我就确定了要获得完全幸福所需的东西:
- 任务
- 关键部分
- 事件和任务通知
- 信号量和互斥量
- Queue列
包装程序应该是
SIL3意识形态的,并且此级别强加了许多“高度推荐”的东西,如果您完全遵循它们,那么最好不要完全编写代码。
但是,该标准管理着许多规则或建议,这一事实并不意味着它们不能被违反-您可以,但是您需要遵循尽可能多的建议才能获得更多积分。 因此,我决定了一些重要的限制:
- 没有宏 ,除了防止头文件重复包含的保护之外。 宏是邪恶的,如果您计算花费了多少时间来搜索与宏相关的错误,那么事实证明宇宙并不那么古老,并且在这段时间内可以做多少事情,最好是在立法层面上禁止它们,因为洪流禁止或为您编写的每个宏取加分
- 当然不要使用指针 。 可以尝试一点都不使用它们,但是仍然有些地方没有它们是没有办法的。 无论如何,包装器的用户(如果可能的话)甚至都不应该知道指针是什么,因为他只是从祖父那里听说过指针,因为现在他只使用链接
- 不使用动态内存分配 -一切都清楚了,仅使用堆会导致首先需要为此堆保留RAM,其次,由于频繁使用堆,会对其进行碎片整理并在其上创建新对象的时间越来越长更长。 因此, 实际上,我通过设置configSUPPORT_STATIC_ALLOCATION 1仅在静态分配的内存上配置了FreeRTOS 。 但是如果要在默认模式下工作。 默认情况下, FreeRTOS使用动态分配的内存来创建OS元素;然后只需设置configSUPPORT_STATIC_ALLOCATION 0 ,然后
configSUPPORT_DYNAMIC_ALLOCATION 1,并且不要忘记从内存管理器连接您自己的Malloc和Calloc的实现,例如,此文件为FreeRtos / Portable / MemMang / heap_1.c。 但请记住,您将不得不为一堆预留空间分配RAM,因为您将无法使用所有设置来计算所需的确切RAM数量(空闲处于启用状态,程序计时器任务处于启用状态,我的两个任务,队列,计时器10和10的队列大小)如此说来,当我这样分配内存时,肯定不是最佳设置):
7357字节的只读代码存储器
535字节的只读数据存储器
6,053字节的读写数据存储器
静态内存分配“紧凑”一些:
7,329字节的只读代码存储器
535字节的只读数据存储器
3,877字节的读写数据存储器
您可能会想,“太棒了……您自己,”,但是现在我们对文章“我为操作系统分配了多达3KB的内存,并仅通过128B堆栈启动了3个任务,并且由于某种原因,第四个内存已经不足”一文中的问题不感兴趣,在这种情况下,为清楚起见,我故意这样做是为了显示具有相同设置的动态和静态内存分配之间的区别。
- 如果可能, 不要转换类型 。 将类型虚拟化为其他类型本身意味着在设计中出现了一些问题,但是像往常一样,有时您仍必须强制转换以易于使用(例如,必须将枚举转换为整数),并且有时您不能这个,但是必须避免。
- 简单方便 。 对于包装纸的使用者来说,所有的困难都必须隐藏起来,所以他的生活还不是油,他还不想使它变得复杂-他创建了任务,执行了任务中需要的一切,开始了任务并享受生活。
我们将从此开始,因此我们为自己设定了创建任务的任务(原来是从“禁止”系列中得出的结果)。
任务创建
通过长期的研究,英国科学家(
有关Coin Walls的RTOS的全部真相。第4条。任务,上下文切换和中断 )(顺便说一句,如果您不知道,ARM的汇编器也是由英国科学家发明的,我对此也不感到惊讶。一次:)),因此英国科学家发现,对于大多数“所有” RTOS,该任务都有一个
名称 ,
堆栈 ,
堆栈大小 ,
“控制单元” ,
标识符或指向“控制单元”的指针 ,
优先级 ,
该任务中执行的
功能 。 仅此而已,可以将其全部塞入一个类中,但是如果我们与您一起编写一个OS,但是我们进行包装,那是正确的,因此将所有这些内容存储在包装中是没有意义的,这一切将由
SIL3意识形态的OS为您
完成 。我们总结。 实际上,我们只需要
在任务中执行的
功能和
存储“控制单元”的
结构,该
结构在创建任务和
任务标识符时就会填写。 因此,任务类(我们称之为
Thread)看起来非常简单:
class Thread { public: virtual void Execute() = 0 ; private: tTaskHandle taskHandle ; tTaskContext context ; } ;
我只想声明我的任务类,在其中可以实现所需的所有内容,然后将指向此类对象的指针传递给包装器,包装器将使用RTOS API创建任务,并在其中启动
Execute()方法:
class MyTask : public Thread { public: virtual void Execute() override { while(true) {
在“所有” RTOS中,为了创建任务,必须将指针传递给将由调度程序启动的功能。 在我们的例子中,这是
Execute()函数,但是由于它不是静态的,因此我无法将指针传递给该方法。 因此,我们研究了如何在“所有”操作系统的API中创建任务,并注意到我们可以通过将参数传递给任务函数来创建任务,例如,对于
embOS :
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-这是解决方案的关键。 让我们有一个静态方法,一个指针将作为指针传递给调度程序调用的方法,而作为参数,我们将指针传递给
Thread类型的对象,在该对象中我们可以直接调用
Execute()方法。 这恰好是没有指针并强制转换为类型的那一刻,但此代码将对用户隐藏:
static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Execute() ; }
即 使用这种操作算法,调度程序将启动
Run方法,将指向
Thread类型的对象的指针传递给
Run方法。
Run方法直接调用
Execute()方法,这是
Thread类的特定对象,这只是我们对任务的实现。
问题几乎解决了,现在我们需要实现这些方法。 所有操作系统都有不同的API,因此要实现例如
embOS的任务创建功能
,必须调用void
OS_TASK_CreateEx(..)方法;对于动态内存分配模式下的
FreeRTOS ,这是
xTaskCreate(..) ,尽管它们具有相同的本质相同,但是语法和参数不同。 但是我们不想遍历文件并为新操作系统每次都为类的每个方法编写代码,因此我们需要以某种方式将其放入一个文件并以宏的形式执行。 很好,但是请停下来,我禁止自己使用宏-我需要一种不同的方法。
对我而言,最简单的事情是使用内联功能为每个OS创建一个单独的文件。 如果要使用任何其他操作系统,则只需要使用此操作系统的API来实现所有这些功能。 原来以下
rtosFreeRtos.cpp文件
#include "rtos.hpp"
embOS
rtosEmbOS.cpp的文件可能看起来完全
一样 #include "rtos.hpp"
不同操作系统的类型也不同,尤其是任务上下文的结构,因此让我们使用自己的包装程序别名创建rtosdefs.hpp文件。
#include <FreeRTOS.h> //For TaskHandle_t namespace OsWrapper { using tTaskContext = StaticTask_t; using tTaskHandle = TaskHandle_t; using tStack = StackType_t ; }
对于
EmbOS,它可能如下所示:
#include <rtos.h> //For OS_TASK namespace OsWrapper { using tTaskContext = OS_TASK; using tTaskHandle = OS_TASK; using tStack = tU16 // void, tU16 ; }
因此,要在其他任何RTOS下进行更改,仅在这两个文件rtosdefs.cpp和rtos.cpp中进行更改就足够了。 现在
Thread和
Rtos类看起来像c图片

启动操作系统并完成任务
对于Cortex M4,“所有”操作系统都使用3种中断,即
系统滴答计时器 ,
通过SWI指令进行系统服务调用 ,
对系统服务中断的可挂起请求 ,这主要是为RTOS发明的。 某些RTOS也使用其他系统中断,但对于大多数“所有”操作系统而言,这些中断就足够了。 如果没有,则可以添加,因此只需为这些中断定义三个处理程序并启动RTOS,我们需要另一个启动方法:
static void HandleSvcInterrupt() ; static void HandleSvInterrupt() ; static void HandleSysTickInterrupt() ; static void Start() ;
我梦and以求的事情是,我需要做的第一件事是没有任务的通知机制。 我通常喜欢事件驱动的编程,因此我需要快速实现包装器来通知任务。
事实证明一切都非常简单,除了
uc-OS-II和
III之外,任何操作系统都可以做到,虽然我可能读得不好,但是我认为事件的机制通常很棘手,但是,“所有”都是剩下的他们当然可以。
为了通知任务,您只需要将事件发送到不是空的,而是发送给任务,为此,通知方法应该具有指向任务上下文或任务标识符的指针。 我只是将它们存储在
Thread类中,这意味着Thread类也应该有一个alert方法。 还应该有一种等待警报的方法。 同时,我们添加了
Sleep(..)方法,该方法暂停了调用任务的执行。 现在两个类都看起来像这样:

rtos.hpp #ifndef __RTOS_HPP #define __RTOS_HPP #include "thread.hpp"
线程 #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
我开始实现它,这里的第一个麻烦就在等我,事实证明,“任何”操作系统都以不同的方式从中断中调用其功能。 例如,
FreeRTOS具有用于从中断执行它们的函数的特殊实现,例如,如果存在
xTaskNotify(..)函数,则无法从中断调用它,但需要调用
xTaskNotifyFromISR(..) 。
对于
embOS,如果从中断调用任何函数,请在输入中断时使用
OS_InInterrupt() ,在退出时使用
OS_LeaveInterrupt() 。 我必须制作一个
InterruptEntry类,它只有一个构造函数和一个析构函数:
namespace OsWrapper { extern void wEnterInterrupt() ; extern void wLeaveInterrupt() ; class InterruptEntry { public: inline InterruptEntry() { wEnterInterrupt() ; } inline ~InterruptEntry() { wLeaveInterrupt() ; } } ; } ;
您可以像这样使用它:
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) ; } } } ;
显然,对于
FreeRTOS,构造函数和析构函数都是空的。 为了进行通知,您可以使用
xTaskNotifyFromISR(..)函数,无论从何处调用它都有点
麻烦 ,但是为了通用起见,您不会使用它。 您当然可以创建用于从中断进行调用的单独方法,但是到目前为止,我决定只进行通用处理。
关键部分可以完成与
InterruptEntry相同的技巧:
namespace OsWrapper{ class CriticalSection { public: inline CriticalSection() { wEnterCriticalSection() ; } inline ~CriticalSection() { wLeaveCriticalSection() ; } } ; } ;
现在,只需将使用
FreeRtos API的功能实现添加到文件中,然后运行检查即可,尽管您无法运行它,因此很明显它可以工作:)
rtosFreeRtos.cpp #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(); } }

我们继续完善任务
现在,该任务几乎具有您所需的一切,我们添加了Sleep()方法。 此方法将任务暂停指定的时间。 在大多数情况下,这就足够了,但是如果您需要明确确定的时间,那么Sleep()可能会给您带来问题。 例如,您要执行一些计算并使LED闪烁,然后每100 ms精确地执行一次
void MyTask::Execute() { while(true) { DoCalculation();
该代码每110毫秒使LED闪烁一次。 但是,您希望每100ms一次,就可以粗略计算出计算时间,然后将Sleep(睡眠时间)设置为90ms。 但是,如果计算时间取决于输入参数,那么闪烁将完全无法确定。 对于“所有”操作系统中的此类情况,有一些特殊的方法,例如DelayUntil()。 它遵循此原则-首先您需要记住操作系统的滴答计数器的当前值,然后将您需要暂停任务的滴答数添加到此值,一旦滴答计数器达到该值,任务便被解锁。 因此,无论计算时间长短,任务都将完全锁定为您设置的值,并且LED将每100ms精确闪烁一次。
在不同的操作系统中,此机制的实现方式有所不同,但是只有一种算法。 结果,例如在FreeRTOS上实现的机制将简化为下图所示的状态:

如您所见,操作系统的滴答计数器的初始状态的读取发生在进入无限循环之前,因此我们需要想出一些办法来实现这一点。 设计
模板可以解决。 , , , , 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() ; }
SleepUntil() , . , , ,
SleepUntil() , . :

thread.hpp #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
rtos.hpp #ifndef __RTOS_HPP #define __RTOS_HPP #include "thread.hpp"
, , , , , , . , .
, , , , , , , . , . , , , , .

:
OsWrapper::Event event{10000ms, 3};
,
最初,当我写这篇文章时,我还没有实现它们,但是正如我保证我会开发互斥锁和电子邮件框一样,源代码在这里:GitHub OsWrapper。使用邮箱的示例如下: OsWrapper::MailBox<tU32, 10> queue;
如何使用这一切
, , , :
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;
结论
. , ++ ++ . ++.
, ++, ,
, , , , , . , .
但是与此同时,我们大多数人都使用传统的Sishna操作系统,您可以将包装器作为使用C ++过渡到美好未来的最初起点:)我在Clion中整理了一个小型测试项目 . , , IAR toolchain, , , elf , hex , , GDB. — , , , 2 , , , , . , Clion. , IAR toolchain , .
IAR 8.30.1, . :
XNUCLEO-F411RE , ST-Link. , ,
Clion — , :)
IAR :
IAR 8.30.1 , , github, , , FreeRtos.
.. GitHub