C ++ - Wrapper für "alle" Echtzeitbetriebssysteme für CortexM4

Bild

Ich habe bereits im Artikel STM32, C ++ und FreeRTOS darüber gesprochen, wie Sie FreeRtos für in C ++ geschriebene Projekte verwenden können . Entwicklung von Grund auf neu. Teil 1 Seitdem, bis zu 3 Jahre vergangen sind, bin ich ernsthaft gealtert und habe eine Reihe neuronaler Verbindungen verloren. Deshalb habe ich beschlossen, die alten Zeiten aufzurütteln, um diese Verbindungen wiederherzustellen und den Wrapper nach "jedem" beliebten RTOS zu durchsuchen. Dies ist natürlich ein Witz, ich habe absichtlich "alle" in Anführungszeichen gesetzt, aber in jedem Witz steckt etwas Wahres.

Also, was ist die Aufgabe und warum ist sie überhaupt relevant? Im Moment sind in C eine Million verschiedener Betriebssysteme geschrieben - ich möchte nicht für jeden Geschmack wählen, bezahlt, kostenlos, klein, groß ... Aber für Projekte, an denen ich teilnehme, werden all diese Chips verschiedener Betriebssysteme nicht benötigt, grundlegende Funktionen wie eine Aufgabe reichen aus , Ereignisse, Aufgabenbenachrichtigung, kritischer Abschnitt, Mutexe und Semaphoren (obwohl ich versuche, sie nicht zu verwenden), Warteschlangen. Und das alles in einer ziemlich einfachen Form, ohne Schnickschnack.

Meiner Meinung nach ist der in C ++ geschriebene inländische OSRV MAX ideal für meine Projekte geeignet, und es ist eine Freude, ihn zu verwenden.

Der Haken ist jedoch, dass unsere Geräte der Norm IEC_61508 entsprechen müssen. Eine der Anforderungen ist die E.29-Anwendung einer bewährten Zielbibliothek . Nun, oder in einfachen Worten, wenn Sie ein Gerät herstellen, das die SIL3- Stufe erfüllt, verwenden Sie bitte (Höher empfohlen) Bibliotheken, die dieser Stufe entsprechen und erprobt sind.

In Bezug auf unsere Aufgabe bedeutet dies, dass es möglich ist, das MAX MAX RTOS für solche Geräte zu verwenden, aber Zuverlässigkeitspunkte werden nicht hinzugefügt. Daher stellen RTOS-Hersteller spezielle Versionen ihrer Betriebssysteme her, die den IEC_61508-Standards entsprechen. FreeRTOS verfügt beispielsweise über einen SafeRTOS- Klon und embOs über einen embOS-Safe- Klon. Hersteller verdienen damit natürlich sehr viel Geld, da Lizenzen für diese Betriebssysteme mehrere tausend oder sogar zehn kosten tausend Dollar.

Ein gutes Beispiel ist übrigens der IAR-Compiler, dessen Lizenz ungefähr 1.500 US-Dollar kostet, aber IAR-zertifizierte Versionen kosten bereits ungefähr 10.000 Dollar, obwohl ich mehrere Projekte überprüft habe - die Ausgabedatei der Version ohne Zertifikat und mit Zertifikat ist völlig identisch. Nun, Sie verstehen, dass Sie für den Frieden bezahlen müssen.

Also haben wir zuerst ein Betriebssystem verwendet , dann habe ich angefangen, FreeRTOS für meine Bedürfnisse zu verwenden, dann sind wir zu einem anderen gewechselt. Im Allgemeinen mussten wir den fertigen Code ständig neu schreiben. Außerdem möchte ich, dass es schön und einfach aussieht, damit jeder anhand des Codes verstehen kann, was passiert. Dann wird die Codeunterstützung für Studenten und Praktiker eine einfache Aufgabe sein, und die Gurus können weiterhin an innovativen Geräten arbeiten, anstatt den Stapel Nudeln zu verstehen . Im Allgemeinen möchte ich so etwas versteinert sehen:

Bild

Na ja oder so ...

Bild

Aus diesem Grund habe ich beschlossen, einen Wrapper zu schreiben, der sowohl zu FreeRTOS als auch zu embOS passt, also auch für alle anderen :). Zunächst habe ich festgestellt, was ich wirklich für ein vollständiges Glück brauche:

  • Die Aufgaben
  • Kritische Abschnitte
  • Ereignisse und Aufgabenbenachrichtigung
  • Semaphoren und Mutexe
  • Warteschlangen

