Enveloppeur C ++ pour "tous" les systèmes d'exploitation en temps réel pour CortexM4

image

J'ai déjà expliqué comment utiliser FreeRtos pour des projets écrits en C ++ dans l'article STM32, C ++ et FreeRTOS. Développement à partir de zéro. Partie 1 Depuis lors, pas moins de 3 ans se sont écoulés, j'ai sérieusement vieilli, perdu un tas de connexions neuronales, j'ai donc décidé de secouer les vieux jours afin de rétablir ces connexions et de balayer le wrapper pour "n'importe quel" RTOS populaire. C'est bien sûr une blague, j'ai délibérément mis «tout le monde» entre guillemets, mais il y a du vrai dans chaque blague.

Alors, quelle est la tâche et pourquoi est-elle pertinente du tout? Pour le moment, il y a un million de systèmes d'exploitation différents écrits en C - je ne veux pas choisir pour tous les goûts, payé, gratuit, petit, grand ... Mais pour les projets auxquels je participe, toutes ces puces de différents systèmes d'exploitation ne sont pas nécessaires, les fonctionnalités de base telles qu'une tâche suffisent , événements, notification de tâche, section critique, mutex et sémaphores (bien que j'essaie de ne pas les utiliser), files d'attente. Et tout cela est nécessaire sous une forme assez simple, sans fioritures.

À mon avis, l' OSRV MAX domestique, écrit en C ++, convient parfaitement à mes projets et c'est un plaisir de l'utiliser.

Mais le hic, c'est que nos appareils doivent être conformes à la norme IEC_61508, dont l'une des exigences est l' application E.29 de bibliothèque cible éprouvée . Eh bien, ou en termes simples, si vous fabriquez un appareil pour atteindre le niveau SIL3 , veuillez (supérieur recommandé) utiliser des bibliothèques qui correspondent à ce niveau et sont testées dans le temps.

En ce qui concerne notre tâche, cela signifie qu'il est possible d'utiliser le MAX MAX RTOS pour de tels appareils, mais aucun point de fiabilité ne sera ajouté. Par conséquent, les fabricants de RTOS fabriquent des versions spéciales de leurs systèmes d'exploitation qui sont conformes aux normes CEI_61508, par exemple, FreeRTOS a un clone SafeRTOS et embOs a un clone embOS-Safe , bien sûr, les fabricants gagnent beaucoup d'argent à ce sujet, car les licences pour ces systèmes d'exploitation coûtent plusieurs milliers, voire des dizaines mille dollars.

À propos, un bon exemple est le compilateur IAR, dont la licence coûte environ 1500 $, mais les versions certifiées IAR coûtent déjà environ 10000 dollars, bien que j'aie vérifié plusieurs projets - le fichier de sortie de la version sans certificat et avec certificat est complètement identique. Eh bien, vous comprenez que vous devez payer pour la paix.

Donc, nous avons d'abord utilisé un système d'exploitation , puis j'ai commencé à utiliser FreeRTOS pour mes besoins, puis nous sommes passés à un autre , en général, nous avons constamment dû réécrire le code fini. De plus, je voudrais que ce soit beau et simple, afin que tout le monde puisse comprendre par le code ce qui se passe, alors le support du code sera un travail simple pour les étudiants et les praticiens, et les gourous pourront continuer à travailler sur des appareils innovants, plutôt que de comprendre la pile de nouilles . En général, je veux voir quelque chose comme ça fossilisé:

image

Eh bien, ou tel ...

image

Par conséquent, j'ai décidé d'écrire un wrapper qui conviendrait aux deux FreeRTOS , et de dire embOS, eh bien, pour tout le monde aussi :) et pour commencer j'ai déterminé ce dont j'avais besoin pour un bonheur complet:

  • Les tâches
  • Sections critiques
  • ÉvĂ©nements et notification de tâche
  • SĂ©maphores et mutex
  • Files d'attente

L'encapsuleur doit être idéologique SIL3 , et ce niveau impose beaucoup de toutes sortes de choses High Recommended, et si vous les suivez complètement, il s'avère qu'il vaut mieux ne pas écrire du code du tout.

