Por trás da tecnologia

FreeRTOS – Filas, Trocando Informação entre Tarefas

Eletrogate 22 de fevereiro de 2022

Introdução

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).


Fila

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.

Diagrama de troca de mensagem entre tarefas


Criando Fila

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:

  • uxQueueLength: numero de elementos que a fila irá conter.
  • uxItemSize: tamanho em byte de cada elemento da fila.

Retorno:

  • Quando a fila é criada com sucesso, a função retorna o identificador da fila. Na ocorrência de falha na criação, o valor é igual a NULL.

Inserindo Dados na Fila

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:

  • xQueue: variável do tipo QueueHandle_t com o identificador da fila.
  • pvItemToQueue: ponteiro do conteúdo a ser postado na fila.
  • xTicksToWait: tempo máximo que a tarefa deve bloquear esperando que haja espaço disponível na fila, caso a queue esteja cheia.

Retorno:

  • A função retorna o valor pdTRUE quando elemento inserido com sucesso na fila.

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:

  • xQueue: Variável do tipo QueueHandle_t com o identificador da fila.
  • pvItemToQueue: Ponteiro do conteúdo a ser postado na fila.
  • pxHigherPriorityTaskWoken: Esse parâmetro, definido como pdTRUE, diz uma troca de contexto deve ser solicitada ao kernel antes que a interrupção seja encerrada.

Retorno:

  • A função retorna o valor pdTRUE quando elemento inserido com sucesso na fila.

Removendo Dados da Fila

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:

  • xQueue: variável do tipo QueueHandle_t com o identificador da fila.
  • pvBuffer: ponteiro para o buffer no qual o elemento da fila será copiado.
  • xTicksToWait: tempo máximo que a tarefa deve permanecer bloqueada à espera de um elemento na fila, caso a fila esteja vazia.
    Se esse parâmetro for definido com o valor 0, faz com que a tarefa retorne imediatamente se a fila estiver vazia.
    Se o parâmetro for definido com o valor igual a portMAX_DELAY fará com que a tarefa seja bloqueada indefinidamente (sem tempo limite), aguardando por um dado na fila.

Retorno:

  • A função retorna o valor pdTRUE quando o dado é removido com sucesso.

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:

  • xQueue: variável do tipo QueueHandle_t com o identificador da fila.
  • pvBuffer: ponteiro para o buffer no qual o elemento da fila será copiado.
  • pxHigherPriorityTaskWoken: Uma tarefa pode estar bloqueada esperando que o espaço fique disponível na fila. Se xQueueReceiveFromISR fizer com que essa tarefa seja desbloqueada, *pxHigherPriorityTaskWoken será definido como pdTRUE, caso contrário, *pxHigherPriorityTaskWoken permanecerá inalterado.

Deletando Fila

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:

  • xQueue: variável do tipo QueueHandle_t com o identificador da fila que será excluída.

Projeto de Demonstração

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

  1. Módulo WiFi ESP32s Bluetooth 38 pinos
  2. Resistor 10K Ohms
  3. Push Button (Chave Táctil)

Circuito elétrico do projeto de demonstração

/**
 * @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); 
  }
}

Resultado da Aplicação

A seguir, temos a imagem com as mensagem de cada tarefa imprimindo no terminal serial.

LOG de mensagem

 

As mensagem na figura foram enumeradas para detalhar o comportamento do algoritmo implementado no projeto de demonstração:

  1. Após a inicialização da comunicação a serial, é transmitida a mensagem “FreeRTOS – Fila”.
  2. A “Tarefa LED”, por possuir prioridade maior que as demais tarefas, é a primeira a ser executada, antes de entrar em seu loop infinito, é enviado a mensagem “Tarefa_LED” no barramento serial. Uma vez no loop infinito, a mesma é bloqueada, aguardando por elementos na fila.
  3. Observe que a mensagem “Deleta tarefa LOOP” é impressa uma única vez. Isso ocorre por que a função loop se comporta como uma tarefa agendada no escalonador do sistema operacional. Portanto, podemos utilizar os recursos oferecidos pelo FreeRTOS. Neste caso, foi utilizada a função que suspende a execução de tarefa (vTaskSuspend).
  4. As tarefas A e B imprimem suas mensagens iniciais no barramento serial.
  5. As tarefas A e B se comunicam por meio da queue “Msg_A_para_B”. A publica dados na fila e, por sua vez, a tarefa B remove esse conjunto de dados e imprime no barramento serial.
  6. A tarefa A publica os seus dados na fila a cada 05 segundos. B, por sua vez, checa se há dados na queue a cada 01 segundos. Por conta disso, podemos observar B imprimindo mensagens de maneira consecutiva na serial.
  7. Quando o botão é pressionado, a função “Button_ISR” é executada para tratar o evento de interrupção. Essa função contém o algoritmo de “debounce” da leitura do botão e, para todas as leituras válidas, é publicado um conjunto de dados na fila “Button_para_LED”. Uma vez com dados na fila, a Tarefa LED é desbloqueada, remove o conteúdo da fila e, em seguida, processa a informação. Para esse primeiro caso, é o acionamento do LED “on board”.
  8. Mais uma vez o botão é pressionado, a função de interrupção executada, o dado na fila é inserido, a Tarefa LED sai do estado bloqueada, remove o elemento da queue e processa o dado que, dessa vez, desliga o LED.

Conclusã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.


Sobre o Autor


Evandro Teixeira

Desenvolvedor de sistemas embarcados com mais de 10 anos de experiência, com atuação em diferentes segmentos de mercado tais como eletromédicos, automação industrial, ITS (Sistemas Inteligentes de Transporte) e automotivo.


Eletrogate

22 de fevereiro de 2022

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!

Eletrogate Robô

Cadastre-se e fique por
dentro de novidades!