Olá, caro leitor! Tudo bem? No último artigo publicado, apresentamos mecanismos de sincronização e instrumentos para trabalhar com recursos compartilhados entre processos. Neste artigo, será apresentada a ferramenta Software Timer (temporizadores de software).
Em poucas palavras, “Software Timer” é um temporizador que permite que uma determinada função seja executada em certo tempo estabelecido no futuro. A função executada pelo “Software Timer” é chamada de função de retorno do temporizador (timer’s callback function). O intervalo de tempo entre o início do temporizador e o momento em que a função de retorno é executada é chamado de “período do cronômetro” (timer period).
O Software Timer disponível no FreeRTOS permite dois modos de operação:
As funções de retorno de chamada do temporizador são executadas no contexto da tarefa de serviço do “Software Timer”. Portanto, é essencial que as funções de retorno do temporizador nunca tentem o bloquear. Por exemplo, uma função de retorno não deve chamar as API’s vTaskDelay(), vTaskDelayUntil() ou especificar um tempo de bloqueio diferente de zero ao acessar uma fila ou um semáforo.
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 para qualquer outro objeto do FreeRTOS, é necessário, para o uso deste recurso, declarar uma variável. Para o Software Timer, o tipo é TimerHandle_t. Essa variável será utilizada como o identificador do temporizador.
Protótipo da função
TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriod, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction );
Parâmetros:
void vCallbackFunction( TimerHandle_t xTimer );
Retorno:
Se o temporizador foi criado com sucesso, o identificador para o “Software Timer” será retornado. Caso ocorra alguma falha será retornado o valor NULL.
Como já visto nos artigos anteriores, existem duas tratativas distintas na utilização das API’s do FreeRTOS, funções especializadas e funções genéricas. O “Software Timer” obedece a essa regra. Quando o início do temporizador for feito a partir de uma tarefa, deve-se usar a função genérica. Se o início do Software Timer for a partir de uma rotina de interrupção ou de uma função de “callback” é recomendado utilizar a API especializada.
xTimerStart é a função genérica para iniciar o temporizador.
Protótipo da função
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xBlockTime );
Parâmetros:
Retorno
O valor pdPASS será retornado quando o Software Timer iniciado com sucesso. Se o valor for igual a pdFAIL, significa que ocorreu alguma falha em iniciar o temporizador.
xTimerStartFromISR é a função especializada para iniciar o software timer.
Protótipo da função
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken );
Parâmetros:
Retorno:
O valor pdPASS será retornado quando o Software Timer iniciado com sucesso. Se o valor for igual a pdFAIL, significa que ocorreu alguma falha em iniciar o temporizador.
Uma vez inicializado o temporizador, podemos o interromper. Como visto, para iniciar o “Software Timer”, o FreeRTOS possui duas abordagens, oferecendo funções genéricas e especializadas. Para interromper o “Software Timer” é 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.
xTimerStop é a função genérica para interromper o temporizador.
Protótipo da função
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xBlockTime );
Parâmetros:
Retorno:
O valor pdPASS será retornado quando o “Software Timer” for parado/interrompido com sucesso. Se o valor for igual a pdFAIL, ocorreu alguma falha ao parar o temporizador.
xTimerStopFromISR é a função especializada para interromper o temporizador.
Protótipo da função
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer, BaseType_t *pxHigherPriorityTaskWoken );
Parâmetros:
Retorno:
O valor pdPASS será retornado quando o Software Timer for parado com sucesso. Se o valor for igual a pdFAIL, significa que ocorreu alguma falha ao parar o temporizador.
Tão importante como iniciar e parar o temporizador, é alterar o período do Software Timer.
xTimerChangePeriod é a função genérica para alterar o período do temporizador.
Protótipo da função
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer, TickType_t xNewPeriod, TickType_t xBlockTime );
Parâmetros:
Retorno:
O valor pdPASS será retornado quando o “Software Timer” tiver seu período alterado com sucesso. Se o valor for igual a pdFAIL, significa que ocorreu alguma falha na atualização deste valor.
xTimerChangePeriodFromISR é a função especializada para alterar o período do temporizador.
Protótipo da função
BaseType_t xTimerChangePeriodFromISR ( TimerHandle_t xTimer, TickType_t xNewPeriod, BaseType_t *pxHigherPriorityTaskWoken );
Parâmetros:
Retorno:
O valor pdPASS será retornado quando for alterado o período do Software Timer com sucesso. Se o valor for igual a pdFAIL quando ocorrer alguma falha ao altera-lo.
Como qualquer objeto no FreeRTOS, o “Software Timer” pode ser deletado. Quando é excluído um “Software Timer”, é liberada toda a memória RAM alocada para ele.
Protótipo da função
BaseType_t xTimerDelete(TimerHandle_t xTimer, TickType_t xBlockTime);
Parâmetro:
Retorno:
O valor pdPASS será retornado quando o “Software Timer” for deletado com sucesso. Se o valor for igual a pdFAIL quando ocorrer alguma falha ao deletar o temporizador.
O projeto de demonstração é composto por algoritmos de detecção e medição da largura do pulso. A partir da leitura da largura do pulso, é acionado o buzzer com diferente frequência e tons sonoros.
O código fonte desenvolvido para o projeto conta com duas tarefas, a primeira é “Tarefa_Pulso”, responsável pela detecção e leitura da largura do pulso. Para realizar a medida da largura do pulso, foi criado um “Software Timer”. O algoritmo da “Tarefa_Pulso”, quando detecta a borda de descida, inicia o temporizador e, quando detecta a borda de subida, o interrompe. Então, realiza a leitura do pulso e publica em uma fila para a segunda tarefa.
A segunda tarefa é “Tarefa_Buzzer” que, por sua vez, aguarda a informação da largura do pulso por meio da fila. Quando recebe a informação, checa o tamanho da largura para decidir o período, frequência e o tom sonoro a ser executado. Para controlar o tempo de acionamento do buzzer, foi criado um segundo “Software Timer”. Uma vez verificado o comprimento do pulso, o buzzer é acionado por meio deste temporizador.
Quando o tempo do segundo “Software Timer” é expirado, a “Tarefa_Buzzer” é notificada, por meio de um semáforo binário, para realizar o desligamento do buzzer.
A seguir, temos a figura com o circuito elétrico desenvolvido para o projeto de demonstração.
Código fonte
/** * @file main.cpp * @author Evandro Teixeira * @brief * @version 0.1 * @date 26-02-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 "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 PIN_BUTTON 14 // GPIO14 #define LED_BOARD 2 // Pino do LED #define DEBOUNCE_BUTTON 50 // Tempo do debounce do botão #define SW_TIMER_PERIOD_PULSO 10 // 10 ms #define SW_TIMER_PERIOD_BUZZER 500 // 500 ms #define NUMBER_OF_ELEMENTS 8 // Número de elementos na fila /** * @brief Cria objeto Buzzer * @return buzzer */ buzzer Buzzer(BUZZER_CONFIG); /** * @brief */ SemaphoreHandle_t xSemaphore_BuzzerOff = NULL; SemaphoreHandle_t xMutex_PulseTimerCounter = NULL; TimerHandle_t TimerPulso; TimerHandle_t TimerBuzzer; QueueHandle_t QueuePulso; uint32_t PulseTimerCounter; /** * @brief * @param parameters */ void Tarefa_Pulso(void *parameters); void Tarefa_Buzzer(void *parameters); uint32_t PulseTimerCounter_Get(void); void PulseTimerCounter_Clear(void); void PulseTimerCounter_Increment(void); void Callback_TimerPulso(TimerHandle_t timer); void Callback_TimerBuzzer(TimerHandle_t timer); void setup() { // Inicializa a Serial Serial.begin( 115200 ); Serial.printf("\n\rFreeRTOS - Software Timer\n\r"); // Inicializa o Buzzer Buzzer.begin(BUZZER_FREQUENCY,BUZZER_DUTYCYCLE); Buzzer.stop(); // Inicializa pino 14 como entrada pinMode(PIN_BUTTON, INPUT_PULLUP); // Inicializa pino do LED on Board pinMode(LED_BOARD,OUTPUT); digitalWrite(LED_BOARD,LOW); // Cria semafaro binario xSemaphore_Pulso vSemaphoreCreateBinary( xSemaphore_BuzzerOff ); if(xSemaphore_BuzzerOff == NULL) { Serial.printf("\n\rFalha em criar o semafaro xSemaphore_BuzzerOff"); } // Cria Mutex xMutex_PulseTimerCounter xMutex_PulseTimerCounter = xSemaphoreCreateMutex(); if(xMutex_PulseTimerCounter == NULL) { Serial.printf("\n\rFalha em criar o Mutex para variavel global xMutex_PulseTimerCounter"); } // Cria SoftwareTimer TimerPulso TimerPulso = xTimerCreate("TIMER_PULSO",pdMS_TO_TICKS(SW_TIMER_PERIOD_PULSO),pdTRUE,NULL,Callback_TimerPulso); if(TimerPulso == NULL) { Serial.printf("\n\rFalha em criar SW_Timer TimerPulso"); } // Cria SoftwareTimer TimerBuzzer TimerBuzzer = xTimerCreate("TIMER_PULSO",pdMS_TO_TICKS(SW_TIMER_PERIOD_BUZZER),pdTRUE,NULL,Callback_TimerBuzzer); if(TimerBuzzer == NULL) { Serial.printf("\n\rFalha em criar SW_Timer TimerBuzzer"); } // Cria Fila de mensagem QueuePulso QueuePulso = xQueueCreate( NUMBER_OF_ELEMENTS, sizeof(uint32_t) ); if(QueuePulso == NULL) { Serial.printf("\n\rFalha em criar a fila QueuePulso"); } // Cria tarefas da aplicação xTaskCreate(Tarefa_Pulso, "PULSO", 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_Pulso(void *parameters) { static int valueOld = 0xFF; int value = 0; uint32_t valuePulso; while (1) { // le o valor do botão value = digitalRead(PIN_BUTTON); // Detecta borda de descida if((value != valueOld) && (value == LOW)) { // Zera contador do tempo do pulso PulseTimerCounter_Clear(); // Inicia TimerPulso xTimerStart(TimerPulso, 0 ); // Aciona LED digitalWrite(LED_BOARD,HIGH); Serial.print(COLOR_GREEN); Serial.printf("\n\rDetecta borda de descida - Aciona LED on Board"); Serial.print(COLOR_RESET); } else { // Detecta borda de subida if((value != valueOld) && (value == HIGH)) { // Encera o TimerPulso xTimerStop(TimerPulso, 0); // Pega o tempo do pulso valuePulso = PulseTimerCounter_Get(); if(valuePulso != 0) { // Publica na Fila de MSG o tempo do Pulso if(xQueueSend(QueuePulso, &valuePulso, pdMS_TO_TICKS(10) ) != pdTRUE) { Serial.print(COLOR_GREEN); Serial.printf("\n\rFalha em enviar os dados da fila"); Serial.print(COLOR_RESET); } // Desliga o LED digitalWrite(LED_BOARD,LOW); Serial.print(COLOR_GREEN); Serial.printf("\n\rDetecta borda de subida - Desliga LED on Board"); Serial.printf("\n\rLargura Pulso: %d", valuePulso); Serial.print(COLOR_RESET); } } } // update valueOld = value; vTaskDelay(10/portTICK_PERIOD_MS); } } /** * @brief * @param parameters */ void Tarefa_Buzzer(void *parameters) { static bool buzzerSts = false; uint32_t valuePulso; while (1) { // Checar se há conteudo na Fila if(xQueueReceive(QueuePulso, &valuePulso, pdMS_TO_TICKS(10)) == pdPASS) { buzzerSts = true; Serial.print(COLOR_YELLOW); Serial.printf("\n\rvaluePulso: %d",valuePulso); Serial.print(COLOR_RESET); // Checa se o pulso esta entre 1 a 2 segundo if((valuePulso >= 1000) && (valuePulso < 2000)) { // Liga Buzzer por 1 segundo Buzzer.start(); Buzzer.note(Buzzer_Note_C,1); // Inicia TimerBuzzer com 1 segundo xTimerChangePeriod(TimerBuzzer,pdMS_TO_TICKS(1000), 0); xTimerStart(TimerBuzzer, 0 ); Serial.print(COLOR_YELLOW); Serial.printf("\n\rLiga Buzzer por 1 segundo c/ Nota C"); Serial.print(COLOR_RESET); } else { // Checa se o pulso esta entre 2 a 3 segundo if((valuePulso >= 2000) && (valuePulso < 3000)) { // Liga Buzzer por 2 segundo Buzzer.start(); Buzzer.note(Buzzer_Note_E,2); // Inicia TimerBuzzer com 2 segundo xTimerChangePeriod(TimerBuzzer,pdMS_TO_TICKS(2000), 0); xTimerStart(TimerBuzzer, 0 ); Serial.print(COLOR_YELLOW); Serial.printf("\n\rLiga Buzzer por 2 segundo c/ Nota E"); Serial.print(COLOR_RESET); } else { if((valuePulso > 1) && (valuePulso < 1000)) { // Liga Buzzer Buzzer.start(); Buzzer.note(Buzzer_Note_F,0); // Inicia TimerBuzzer com 500 millessegundos xTimerChangePeriod(TimerBuzzer,pdMS_TO_TICKS(valuePulso), 0); xTimerStart(TimerBuzzer, 0 ); Serial.print(COLOR_YELLOW); Serial.printf("\n\rLiga Buzzer por %d milissegundos c/ Nota F",valuePulso); Serial.print(COLOR_RESET); } else { // Liga Buzzer por 500 millessegundos Buzzer.start(); Buzzer.note(Buzzer_Note_A,4); // Inicia TimerBuzzer com 500 millessegundos xTimerChangePeriod(TimerBuzzer,pdMS_TO_TICKS(500), 0); xTimerStart(TimerBuzzer, 0 ); Serial.print(COLOR_YELLOW); Serial.printf("\n\rLiga Buzzer por 0,5 segundo c/ Nota A"); Serial.print(COLOR_RESET); } } } } // Checa se o semaforo binario esta live if(xSemaphoreTake(xSemaphore_BuzzerOff, pdMS_TO_TICKS(10)) == pdTRUE) { if(buzzerSts == true) { buzzerSts = false; // Encera o TimerBuzzer xTimerStop(TimerBuzzer, 0); // Desliga Buzzer Buzzer.stop(); Serial.print(COLOR_YELLOW); Serial.printf("\n\rDesliga Buzzer"); Serial.print(COLOR_RESET); } } } } /** * @brief * @param timer */ void Callback_TimerPulso(TimerHandle_t timer) { if(timer == TimerPulso) { PulseTimerCounter_Increment(); } } /** * @brief * @param timer */ void Callback_TimerBuzzer(TimerHandle_t timer) { if(timer == TimerBuzzer) { xSemaphoreGiveFromISR(xSemaphore_BuzzerOff, (BaseType_t)(pdFALSE)); } } /** * @brief * @return uint32_t */ uint32_t PulseTimerCounter_Get(void) { uint32_t ret; // Obtem o Mutex Variavel Global xMutex_PulseTimerCounter xSemaphoreTake(xMutex_PulseTimerCounter,portMAX_DELAY ); ret = PulseTimerCounter; // libera o Mutex Variavel Global xMutex_PulseTimerCounter xSemaphoreGive(xMutex_PulseTimerCounter); return ret; } /** * @brief */ void PulseTimerCounter_Clear(void) { // Obtem o Mutex Variavel Global xMutex_PulseTimerCounter xSemaphoreTake(xMutex_PulseTimerCounter,portMAX_DELAY ); PulseTimerCounter = 0; // libera o Mutex Variavel Global xMutex_PulseTimerCounter xSemaphoreGive(xMutex_PulseTimerCounter); } /** * @brief */ void PulseTimerCounter_Increment(void) { // Obtem o Mutex Variavel Global xMutex_PulseTimerCounter xSemaphoreTake(xMutex_PulseTimerCounter,portMAX_DELAY ); PulseTimerCounter += SW_TIMER_PERIOD_PULSO; // libera o Mutex Variavel Global xMutex_PulseTimerCounter xSemaphoreGive(xMutex_PulseTimerCounter); }
A seguir, temos a imagem com as mensagem de cada tarefa impressas no terminal serial.
As mensagens na figura foram enumeradas para detalhar o comportamento do algoritmo implementado no projeto de demonstração:
Podemos observar que a cada novo pulso detectado, dependendo do tamanho da largura do pulso o buzzer é acionado com diferentes períodos de tempo e diferentes tons sonoros.
Neste artigo, apresentamos mais um recurso bem interessante do FreeRTOS, o “Software Timer”. Esse recurso é muito útil, pois permite criar inúmeros temporizadores sem a necessidade de utilizar periféricos de hardware. No artigo, detalhamos as principais API’s para manipular o “Software Timer” no FreeRTOS. Também foi apresentada uma aplicação de demonstração. O projeto desenvolvido contava com dois temporizadores para exemplificar o seu uso. 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.
Software Timers
https://www.freertos.org/RTOS-software-timer.html
FreeRTOS Software Timer API Functions
https://www.freertos.org/FreeRTOS-Software-Timer-API-Functions.html
Github – Biblioteca Buzzer
https://github.com/evandro-teixeira/Buzzer
Github – Projeto de demonstração
https://github.com/evandro-teixeira/Buzzer/blob/main/Example/main.cpp
Conheça a Metodologia Eletrogate e ofereça aulas de robótica em sua escola!
|
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!