Der Wrapper sollte SIL3- ideologisch sein, und diese Ebene enthält viele aller Arten von empfohlenen Dingen. Wenn Sie sie vollständig befolgen, stellt sich heraus, dass es besser ist, den Code überhaupt nicht zu schreiben.

Die Tatsache, dass der Standard eine Reihe von Regeln oder vielmehr Empfehlungen regelt, bedeutet jedoch nicht, dass diese nicht verletzt werden können - Sie können, aber Sie müssen so viele Empfehlungen wie möglich befolgen, um mehr Punkte zu erhalten. Daher habe ich mich für einige wichtige Einschränkungen entschieden:

  • Keine Makros , außer zum Schutz vor doppelter Aufnahme von Header-Dateien. Makros sind böse. Wenn Sie berechnen, wie viel Zeit für die Suche nach Fehlern im Zusammenhang mit Makros aufgewendet wurde, stellt sich heraus, dass das Universum nicht so alt ist und wie viel Gutes in dieser Zeit getan werden könnte. Wahrscheinlich ist es besser, sie auf gesetzlicher Ebene zu verbieten, da Torrents dies verbieten oder nehmen Sie einen Bonus für jedes Makro, das Sie schreiben
  • Verwenden Sie natürlich keine Zeiger , wann immer dies möglich ist. Man könnte versuchen, sie überhaupt nicht zu benutzen, aber es gibt immer noch Orte, an denen es ohne sie keinen Weg gibt. In jedem Fall sollte der Benutzer des Wrappers, wenn möglich, nicht einmal wissen, was ein Zeiger ist, da er nur von seinem Großvater davon gehört hat, da er jetzt ausschließlich mit Links arbeitet
  • Keine dynamische Speicherzuweisung verwenden - alles ist klar, nur die Verwendung eines Heaps führt zum einen dazu, dass RAM für diesen Heap reserviert werden muss, und zum anderen wird er bei häufiger Verwendung des Heaps defragmentiert und es werden immer länger neue Objekte darauf erstellt länger. Daher habe ich FreeRTOS tatsächlich nur für statisch zugewiesenen Speicher konfiguriert, indem ich configSUPPORT_STATIC_ALLOCATION 1 festgelegt habe . Aber wenn Sie im Standardmodus arbeiten möchten. Standardmäßig verwendet FreeRTOS dynamisch zugewiesenen Speicher, um Betriebssystemelemente zu erstellen. Setzen Sie dann einfach configSUPPORT_STATIC_ALLOCATION 0 und
    configSUPPORT_DYNAMIC_ALLOCATION 1 und vergessen Sie nicht, die Implementierung Ihrer eigenen Mallocs und Callocs über den Speichermanager zu verbinden. Diese Datei lautet beispielsweise FreeRtos / portable / MemMang / heap_1.c. Beachten Sie jedoch, dass Sie RAM mit einer Reserve für ein Bündel zuweisen müssen, da Sie nicht in der Lage sind, die genaue benötigte RAM-Menge mit allen Einstellungen zu berechnen (Leerlauf ist aktiviert, die Programm-Timer-Aufgabe ist aktiviert, meine beiden Aufgaben, Warteschlangen, Warteschlangengröße für Timer 10 und Nehmen wir an, es sind definitiv nicht die optimalsten Einstellungen, die funktioniert haben, als ich den Speicher wie folgt zugewiesen habe:
    7 357 Bytes schreibgeschützter Codespeicher
    535 Bytes schreibgeschützter Datenspeicher
    6.053 Bytes Readwrite-Datenspeicher

    Die statische Speicherzuordnung ist „etwas“ kompakter:
    7.329 Bytes schreibgeschützter Codespeicher
    535 Bytes schreibgeschützter Datenspeicher
    3.877 Bytes Readwrite-Datenspeicher

    Sie mögen denken, "großartig ... Sie selbst", aber jetzt interessiert uns die Frage nicht mehr, die im Artikel "Ich habe dem Betriebssystem bis zu 3 KB zugewiesen und nur 3 Aufgaben mit einem 128B-Stapel gestartet, und aus irgendeinem Grund ist bereits nicht genügend Speicher für den vierten vorhanden" . In dieser Situation habe ich es aus Gründen der Klarheit absichtlich gemacht, um den Unterschied zwischen dynamischer und statischer Speicherzuordnung mit denselben Einstellungen zu zeigen.
  • Gießen Sie nach Möglichkeit keine Typen . Ghosting-Typen für andere Typen an sich bedeuten, dass etwas im Design nicht stimmt. Manchmal müssen Sie es jedoch aus Bequemlichkeitsgründen noch umwandeln (z. B. muss Enum in Ganzzahlen umgewandelt werden), und manchmal können Sie nicht darauf verzichten dies, aber dies muss vermieden werden.
  • Einfachheit und Bequemlichkeit . Für den Benutzer des Wrappers müssen alle Schwierigkeiten verborgen sein, damit sein Leben kein Öl ist und er es noch nicht komplizieren möchte - er hat die Aufgabe erstellt, alles implementiert, was darin benötigt wird, sie gestartet und das Leben genießen lassen.

Wir werden damit beginnen, also haben wir uns die Aufgabe gestellt, eine Aufgabe zu erstellen (es stellte sich direkt aus der Reihe "Verboten zu verbieten" heraus).

Aufgabenerstellung


Nach langem Nachforschen haben britische Wissenschaftler ( Die ganze Wahrheit über RTOS von Colin Walls. Artikel Nr. 4. Aufgaben, Kontextwechsel und Unterbrechungen ) (übrigens, wenn Sie nicht wussten, wurde der Assembler für ARM auch von einem britischen Wissenschaftler erfunden, was mich auch nicht überraschte einmal :)), und so fanden die britischen Wissenschaftler heraus, dass die Aufgabe für die Mehrheit aller RTOS einen Namen , einen Stapel , eine Stapelgröße , eine „Steuereinheit“ , eine Kennung oder einen Zeiger auf eine „Steuereinheit“ , eine Priorität und eine Funktion hat, die in der Aufgabe ausgeführt wird . Das ist alles, und es war möglich, alles in eine Klasse zu packen, aber es war richtig, wenn wir mit Ihnen ein Betriebssystem geschrieben haben, aber wir machen einen Wrapper. Es macht also keinen Sinn, all diese Dinge in einem Wrapper zu speichern. All dies wird von SIL3 ideological OS für Sie erledigt wir wickeln ab. Tatsächlich benötigen wir nur eine Funktion, die in der Aufgabe ausgeführt wird, und eine Struktur, in der die „Steuereinheit“ gespeichert ist, die beim Erstellen der Aufgabe und der Aufgabenkennung ausgefüllt wird . Daher kann die Task-Klasse, nennen wir sie Thread, sehr einfach aussehen:

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

