Wrapper C ++ para "todos" sistemas operacionais em tempo real para CortexM4

imagem

Eu já falei sobre como você pode usar o FreeRtos para projetos escritos em C ++ no artigo STM32, C ++ e FreeRTOS. Desenvolvimento a partir do zero. Parte 1 Desde então, até três anos se passaram, eu envelheci seriamente, perdi várias conexões neurais, então decidi mudar os velhos tempos para restaurar essas conexões e varrer o invólucro para "qualquer" RTOS popular. É claro que isso é uma piada, eu deliberadamente coloquei "todos" entre aspas, mas há alguma verdade em cada piada.

Então, qual é a tarefa e por que é relevante? No momento, existem um milhão de sistemas operacionais diferentes escritos em C - não quero escolher para todos os gostos, pagos, gratuitos, pequenos, grandes ... Mas, para os projetos em que participo, todos esses chips de diferentes sistemas operacionais não são necessários, funcionalidades básicas como uma tarefa são suficientes , eventos, notificação de tarefas, seção crítica, mutexes e semáforos (embora eu tente não usá-los), filas. E tudo isso é necessário de uma forma bastante simples, sem frescuras.

Na minha opinião, o OSRV MAX doméstico, escrito em C ++, é ideal para meus projetos e é um prazer usá-lo.

Mas o problema é que nossos dispositivos devem estar em conformidade com o padrão IEC_61508, um dos requisitos é o E.29 Aplicativo da biblioteca de destino comprovada em uso . Bem, ou em palavras simples, se você criar um dispositivo para atingir o nível SIL3 , use o (Higher Recommended) bibliotecas que correspondem a esse nível e são testadas pelo tempo.

Em relação à nossa tarefa, isso significa que é possível usar o MAX MAX RTOS para esses dispositivos, mas os pontos de confiabilidade não serão adicionados. Portanto, os fabricantes de RTOS criam versões especiais de seus sistemas operacionais que atendem aos padrões IEC_61508, por exemplo, o FreeRTOS possui um clone SafeRTOS e embOs possui um clone embOS-Safe , é claro que os fabricantes ganham muito dinheiro com isso, porque as licenças para esses sistemas operacionais custam vários milhares ou até dezenas mil dólares.

A propósito, um bom exemplo é o compilador IAR, cuja licença custa cerca de US $ 1.500, mas as versões certificadas pelo IAR já custam cerca de 10.000 dólares, embora eu tenha verificado em vários projetos - o arquivo de saída da versão sem certificado e com certificado é completamente idêntico. Bem, você entende que precisa pagar pela paz.

Então, primeiro usamos um sistema operacional , depois comecei a usar o FreeRTOS para minhas necessidades, depois mudamos para outro , em geral, sempre tivemos que reescrever o código final. Além disso, gostaria que parecesse bonito e simples, para que qualquer pessoa possa entender pelo código o que está acontecendo, o suporte ao código será um trabalho simples para estudantes e profissionais, e os gurus poderão continuar trabalhando em dispositivos inovadores, em vez de entender a pilha de macarrão . Em geral, quero ver algo assim fossilizado:

imagem

Bem, ou tal ...

imagem

Portanto, decidi escrever um invólucro que se encaixasse no FreeRTOS e dizer embOS, bem, para todos os demais :) e, para começar, determinei o que precisava para uma felicidade completa:

  • As tarefas
  • Seções críticas
  • Eventos e notificação de tarefas
  • Semáforos e Mutexes
  • Filas

O invólucro deve ser ideológico do SIL3 , e esse nível impõe muitos tipos de itens altamente recomendados, e se você os seguir completamente, é melhor não escrever o código.

