Contenedor C ++ para "todos" los sistemas operativos en tiempo real para CortexM4

imagen

Ya hablé sobre cómo puede usar FreeRtos para proyectos escritos en C ++ en el artículo STM32, C ++ y FreeRTOS. Desarrollo desde cero. Parte 1 Desde entonces, han pasado 3 años, envejecí seriamente, perdí un montón de conexiones neuronales, así que decidí sacudir los viejos tiempos para restaurar estas conexiones y barrer el envoltorio para "cualquier" RTOS popular. Esto es, por supuesto, una broma, deliberadamente puse "todos" entre comillas, pero hay algo de verdad en cada broma.

Entonces, ¿cuál es la tarea y por qué es relevante? Por el momento, hay un millón de sistemas operativos diferentes escritos en C: no quiero elegir para todos los gustos, pagados, gratuitos, pequeños, grandes ... Pero para los proyectos en los que participo, no se necesitan todos estos chips de diferentes sistemas operativos, la funcionalidad básica como una tarea es suficiente , eventos, notificación de tareas, sección crítica, mutexes y semáforos (aunque trato de no usarlos), colas. Y todo esto es necesario en una forma bastante simple, sin lujos.

En mi opinión, el OSRV MAX doméstico, escrito en C ++, es ideal para mis proyectos y es un placer usarlo.

Pero el problema es que nuestros dispositivos deben cumplir con el estándar IEC_61508, uno de cuyos requisitos es la aplicación E.29 de la biblioteca de destino probada en uso . Bueno, o en palabras simples, si crea un dispositivo para cumplir con el nivel SIL3 , entonces (Recomendado más alto) use bibliotecas que correspondan a este nivel y sean probadas en el tiempo.

Con respecto a nuestra tarea, esto significa que es posible usar el MAX MAX RTOS para tales dispositivos, pero no se agregarán puntos de confiabilidad. Por lo tanto, los fabricantes de RTOS hacen versiones especiales de sus sistemas operativos que cumplen con los estándares IEC_61508, por ejemplo, FreeRTOS tiene un clon SafeRTOS y embOs tiene un clon embOS-Safe , por supuesto, los fabricantes ganan muy buen dinero en esto, porque las licencias para estos sistemas operativos cuestan varios miles, o incluso decenas mil dolares

Por cierto, un buen ejemplo es el compilador IAR, cuya licencia cuesta alrededor de $ 1,500, pero las versiones certificadas por IAR ya cuestan alrededor de 10,000 dólares, aunque verifiqué en varios proyectos: el archivo de salida de la versión sin un certificado y con un certificado es completamente idéntico. Bueno, entiendes que debes pagar por la paz.

Entonces, primero usamos un sistema operativo , luego comencé a usar FreeRTOS para mis necesidades, luego cambiamos a otro , en general, tuvimos que reescribir constantemente el código terminado. Además, me gustaría que se vea hermoso y simple, para que cualquiera pueda entender por código lo que está sucediendo, entonces el soporte de código será un trabajo simple para estudiantes y profesionales, y los gurús podrán continuar trabajando en dispositivos innovadores, en lugar de comprender la pila de fideos . En general, quiero ver algo como esto fosilizado:

imagen

Bueno, o tal ...

imagen

Por lo tanto, decidí escribir un contenedor que se adaptara a FreeRTOS y decir embOS, bueno, para todos los demás también :) y, para comenzar, determiné lo que realmente necesito para una felicidad completa:

  • Las tareas
  • Secciones críticas
  • Notificación de eventos y tareas
  • Semáforos y mutexes
  • Colas

El contenedor debe ser ideológico SIL3 , y este nivel impone una gran cantidad de todo tipo de cosas recomendadas, y si las sigues por completo, resulta que es mejor no escribir el código.

