Olá, caro leitor. Tudo bem? Esse é o segundo artigo da série sobre o FreeRTOS. No primeiro post, foi passado de maneira bem sucinta os conceitos de kernel, escalonador e processo. O artigo tinha como objetivo apresentar os principais recursos oferecidos pelo FreeRTOS para manipular tarefas. Neste artigo, será mostrado o mecanismo para troca de informação entre tarefas, a fila (queue).
Filas, ou queues, em inglês, basicamente, são estruturas de dados do tipo FIFO (first-in first-out). Ou seja, o primeiro elemento adicionado à fila será o primeiro a ser removido. No FreeRTOS, filas são o principal meio de comunicação entre tarefas e interrupções. Além de possibilitar a troca de mensagem entre tarefas, o FreeRTOS permite que as queue sejam utilizadas como mecanismo de sincronização entre elas. A seguir, temos uma figura ilustrando o compartilhamento de dados entre duas tarefas.
A criação de queue é feita por meio da função xQueueCreate(). Basicamente, o sistema operacional cria um “buffer” na memória RAM. Para utilizar a fila no FreeRTOS, é necessário criar variável do tipo QueueHandle_t. Essa variável é utilizada como identificador da queue.
Nota: A criação de queue no FreeRTOS pode ser feita utilizando duas abordagens, a primeira, utilizando o método de alocação na memória RAM dinâmica, a partir do Heap. O segundo modo é por alocação de memória RAM estática em tempo de compilação. Para entender melhor a diferença entre alocação de memória estática vs dinâmica, consulte o link.
Protótipo da função:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
Parâmetros:
Retorno:
Para adicionar elementos em uma fila no FreeRTOS, existem duas tratativas. Quando a troca de informação ocorre entre tarefas, é recomendado o uso da função genérica para inserção de dados na fila. Quando a comunicação é feita entre interrupção ou rotina de “callback” para tarefa, deve ser utilizada uma função especializada.
xQueueSend é função genérica para inserir os dados na fila.
Protótipo da função:
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
Parâmetro:
Retorno:
A função especializada é xQueueSendFromISR. É recomendado utilizar essa função quando o dado é inserido a partir de uma interrupção ou uma função de “callback”.
Protótipo da função:
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
Parâmetros:
Retorno:
Semelhante com o que ocorre para inserir dados na fila, onde temos função genérica e especializada. Para remover dados da queue, é necessário obedecer a mesma regra. Quando a troca de informação ocorre entre tarefas, é recomendado o uso da função genérica para remoção de dados na fila. Quando a comunicação é feita entre interrupção ou “callback” para tarefa, deve ser utilizada uma função especializada.
xQueueReceive é função genérica para remover elementos da fila.
Protótipo da função:
BaseType_t xQueueReceive( QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
Parâmetros:
Retorno:
A função especializada que remove elementos da fila é xQueueReceiveFromISR.
Protótipo da função:
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken);
Parâmetros:
Para deletar uma fila, o FreeRTOS fornece a função vQueueDelete. Quando é excluída uma queue, é liberada toda a memória RAM alocada.
Protótipo da função:
void vQueueDelete( QueueHandle_t xQueue );
Parâmetros:
A aplicação desenvolvida para o projeto de demonstração executa a leitura de um botão do tipo “push button”, aciona o LED “on board” e publica no barramento serial o LOG de mensagem. O projeto consta com 03 tarefas e uma função trata da interrupção gerada pelo pressionar do botão. Para a troca de informação entre as tarefas e a função de interrupção, foram criadas duas filas.
A seguir, temos o código fonte desenvolvido para o projeto de demonstração, trazendo as funções apresentadas para o manuseio de queue no FreeRTOS.
Materiais necessários para o projeto Trocando Informação entre Tarefas com Filas
/** * @file main.cpp * @author Evandro Teixeira * @brief * @version 0.1 * @date 14-01-2022 * * @copyright Copyright (c) 2022 * */ #include <Arduino.h> #include <freertos/queue.h> #include <freertos/task.h> #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 MESSAGE_SIZE 32 // Tamanho da mensagem #define NUMBER_OF_ELEMENTS 8 // Número de elementos na fila #define BUTTON 15 // Pino do botão #define LED_BOARD 2 // Pino do LED #define DEBOUNCE_BUTTON 1000 // Tempo do debounce do botão // Estruta dados mensagem typedef struct { uint8_t Sts; char Txt[MESSAGE_SIZE]; }DataMsg_t; // Prototipo das tarefas void Tarefa_A(void *parameters); void Tarefa_B(void *parameters); void Tarefa_LED(void *parameters); // Cria Handle da Mensagem de tarefa de A para tarefa B QueueHandle_t Msg_A_para_B; // Cria Handle da Mensagem da interrupção do botão para a tarefa LED QueueHandle_t Button_para_LED; /** * @brief Função da interrupção botão */ void IRAM_ATTR Button_ISR() { // tempo da ultima leitura do botão static uint32_t last_time = 0; static DataMsg_t Msg = { .Sts = 0, .Txt = {0} }; // Algoritmo de debounce do botão if( (millis() - last_time) >= DEBOUNCE_BUTTON) { last_time = millis(); if(Msg.Sts == 1) { // Prepara dado para ser publicado na fila Msg.Sts = 0; sprintf(Msg.Txt,"LED BOARD: OFF"); } else { // Prepara dado para ser publicado na fila Msg.Sts = 1; sprintf(Msg.Txt,"LED BOARD: ON"); } // Publica dado na fila xQueueSendFromISR(Button_para_LED,&Msg,pdFALSE); } } /** * @brief */ void setup() { // Inicializa a Serial Serial.begin(115200); Serial.printf("\n\rFreeRTOS - Fila\n\r"); // Inicializa pino 15 como entra e inicializa interrupção do botão pinMode(BUTTON, INPUT); attachInterrupt(BUTTON, Button_ISR, RISING); // Inicializa pino do LED on Board pinMode(LED_BOARD,OUTPUT); digitalWrite(LED_BOARD,LOW); // Cria Fila de mensagem para comunicação entre as Tarefas A e B Msg_A_para_B = xQueueCreate( NUMBER_OF_ELEMENTS, (MESSAGE_SIZE * sizeof(char)) ); if(Msg_A_para_B == NULL) { Serial.printf("\n\rFalha em criar a fila Msg_A_para_B"); } // Cria Fila de mensagem para função da inturrupção do botão para a Tarefa LED Button_para_LED = xQueueCreate( NUMBER_OF_ELEMENTS, sizeof(DataMsg_t) ); if(Button_para_LED == NULL) { Serial.printf("\n\rFalha em criar a fila Button_para_LED"); } // Cria as tarefas da aplicação xTaskCreate(Tarefa_A, "Tarefa_A", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(Tarefa_B, "Tarefa_B", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY + 1, NULL); xTaskCreate(Tarefa_LED, "Tarefa_LED", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY + 2, NULL); } /** * @brief */ void loop() { Serial.printf("\n\rDeleta tarefa LOOP"); vTaskSuspend(NULL); } /** * @brief Tarefa A * * @param parameters */ void Tarefa_A(void *parameters) { // Variavel locais char txt[MESSAGE_SIZE] = {0}; uint8_t msg_counter = 0; // Imprime informação da tarefa no barramento serial Serial.print(COLOR_GREEN); Serial.printf("\n\r%s", pcTaskGetTaskName(NULL) ); Serial.print(COLOR_RESET); while (1) { // Prepara dado para ser publicado na fila sprintf(txt,"Msg number: %3d, task A to B",msg_counter++); // Imprime o conteudo a ser publicado no barramento serial Serial.print(COLOR_GREEN); Serial.printf("\n\r%s Envia -> %s", pcTaskGetTaskName(NULL), txt); Serial.print(COLOR_RESET); // Publica dado na fila if( xQueueSend( Msg_A_para_B, (void*)&txt, (TickType_t)1000 ) != pdTRUE) { Serial.print(COLOR_GREEN); Serial.printf("\n\rFalha em enviar os dados da fila"); Serial.print(COLOR_RESET); } // Pausa a Tarefa A por 05 segundos vTaskDelay(5000/portTICK_PERIOD_MS); } } /** * @brief Tarefa B * * @param parameters */ void Tarefa_B(void *parameters) { // Variavel locais char txt[MESSAGE_SIZE] = {0}; // Imprime informação da tarefa Serial.print(COLOR_YELLOW); Serial.printf("\n\r%s", pcTaskGetTaskName(NULL) ); Serial.print(COLOR_RESET); while (1) { // Checa se há dados na fila - e pausa a tarefa por 01 segundo if( xQueueReceive(Msg_A_para_B,&txt,(TickType_t)1000 ) == pdPASS) { // Imprime conteudo da fila no barramento serial Serial.print(COLOR_YELLOW); Serial.printf("\n\r%s Recebe -> %s", pcTaskGetTaskName(NULL),txt ); Serial.print(COLOR_RESET); } // Imprime informação da tarefa no barramento serial Serial.print(COLOR_YELLOW); Serial.printf("\n\r%s - time: %d s", pcTaskGetTaskName(NULL),(uint)(millis()/1000) ); Serial.print(COLOR_RESET); } } /** * @brief Tarefa LED * * @param parameters */ void Tarefa_LED(void *parameters) { DataMsg_t Msg; // Imprime informação da tarefa Serial.print(COLOR_RED); Serial.printf("\n\r%s", pcTaskGetTaskName(NULL) ); Serial.print(COLOR_RESET); while (1) { // Checa se há dados na fila - e suspende a tarefa enquato a fila estiver vazia if( xQueueReceive(Button_para_LED, &Msg, portMAX_DELAY) == pdPASS) { // Imprime conteudo da fila no barramento serial Serial.print(COLOR_RED); Serial.printf("\n\r%s Recebe -> %s | Sts: %d", pcTaskGetTaskName(NULL),Msg.Txt,Msg.Sts); Serial.print(COLOR_RESET); // Altera o valor do pino do LED on Board digitalWrite(LED_BOARD,Msg.Sts); } // Imprime informação da tarefa no barramento serial Serial.print(COLOR_RED); Serial.printf("\n\r%s - time: %d s", pcTaskGetTaskName(NULL),(uint)(millis()/1000) ); Serial.print(COLOR_RESET); } }
A seguir, temos a imagem com as mensagem de cada tarefa imprimindo no terminal serial.
As mensagem na figura foram enumeradas para detalhar o comportamento do algoritmo implementado no projeto de demonstração:
Neste segundo artigo da série sobre o FreeRTOS, foi apresentado o recurso de Fila (em inglês queue). Esse recurso é o principal mecanismo de troca de informação entre tarefas e processo (interrupções e funções de callback). No FreeRTOS, esse recurso além de permitir o compartilhamento de informação, funciona, também, como um instrumento para o sincronização de tarefas do sistema.
Após detalhar as principais funções para o manuseio de fila oferecida pelo o FreeRTOS, foi apresentado o projeto simples de demonstração. O projeto desenvolvido contém 03 tarefas, sendo que as tarefas A e B trocam mensagens entre si por meio de fila. A terceira tarefa é responsável pelo o acionamento do LED “on board”, que possui uma queue que recebe informação da função de interrupção que trata o evento do botão. Ao final, analisamos o LOG de mensagem impressa no barramento serial para o melhor entendimento do algoritmo.
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 Queues
https://www.freertos.org/Embedded-RTOS-Queues.html
Queue Management
https://www.freertos.org/a00018.html
Princípios básicos do kernel do FreeRTOS
https://docs.aws.amazon.com/pt_br/freertos/latest/userguide/dev-guide-freertos-kernel.html
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!