Mas o fato de o padrão governar várias regras, ou melhor, recomendações, não significa que elas não possam ser violadas - você pode, mas precisa seguir o máximo de recomendações possível para obter mais pontos. Portanto, decidi algumas limitações importantes:

  • sem macros , bem, exceto pela proteção contra a dupla inclusão de arquivos de cabeçalho. As macros são más, se você calcular quanto tempo foi gasto na busca de erros relacionados às macros, o universo não é tão antigo e quanto bem poderia ser feito durante esse período; provavelmente é melhor proibi-los no nível legislativo, como as torrentes proibiam. ou receba um bônus por cada macro que você escrever
  • não use ponteiros , é claro, sempre que possível. Pode-se tentar não usá-los, mas ainda existem lugares onde, sem eles, não há como. De qualquer forma, o usuário do invólucro, se possível, nem deve saber o que é um ponteiro, pois só ouviu falar do avô sobre ele, porque agora ele trabalha exclusivamente com links
  • para não usar alocação dinâmica de memória - tudo fica claro, apenas o uso de um heap leva, primeiramente, à necessidade de reservar RAM para esse heap e, em segundo lugar, com o uso frequente do heap, ele é desfragmentado e novos objetos são criados mais e mais mais tempo. Portanto, de fato, configurei o FreeRTOS apenas na memória alocada estaticamente, definindo configSUPPORT_STATIC_ALLOCATION 1 . Mas se você quiser trabalhar no modo padrão. Por padrão, o FreeRTOS usa memória alocada dinamicamente para criar elementos do sistema operacional; em seguida, basta configurar configSUPPORT_STATIC_ALLOCATION 0 e
    configSUPPORT_DYNAMIC_ALLOCATION 1 e não se esqueça de conectar a implementação de seus próprios Mallocs e Callocs a partir do gerenciador de memória, por exemplo, este arquivo é FreeRtos / portable / MemMang / heap_1.c. Mas lembre-se de que você terá que alocar RAM com uma reserva para muitos, pois não poderá calcular a quantidade exata de RAM necessária, com todas as configurações (Ocioso está ativado, a tarefa de timer do programa está ativada, minhas duas tarefas, filas, tamanho da fila para os temporizadores 10 e por exemplo, digamos que definitivamente não são as configurações mais ideais) que funcionaram quando aloquei a memória assim:
    7 357 bytes de memória de código somente leitura
    535 bytes de memória de dados somente leitura
    6.053 bytes de memória de dados readwrite

    A alocação de memória estática é "um pouco" mais compacta:
    7.329 bytes de memória de código somente leitura
    535 bytes de memória de dados somente leitura
    3.877 bytes de memória de dados readwrite

    Você pode pensar: "incrível ... você mesmo", mas agora não estamos interessados ​​na pergunta formulada no artigo "Eu aloquei até 3 KB no sistema operacional e lancei apenas três tarefas com uma pilha de 128B e, por alguma razão, ainda não há memória suficiente para a quarta" , Nessa situação, fiz isso de propósito, para maior clareza, para mostrar a diferença entre alocação de memória dinâmica e estática com as mesmas configurações.
  • não transmita tipos , se possível. Criar fantasmas para outros tipos significa o fato de que algo está errado no design, mas, como de costume, às vezes você ainda precisa convertê-lo por conveniência (por exemplo, enum precisa ser convertido em números inteiros) e, às vezes, não isso, mas isso deve ser evitado.
  • simplicidade e conveniência . Para o usuário do invólucro, todas as dificuldades devem estar ocultas, para que sua vida não seja de petróleo e ele ainda não quer complicar - ele criou a tarefa, implementou tudo o que é necessário nela, iniciou e saiu para aproveitar a vida.

Como começaremos, definimos a tarefa de criar uma tarefa (resultou diretamente da série "proibido de banir").

Criação de tarefas


Por uma longa pesquisa, cientistas britânicos ( toda a verdade sobre RTOS de Colin Walls. Artigo nº 4. Tarefas, mudança de contexto e interrupções ) (a propósito, se você não sabia, o montador de ARM também foi inventado por um cientista britânico, algo que também não fiquei surpreso uma vez :)), e assim os cientistas britânicos descobriram que, para a maioria de "todos" RTOS, a tarefa tem um nome , uma pilha , um tamanho de pilha , uma "unidade de controle" , um identificador ou um ponteiro para uma "unidade de controle" , prioridade , uma função executada na tarefa . Isso é tudo, e foi possível agrupar tudo em uma classe, mas estava certo se escrevêssemos um sistema operacional com você, mas fazemos um invólucro, portanto não faz sentido armazenar todas essas coisas em um invólucro, tudo isso será feito pelo sistema ideológico SIL3 nós terminamos. De fato, precisamos apenas de uma função executada na tarefa e de uma estrutura que armazene a "unidade de controle" , que é preenchida ao criar a tarefa e o identificador da tarefa . Portanto, a classe task, vamos chamá-la Thread, pode parecer muito simples:

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