Pero el hecho de que el estándar rige un conjunto de reglas, o más bien recomendaciones, no significa que no se puedan violar; usted puede, pero debe seguir tantas recomendaciones como sea posible para obtener más puntos. Por lo tanto, decidí algunas limitaciones importantes:

  • sin macros , bueno, excepto por la protección contra la doble inclusión de archivos de encabezado. Las macros son malvadas, si calcula cuánto tiempo pasó buscando errores relacionados con las macros, resulta que el universo no es tan viejo, y cuánto bien podría hacerse durante este tiempo, probablemente sea mejor prohibirlos a nivel legislativo, ya que los torrentes lo prohibieron o tome un bono por cada macro que escriba
  • no use punteros , por supuesto, siempre que sea posible. Uno podría tratar de no usarlos en absoluto, pero todavía hay lugares donde sin ellos no hay forma. En cualquier caso, el usuario del envoltorio, si es posible, ni siquiera debería saber qué es un puntero, ya que solo escuchó sobre ellos de su abuelo, porque ahora trabaja exclusivamente con enlaces
  • no usar la asignación dinámica de memoria : todo está claro, solo usar un montón conduce, en primer lugar, a la necesidad de reservar RAM para este montón, y en segundo lugar, con el uso frecuente del montón, se desfragmenta y se crean nuevos objetos en él más y más tiempo más largo Por lo tanto, de hecho, configuré FreeRTOS solo en la memoria asignada estáticamente al configurar configSUPPORT_STATIC_ALLOCATION 1 . Pero si quieres trabajar en modo predeterminado. Y de forma predeterminada, FreeRTOS usa memoria asignada dinámicamente para crear elementos del sistema operativo; luego simplemente configure configSUPPORT_STATIC_ALLOCATION 0 , y
    configSUPPORT_DYNAMIC_ALLOCATION 1 y no olvide conectar la implementación de sus propios mallocs y callocs desde el administrador de memoria, por ejemplo, este archivo es FreeRtos / portable / MemMang / heap_1.c. Pero tenga en cuenta que tendrá que asignar RAM con una reserva para un grupo, ya que no podrá calcular la cantidad exacta de RAM necesaria, con todas las configuraciones (Idle está activado, la tarea del temporizador del programa está activada, mis dos tareas, colas, tamaño de cola para los temporizadores 10 y etc., digamos que definitivamente no es la configuración más óptima) que funcionó cuando asigné la memoria de esta manera:
    7 357 bytes de memoria de código de solo lectura
    535 bytes de memoria de datos de solo lectura
    6.053 bytes de memoria de datos readwrite

    La asignación de memoria estática es "un poco" más compacta:
    7.329 bytes de memoria de código de solo lectura
    535 bytes de memoria de datos de solo lectura
    3.877 bytes de memoria de datos readwrite

    Puede pensar: "increíble ... usted mismo", pero ahora no estamos interesados ​​en la pregunta formulada en el artículo "Asigné hasta 3 KB al sistema operativo y lancé solo 3 tareas con una pila de 128 B, y por alguna razón ya no hay suficiente memoria para el cuarto" , En esta situación, lo hice a propósito, para mayor claridad, para mostrar la diferencia entre la asignación de memoria dinámica y estática con la misma configuración.
  • No lanzar tipos , si es posible. Los tipos de imágenes fantasma a otros tipos en sí mismos significan el hecho de que algo está mal en el diseño, pero como de costumbre, a veces todavía tiene que emitir para facilitar su uso (por ejemplo, la enumeración debe convertirse a enteros), y a veces no puede prescindir esto, pero esto debe ser evitado.
  • simplicidad y conveniencia . Para el usuario del envoltorio, todas las dificultades deben estar ocultas, por lo que su vida no es petróleo, y no quiere complicarlo todavía: creó la tarea, implementó todo lo que se necesita en ella, la inició y se fue para disfrutar de la vida.

Comenzaremos con esto, así que nos propusimos la tarea de crear una tarea (resultó de la serie "prohibido prohibir").

Creación de tareas


