O desenvolvimento de software usando uma topologia de máquina de estados (cooperative multitasking), como já foi descrita no artigo Arquitetura de desenvolvimento de software – parte III, permite o desenvolvimento de software com um elevado grau de complexidade.
Qualquer microcontrolador tem um número limitado de timers de hardware, sendo que por vezes esses timers são compartilhados com outros periféricos (PWM, capture, etc). Na topologia de máquina de estados existem várias tarefas em execução “paralela”, tarefas essas que não podem ser bloqueantes e poderão ao “mesmo tempo” necessitar realizar temporizações.
Dado o número limitado de timers por hardware, não é possível atribuir a cada tarefa um timer. Veja o exemplo seguinte, onde existe um led a comutar a cada segundo e o ADC é lido a cada 100ms. Neste exemplo como referido, cada máquina de estados usa um timer por hardware, solução essa que apesar de ser exequível, rapidamente o deixará de ser assim que o sistema aumentar de complexidade.
static uint8_t estadoLed;
static uint8_t estadoAdc;
static void Maquina_Led(void)
{
switch(estadoLed)
{
case 0:
Acende_led();
Arranca_Timer1();
estadoLed = 1;
break;
case 1:
if(Passou_1s_Timer1())
estadoLed = 2;
break;
case 2:
Apaga_led();
Arranca_Timer1();
estadoLed = 4;
break;
case 3:
if(Passou_1s_Timer1())
estadoLed = 0;
break;
default:
estadoLed = 0;
break;
}
}
static void Maquina_Adc(void)
{
switch(estadoAdc)
{
case 0:
Le_Adc();
estadoAdc = 1;
break;
case 1:
ConverteDigitalEmAnalogico();
Arranca_Timer2();
estadoAdc = 2;
break;
case 2:
if(Passou_100ms_Timer2())
estadoAdc = 0;
break;
default:
estadoAdc = 0;
break;
}
}
static void Main_ini(void)
{
estadoLed = 0;
estadoAdc = 0;
Inicia_Led();
Inicia_Adc();
}
int main(void)
{
Main_ini();
while(1)
{
Maquina_Led();
Maquina_Adc();
}
}
A solução para este problema é a utilização de um único timer por hardware para a realização de múltiplos timers por software, à semelhança do que acontece num sistema com RTOS (Real-Time Operating System). Esta solução foi aliás pensada pela ARM quando desenhou os núcleos ARM Cortex-M e incorporou o timer dedicado SysTick, timer esse que será o timer de base do nosso exemplo.
A solução apresentada será o mais simplista possível, de modo a não só simplificar a implementação e utilização, bem como a minimizar os recursos necessários e o tempo de execução da interrupção do timer. A solução apresentada consiste numa variável global que incrementa continuamente a cada tick do timer, tick esse definido na configuração do timer SysTick (deverá ser menor que o tempo mínimo desejável), e que pelo fato de ser uma variável de 64 bits de dimensão levará muito tempo para voltar a ser 0 (zero), tomando por exemplo um tick de 100us, será necessária a passagem de mais de 58 milhões de anos (264 *100us) para que esta variável volte a 0.
/** * @file * @brief Temporizador principal. * @details Utiliza um tick global, com um periodo definido. * @author MM */ #ifndef TIMERTICKS_H #define TIMERTICKS_H /******************************************************************************* * Includes ******************************************************************************/ #include "mytypes.h" /******************************************************************************* * Typedefs ******************************************************************************/ /** @brief Redefinicao de tipo para utilizacao dos timerticks. */ typedef uint64_t T_TimerTicks; // Timer /******************************************************************************* * Public Functions Prototypes ******************************************************************************/ /** * @brief Verifica se o tick desejado foi atingido. * @details O valor do tick foi previamente calculado. * @param[in] dTick Valor do tick. * @retval TRUE Atingiu tick, ou seja, passou tempo desejado. * @retval FALSE Nao atingiu tick, ou seja, nao passou tempo desejado. */ Boolean TimerTicks_isTimer(uint64_t dTick); /** * @brief Calculo do tick para dezenas de segundos. * @param[in] time Tempo desejado, em dezenas de segundos. * @return Valor de tick a esperar para a passagem do tempo desejado. */ uint64_t TimerTicks_setTimerDSec(uint64_t time); /** * @brief Calculo do tick para segundos. * @param[in] time Tempo desejado, em segundos. * @return Valor de tick a esperar para a passagem do tempo desejado. */ uint64_t TimerTicks_setTimerSec(uint64_t time); /** * @brief Calculo do tick para centenas de milisegundos. * @param[in] time Tempo desejado, em centenas de milisegundos. * @return Valor de tick a esperar para a passagem do tempo desejado. */ uint64_t TimerTicks_setTimerCMSec(uint64_t time); /** * @brief Calculo do tick para dezenas de milisegundos. * @param[in] time Tempo desejado, em dezenas de milisegundos. * @return Valor de tick a esperar para a passagem do tempo desejado. */ uint64_t TimerTicks_setTimerDMSec(uint64_t time); /** * @brief Calculo do tick para milisegundos. * @param[in] time Tempo desejado, em milisegundos. * @return Valor de tick a esperar para a passagem do tempo desejado. */ uint64_t TimerTicks_setTimerMSec(uint64_t time); /** * @brief Inicializacao do modulo. * @return Nada. */ void TimerTicks_ini(void); #endif /* TIMERTICKS_H */ /* -------------------------------- End Of File ----------------------------- */
/**
* @file
* @brief Temporizador principal.
* @details Utiliza um tick global, com um periodo definido.
* @author MM
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include "sys/timerTicks.h"
#include "mytypes.h"
#include "chip.h"
/*******************************************************************************
* Global variables
******************************************************************************/
/** @brief Tempo de base para os ticks em segundos. */
static const float GC_TimerTicks_baseTickSec = 0.0001;
/** @brief Variavel global de ticks. */
static volatile T_TimerTicks G_TimerTicks_gTick;
/*******************************************************************************
* Interrupts Functions
******************************************************************************/
/**
* @brief Interrupcao do SysTick, incremento da variavel global.
* @ingroup systick
* @return Nada.
*/
void SysTick_Handler(void)
{
G_TimerTicks_gTick++;
}
/*******************************************************************************
* Public Functions
******************************************************************************/
/* Verifica se o tick desejado foi atingido. */
Boolean TimerTicks_isTimer(uint64_t dTick)
{
Boolean flag = FALSE;
if (G_TimerTicks_gTick >= dTick)
flag = TRUE;
return (flag);
}
/* Calculo do tick para dezenas de segundos. */
uint64_t TimerTicks_setTimerDSec(uint64_t time)
{
uint64_t ticks;
ticks = (uint64_t) ((float) (time * 10) / GC_TimerTicks_baseTickSec)
+ G_TimerTicks_gTick;
return (ticks);
}
/* Calculo do tick para segundos. */
uint64_t TimerTicks_setTimerSec(uint64_t time)
{
uint64_t ticks;
ticks = (uint64_t) ((float) time / (GC_TimerTicks_baseTickSec))
+ G_TimerTicks_gTick;
return (ticks);
}
/* Calculo do tick para centenas de milisegundos. */
uint64_t TimerTicks_setTimerCMSec(uint64_t time)
{
uint64_t ticks;
ticks = (uint64_t) ((float) time / (GC_TimerTicks_baseTickSec * 10))
+ G_TimerTicks_gTick;
return (ticks);
}
/* Calculo do tick para dezenas de milisegundos. */
uint64_t TimerTicks_setTimerDMSec(uint64_t time)
{
uint64_t ticks;
ticks = (uint64_t) ((float) time / (GC_TimerTicks_baseTickSec * 100))
+ G_TimerTicks_gTick;
return (ticks);
}
/* Calculo do tick para milisegundos. */
uint64_t TimerTicks_setTimerMSec(uint64_t time)
{
uint64_t ticks;
ticks = (uint64_t) ((float) time / (GC_TimerTicks_baseTickSec * 1000))
+ G_TimerTicks_gTick;
return (ticks);
}
/* Inicializacao do modulo. */
void TimerTicks_ini(void)
{
G_TimerTicks_gTick = 0;
// Configuracao do SysTick Timer para interrupcoes de 100usec
if (SysTick_Config(
(uint32_t) ((float) SystemCoreClock * GC_TimerTicks_baseTickSec)))
{
while (1)
; // Capture error
}
}
/* -------------------------------- End Of File ----------------------------- */
Quando é necessário um timer, é criada uma variável estática do tipo T_TimerTicks, na qual é armazenado o valor atual do tick mais o número de ticks necessários para a passagem do tempo desejado. É essa a utilidade das funções TimerTicks_setTimerXXX, calcular o valor futuro do tick na base de tempo que pretendermos (segundos, milissegundos, etc.). O próximo passo na nossa máquina de estados é fazer polling ao valor do tick e aguardar que iguale o valor da variável estática, assinalando assim a passagem do tempo desejado.
#include "timerTicks.h"
static uint8_t estadoLed;
static uint8_t estadoAdc;
static void Maquina_Led(void)
{
static T_TimerTicks timerLed;
switch(estadoLed)
{
case 0:
Acende_led();
timerLed = TimerTicks_setTimerSec(1);
estadoLed = 1;
break;
case 1:
if(TimerTicks_isTimer(timerLed))
estadoLed = 2;
break;
case 2:
Apaga_led();
timerLed = TimerTicks_setTimerSec(1);
estadoLed = 4;
break;
case 3:
if(TimerTicks_isTimer(timerLed))
estadoLed = 0;
break;
default:
estadoLed = 0;
break;
}
}
static void Maquina_Adc(void)
{
static T_TimerTicks timerAdc;
switch(estadoAdc)
{
case 0:
Le_Adc();
estadoAdc = 1;
break;
case 1:
ConverteDigitalEmAnalogico();
timerAdc = TimerTicks_setTimerMSec(100);
estadoAdc = 2;
break;
case 2:
if(TimerTicks_isTimer(timerAdc))
estadoAdc = 0;
break;
default:
estadoAdc = 0;
break;
}
}
static void Main_ini(void)
{
estadoLed = 0;
estadoAdc = 0;
Inicia_Led();
Inicia_Adc();
TimerTicks_ini();
}
int main(void)
{
Main_ini();
while(1)
{
Maquina_Led();
Maquina_Adc();
}
}
Esta abordagem tem a limitação da utilização de uma variável de 64 bits poder não ser suportada pela arquitetura, limitando assim o tempo máximo de funcionamento contínuo do timer. Esta limitação não é significativa se o tempo máximo de execução contínuo do firmware não ultrapassar o tempo máximo contabilizável pela variável global.
Uma abordagem diferente passa pela utilização de uma variável dedicada a cada timer na interrupção do SysTick, como no exemplo seguinte, tendo como contrapartida o aumento do tempo de execução da interrupção.
/**
* @file
* @brief Temporizador principal.
* @details Utiliza uma lista de timers, com um periodo definido.
* @author MM
*/
#ifndef TIMERTICKS_H
#define TIMERTICKS_H
/*******************************************************************************
* Includes
******************************************************************************/
#include "mytypes.h"
/*******************************************************************************
* Typedefs
******************************************************************************/
/** @brief Lista de indices dos timers. */
typedef enum {
TIMERTICKS_LED ,
TIMERTICKS_ADC ,
TIMERTICKS_COUNT
} T_TimerTicks_list;
/*******************************************************************************
* Defines
******************************************************************************/
/** @brief Tempo em ms entre interrupcoes. */
#define TIMERTICKS_MS 10UL //Tempo em ms entre 2 interrupts
/** @brief Macros para o calculo dos ticks em diferentes base de tempo. */
#define TimerTicks_dezsecToTics(v) ((((uint32_t)(v))*10000L)/(TIMERTICKS_MS))
#define TimerTicks_secToTics(v) ((((uint32_t)(v))*1000L)/(TIMERTICKS_MS))
#define TimerTicks_dsecToTics(v) ((((uint32_t)(v))*100L )/(TIMERTICKS_MS))
#define TimerTicks_csecToTics(v) ((((uint32_t)(v))*10L )/(TIMERTICKS_MS))
#define TimerTicks_msecToTics(v) (((uint32_t)(v))/(TIMERTICKS_MS))
/*******************************************************************************
* Public Functions Prototypes
******************************************************************************/
/**
* @brief Verifica se o timer atingiu a contagem.
* @details O fim de contagem e assinalada pelo set do bit 31.
* @param[in] timer Timer a verificar.
* @retval TRUE Atingiu contagem.
* @retval FALSE Não atingiu contagem.
*/
Boolean TimerTicks_isTimer ( utin8_t timer );
/**
* @brief Activa o timer indicado.
* @details O fim de contagem e assinalada pelo set do bit 31.
* @param[in] timer Timer a configurar
* @param[in] delta Valor de contagem, temporizacao convertida em nº de ticks.
* @return Nada.
*/
void TimerTicks_setTimer ( utin8_t timer, uint32_t delta );
/**
* @brief Inicialização do módulo.
* @return Nada
*/
void TimerTicks_ini ( void );
#endif /* TIMERTICKS_H */
/* -------------------------------- End Of File ----------------------------- */
/**
* @file
* @brief Temporizador principal.
* @details Utiliza uma lista de timers, com um periodo definido.
* @author MM
*/
/*******************************************************************************
* Includes
******************************************************************************/
#include <stdint.h>
#include "sys/timerTicks.h"
#include "mytypes.h"
#include "chip.h"
/*******************************************************************************
* Defines and typedefs
******************************************************************************/
/** @brief Valor de overflow para uma variavel de 31 bits. */
#define TIMERTICKS_ROLLOVER INT32_MIN // 2147483648(2^31)
/*******************************************************************************
* Global variables
******************************************************************************/
/** @brief Array de timers. */
static volatile uint32_t G_TimerTicks_list[TIMERTICKS_COUNT];
/*******************************************************************************
* Private Functions Prototypes
******************************************************************************/
static void TimerTicks_iniList(void);
/*******************************************************************************
* Interrupts Functions
******************************************************************************/
/**
* @brief Interrupcao do SysTick, incremento da variavel global.
* @ingroup systick
* @return Nada.
*/
void SysTick_Handler(void)
{
G_TimerTicks_list[TIMERTICKS_LED]++;
G_TimerTicks_list[TIMERTICKS_ADC]++;
}
/*******************************************************************************
* Private Functions
******************************************************************************/
/**
* @brief Inicializacao da lista de timer.
* @return None
*/
static void TimerTicks_iniList(void)
{
utin8_t timer_ind;
for(timer_ind=0;timer_ind<((uint8_t)TIMERTICKS_COUNT;timer_ind++)
G_TimerTicks_list[timer_ind++]=0;
}
/*******************************************************************************
* Public Functions
******************************************************************************/
/* Verifica se o timer atingiu a contagem. */
Boolean TimerTicks_isTimer ( uint8_t timer )
{
return ((G_TimerTicks_list[timer]&0x80000000) ? TRUE : FALSE);
}
/* Activa o timer indicado. */
void TimerTicks_setTimer ( uint8_t timer, uint32_t delta )
{
G_TimerTicks_list[timer]=(uint32_t)(TIMERTICKS_ROLLOVER)-delta;
}
/* Inicializacao do modulo. */
void TimerTicks_ini ( void )
{
TimerTicks_iniList();
// Configuracao do SysTick Timer para interrupcoes de 10msec
if (SysTick_Config(SystemCoreClock / (10*(uint32_t)TIMERTICKS_MS))
{
while (1); // Capture error
}
}
/* -------------------------------- End Of File ----------------------------- */
#include "timerTicks.h"
static uint8_t estadoLed;
static uint8_t estadoAdc;
static void Maquina_Led(void)
{
switch(estadoLed)
{
case 0:
Acende_led();
TimerTicks_setTimerSec(TIMERTICKS_LED,TimerTicks_secToTics(1));
estadoLed = 1;
break;
case 1:
if(TimerTicks_isTimer(TIMERTICKS_LED))
estadoLed = 2;
break;
case 2:
Apaga_led();
TimerTicks_setTimerSec(TIMERTICKS_LED,TimerTicks_secToTics(1));
estadoLed = 4;
break;
case 3:
if(TimerTicks_isTimer(TIMERTICKS_LED))
estadoLed = 0;
break;
default:
estadoLed = 0;
break;
}
}
static void Maquina_Adc(void)
{
switch(estadoAdc)
{
case 0:
Le_Adc();
estadoAdc = 1;
break;
case 1:
ConverteDigitalEmAnalogico();
TimerTicks_setTimerSec(TIMERTICKS_ADC,TimerTicks_msecToTics(100));
estadoAdc = 2;
break;
case 2:
if(TimerTicks_isTimer(TIMERTICKS_ADC))
estadoAdc = 0;
break;
default:
estadoAdc = 0;
break;
}
}
static void Main_ini(void)
{
estadoLed = 0;
estadoAdc = 0;
Inicia_Led();
Inicia_Adc();
TimerTicks_ini();
}
int main(void)
{
Main_ini();
while(1)
{
Maquina_Led();
Maquina_Adc();
}
}
Um fator importante em qualquer temporização é a sua precisão, e nas propostas acima apresentadas o erro máximo é igual ao valor de base do tick. Isto é, pelo fato de o SysTick estar sempre em funcionamento, os seus registros estão em constante atualização. No momento em que ativamos um timer a interrupção do SysTick pode estar prestes a disparar, o que faz com que o primeiro tick no nosso timer seja mal contabilizado, como demonstra a figura seguinte.
Entra aqui a importância da correta seleção do tick de base. Se pretendermos, por exemplo, que o nosso timer contabilize tempos de 5s e usarmos um tick de base de 10ms, será igual contar 5s ou 4,99s (5s-10ms). No entanto, se utilizarmos um tick de base de 1s, já não é igual. Poderemos contar 5s ou 4s (5s-1s).
Conclusão
Como pretendido, esta solução é não-bloqueante, escalonável e universal, e o seu desempenho independente do aumento do número de timers simultâneos (apenas na primeira proposta). Apesar de algumas limitações, estas pouco influenciam o resultado final.









Muito bom o artigo Mário, parabéns!
Uma questão importante e que deve se levar em consideração é quanto a precisão dos temporizadores, como tu citou. Em microcontroladores com osciladores internos, como poderíamos resolver o problema de precisão resultante de um grande tempo de operação do circuito, por exemplo:
– A cada 1 hora de execução, percebe-se que o delay acumulado é de 1 segundo;
Tu já passou por algum caso deste tipo?
Obrigado pelo comentário. Tipicamente uso esta implementação em casos em que não tem problema contar 5,01ms em vez de 5ms, ou seja tarefas não criticas, se precisas de facto desse grau de precisão é recomendável a utilização de um oscilador externo devidamente calibrado ou um timer de hardware dedicado à tarefa que necessitas. Recentemente tive um problema parecido com o que descreves, precisava de gerar um pulso com uma largura de 5ms e usava esta biblioteca com uma máquina de estados. Esporadicamente o pulso tinha uma duração superior aos 5ms o que fazia o sistema falhar, o problema detectei mais… Leia mais »