Eu só quero declarar a classe da minha tarefa, onde eu poderia implementar tudo o que preciso e depois passar o ponteiro para o objeto dessa classe para o wrapper, o que criaria uma tarefa usando a API do RTOS, na qual executaria o 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") ; } 

No RTOS "all", para que a tarefa seja criada, é necessário passar um ponteiro para uma função que será iniciada pelo agendador. No nosso caso, esta é a função Execute () , mas não posso passar um ponteiro para esse método, pois não é estático. Portanto, examinamos como uma tarefa é criada na API de "todos" sistemas operacionais e percebemos que podemos criar uma tarefa passando um parâmetro para a função de tarefa, por exemplo, para 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 - esta é a chave da solução. Vamos ter um método estático, um ponteiro para o qual passaremos como ponteiro para um método chamado pelo agendador e, como parâmetro, transferiremos um ponteiro para um objeto do tipo Thread, onde podemos chamar diretamente o método Execute () . Este é exatamente o momento em que não há como deixar um ponteiro e converter os tipos, mas esse código ficará oculto para o usuário:

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

I.e. como um algoritmo de operação, o planejador inicia o método Run , um ponteiro para um objeto do tipo Thread é passado para o método Run . O método Run chama diretamente o método Execute () , um objeto específico da classe Thread , que é apenas a nossa implementação da tarefa.

O problema está quase resolvido, agora precisamos implementar os métodos. Todos os sistemas operacionais possuem APIs diferentes; portanto, para implementar, por exemplo, a função de criação de tarefas para o embOS, você deve chamar o método nulo OS_TASK_CreateEx (..) e para o FreeRTOS no modo de alocação de memória dinâmica, esse é o xTaskCreate (..) e, embora eles tenham a mesma essência mesmo, mas a sintaxe e os parâmetros são diferentes. Mas não queremos executar os arquivos e escrever código para cada um dos métodos da classe todas as vezes para um novo sistema operacional; portanto, precisamos colocar isso em um arquivo e executá-lo na forma de macros. Ótimo, mas pare, eu proibi as macros para mim - preciso de uma abordagem diferente.

A coisa mais simples que me ocorreu foi criar um arquivo separado para cada sistema operacional com funções embutidas. Se quisermos usar qualquer outro sistema operacional, precisaremos apenas implementar cada uma dessas funções usando a API deste sistema operacional. O seguinte arquivo rtosFreeRtos.cpp acabou

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

O arquivo para embOS rtosEmbOS.cpp pode parecer exatamente o mesmo

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

Tipos de sistemas operacionais diferentes também são diferentes, especialmente a estrutura do contexto da tarefa; portanto, vamos criar o arquivo rtosdefs.hpp com nossos próprios aliases de wrapper.

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

Para o EmbOS, pode ser assim:

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

Como resultado, para alteração em qualquer outro RTOS, basta fazer alterações apenas nesses dois arquivos rtosdefs.cpp e rtos.cpp. Agora as classes Thread e Rtos se parecem com imagens c

imagem

Ativando SOs e finalizando a tarefa


Para o Cortex M4, os sistemas operacionais “todos” usam 3 interrupções, timer de verificação do sistema , chamada de serviço do sistema por meio da instrução SWI , solicitação pendente de interrupção do serviço do sistema , que foi inventada principalmente para o RTOS. Alguns RTOS também usam outras interrupções do sistema, mas serão suficientes para a maioria dos sistemas operacionais "todos". Caso contrário, será possível adicionar, então apenas defina três manipuladores para essas interrupções e para iniciar o RTOS, precisamos de outro método de inicialização:

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

A primeira coisa que eu precisava e sem a qual não posso viver, o que estou sonhando é um mecanismo de notificação para tarefas. Geralmente gosto de programação orientada a eventos, por isso preciso implementar rapidamente um wrapper para notificar tarefas.

