Olá, caro leitor, tudo bem? Esse é primeiro de uma série de artigos sobre o FreeRTOS. Aqui, no Blog da Eletrogate, temos artigos explicando o que são RTOS e detalhando o processo de instalação na IDE do Arduino. Portanto, a série será focada em apresentar os recursos do FreeRTOS, tais como Task, Semaphore/Mutexes, Software Timers, Event Groups (Flag’s) e Queues. O objetivo de cada artigo é apresentar um recurso do FreeRTOS, explicar o conceito e, por fim, demonstrar a utilização do mesmo em projeto.
As ferramentas utilizadas nos projetos de demonstração serão o ESP32, VSCode e PlatformIO. Para mais detalhes, sugiro consultar o artigo “Como programar o Arduino com VS Code e PlatformIO”, escrito pelo Ricardo Lousada.
O FreeRTOS, tecnicamente, é um Kernel de Sistema Operacional de Tempo Real. Diferente de RTOS completo, que pode prover recursos para o gerenciar o uso da CPU, driver para acesso aos periféricos, driver de dispositivos, pilhas de protocolo e outros, o FreeRTOS gerencia apenas a CPU. Basicamente, ele fornece instrumentos para o gerenciamento de processo, mecanismos para comunicação e sincronismo entre estes e recursos de software timer.
O projeto teve início por volta de 2003, por Richard Barry. Em 2017, o projeto foi adquirido pela empresa Amazon Web Services (AWS). O FreeRTOS é distribuído sob a licença MIT.
Antes de começar a explicar sobre a utilização de “Tarefa” com o FreeRTOS, é importante falarmos um pouco do escalonador e kernel. Escalonador é o algoritmo do sistema operacional responsável pelo gerenciamento dos processos a serem executados pela CPU. Basicamente, ele é responsável por selecionar qual será a próxima tarefa a ser executada.
O kernel é núcleo do sistema operacional, ele é responsável por gerenciar a execução dos processos, seguindo determinado conjunto de regras.
O kernel do FreeRTOS permite trabalhar no modo cooperativo (não-preemptivo) ou preemptivo. No modo cooperativo, cada tarefa é executava do início ao fim, isso significa que a próxima tarefa a ser executada pela a CPU tem que aguardar o término do processo em execução, mesmo que a tarefa que está aguardando seja de maior prioridade. Por padrão é o preemptivo que vem habilitado, assim, o kernel possui a capacidade de interromper a execução de tarefa, salvar os seus parâmetros e dar início à execução de uma tarefa de maior prioridade. Ao término da execução da tarefa de maior prioridade, o kernel retorna com a tarefa que havia sido interrompida. Esse recurso que permite pausar a execução de processo para iniciar um novo é chamado de troca de contexto.
Uma tarefa dentro de sistema operacional pode ser entendida como um pequeno algoritmo independente das outras tarefas do sistema, ficando a cargo do escalonador gerenciar o processamento de cada tarefa. O FreeRTOS permite classificar as tarefas por prioridade de execução.
Protótipo da função de uma tarefa:
void vTaskCode(void *pvParameters);
Estrutura da função de uma tarefa:
void vTaskCode(void *pvParameters) { for(;;) { /* task code */ } }
Para criar tarefas a serem executadas pelo o RTOS, temos que, basicamente, agendar a execução de uma determinada função no escalonador. Para essa etapa de criação de tarefas no FreeRTOS, a função padrão é “xTaskCreate”. Para o ESP32, Dual Core, temos a função “xTaskCreatePinnedToCore” que permite selecionar em qual CPU será executada a tarefa.
Protótipo da função “xTaskCreate”:
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask)
Retorno da função:
Parâmetros da função:
Nota: Para o ESP32, quando criada uma tarefa utilizando a função “xTaskCreate”, fica a cargo do escalonador selecionar em qual núcleo será executada, dependendo da disponibilidade das CPUs.
Protótipo da função “xTaskCreatePinnedToCore”:
BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pvTaskCode, const char * const pcName, const uint32_t usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pvCreatedTask, const BaseType_t xCoreID);
Essa função é semelhante a “xTaskCreate” com a diferença que permite a escolha do núcleo que será executada a tarefa. Essa configuração é feita por meio do parâmetro “xCoreID”.
Aplicações com o FreeRTOS podem conter diversas tarefas criadas. A grande maioria dos microcontroladores possui apenas uma CPU, portanto, apenas uma tarefa por vez pode ser executada. Então, podemos concluir que uma tarefa poder assumir apenas dois estados; executando e não-executando, sendo que não-executando é dividida em sub-estados, que são eles:
Para manipular as tarefas, o FreeRTOS possui algumas funções. A seguir, serão apresentadas algumas delas. Para encontrar mais funções para controlar as tarefas, sugiro consultar o link.
vTaskDelay
Pausa a execução da tarefa por um determinado tempo. O tempo real que a tarefa permanece bloqueada depende da taxa de tique. A constante “portTICK_PERIOD_MS” pode ser usada para calcular o tempo real.
Protótipo da função vTaskDelay:
void vTaskDelay( const TickType_t xTicksToDelay )
Parâmetro da função:
xTaskAbortDelay
Essa função faz com que uma determinada tarefa saia do estado Bloqueada e passe para o estado Pronta, mesmo se o evento pelo qual a tarefa estava no estado Bloqueado a aguardar não tenha ocorrido e qualquer tempo limite especificado não tenha expirado.
Protótipo da função:
BaseType_t xTaskAbortDelay (TaskHandle_t xTask);
Retorno da função:
Parâmetro da função:
vTaskSuspend
Suspende a execução da tarefa. A tarefa permanecerá no estado Bloqueado enquanto outra tarefa não a tirar desse estado. As chamadas para “vTaskSuspend” não são cumulativas.
Protótipo da função:
void vTaskSuspend (TaskHandle_t xTaskToSuspend);
Parâmetro da função:
vTaskResume
Uma tarefa que foi suspensa será disponibilizada para execução novamente, o seu estado passará de Bloqueado para Pronto.
Protótipo da função:
void vTaskResume (TaskHandle_t xTaskToResume);
Parâmetro da função:
vTaskDelete
Remova uma tarefa do gerenciamento de kernels RTOS. A tarefa que está sendo excluída será removida de todas as listas de prontas, bloqueadas, suspensas e de eventos.
Protótipo da função:
void vTaskDelete (TaskHandle_t xTask);
Parâmetro da função:
Para finalizar, será apresentado um projeto de demonstração utilizando o ESP32 e as funções de criação e controle de tarefas do FreeRTOS. O projeto desenvolvido é bem simples, tendo como objetivo exemplificar cada um dos recursos apresentados. A aplicação conta com três tarefas (Tarefa_A, Tarefa_B e Tarefa_C) que imprime na serial seu nome e número da CPU que está executando.
Código fonte do projeto
/** * @file main.cpp * @author Evandro Teixeira * @brief * @version 0.1 * @date 06-01-2022 * * @copyright Copyright (c) 2022 * */ #include <Arduino.h> #include <freertos/queue.h> #include <freertos/task.h> // Macro com as cores #define COLOR_BLACK "\e[0;30m" #define COLOR_RED "\e[0;31m" #define COLOR_GREEN "\e[0;32m" #define COLOR_YELLOW "\e[0;33m" #define COLOR_BLUE "\e[0;34m" #define COLOR_PURPLE "\e[0;35m" #define COLOR_CYAN "\e[0;36m" #define COLOR_WRITE "\e[0;37m" #define COLOR_RESET "\e[0m" #define PRIMARY_CORE PRO_CPU_NUM #define SECONDARY_CORE APP_CPU_NUM #define TASK_DELAY 1000 enum // Estado da tarefa A { task_unsuspended = false, //tarefa não suspensa task_suspended = true //tarefa suspensa }; // Prototipo das funções void Tarefa_A(void *parameters); void Tarefa_B(void *parameters); void Tarefa_C(void *parameters); void status_tarefa_a_set(bool sts); bool status_tarefa_a_get(void); // Variaveis globais TaskHandle_t Handle_Tarefa_A = NULL; TaskHandle_t Handle_Tarefa_B = NULL; const uint32_t TaskDelay = TASK_DELAY; static bool status_tarefa_a; void setup() { // Inicializa a Serial Serial.begin(115200); Serial.printf("\n\rFreeRTOS - Tarefas\n\r"); // Set estado inicial da variavel status_tarefa_a como tarefa não suspensa status_tarefa_a_set(task_unsuspended); // Cria as tarefa do projeto xTaskCreatePinnedToCore(Tarefa_A, "Tarefa_A", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &Handle_Tarefa_A, PRIMARY_CORE); //xTaskCreatePinnedToCore(Tarefa_B, "Tarefa_B", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &Handle_Tarefa_B, tskNO_AFFINITY); xTaskCreate(Tarefa_B, "Tarefa_B", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &Handle_Tarefa_B); xTaskCreatePinnedToCore(Tarefa_C, "Tarefa_C", configMINIMAL_STACK_SIZE, (void*)TaskDelay, tskIDLE_PRIORITY + 3, NULL, SECONDARY_CORE); } void loop() { Serial.printf("LOOP\n\r"); vTaskSuspend(NULL); } /** * @brief * * @param sts */ void status_tarefa_a_set(bool sts) { status_tarefa_a = sts; } /** * @brief * * @return true * @return false */ bool status_tarefa_a_get(void) { return status_tarefa_a; } /** * @brief * * @param parameters */ void Tarefa_A(void *parameters) { static uint8_t counter_to_suspend = 0; // Contador de suspender a tarefa A while (1) { Serial.print(COLOR_RED); // altera para vermelho impressão da mensagem na serial Serial.print( pcTaskGetTaskName(NULL) ); Serial.print(" Core: "); Serial.println( xPortGetCoreID() ); // busca o identificador do CPU que esta executando a tarefa Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial if(counter_to_suspend < 4) { counter_to_suspend++; // incrementa contador vTaskDelay(TASK_DELAY/portTICK_PERIOD_MS); // Pausa a execução da tarefa por 1000 milessegundos } else { counter_to_suspend = 0; // reset contador status_tarefa_a_set(task_suspended); // Set estado inicial da variavel status_tarefa_a como tarefa suspensa vTaskSuspend(Handle_Tarefa_A); // Suspende a execução da tarefa } } } /** * @brief * * @param parameters */ void Tarefa_B(void *parameters) { static uint8_t counter_to_activate_task = 0; // contador para tirar da suspensão a tarefa A while (1) { Serial.print(COLOR_GREEN); // altera para verde impressão da mensagem na serial Serial.print( pcTaskGetTaskName(NULL) ); Serial.print(" Core: "); Serial.println(xPortGetCoreID()); // busca o identificador do CPU que esta executando a tarefa Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial if(status_tarefa_a_get() == task_suspended) // Checa se a tarefa A esta suspensa { if(counter_to_activate_task < 2) { counter_to_activate_task++; // Incrementa contador } else { counter_to_activate_task = 0; // Reset contador status_tarefa_a_set(task_unsuspended); // Set estado inicial da variavel status_tarefa_a como tarefa não suspensa vTaskResume(Handle_Tarefa_A); // Reativa a tarefa A } } vTaskDelay(TASK_DELAY/portTICK_PERIOD_MS); // Pausa a execução da tarefa por 1000 milessegundos } } /** * @brief * * @param parameters */ void Tarefa_C(void *parameters) { const TickType_t Delay = (TickType_t)parameters; // recebe o parametro adicionado na criação da tarefa static uint8_t counter_to_delete_task = 0; // contador para deletar a tarefa while (1) { Serial.print(COLOR_YELLOW); // altera para verde impressão da mensagem na serial Serial.print( pcTaskGetTaskName(NULL) ); Serial.print(" Core: "); Serial.println(xPortGetCoreID()); // busca o identificador do CPU que esta executando a tarefa Serial.print(COLOR_RESET); // reset a cor da impressão da mensagem na serial if(counter_to_delete_task < 4) { counter_to_delete_task++; // Incrementa contador vTaskDelay(Delay/portTICK_PERIOD_MS); // Pausa a execução da tarefa por 1000 milessegundos } else { vTaskDelete(NULL); // Deleta tarefa da execução } } }
A seguir temos a imagem com as mensagem de cada tarefa impressas no terminal serial.
LOG das mensagens no barramento serial
As mensagem na figura foram enumeradas para detalhar o comportamento do algoritmo implementado no projeto de demonstração:
Nota: O processo de troca de informação entre tarefas pode ser feita utilizando recursos do FreeRTOS. Utilizei variável global em conjunto com as funções de “get” e “set” simplesmente para facilitar o entendimento. Nos próximos artigos, serão detalhados os mecanismos de troca de informação entre tarefas.
Neste artigo inicial sobre os recursos oferecidos pelo o FreeRTOS, foram apresentadas as principais funções para manusear as tarefas no sistema operacional. Além disso, foi apresentado um projeto de demonstração, onde podemos observar a implementação destas funções. Ao final, analisamos o LOG das mensagens no barramento serial e pontuamos o comportamento de cada tarefa.
Para os próximos artigos, serão apresentados mais recursos do FreeRTOS. O que você achou? Você já utiliza o FreeRTOS em seus projetos? Deixe o seu comentário abaixo.
FreeRTOS
https://aws.amazon.com/pt/freertos/
FreeRTOS – API Reference
https://www.freertos.org/a00106.html
FreeRTOS Wikipédia
https://en.wikipedia.org/wiki/FreeRTOS
RTOS – Sistema Operacional de Tempo Real
https://blog.eletrogate.com/rtos-sistema-operacional-de-tempo-real/
RTOS com ESP32: Como Programar Multitarefas
https://blog.eletrogate.com/rtos-com-esp32-como-programar-multitarefas/
Como programar o Arduino com VS Code e PlatformIO
https://blog.eletrogate.com/como-programar-o-arduino-com-vs-code-e-platformio/
Troca de contexto Wikipédia
https://pt.wikipedia.org/wiki/Troca_de_contexto
ESP-IDF Programming Guide / FreeRTOS
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html#overview
Tenha a Metodologia Eletrogate na sua Escola! Conheça nosso Programa de Robótica Educacional.
|
A Eletrogate é uma loja virtual de componentes eletrônicos do Brasil e possui diversos produtos relacionados à Arduino, Automação, Robótica e Eletrônica em geral.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!