Sistemas Operacionais

FreeRTOS – Sinalizando eventos com Event Bit e Group

Eletrogate 14 de abril de 2022

Introdução

Olá, caro leitor! Tudo bem? Estamos dando continuidade à série de artigos sobre o sistema operacional de tempo real, o FreeRTOS. Nos posts que precederam o post anterior, foram apresentados recursos como semáforo e mutex, mecanismos para sincronização de tarefas. Neste artigo, será apresentado mais um recurso para sincronização de tarefas, o Event Bits (ou Flags) e Event Groups.


Eventos

Eventos são implementados para sincronização de tarefas, como uma tarefa que espera no estado bloqueado, aguardando pela ocorrência de um determinado evento. Em sua ocorrência, a tarefa é desbloqueada.
Como visto nos artigos anteriores, podemos utilizar semáforos e filas para sincronizar o evento com a execução de uma tarefa. Porém, esses recursos (filas e semáforos) permitem vincular apenas um evento a uma tarefa.
Os “Event Bits (ou Flags)” e “Event Groups” podem ser usados para sincronizar várias tarefas usando um único evento ou vários eventos. Além disso, eles também podem bloquear uma única tarefa, aguardando a ocorrência de vários eventos, e desbloquear a tarefa quando o conjunto de eventos ocorrer.


Event Bits (or Flags)

Event Bits (em português, bits de evento) são utilizados para indicar se um determinado evento ocorreu ou não. Frequentemente, são chamados de sinalizadores de evento.


Event Groups

Event Groups (em português grupos de eventos). Um grupo de eventos é um conjunto de sinalizadores de eventos. São bits de evento individuais dentro de grupo que são referenciados por um número de bit. 


Tipo de Dados

Como qualquer outro objeto no FreeRTOS, grupos de eventos são um determinado tipo de variável. No caso de Event Groups, o tipo é EventGroupHandle_t

O FreeRTOS possui a macro configUSE_16_BIT_TICKS, que permite configurar o número de sinalizadores armazenados em um grupo de eventos. Se a macro estiver definida como valor igual a 1, significa que serão armazenados 8 event bits. Caso a macro esteja definida com o valor 0, temos 24 event bits. 

Todos os bits de evento em um grupo de eventos são armazenados em uma única variável sem sinal do tipo EventBits_t. O bit de evento 0 é armazenado na posição de bit 0, o bit de evento 1 é armazenado na posição de bit 1, e assim por diante.

A seguir, temos a imagem de representação de um grupo de evento de 24 bits que contém 3 event bits, de forma que apenas o bit 2 está definido.


Criando Event Group

Conforme citado nos artigos anteriores, o FreeRTOS contém duas abordagem para manipular a memória RAM. A primeira, utilizando o método de alocação da memória RAM dinâmica, a partir do Heap e a segunda, 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. Para esse e os demais artigos, vamos explicar as funções do FreeRTOS que trabalham com alocação de memória RAM dinâmica.

Como dito anteriormente, o EventGroupHandle_t é o tipo de variável do event groups. Para criar um grupo de eventos, é necessário declarar uma variável. Essa variável será utilizada como o identificador. 

Protótipo da função:

EventGroupHandle_t xEventGroupCreate( void );

Parâmetro: Nenhum

Retorno: Se Event Group for criado com sucesso, o identificador será retornado. Na ocorrência de algum erro na criação do grupo de evento, o valor a ser retornado será NULL


Checando Event Bits no Event Group

Diferente de outras API’s do FreeRTOS, não existem funções genérica e especializada. Esta função pode ser executada a partir de uma rotina de interrupção.  

Protótipo da função:

EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToWaitFor,
                                 const BaseType_t xClearOnExit,
                                 const BaseType_t xWaitForAllBits,
                                 TickType_t xTicksToWait );

Parâmetros :

  • xEventGroup: O identificador do grupo de eventos no qual os bits serão checados.
  • uxBitsToWaitFor: O valor, bit a bit, de quais sinalizadores serão testados no Event Group. O valor não deve ser definido como 0.
  • xClearOnExit: Se xClearOnExit for definido como pdTRUE, quaisquer bits definidos no valor passado no parâmetro uxBitsToWaitFor serão limpos no grupo de eventos. Se o valor for igual a pdFALSE os bits definidos no grupo de eventos não serão alterados.
  • xWaitForAllBits: Utilizado para criar um teste AND lógico (onde todos os bits devem ser definidos) ou um teste OR lógico (onde um ou mais bits devem ser definidos). Se o parâmetro for definido como pdTRUE, será aplicado o teste de AND lógico. Se o valor for igual a pdFALSE, será executado o teste de OR lógico.
  • xTicksToWait: Especifica a quantidade máxima de tempo (especificada em ‘ticks’) para aguardar que um/todos os bits estejam acionados (dependendo do valor de xWaitForAllBits). 