Ich möchte nur die Klasse meiner Aufgabe deklarieren, in der ich alles implementieren kann, was ich brauche, und dann den Zeiger auf das Objekt dieser Klasse an den Wrapper übergeben, der eine Aufgabe mithilfe der RTOS-API erstellt, in der die Execute () -Methode ausgeführt wird:

 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" RTOS muss zum Erstellen der Aufgabe ein Zeiger auf eine Funktion übergeben werden, die vom Scheduler gestartet wird. In unserem Fall ist dies die Execute () - Funktion, aber ich kann keinen Zeiger auf diese Methode übergeben, da sie nicht statisch ist. Daher sehen wir uns an, wie eine Aufgabe in der API "aller" Betriebssysteme erstellt wird, und stellen fest, dass wir eine Aufgabe erstellen können, indem wir einen Parameter an die Aufgabenfunktion übergeben, z. B. für 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 - Dies ist der Schlüssel zur Lösung. Lassen Sie uns eine statische Methode haben, einen Zeiger, auf den wir als Zeiger auf eine vom Scheduler aufgerufene Methode übergeben, und als Parameter einen Zeiger auf ein Objekt vom Typ Thread übertragen, wo wir die Execute () -Methode direkt aufrufen können. Dies ist genau der Moment, in dem es keinen Weg ohne Zeiger und Umwandlung in Typen gibt, dieser Code jedoch vor dem Benutzer verborgen bleibt:

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

Das heißt, Bei einem solchen Operationsalgorithmus startet der Scheduler die Run- Methode, ein Zeiger auf ein Objekt vom Typ Thread wird an die Run- Methode übergeben. Die Run- Methode ruft direkt die Execute () -Methode auf, ein bestimmtes Objekt der Thread- Klasse, die nur unsere Implementierung der Aufgabe ist.