Según una larga investigación, científicos británicos ( Toda la verdad sobre RTOS de Colin Walls. Artículo # 4. Tareas, cambio de contexto e interrupciones ) (por cierto, si no lo sabían, el ensamblador de ARM también fue inventado por un científico británico, algo que tampoco me sorprendió una vez :)), y así los científicos británicos descubrieron que para la mayoría de "todos" RTOS la tarea tiene un nombre , una pila , un tamaño de pila , una "unidad de control" , un identificador o un puntero a una "unidad de control" , prioridad , una función que se realiza en la tarea . Eso es todo, y fue posible agruparlo todo en una clase, pero fue correcto si escribimos un sistema operativo con usted, pero hacemos un contenedor, por lo que no tiene sentido almacenar todas estas cosas en un contenedor, todo esto lo hará el sistema operativo ideológico SIL3 terminamos De hecho, solo necesitamos una función que se ejecute en la tarea y una estructura que almacene la “unidad de control” , que se completa al crear la tarea y el identificador de la tarea . Por lo tanto, la clase de tarea, llamémosla Hilo, puede parecer muy simple:

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

Solo quiero declarar la clase de mi tarea donde podría implementar todo lo que se necesita y luego pasar el puntero al objeto de esta clase al contenedor, lo que crearía una tarea usando la API RTOS donde lanzaría el método 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") ; } 

En "todos" RTOS, para que se cree la tarea, es necesario pasar un puntero a una función que será iniciada por el planificador. En nuestro caso, esta es la función Execute () , pero no puedo pasar un puntero a este método, ya que no es estático. Por lo tanto, observamos cómo se crea una tarea en la API de "todos" los sistemas operativos y observamos que podemos crear una tarea pasando un parámetro a la función de tarea, por ejemplo, para embOS es:

 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 : esta es la clave de la solución. Tengamos un método estático, un puntero al que pasaremos como puntero a un método llamado por el planificador, y como parámetro pasaremos un puntero a un objeto de tipo Thread donde podemos llamar directamente al método Execute () . Este es exactamente el momento en el que no hay forma sin puntero y conversión a tipos, pero este código estará oculto para el usuario:

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

Es decir tal algoritmo de operación, el planificador lanza el método Run , un puntero a un objeto de tipo Thread se pasa al método Run . El método Run llama directamente al método Execute () , un objeto específico de la clase Thread , que es solo nuestra implementación de la tarea.

El problema está casi resuelto, ahora necesitamos implementar los métodos. Todos los sistemas operativos tienen API diferentes, por lo que para implementar, por ejemplo, la función de creación de tareas para embOS, debe llamar al método vacío OS_TASK_CreateEx (..) , y para FreeRTOS en el modo de asignación de memoria dinámica, esto es xTaskCreate (..) y aunque tienen una misma esencia igual, pero la sintaxis y los parámetros son diferentes. Pero no queremos ejecutar los archivos y escribir código para cada uno de los métodos de la clase cada vez para un nuevo sistema operativo, por lo que debemos poner esto de alguna manera en un archivo y ... ejecutarlo en forma de macros. Genial, pero detente, me prohibí las macros: necesito un enfoque diferente.

Lo más simple que se me ocurrió fue crear un archivo separado para cada sistema operativo con funciones en línea. Si queremos usar cualquier otro sistema operativo, solo necesitaremos implementar cada una de estas funciones usando la API de este sistema operativo. El siguiente archivo rtosFreeRtos.cpp resultó

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

El archivo para embOS rtosEmbOS.cpp puede verse exactamente igual

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

Los tipos de sistemas operativos diferentes también son diferentes, especialmente la estructura del contexto de la tarea, así que creemos el archivo rtosdefs.hpp con nuestros propios alias de contenedor.

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

Para EmbOS, podría verse así:

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

Como resultado, para alterar cualquier otro RTOS, es suficiente hacer cambios solo en estos dos archivos rtosdefs.cpp y rtos.cpp. Ahora las clases Thread y Rtos se ven como imágenes c

imagen

Lanzar OS y finalizar la tarea


Para Cortex M4, "todos" los sistemas operativos usan 3 interrupciones, temporizador de marcación del sistema , llamada de servicio del sistema mediante instrucciones SWI , solicitud pendiente de interrupciones de servicio del sistema , que se inventaron principalmente para el RTOS. Algunos RTOS también usan otras interrupciones del sistema, pero serán suficientes para la mayoría de los sistemas operativos "todos". Y si no, entonces será posible agregar, así que solo defina tres controladores para estas interrupciones y para iniciar el RTOS necesitamos otro método de inicio:

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

Lo primero que necesitaba y sin lo cual no puedo vivir, lo que estoy soñando es un mecanismo de notificación para las tareas. En general, me gusta la programación dirigida por eventos, por lo que necesito implementar rápidamente un contenedor para notificar las tareas.

Todo resultó ser bastante simple, cualquier sistema operativo puede hacerlo, bueno, excepto tal vez uc-OS-II y III , aunque tal vez no lo leí bien, pero, en mi opinión, el mecanismo de los eventos es generalmente complicado, pero bueno, "todo" es el resto Ciertamente pueden.

Para notificar la tarea, solo necesita enviar el evento no al vacío, sino específicamente a la tarea, para esto, el método de notificación debe tener un puntero al contexto de la tarea o al identificador de la tarea. Solo almaceno estos en la clase Thread , lo que significa que la clase Thread también debería tener un método de alerta. También debería haber un método para esperar una alerta. Al mismo tiempo, agregamos el método Sleep (..) , que detiene la ejecución de la tarea de llamada. Ahora ambas clases se ven así:

imagen

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 


Comencé a implementarlo, y aquí el primer problema me estaba esperando, resulta que "cualquier" sistema operativo llama a sus funciones de interrupciones de diferentes maneras. Por ejemplo, FreeRTOS tiene implementaciones especiales de funciones para ejecutarlas desde interrupciones, por ejemplo, si hay una función xTaskNotify (..) , entonces no puede llamarla desde una interrupción, pero necesita llamar a xTaskNotifyFromISR (..) .
Para embOS, si llama a cualquier función desde una interrupción, utilice OS_InInterrupt () al ingresar una interrupción y OS_LeaveInterrupt () al salir. Tuve que hacer una clase InterruptEntry , que solo tiene un constructor y un destructor:

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

Puedes usarlo así:

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

Obviamente, para FreeRTOS, tanto el constructor como el destructor estarán vacíos. Y para la notificación, puede usar la función xTaskNotifyFromISR (..) , que no importa de dónde se llame, es un poco sobrecarga, pero no lo hará por el bien de la universalidad. Por supuesto, puede crear métodos separados para llamar desde interrupciones, pero por ahora decidí hacerlo universalmente.
El mismo truco que con InterruptEntry se puede hacer con la sección crítica:

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

Ahora solo agregue la implementación de funciones usando la API de FreeRtos al archivo y ejecute la verificación, aunque no pudo ejecutarla, por lo que está claro que funcionará :)
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(); } } 