Mais le fait que la norme régisse un tas de règles, ou plutôt de recommandations, ne signifie pas qu'elles ne peuvent pas être violées - vous pouvez, mais vous devez suivre autant de recommandations que possible pour obtenir plus de points. Par conséquent, j'ai décidé de certaines limitations importantes:

  • pas de macros , enfin, sauf pour la protection contre la double inclusion des fichiers d'en-tĂŞte. Les macros sont mauvaises, si vous calculez le temps passĂ© Ă  rechercher des erreurs liĂ©es aux macros, il s'avère que l'univers n'est pas si vieux et combien de bien pourrait ĂŞtre fait pendant cette pĂ©riode, il vaut probablement mieux les interdire au niveau lĂ©gislatif, comme les torrents l'ont interdit ou prenez un bonus pour chaque macro que vous Ă©crivez
  • n'utilisez pas de pointeurs , bien sĂ»r autant que possible. On pourrait essayer de ne pas les utiliser du tout, mais il y a encore des endroits oĂą sans eux, il n'y a aucun moyen. Dans tous les cas, l'utilisateur du wrapper, si possible, ne devrait mĂŞme pas savoir ce qu'est un pointeur, car il n'en a entendu parler que par son grand-père, car maintenant il travaille exclusivement avec des liens
  • ne pas utiliser l'allocation dynamique de mĂ©moire - tout est clair, le simple fait d'utiliser un tas conduit, d'une part, Ă  la nĂ©cessitĂ© de rĂ©server de la RAM pour ce tas, et d'autre part, avec une utilisation frĂ©quente du tas, il est dĂ©fragmentĂ© et de nouveaux objets y sont créés de plus en plus longtemps plus. Par consĂ©quent, en fait, j'ai configurĂ© FreeRTOS uniquement sur la mĂ©moire allouĂ©e statiquement en dĂ©finissant configSUPPORT_STATIC_ALLOCATION 1 . Mais si vous voulez travailler en mode par dĂ©faut. Et par dĂ©faut, FreeRTOS utilise de la mĂ©moire allouĂ©e dynamiquement pour crĂ©er des Ă©lĂ©ments de système d'exploitation; puis dĂ©finissez simplement configSUPPORT_STATIC_ALLOCATION 0 , et
    configSUPPORT_DYNAMIC_ALLOCATION 1 et n'oubliez pas de connecter l'implémentation de vos propres Mallocs et Callocs à partir du gestionnaire de mémoire, par exemple, ce fichier est FreeRtos / portable / MemMang / heap_1.c. Mais gardez à l'esprit que vous devrez allouer de la RAM avec une réserve pour un groupe, car vous ne pourrez pas calculer la quantité exacte de RAM nécessaire, avec tous les paramètres (inactif est activé, la tâche du minuteur du programme est activée, mes deux tâches, les files d'attente, la taille de la file d'attente pour les minuteurs 10 et ainsi de suite, disons que ce ne sont certainement pas les paramètres les plus optimaux) qui ont fonctionné lorsque j'ai alloué la mémoire comme ceci:
    7 357 octets de mémoire de code en lecture seule
    535 octets de mémoire de données en lecture seule
    6 053 octets de mémoire de données en lecture-écriture

    L'allocation de mémoire statique est «un peu» plus compacte:
    7 329 octets de mémoire de code en lecture seule
    535 octets de mémoire de données en lecture seule
    3 877 octets de mémoire de données en lecture-écriture

    Vous pourriez penser, "génial ... vous-même", mais maintenant nous ne sommes pas intéressés par la question formulée dans l'article "J'ai alloué jusqu'à 3 Ko au système d'exploitation et lancé seulement 3 tâches avec une pile de 128 Go, et pour une raison quelconque, il n'y a déjà pas assez de mémoire pour le quatrième" , Dans cette situation, je l'ai fait exprès, pour plus de clarté, pour montrer la différence entre l'allocation de mémoire dynamique et statique avec les mêmes paramètres.
  • si possible, ne lancez pas de types . Les types fantĂ´mes en d'autres types en eux-mĂŞmes signifient le fait que quelque chose ne va pas dans la conception, mais comme d'habitude, parfois vous devez toujours le convertir pour plus de commoditĂ© (par exemple, l'Ă©numĂ©ration doit ĂŞtre castĂ©e en nombres entiers), et parfois vous ne pouvez pas vous en passer cela, mais cela doit ĂŞtre Ă©vitĂ©.
  • simplicitĂ© et commoditĂ© . Pour l'utilisateur de l'emballage, toutes les difficultĂ©s doivent ĂŞtre cachĂ©es, donc sa vie n'est pas de l'huile, et il ne veut pas encore la compliquer - il a créé la tâche, mis en Ĺ“uvre tout ce qui est nĂ©cessaire, dĂ©marrĂ© et parti pour profiter de la vie.

Nous allons partir de là, donc nous nous sommes donné pour tâche de créer une tâche (il s’est avéré être directement issu de la série «interdit d’interdire»).

Création de tâche


Par de longues recherches, des scientifiques britanniques ( Toute la vérité sur RTOS de Colin Walls. Article # 4. Tâches, changement de contexte et interruptions ) (à propos, si vous ne le saviez pas, l'assembleur pour ARM a également été inventé par un scientifique britannique, quelque chose que je n'ai pas été surpris non plus une fois :)), et ainsi les scientifiques britanniques ont découvert que pour la majorité de "tous" les RTOS, la tâche a un nom , une pile , une taille de pile , une "unité de contrôle" , un identifiant ou un pointeur vers une "unité de contrôle" , une priorité , une fonction qui est exécutée dans la tâche . C'est tout, et il était possible de tout ranger dans une classe, mais c'était bien si nous écrivions un système d'exploitation avec vous, mais nous faisons un wrapper, donc cela n'a aucun sens de stocker toutes ces choses dans un wrapper, tout cela sera fait pour vous par le système d' exploitation idéologique SIL3 nous terminons. En fait, nous avons seulement besoin d'une fonction qui est exécutée dans la tâche et d'une structure qui stocke «l'unité de contrôle» , qui est remplie lors de la création de la tâche et de l'identifiant de la tâche . Par conséquent, la classe de tâches, appelons-la Thread, peut sembler très simple:

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

Je veux simplement déclarer la classe de ma tâche où je pourrais implémenter tout ce qui est nécessaire, puis passer le pointeur vers l'objet de cette classe à l'encapsuleur, ce qui créerait une tâche à l'aide de l'API RTOS où il lancerait la méthode Execute () :

 class MyTask : public Thread { public: virtual void Execute() override { while(true) { //do something.. } } ; using tMyTaskStack = std::array<OsWrapper::tStack, static_cast<tU16>(OsWrapper::StackDepth::minimal)> ; inline static tMyTaskStack Stack; //!C++17 } ; MyTask myDesiredTask int main() { Rtos::CreateThread(myTask, MyTask::Stack.data(), "myTask") ; } 

Dans "tous" RTOS, pour que la tâche soit créée, il faut passer un pointeur vers une fonction qui sera lancée par le planificateur. Dans notre cas, il s'agit de la fonction Execute () , mais je ne peux pas passer de pointeur vers cette méthode, car elle n'est pas statique. Par conséquent, nous regardons comment une tâche est créée dans l'API de "tous" les OS et notons que nous pouvons créer une tâche en passant un paramètre à la fonction de tâche, par exemple, pour embOS ceci:

 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 - c'est la clé de la solution. Ayons une méthode statique, un pointeur vers lequel nous passerons comme pointeur vers une méthode appelée par l'ordonnanceur, et comme paramètre nous passerons un pointeur vers un objet de type Thread où nous pourrons appeler directement la méthode Execute () . C'est exactement le moment où il n'y a aucun moyen sans pointeur et transtypé en types, mais ce code sera caché à l'utilisateur:

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

C'est-à-dire un tel algorithme d'opération, le planificateur lance la méthode Run , un pointeur vers un objet de type Thread est passé à la méthode Run . La méthode Run appelle directement la méthode Execute () , un objet spécifique de la classe Thread , qui n'est que notre implémentation de la tâche.

Le problème est presque résolu, nous devons maintenant implémenter les méthodes. Tous les systèmes d'exploitation ont des API différentes, donc pour implémenter, par exemple, la fonction de création de tâches pour embOS, vous devez appeler la méthode void OS_TASK_CreateEx (..) , et pour FreeRTOS en mode d'allocation de mémoire dynamique, il s'agit de xTaskCreate (..) et bien qu'ils aient une seule et même essence même, mais la syntaxe et les paramètres sont différents. Mais nous ne voulons pas parcourir les fichiers et écrire du code pour chacune des méthodes de la classe à chaque fois pour un nouveau système d'exploitation, nous devons donc en quelque sorte mettre cela dans un seul fichier et ... l'exécuter sous forme de macros. Génial, mais arrêtez, je me suis interdit les macros - j'ai besoin d'une approche différente.

La chose la plus simple qui m'est venue à l'esprit était de créer un fichier séparé pour chaque système d'exploitation avec des fonctions en ligne. Si nous voulons utiliser un autre système d'exploitation, nous aurons juste besoin d'implémenter chacune de ces fonctions à l'aide de l'API de ce système d'exploitation. Le fichier rtosFreeRtos.cpp suivant s'est avéré

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

Le fichier pour embOS rtosEmbOS.cpp peut avoir exactement la mĂŞme apparence

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

Les types de systèmes d'exploitation différents sont également différents, en particulier la structure du contexte de tâche, alors créons le fichier rtosdefs.hpp avec nos propres alias d'encapsuleur.

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

Pour EmbOS, cela pourrait ressembler Ă  ceci:

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

Par conséquent, pour une modification sous n'importe quel autre RTOS, il suffit d'effectuer des modifications uniquement dans ces deux fichiers rtosdefs.cpp et rtos.cpp. Maintenant, les classes Thread et Rtos ressemblent à des images c

image

Lancement des OS et finalisation de la tâche


Pour Cortex M4, «tous» les systèmes d'exploitation utilisent 3 interruptions, minuterie de tick système, appel de service système via l'instruction SWI , demande d'interruption de service système en attente , qui ont été principalement inventées pour le RTOS. Certains RTOS utilisent également d'autres interruptions système, mais celles-ci seront suffisantes pour la plupart des "tous" OS. Et sinon, il sera possible d'ajouter, il suffit donc de définir trois gestionnaires pour ces interruptions et pour démarrer le RTOS nous avons besoin d'une autre méthode de démarrage:

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

La première chose dont j'avais besoin et sans laquelle je ne peux pas vivre, je rêve d'un mécanisme de notification des tâches. J'aime généralement la programmation basée sur les événements, j'ai donc besoin d'implémenter rapidement un wrapper pour notifier les tâches.

Tout s'est avéré être assez simple, tout système d'exploitation peut le faire, sauf peut - être uc-OS-II et III , bien que je ne l'ai peut-être pas bien lu, mais, à mon avis, le mécanisme des événements est généralement délicat, mais bon, "tout" est le reste ils le peuvent certainement.

Afin de notifier la tâche, il vous suffit d'envoyer l'événement non pas au vide, mais spécifiquement à la tâche, pour cela, la méthode de notification doit avoir un pointeur vers le contexte de la tâche ou l'identifiant de la tâche. Je viens de les stocker dans la classe Thread , ce qui signifie que la classe Thread devrait également avoir une méthode d'alerte. Il devrait également y avoir une méthode pour attendre une alerte. En même temps, nous ajoutons la méthode Sleep (..) , qui suspend l'exécution de la tâche appelante. Maintenant, les deux classes ressemblent à ceci:

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 


J'ai commencé à l'implémenter, et ici le premier problème m'attendait, il s'avère que «tout» OS appelle ses fonctions à partir d'interruptions de différentes manières. Par exemple, FreeRTOS a des implémentations spéciales de fonctions pour les exécuter à partir d'interruptions, par exemple, s'il existe une fonction xTaskNotify (..) , vous ne pouvez pas l'appeler à partir d'une interruption, mais vous devez appeler xTaskNotifyFromISR (..) .
Pour embOS, si vous appelez une fonction à partir d'une interruption, veuillez utiliser OS_InInterrupt () lors de la saisie d'une interruption et OS_LeaveInterrupt () lors de la fermeture. J'ai dû créer une classe InterruptEntry , qui n'a qu'un constructeur et un destructeur:

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

Vous pouvez l'utiliser comme ceci:

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

Évidemment, pour FreeRTOS, le constructeur et le destructeur seront vides. Et pour la notification, vous pouvez utiliser la fonction xTaskNotifyFromISR (..) , qui, peu importe d'où elle est appelée, est un peu plus lourde, mais ce que vous ne pouvez pas faire dans un souci d'universalité. Vous pouvez bien sûr créer des méthodes distinctes pour appeler à partir d'interruptions, mais pour l'instant j'ai décidé de le faire universellement.
La même astuce qu'avec InterruptEntry peut être effectuée avec la section critique:

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

Maintenant, ajoutez simplement l'implémentation de fonctions à l'aide de l'API FreeRtos au fichier et exécutez la vérification, bien que vous ne puissiez pas l'exécuter, il est donc clair que cela fonctionnera :)
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

Nous continuons d'affiner la tâche


La tâche a maintenant presque tout ce dont vous avez besoin, nous avons ajouté la méthode Sleep (). Cette méthode interrompt la tâche pendant une durée spécifiée. Dans la plupart des cas, cela suffit, mais si vous avez besoin d'une heure clairement déterminée, Sleep () peut vous poser des problèmes. Par exemple, vous souhaitez effectuer un calcul et faire clignoter la LED et le faire exactement une fois toutes les 100 ms

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

Ce code fera clignoter une LED toutes les 110 ms. Mais vous voulez une fois toutes les 100 ms, vous pouvez calculer approximativement le temps de calcul et mettre Sleep (90ms). Mais que faire si le temps de calcul dépend des paramètres d'entrée, alors le clignotement ne sera pas du tout déterministe. Pour de tels cas dans "tous" les OS, il existe des méthodes spéciales, telles que DelayUntil (). Cela fonctionne sur ce principe - vous devez d'abord vous souvenir de la valeur actuelle du compteur de ticks du système d'exploitation, puis ajouter à cette valeur le nombre de ticks pour lesquels vous devez interrompre la tâche, dès que le compteur de ticks atteint cette valeur, la tâche est déverrouillée. Ainsi, la tâche sera verrouillée exactement à la valeur que vous avez définie et votre LED clignotera exactement toutes les 100 ms, quelle que soit la durée du calcul.
Ce mécanisme est implémenté différemment dans différents systèmes d'exploitation, mais il a un algorithme. En conséquence, le mécanisme, disons, implémenté sur FreeRTOS, sera simplifié à l'état montré dans l'image suivante:

image

Comme vous pouvez le voir, la lecture de l'état initial du compteur de ticks du système d'exploitation se produit avant d'entrer dans une boucle infinie, et nous devons trouver quelque chose pour l'implémenter. Un modèle de conception vient à la rescousse.Il est implémenté très simplement, nous avons juste besoin d'ajouter une autre méthode non virtuelle, où nous appelons d'abord la méthode qui lit et se souvient du compteur de ticks du système d'exploitation, puis nous appelons la méthode virtuelle Execute (), qui sera implémentée dans le descendant, c'est-à-dire dans la mise en œuvre de votre tâche. Comme nous n'avons pas besoin que cette méthode ressorte pour l'utilisateur (c'est juste une méthode auxiliaire), nous la cacherons dans la section privée.

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

Par conséquent, dans la méthode Run statique de la classe Rtos, il sera désormais nécessaire d'appeler non pas la méthode Execute (), mais la méthode Run () de l'objet Thread. Nous venons de rendre la classe Rtos conviviale afin d'accéder à la méthode Run () privée dans la classe Thread.

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

SleepUntil() , . , , , SleepUntil() , . :
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 



Les événements


, , , , , , . , .

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



:

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


,


Au début, lorsque j'écrivais cet article, je ne les avais pas encore mis en œuvre, mais comme je l'avais promis, j'ai développé des mutex et des boîtes de messagerie, la source se trouve ici: GitHub OsWrapper . Voici un exemple d'utilisation d'une boîte aux lettres:
 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); } } ; 


Comment utiliser toutes ces affaires


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


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

Mais en attendant, la plupart d'entre nous utilisent des OS Sishna traditionnels, vous pouvez utiliser le wrapper comme un début initial pour la transition vers un avenir heureux avec C ++ :)

J'ai mis en place un petit projet de test dans 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.

Z.Y. GitHub

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


All Articles