Retorno: 

  • Retorna o valor do Event Group no momento em que os Event Bits que estão sendo aguardados foram definidos ou que o tempo de bloqueio expirou. 

Definindo Event Bits

Diferente da API de checagem de bits do grupo de eventos, para definir bits em Event Group temos duas tratativas distintas na utilização das API’s do FreeRTOS, existem funções especializadas e funções genéricas. Quando for definir algum bit do grupo de eventos a partir de uma tarefa, deve-se utilizar a função genérica. Se o event bit for definido a partir de uma rotina de interrupção ou de uma função de callback, é recomendado usar a API especializada.

xEventGroupSetBits é função genérica para definir event bits.

Protótipo da função: 

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet );

Parâmetros: 

  • xEventGroup:  O identificador do grupo de eventos no qual o Event Bits deve ser definido.
  • uxBitsToSet:  O valor, bit a bit, de quais sinalizadores devem ser definidos. 

Retorno:

  • Retorna o valor do Event Group no momento em que os bits foram acionados.

xEventGroupSetBitsFromISR é função especializada para definir event bits.

BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                      const EventBits_t uxBitsToSet,
     	                              BaseType_t *pxHigherPriorityTaskWoken );

Parâmetros: 

  • xEventGroup:  O identificador do grupo de eventos no qual o Event Bits deve ser definido.
  • uxBitsToSet:  O valor, bit a bit, de quais sinalizadores devem ser definidos. 
  • pxHigherPriorityTaskWoken: Se for igual a pdTRUE, indica que uma troca de contexto deve ser solicitada antes que a interrupção termine. 

Retorno:

  • O valor pdPASS será retornado quando os event bits forem definidos com sucesso. Se o valor for igual a pdFAIL, ocorreu alguma falha ao definir os bits de evento. 

Limpando Event Bits

Após sinalizar a ocorrência de um determinado evento, é necessário limpar o sinalizador, para que, quando acontecer novamente, possa ser sinalizado. Para limpar os bits de evento, deve-se aplicar a mesma trativa: função genérica para ser utilizada a partir de uma tarefa e API especializada para uso em interrupções e funções de callback. 

xEventGroupClearBits é a função genérica para limpar os bit de eventos.

Protótipo da função:

EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
                                  const EventBits_t uxBitsToClear );

 

Parâmetros: 

  • xEventGroup: O identificador do grupo de eventos no qual se pretende limpar o Event Bit.
  • uxBitsToClear: O valor, bit a bit, de quais bits de evento se pretende limpar.

Retorno:

  • O valor do grupo de eventos antes que os bits especificados fossem “limpos”.

xEventGroupClearBitsFromISR é a função especializada para limpar os bit de eventos

Protótipo da função:

BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup,
                                        const EventBits_t uxBitsToClear );

Parâmetros:

  • xEventGroup: O identificador do grupo de eventos no qual se pretende limpar o Event Bit. 
  • uxBitsToClear: O valor, bit a bit, de quais bits de evento se pretende limpar. 

Retorno:

  • O valor pdPASS será retornado quando os event bits forem limpos com sucesso. Se o valor for igual a pdFAIL, ocorreu alguma falha ao limpar os bit de eventos.

Obtendo Valores Event Group

Além das APIs para definir, checar e limpar os bits de eventos, o FreeRTOS oferece funções para ler o valor do event groups. Para obter os valores, deve-se aplicar a tratativa de função genérica para ser utilizada a partir de uma tarefa e API especializada para uso em interrupções e funções de callback.   

xEventGroupGetBits é a genérica.

Protótipo da função:

EventBits_t xEventGroupGetBits( EventGroupHandle_t xEventGroup );

Parâmetro: 

  • xEventGroup: O identificador do grupo de eventos do qual se pretende checar os Event Bits.

Retorno: 

  • O valor dos bits do Event Group. 

xEventGroupGetBitsFromISR é a função especializada.

Protótipo da função:

EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );

Parâmetro: 

  • xEventGroup: O identificador do grupo de eventos do qual se pretende checar os Event Bits.

Retorno: 

  • O valor dos bits do Event Group.

Excluindo Event Group

Como qualquer outro objeto do FreeRTOS, os event groups podem ser deletados. Quando é excluído um grupo de eventos, é liberada toda a memória RAM alocada. As tarefas bloqueadas no grupo de eventos que está sendo excluído serão desbloqueadas.

Protótipo da função:

void vEventGroupDelete( EventGroupHandle_t xEventGroup );

Parâmetros:

  • xEventGroup: O grupo de eventos que deve ser deletado.

Retorno: Nenhum.


Projeto de Demonstração

O projeto desenvolvido para exemplificar o uso de “Event Bits (flags) and Event Groups” foi um “Piano Digital”. O projeto conta com teclado de membrana de 16 teclas e um buzzer. 