Tudo acabou sendo muito simples, qualquer sistema operacional pode fazer isso, bem, exceto talvez o uc-OS-II e III , embora talvez eu não tenha lido bem, mas, na minha opinião, o mecanismo de eventos lá geralmente é complicado, mas, oh, tudo é o resto eles certamente podem.

Para notificar uma tarefa, basta enviar o evento não para o vazio, mas especificamente para a tarefa; para isso, o método de notificação deve ter um ponteiro para o contexto da tarefa ou o identificador da tarefa. Eu apenas os armazeno na classe Thread , o que significa que a classe Thread também deve ter um método de alerta. Também deve haver um método para aguardar um alerta. Ao mesmo tempo, adicionamos o método Sleep (..) , que interrompe a execução da tarefa de chamada. Agora, as duas classes ficam assim:

imagem

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 


Comecei a implementá-lo e, aqui, o primeiro problema estava me esperando, verifica-se que "qualquer" sistema operacional chama suas funções de interrupções de diferentes maneiras. Por exemplo, o FreeRTOS possui implementações especiais de funções para executá-las a partir de interrupções, por exemplo, se houver uma função xTaskNotify (..) , você não poderá chamá-lo de uma interrupção, mas precisará chamar xTaskNotifyFromISR (..) .
Para o embOS, se você chamar alguma função de uma interrupção, use OS_InInterrupt () ao inserir uma interrupção e OS_LeaveInterrupt () ao sair. Eu tive que criar uma classe InterruptEntry , que possui apenas um construtor e um destruidor:

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

Você pode usá-lo assim:

 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 o FreeRTOS, o construtor e o destruidor estarão vazios. E para notificação, você pode usar a função xTaskNotifyFromISR (..) , que não importa de onde é chamada, é um pouco sobrecarga, mas o que você não pode fazer em prol da universalidade. É claro que você pode criar métodos separados para chamar de interrupções, mas por enquanto decidi fazer isso universalmente.
O mesmo truque do InterruptEntry pode ser feito com a seção crítica:

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

Agora basta adicionar a implementação de funções usando a API do FreeRtos ao arquivo e executar a verificação, embora você não possa executá-la, portanto é 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(); } } 


imagem

Continuamos a refinar a tarefa


Agora, a tarefa tem quase tudo o que você precisa. Adicionamos o método Sleep (). Este método pausa a tarefa por um tempo especificado. Na maioria dos casos, isso é suficiente, mas se você precisar de um tempo claramente determinado, o Sleep () poderá causar problemas. Por exemplo, você deseja executar algum cálculo e piscar o LED e fazê-lo exatamente uma vez a cada 100 ms

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

Este código piscará um LED a cada 110 ms. Mas se você quiser uma vez a cada 100ms, pode calcular aproximadamente o tempo de cálculo e colocar em suspensão (90ms). Mas e se o tempo de cálculo depender dos parâmetros de entrada, o piscar não será determinístico? Para esses casos em sistemas operacionais "todos", existem métodos especiais, como DelayUntil (). Ele funciona com esse princípio - primeiro você precisa lembrar o valor atual do contador de ticks do sistema operacional e, em seguida, adicionar a esse valor o número de ticks pelos quais você precisa pausar a tarefa, assim que o contador de ticks atingir esse valor, a tarefa será desbloqueada. Portanto, a tarefa será bloqueada exatamente no valor definido e o LED piscará exatamente a cada 100 ms, independentemente da duração do cálculo.
Esse mecanismo é implementado de maneira diferente em diferentes sistemas operacionais, mas possui um algoritmo. Como resultado, o mecanismo, digamos, implementado no FreeRTOS, será simplificado para o estado mostrado na figura a seguir:

imagem

Como você pode ver, a leitura do estado inicial do contador de ticks do sistema operacional ocorre antes de entrar em um loop infinito, e precisamos criar algo para implementar isso. Um modelo de design vem ao resgate. , , , , 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() , . :
imagem

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


Conclusão


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

, , ++ :)

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

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

imagem

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

Z.Y. GitHub

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


All Articles