imagen

Seguimos refinando la tarea.


La tarea ahora tiene casi todo lo que necesita, agregamos el método Sleep (). Este método detiene la tarea durante un tiempo específico. En la mayoría de los casos, esto es suficiente, pero si necesita un tiempo claramente determinado, Sleep () puede traerle problemas. Por ejemplo, desea realizar algunos cálculos y parpadear el LED y hacerlo exactamente una vez cada 100 ms

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

Este código parpadeará un LED cada 110 ms. Pero si desea una vez cada 100 ms, puede calcular aproximadamente el tiempo de cálculo y poner Sleep (90 ms). Pero, si el tiempo de cálculo depende de los parámetros de entrada, el parpadeo no será determinista en absoluto. Para tales casos en "todos" los sistemas operativos, existen métodos especiales, como DelayUntil (). Funciona de acuerdo con este principio: primero debe recordar el valor actual del contador de tics del sistema operativo, luego agregar a este valor el número de ticks para los que necesita pausar la tarea, tan pronto como el contador de ticks alcance este valor, la tarea se desbloqueará. Por lo tanto, la tarea se bloqueará exactamente al valor que configuró y su LED parpadeará exactamente cada 100 ms, independientemente de la duración del cálculo.
Este mecanismo se implementa de manera diferente en diferentes sistemas operativos, pero tiene un algoritmo. Como resultado, el mecanismo, por ejemplo, implementado en FreeRTOS, se simplificará al estado que se muestra en la siguiente imagen:

imagen

Como puede ver, la lectura del estado inicial del contador de tics del sistema operativo se produce antes de ingresar a un bucle infinito, y debemos encontrar algo para implementar esto. Una plantilla de diseño viene al rescate Método de plantilla . , , , , 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() , . :
imagen

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 



Eventos


, , , , , , . , .

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



:

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


Conclusión


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

, , ++ :)

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

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

imagen

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

Z.Y. GitHub

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


All Articles