Para cada tecla pressionada, o buzzer reproduz um som diferente. Para isso, o algoritmo da aplicação conta com duas tarefas. A primeira é a “Tarefa_Teclado”, responsável por ler os sinais do teclado. A segunda é “Tarefa_Buzzer”, encarregada de gerenciar o acionamento do buzzer. 

A “Tarefa_Teclado”, quando detecta se alguma tecla foi pressionada, define o event bit corresponde da tecla. Por sua vez, a “Tarefa_Buzzer” checa event groups e, se tiver algum bit ativo, é acionado o buzzer.

 

 

Material necessário para o projeto Sinalizando eventos com Event Bit e Group

/**
 * @file main.cpp
 * @author Evandro Teixeira
 * @brief 
 * @version 0.1
 * @date 06-03-2022
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <Arduino.h>
#include <freertos/queue.h>
#include <freertos/semphr.h>
#include <freertos/timers.h>
#include <freertos/task.h>
#include "MatrixKeyboard.hpp"
#include "Buzzer/buzzer.hpp"

#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 BUZZER_FREQUENCY        30                  // Frequency 2000 Hz
#define BUZZER_DUTYCYCLE        75                  // Init DutyCycle 50%
#define BUZZER_CONFIG           (BuzzerPin_t){15,4} // Pin, Channel PWM
#define SW_TIMER_PERIOD_BUZZER  500                 // 500 ms
#define KEY_ALL_BITS            0x0000FFFF
#define BAUDE_RATE              115200              

typedef struct 
{
  BuzzerNote_t Note;
  uint8_t Octave;
  char StringNote[8];
}BuzzerApp_t; 

const BuzzerApp_t NoteBuzzer[16] = {
  {Buzzer_Note_A,0,"Nota A"},
  {Buzzer_Note_A,4,"Nota A"},
  {Buzzer_Note_B,0,"Nota B"},
  {Buzzer_Note_B,4,"Nota B"},
  {Buzzer_Note_C,0,"Nota C"},
  {Buzzer_Note_C,4,"Nota C"},
  {Buzzer_Note_D,0,"Nota D"},
  {Buzzer_Note_D,4,"Nota D"},
  {Buzzer_Note_E,0,"Nota E"},
  {Buzzer_Note_E,4,"Nota E"},
  {Buzzer_Note_F,0,"Nota F"},
  {Buzzer_Note_F,4,"Nota F"},
  {Buzzer_Note_G,0,"Nota G"},
  {Buzzer_Note_G,4,"Nota G"},
  {Buzzer_Note_Gs,0,"Nota Gs"},
  {Buzzer_Note_Gs,4,"Nota Gs"},
};

/**
 * @brief Cria objeto Buzzer
 * @return buzzer 
 */
buzzer Buzzer(BUZZER_CONFIG);
/**
 * @brief Cria objeto Teclado
 * @return MatrixKeyboard 
 */
MatrixKeyboard Teclado(Key4x4);
EventGroupHandle_t xEventGroupKey;
TimerHandle_t TimerBuzzer;

/**
 * @brief 
 * @param parameters 
 */
void Tarefa_Teclado(void *parameters);
void Tarefa_Buzzer(void *parameters);
void Callback_TimerBuzzer(TimerHandle_t timer);

void setup() 
{
  KeyboardPin_t ConfigPin;
  ConfigPin.line1 = 16;
  ConfigPin.line2 = 17;
  ConfigPin.line3 = 18;
  ConfigPin.line4 = 32;
  ConfigPin.column1 = 33;
  ConfigPin.column2 = 13;
  ConfigPin.column3 = 2;
  ConfigPin.column4 = 4;

  // Inicializa Serial
  Serial.begin(BAUDE_RATE);
  Serial.printf("\n\rFreeRTOS - Event Groups\n\r");

  // Inicializa o Buzzer 
  Buzzer.begin(BUZZER_FREQUENCY,BUZZER_DUTYCYCLE);
  Buzzer.stop();

  // Inicializa Teclado
  Teclado.Init(ConfigPin);

  // Cria Event Group
  xEventGroupKey = xEventGroupCreate();
  if(xEventGroupKey == NULL)
  {
    Serial.printf("\n\rFalha em criar a Event Group xEventGroupKey");
  }

  // Cria SoftwareTimer  TimerBuzzer
  TimerBuzzer = xTimerCreate("TIMER_PULSO",pdMS_TO_TICKS(SW_TIMER_PERIOD_BUZZER),pdFALSE,NULL,Callback_TimerBuzzer);
  if(TimerBuzzer == NULL)
  {
    Serial.printf("\n\rFalha em criar SW_Timer TimerBuzzer");
  }

  // Cria tarefas da aplicação
  xTaskCreate(Tarefa_Teclado, "TECLADO", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY + 2, NULL);
  xTaskCreate(Tarefa_Buzzer, "BUZZER", configMINIMAL_STACK_SIZE * 2, NULL, tskIDLE_PRIORITY + 1, NULL);
}

