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 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 (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 (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.
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.
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.
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 :
Retorno:
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:
Retorno:
xEventGroupSetBitsFromISR é função especializada para definir event bits.
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup, const EventBits_t uxBitsToSet, BaseType_t *pxHigherPriorityTaskWoken );
Parâmetros:
Retorno:
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:
Retorno:
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:
Retorno:
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:
Retorno:
xEventGroupGetBitsFromISR é a função especializada.
Protótipo da função:
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup );
Parâmetro:
Retorno:
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:
Retorno: Nenhum.
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.
/** * @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); } }
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:
Os ciclos se repetem para cada nova tecla pressionada, mudando apenas o som reproduzido pelo buzzer.
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.
Event Groups
https://www.freertos.org/event-groups-API.html
Event Bits (or flags) and Event Groups
https://www.freertos.org/FreeRTOS-Event-Groups.html
Github – Biblioteca teclado de membrana 4×4
https://github.com/evandro-teixeira/Membrane_keypad_4x4
Github – Projeto de demonstração
https://github.com/evandro-teixeira/Membrane_keypad_4x4/blob/main/Example/main.cpp
Imagem – Event Group variável de 24 bits
https://www.freertos.org/fr-content-src/uploads/2018/07/24-bit-event-group.gif
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.
Conheça a Metodologia Eletrogate e Lecione um Curso de Robótica nas Escolas da sua Região!