Das Problem ist fast gelöst, jetzt müssen wir die Methoden implementieren. Alle Betriebssysteme verfügen über unterschiedliche APIs. Um beispielsweise die Funktion zum Erstellen von Aufgaben für embOS zu implementieren, müssen Sie die Methode void OS_TASK_CreateEx (..) aufrufen. Für FreeRTOS im dynamischen Speicherzuweisungsmodus ist dies xTaskCreate (..). Obwohl sie ein und dieselbe Essenz haben gleich, aber die Syntax und Parameter sind unterschiedlich. Wir möchten jedoch nicht jedes Mal die Dateien durchgehen und Code für jede der Methoden der Klasse für ein neues Betriebssystem schreiben. Daher müssen wir dies irgendwie in eine Datei packen und ... in Form von Makros ausführen. Großartig, aber hör auf, ich habe mir die Makros verboten - ich brauche einen anderen Ansatz.

Am einfachsten fiel mir ein, für jedes Betriebssystem eine eigene Datei mit Inline-Funktionen zu erstellen. Wenn wir ein anderes Betriebssystem verwenden möchten, müssen wir nur jede dieser Funktionen mithilfe der API dieses Betriebssystems implementieren. Die folgende Datei rtosFreeRtos.cpp hat sich herausgestellt

 #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 } 

Die Datei für embOS rtosEmbOS.cpp sieht möglicherweise genauso aus

 #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) ; } } 

Die Typen verschiedener Betriebssysteme sind ebenfalls unterschiedlich, insbesondere die Struktur des Aufgabenkontexts. Erstellen wir daher die Datei rtosdefs.hpp mit unseren eigenen Wrapper-Aliasen.

 #include <FreeRTOS.h> //For TaskHandle_t namespace OsWrapper { using tTaskContext = StaticTask_t; using tTaskHandle = TaskHandle_t; using tStack = StackType_t ; } 

Für EmbOS könnte es so aussehen:

 #include <rtos.h> //For OS_TASK namespace OsWrapper { using tTaskContext = OS_TASK; using tTaskHandle = OS_TASK; using tStack = tU16 //   void,      tU16 ; } 

Daher ist es für Änderungen unter anderen RTOS ausreichend, Änderungen nur in diesen beiden Dateien rtosdefs.cpp und rtos.cpp vorzunehmen. Jetzt sehen Thread- und Rtos- Klassen wie c-Bilder aus

Bild

Starten von Betriebssystemen und Abschließen der Aufgabe


Für Cortex M4 verwenden "alle" Betriebssysteme 3 Interrupts, einen System-Tick-Timer , einen Systemdienst-Aufruf über eine SWI-Anweisung und eine anhängbare Anforderung für Systemdienst-Interrupts , die hauptsächlich für das RTOS erfunden wurden. Einige RTOS verwenden auch andere Systeminterrupts, diese reichen jedoch für die meisten "alle" Betriebssysteme aus. Wenn nicht, können Sie hinzufügen. Definieren Sie einfach drei Handler für diese Interrupts. Um das RTOS zu starten, benötigen Sie eine andere Startmethode:

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

Das erste, was ich brauchte und ohne das ich nicht leben kann, ist ein Benachrichtigungsmechanismus für Aufgaben. Im Allgemeinen mag ich ereignisgesteuerte Programmierung, daher muss ich schnell einen Wrapper implementieren, um Aufgaben zu benachrichtigen.

Alles stellte sich als recht einfach heraus, jedes Betriebssystem kann dies, außer vielleicht uc-OS-II und III , obwohl ich es vielleicht nicht gut gelesen habe, aber meiner Meinung nach ist der Mechanismus der Ereignisse dort im Allgemeinen schwierig, aber na ja , "alles" ist der Rest sie können es auf jeden Fall.

Um eine Aufgabe zu benachrichtigen, müssen Sie das Ereignis nur nicht an die Leere, sondern speziell an die Aufgabe senden. Dazu sollte die Benachrichtigungsmethode einen Zeiger auf den Aufgabenkontext oder die Aufgabenkennung haben. Ich speichere diese nur in der Thread- Klasse, was bedeutet, dass die Thread-Klasse auch eine Alert-Methode haben sollte. Es sollte auch eine Methode zum Warten auf eine Warnung geben. Gleichzeitig fügen wir die Sleep (..) -Methode hinzu, mit der die Ausführung der aufrufenden Task angehalten wird. Jetzt sehen beide Klassen so aus:

Bild

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 