void loop() 
{
  Serial.printf("\n\rSupende tarefa LOOP");
  vTaskSuspend(NULL);
}

/**
 * @brief 
 * @param parameters 
 */
void Tarefa_Teclado(void *parameters)
{
  uint16_t keyOld = 0xFFFF;
  static Key_t Key;
  char ch;

  while(1)
  {
    // Executa rotina do teclado
    Teclado.Run();
    // Le as informações do teclado
    Key = Teclado.Read();
    ch = Teclado.GetCharKey();

    if(keyOld != Key.data)
    {
      if(Key.data != 0)
      {
        Serial.print(COLOR_RED);
        Serial.printf("\n\rInformações do teclado: %d | Tecla: %c ",Key.data, ch);
        Serial.printf("\n\rDefine Bit em xEventGroupKey %04X",Key.data);
        Serial.print(COLOR_RESET);

        xEventGroupSetBits(xEventGroupKey,(EventBits_t)(Key.data));
      }
    }
    // update
    keyOld = Key.data;

    vTaskDelay(50/portTICK_PERIOD_MS);
  }
}

/**
 * @brief 
 * @param parameters 
 */
void Tarefa_Buzzer(void *parameters)
{
  EventBits_t keyBits;
  uint16_t value = 0x0001;
  uint8_t i = 0;

  while(1)
  { 
    /* Le os bits xEventGroupKey */
    keyBits = xEventGroupWaitBits(xEventGroupKey, (EventBits_t)(KEY_ALL_BITS), true, true, pdMS_TO_TICKS(1000) );

    /* Busca qual bit esta definido */
    for(i=0;i<16;i++)
    {
      value = 1 << i;
      if(keyBits & value)
      {
        Serial.print(COLOR_YELLOW);
        Serial.printf("\n\rkeyBits:%5d - %s Octave: %d",keyBits,NoteBuzzer[i].StringNote,NoteBuzzer[i].Octave);
        Serial.printf("\n\rLiga Buzzer | Inicia TimerBuzzer | Limpa bit xEventGroupKey "); 
        Serial.print(COLOR_RESET);

        // Liga Buzzer por 500 millessegundos
        Buzzer.start();
        Buzzer.note(NoteBuzzer[i].Note, NoteBuzzer[i].Octave);
        
        // Inicia TimerBuzzer com 500 millessegundos
        xTimerStart(TimerBuzzer, 0 );

        // Limpa bit xEventGroupKey
        xEventGroupClearBits(xEventGroupKey, value);
        break;
      }
    }
  }
}

/**
 * @brief 
 * @param timer 
 */
void Callback_TimerBuzzer(TimerHandle_t timer)
{
  if(timer == TimerBuzzer)
  {
    // Encera o TimerBuzzer
    xTimerStopFromISR(TimerBuzzer, pdFALSE);
    // Desliga Buzzer
    Buzzer.stop();

    Serial.print(COLOR_BLUE);
    Serial.printf("\n\rDesliga Buzzer | Encera o TimerBuzzer "); 
    Serial.print(COLOR_RESET);
  }
}

Resultado da Aplicação

A seguir, temos a imagem com o LOG de mensagem de cada tarefa impressa no terminal serial.

As mensagens 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 serial, é transmitida a mensagem “FreeRTOS – Mutex”.
  2. Observe que a mensagem “Suspende tarefa LOOP” é impressa uma única vez. Isso ocorre porque 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).
  3. A “Tarefa_Teclado” identifica que a tecla número 1 foi pressionada. Em seguida, define o bit correspondente a tecla no Event Groups. 
  4. A “Tarefa_Buzzer” checa que o Event Groups possui o bit ativo correspondente à tecla número 1, aciona o buzzer e dispara o temporizador “TimerBuzzer”. 
  5. Expirando o “TimerBuzzer”, desliga-se o buzzer e é encerrado o temporizador.     

Os ciclos se repetem para cada nova tecla pressionada, mudando apenas o som reproduzido pelo buzzer.


Conclusão

Neste artigo, apresentamos mais um recurso bem interessante do FreeRTOS, o “Event Bits (flags) e Event Groups”. Esse mecanismo permite sincronizar várias tarefas usando um único evento ou vários eventos. Além disso, ele também pode bloquear uma única tarefa aguardando a ocorrência de vários eventos e desbloquear a tarefa quando o conjunto de eventos ocorrer. No artigo, detalhamos as principais API’s para o manipular o “Event Bits (flags) e Event Groups” no FreeRTOS e apresentamos uma aplicação de demonstraçã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

14 de abril 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!