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.
Tenha a Metodologia Eletrogate dentro da sua Escola! Conheça nosso Programa de Robótica nas Escolas!
Este projeto demonstra como integrar o ESP32 a um sistema moderno de notificações push utilizando o serviço ntfy.sh