Ich habe angefangen, es zu implementieren, und hier wartete das erste Problem auf mich. Es stellte sich heraus, dass „jedes“ Betriebssystem seine Funktionen von Interrupts auf unterschiedliche Weise aufruft. FreeRTOS verfügt beispielsweise über spezielle Implementierungen von Funktionen, um sie aus Interrupts auszuführen. Wenn beispielsweise eine xTaskNotify (..) -Funktion vorhanden ist, können Sie diese nicht über einen Interrupt aufrufen, sondern müssen xTaskNotifyFromISR (..) aufrufen.
Wenn Sie für embOS eine Funktion von einem Interrupt aus aufrufen, verwenden Sie beim Eingeben eines Interrupts OS_InInterrupt () und beim Beenden OS_LeaveInterrupt () . Ich musste eine InterruptEntry- Klasse erstellen , die nur einen Konstruktor und einen Destruktor hat:

 namespace OsWrapper { extern void wEnterInterrupt() ; extern void wLeaveInterrupt() ; class InterruptEntry { public: inline InterruptEntry() { wEnterInterrupt() ; } inline ~InterruptEntry() { wLeaveInterrupt() ; } } ; } ; 

Sie können es so verwenden:

 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) ; } } } ; 

Bei FreeRTOS sind natürlich sowohl der Konstruktor als auch der Destruktor leer. Und für die Benachrichtigung können Sie die Funktion xTaskNotifyFromISR (..) verwenden, die unabhängig davon, von wo sie aufgerufen wird, ein wenig Aufwand bedeutet, aber aus Gründen der Universalität nicht möglich ist. Sie können natürlich separate Methoden zum Aufrufen von Interrupts erstellen, aber im Moment habe ich beschlossen, dies nur universell zu tun.
Der gleiche Trick wie bei InterruptEntry kann mit dem kritischen Abschnitt ausgeführt werden:

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

Fügen Sie nun einfach die Implementierung von Funktionen mithilfe der FreeRtos- API zur Datei hinzu und führen Sie die Prüfung aus, obwohl Sie sie nicht ausführen konnten. Es ist also klar, dass sie funktionieren wird :)
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(); } } 


Bild

Wir verfeinern die Aufgabe weiter


Die Aufgabe hat jetzt fast alles, was Sie brauchen, wir haben die Sleep () -Methode hinzugefügt. Diese Methode pausiert die Aufgabe für eine bestimmte Zeit. In den meisten Fällen reicht dies aus, aber wenn Sie eine klar festgelegte Zeit benötigen, kann Sleep () Ihnen Probleme bereiten. Sie möchten beispielsweise eine Berechnung durchführen, die LED blinken lassen und dies genau alle 100 ms tun

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

Dieser Code blinkt alle 110 ms eine LED. Wenn Sie jedoch alle 100 ms einmal möchten, können Sie die Berechnungszeit grob berechnen und den Ruhezustand (90 ms) festlegen. Was aber, wenn die Berechnungszeit von den Eingabeparametern abhängt, ist das Blinken überhaupt nicht deterministisch. Für solche Fälle gibt es in "allen" Betriebssystemen spezielle Methoden wie DelayUntil (). Es funktioniert nach diesem Prinzip: Zuerst müssen Sie sich den aktuellen Wert des Tick-Zählers des Betriebssystems merken und dann zu diesem Wert die Anzahl der Ticks hinzufügen, für die Sie die Aufgabe anhalten müssen. Sobald der Tick-Zähler diesen Wert erreicht, wird die Aufgabe entsperrt. Somit wird die Aufgabe genau auf den von Ihnen eingestellten Wert festgelegt und Ihre LED blinkt unabhängig von der Dauer der Berechnung genau alle 100 ms.
Dieser Mechanismus ist in verschiedenen Betriebssystemen unterschiedlich implementiert, verfügt jedoch über einen Algorithmus. Infolgedessen wird der beispielsweise auf FreeRTOS implementierte Mechanismus auf den im folgenden Bild gezeigten Zustand vereinfacht:

Bild

Wie Sie sehen können, erfolgt das Lesen des Anfangszustands des Tick-Zählers des Betriebssystems vor dem Eintritt in eine Endlosschleife, und wir müssen uns etwas einfallen lassen, um dies zu implementieren. Eine Entwurfsvorlage hilft . Template-Methode . , , , , 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() , . :
Bild

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 



Ereignisse


, , , , , , . , .

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



:

 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) ; } } } ; 


,


, , : GitHub OsWrapper . , :
 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); } } ; 



, , , : 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; } 


Fazit


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

, , ++ :)

Clion . , , IAR toolchain, , , elf , hex , , GDB. — , , , 2 , , , , . , Clion. , IAR toolchain , .

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

Bild

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

Z.Y. GitHub

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


All Articles