Tutorial: Executando funções em intervalos de tempo fixos (timers) com Arduino

Esse tutorial eu postei originalmente no meu blog: blog.tiago.eti.br, e decidi reproduzi-lo aqui.

Embora o Ardiono seja uma plataforma muito fácil de utilizar e com bastante recursos, eu acho que ela ainda deixa a desejar em alguns apectos. Um deles é a falta do acesso direto aos timers pela biblioteca padrão do arduino. Nesse post eu vou mostrar como utilizar uma biblioteca para configurar de forma simples os timers do Arduino.

Mas o que são os Timers?

Um timer nada mais é do que um contador que é incrementado a cada intervalo de tempo (em alguns microcontroladores intervalo pode ser configurado, o Arduino é um deles). Os timers funcionam como um relógio que pode ser usado para contar o tempo, medir a duração de certos eventos, entre outras aplicações.

O Arduino vem equipado com um microcontrolador ATmega168 ou ATmega328 (que diferem apenas na quantidade de memória interna). Esses microcontroladores possuem e timrestimer0, timer1 and timer2, Timer0 e timer2 são contadores de 8bits, ou seja, contam de 0 a 255, e o timer1 é um contador de 16bits, conta de 0 a 65535.

O Arduino Mega vem equipado com o ATmega1280 ou ATmega2560 (que diferem apenas na quantidade de memória). Eles possuem 6 timerstimer0, timer1, timer2, timer3, timer4, timer5. Os timers 0, 1 e 2 são idênticos aos do ATmega168/328, e os timers 3, 4 e 5 são ambos de 16bits.

Timer0:

timer0 é utilizado pelo Arduino para funções como delay()millis() e micros(). Então não se deve utilizar esse timer para evitar comprometer essa funções.

Timer1:

No Arduino UNO esse é o timer utilizado pela biblioteca de controle de servos. Caso você não esteja utilizando essa biblioteca, esse timer está livre para ser utilizado para outros propósitos. No Arduino Mega esse timer só será utilizado para controlar os servos se você estiver usando mais de 12 servos.

Timer2:

Esse timer é utilizado pela função tone(). Então se você não precisar da função tone() esse timer está livre para outras aplicações.

Timer3 e Timer4:

Esses timers só estão presentes no Arduino Mega e eles só serão utilizados pela biblioteca do Arduino caso vocês esteja utilizando mais de 24 servos. De 25 a 36 servos o timer3 será utilizado e de 37 a 48 servos o timer4 será utilizado.

Timer5:

No Arduino Mega esse é o timer padrão para o controle de servos, ou seja, se você estiver utilizando de 1 a 12 servos, apenas esse timer será utilizado pela biblioteca.

Usando os timers

Os timers podem ser configurados para gerar uma interrupção quando o seu contador chegar ao valor máximo (timer overflow). Mas o que são interrupções?

Interrupções

No Arduino (e nos microcontroladores de forma geral) o programa é executado sequencialmente, linha após linha, instrução após instrução. Uma interrupção é um evento externo que interrompe a execução do programa e chama uma função para tratar a interrupção, após o fim dessa função a execução normal do programa é retomada de onde parou.

Os timers podem ser configurados para quando atingirem o valor máximo da contagem (overflow) gerarem uma interrupção e chamarem uma função específica.

Configurando os timers

A configuração "manual" dos timers é bem chata e trabalhosa pois exige a manipulação de vários registradores do microcontrolador, mas felizmente existem algumas bibliotecas que permitem fazer isso de forma bem simples. O meu objetivo é mostrar como usar a biblioteca Timer1 e Timer3 que está no Arduino Playground, e também em uma versão modificada delas que eu fiz para poder trabalhar com os timers 4 e 5. A minha versão modificada pode ser baixada aqui.

O primeiro passo é baixar as bibliotecas e coloca-las na pasta hardware/libraries/ que está dentro da pasta do Arduino ou em sketchbook/libraries/. O local exato destes diretórios depende do sistema operacional (Linux, Mac ou Windows) e também da forma como a IDE do Arduino foi instalada.

Usando a biblioteca

A descrição completa do funcionamento da biblioteca está neste link, mas eu irei fazer um breve resumo aqui. As principais funções da biblioteca são:

initialize(periodo)

Esse método deve ser chamado antes de qualquer outro método, pois ele é responsável pelas configurações iniciais. Você pode opcionalmente informar o intervalo (em microsegundos) no qual a interrupção deve ser gerada. O menor tempo aceito é 1 micro-segundo e o tempo máximo é de 8.388.480 micro-segundos, ou seja, aproximadamente 8,3 segundos. Caos não seja informado nenhum valor, será atribuído o valor padrão de 1.000.000 micro-segundos (1 segudo). Quando usado o timer1 o analogWrite() nos pinos 9 e 10 do Arduino param de funcionar.

setPeriod(periodo)

Modifica o período da interrupção.

attachInterrupt(funcao, periodo)

Atribui uma função para ser chamada a cada interrupção gerada pelo timer. Se não for especificado um período, será utilizado o período definido na inicialização ou por setPeriod.

Testando a biblioteca

Para testar essa biblioteca não é necessário nenhum hardware adicional, basta usarmos o LED conectado ao pino 13 da placa do Arduino e escrever um novo blink.

Aqui está a versão traduzida do exemplo que está na página da biblioteca:

/*
* Timer1 library example
* August 2012 Translated by Tiago de França Queiroz
* June 2008 | jesse dot tane at gmail dot com
*/

#include "TimerOne.h"

void setup()
{
pinMode(13, OUTPUT);
Timer1.initialize(500000); // Inicializa o Timer1 e configura para um período de 0,5 segundos
Timer1.attachInterrupt(callback); // Configura a função callback() como a função para ser chamada a cada interrupção do Timer1
}

void callback()
{
digitalWrite(13, digitalRead(13) ^ 1);
}

void loop()
{
// Seu código vai aqui...
}

 

Para usar os outros timers basta substituir o #include <TimerOne.h> por  #include <TimerTree.h>, #include <TimerFour.h> ou #include <TimerFive.h> e no resto do código substitua Timer1 por Timer3, Timer4, ou Timer5.

Essas bibliotecas são úteis em várias situações, comoquando o loop principal do seu programa está bloqueado por algum motivo (esperando um botão ser pressionado, por exemplo) e você precisa atualizar um display, ou ler um sensor independentemente do loop principal, entre outras.

É importante lembrar que o tempo necessário para executar as funções que são chamadas na interrupção dos timers deve ser MENOR do que o tempo entre as interrupções!

Lembre-se que a versão que eu modifiquei a função de pwm (que eu não falei nesse post) está desativado.

 

Referências:

http://letsmakerobots.com/node/28278

http://arduino.cc/playground/Code/Timer1

Exibições: 99457

Comentar

Você precisa ser um membro de Laboratorio de Garagem (arduino, eletrônica, robotica, hacking) para adicionar comentários!

Entrar em Laboratorio de Garagem (arduino, eletrônica, robotica, hacking)

Comentário de José Ewerton da Silva Sousa em 21 agosto 2018 às 16:54

Boa tarde, gostaria de saber como faço ara alterar o tempo que o led fica ligado no void loop usando a biblioteca timer1

#include "TimerOne.h"

int timer;


void setup()
{
pinMode(13, OUTPUT);
Timer1.initialize(timer); // Inicializa o Timer1 e configura para um período de 0,5 segundos
Timer1.attachInterrupt(callback); // Configura a função callback() como a função para ser chamada a cada interrupção do Timer1
}

void callback()
{
digitalWrite(13, digitalRead(13) ^ 1);
}

void loop()
{


timer = 5000000; // novo tempo
// CODIGO
}

Comentário de Frank Albrecht Andriessen em 1 dezembro 2017 às 6:57

Bom dia Tiago, pode me ajudar? estou montando um sistema que me avisa quando a caixa d'água chega no vazio, ai dispara um BUZZER, queria adicionar um botao que quando presionado espera 5 minutos para verificar se a caixa encheu, caso contrario ele dispara o buzzer novamente.

Se quiser e puder,  te mando o programa  para analisar.

Obrigado

Comentário de João Estevão em 14 julho 2015 às 11:47

Bom, ninguém me respondeu kk mas fuçando muito, finalmente consegui uma resposta!
E a solução foi usar o timer1 do arduino Uno mesmo! Só precisei usar algumas funções que já existem kkk
As funções que usei para resolver o meu problema estão em negrito no código:

#include <TimerOne.h>
int time;
int IN1 = 4; //pino 4 do Arduino
int IN2 = 5; //pino 5 do Arduino
void setup()
{
Serial.begin( 9600 );
Timer1.initialize(10000); // Inicializa o Timer1 e configura para um período de 0,01 segundos
Timer1.pwm(9,0);
Timer1.attachInterrupt(callback); // Configura a função callback() como a função para ser chamada a cada interrupção do Timer1

}
void callback()
{
time++;
}
void loop()
{
Serial.println(time);
digitalWrite(IN1, LOW);
digitalWrite(IN2, HIGH);
if(time > 200){
Timer1.setPwmDuty(9,512);
}
}
A primeira linha em negrito diz que vamos usar pwm no pino 9 e que é pra começar com valor 0.

A segunda, seta o pino 9 para um novo valor de pwm.


Elas resolveram meu problema, mas apesar disso, constatei que não poderei variar muito os intervalos das interrupções. Não sei explicar o pq, mas nao funcionou.... só funcionou com centisemos de segundos.

Comentário de João Estevão em 8 julho 2015 às 11:51

Parabéns cara! Muito bom o tutorial! Me ajudou bastante.
Mas estou tendo um problema e gostaria de saber se alguém tem alguma ideia para me ajudar.
Seguinte, estou usando esse exemplo que você deu no meu projeto e estava tudodando certo. Mas, eu precisei começar a usar o Arduino Uno, e parou de funcionar como eu queria. QUando eu usava o arduino Mega conseguia realizar as interrupçoes e controlar a velocidade de um motor dc com pwm, mas com o Uno não consigo! O motor só funciona quanto o valor do pwm é 255, ou seja, quando é maximo. Fiz um teste e quando eu nao uso a interrupção o motor é controlado da maneira como eu projetei. Pelo o que eu li, entendi que usar o timer 1 só afetaria o controle de um servo, não do pwm. Já pensei em usar outro timer pra fazer a interrupção, mas não posso pq preciso dos 16 bits. Enfim, acham que só consigo resolver esse problema se voltar a usar o mega? Ou alguem ja passou por isso e tem alguma solução em mente?

Desde ja agradeço.

Comentário de Thiago Magalhães em 12 janeiro 2015 às 12:37

#include <TimerThree.h>

Void Setup () {

Timer3.initialize(3000000);
Timer3.start();
Timer3.attachInterrupt(par);   // Caso não reiniciar o contador ele para as rodas

}

Void loop () {

Fre(){

analogWrite(enable1, velocidade1); // velociadade do motor 1.
analogWrite(enable2, velocidade1); // velociadade do motor 2.

digitalWrite(30, HIGH); // RODA DO MEIO ESQUERDA
digitalWrite(31, LOW);
digitalWrite(32, HIGH); 
digitalWrite(33, LOW);

// no programa vai ter uma função que ira chamar esse "FRE" nessa funçao está ficando assim:

fre();

Timer3.restart(); // mas não da restart e o robo não funciona quando eu coloco.

.....alguma ideia ?

}



}

Comentário de Thiago Magalhães em 12 janeiro 2015 às 12:31

Cara meu programa não está dando Restart no contador ! no caso o comando seria Timer3.restart();  

?

Comentário de Rafael Poletti em 7 março 2014 às 18:17

Boa noite Tiago.

Tiago por gentileza, vc saberia me informar se é possivel em dado instante desligar temporariamente o Timer?. Por exemplo o Timer 0 ou 1.

Grato

Comentário de Diogo Santos Silva em 20 setembro 2013 às 22:06

Construi um pequeno programa utilizando o millis(), que permite o arduino receber de dados enquanto controla as mini bombas.

Porém estou tendo problema em dois quesitos:

1º_ na hora de inserir o número finito de repetições!

2º_ como intercalar o funcionamento dos componentes (Ex: o primeiro ativa enquanto o segundo continua pausado e/ou o contrário)

.

.

.

int component1 = 8;
int component2 = 9;
int component3 = 10;
int component4 = 11;
int count = 0;


long previousMillis = 0;
long interval = 1000;
int ledState = LOW;

int tecla_pressionada = 0;


int pul = 0;                       //número de pulsos
int binaria = 0;                 //indica o número de intercalações
int replicata = 0;              //número de replicas de amostragem


 void setup()
 {    
   Serial.begin(9600);    

   pinMode(component1,OUTPUT);
   digitalWrite(component1,LOW);
   pinMode(component2,OUTPUT);
   digitalWrite(component2,LOW);
   pinMode(component3,OUTPUT);
   digitalWrite(component3,LOW);
   pinMode(component4,OUTPUT);
   digitalWrite(component4,LOW);
    
 }

 void loop()
 {
 
   Serial.flush();
     
     char tecla_pressionada;
     tecla_pressionada = Serial.read();
      
   if(tecla_pressionada == 'p'|tecla_pressionada =='P')
    {
      unsigned long currentMillis = millis();
 
     if(currentMillis - previousMillis > interval)
      {
       previousMillis = currentMillis;
      
        if (ledState == LOW)
           ledState = HIGH;
        else
           ledState = LOW;

           digitalWrite(8, ledState), digitalWrite(10, ledState);
           digitalWrite(9, ledState);

      }
    
    }

   Serial.println(analogRead(A2));
 }

Alguém saberia como resolver essa treta!?

Tentei aplicar a mesma lógica do outro programa (usando delay()), porém o loop() não só não reconhece o comando de repetição do "processo" como também inviabiliza o controle via teclado!

Comentário de Diogo Santos Silva em 18 setembro 2013 às 21:55

Oh....desculpa tentarei ser mais claro, quando eu disse

                   "Que interrompa uma determinada função, porém permitir que outras ocorram" 

 queria dizer:

Se existe uma forma de interromper determinado processo, pelo tempo que for necessitado, e mesmo saindo do estado de interrompido seja possível executar um segundo processo como por exemplo receber dados de um sensor?

No caso o arduino agiria de forma passiva!

Como o sensor funciona interruptamente e o arduino apenas receberia dados e não o controlaria!

O delay() tem uma aplicabilidade muito simples e eficiente, porém ele interrompe qualquer processo que esteja em execução! A partir disso surgiu a ideia de trabalhar com o ciclos de tempo ( usando uma biblioteca timer ou pelo millis() ). Na qual ainda estava (e ainda estou) com dúvida porém não conseguir expressar essa dúvida de forma clara!

Então para ficar mais claro vou postar parte do meu programa ( para não estender demais o post ). Nele está "algo" que já testei e funciona porém por trabalhar com delay() não permite que seja implementado um sensor de luz.

/*      

Nesse sketch se eu colocar um " Serial.println(fotometro) " em qualquer parte do void loop() não consigo receber nada pelo serial monitor após ter ativado o comando de acionamento pelo teclado, pois o delay() interrompe o processo.

Para ser mais preciso consigo fazer o sistema funcionar, se e somente se, eu estiver trabalhando com duas placas. Onde uma fica por controlar os componentes e a outra fica a cargo de receber os dados do sensor. O que torna o projeto inviável!

Nesse projeto estou trabalho com o arduino due.

*/

.

.

.

int inicio[ ] = {8,9,10,11};             //pinos das mini bombas e válvula
int count = 0;
int fotometro = A2;                    //sensor na porta analógica A2
int teclapressionada = 0;            //irá armazenar o estado de qual tecla(P,L,B,A) está pressionado 
int pul = 0;

void setup()
{

  Serial.begin(9600);     //indicando que será os pinos de saída

 

 for (count = 0; count <4; count++)
  {
    pinMode(inicio[count], OUTPUT); 
    digitalWrite(inicio[count], LOW); 
  }


 }

void loop()                                //etapa de amostragem 
 {

   Serial.flush();
   char tecla_pressionada;

   tecla_pressionada = Serial.read();

if (tecla_pressionada == 'b' | tecla_pressionada == 'B')            
 {

  int pul = 0;                                 //variável numero de pulsos por reagentes
  int binaria = 0;                           //variável número de ciclos binários
  int replicata = 0;                        //variável número de replicatas por amostras

  for (replicata = 0; replicata < 1; replicata++)
     {
      for (binaria = 0; binaria < 2; binaria++)  
        {  

         for (pul = 0; pul <150; pul++)
           {

              digitalWrite(11, HIGH);
              digitalWrite(8, HIGH); digitalWrite(9, HIGH);
              delay(100);
              digitalWrite(9, LOW); digitalWrite(8, LOW);
              delay(100);
           }  
          
              digitalWrite(11, LOW);

        }    
     }  

 }

else
if (tecla_pressionada == 'a' | tecla_pressionada == 'A') 
 {  

  int pul = 0;                             //variável numero de pulsos por reagentes
  int binaria = 0;                        //variável número de ciclos binários
  int replicata = 0;                     //variável número de replicatas por amostras

  for (replicata = 0; replicata < 1; replicata++)
    {
    for (binaria = 0; binaria < 2; binaria++)      //indica o número de intercalações
      {

      for (pul = 0; pul <150; pul++)                 //indica o número de pulso de amostra e reagente.
        {  


           digitalWrite(10, HIGH);

           digitalWrite(9, HIGH); digitalWrite(8, HIGH); digitalWrite(10, LOW); 
           delay(100);
           digitalWrite(9, LOW); digitalWrite(8, LOW);
           delay(100);
       } 

     }

   }

 }

}

.

.

.

/*

No momento consegui (um pouco antes do computador "bugar" e me obrigar a formatá-lo) fazer os componentes e sensor trabalharem juntos. Porém não estou acertando como devo escrever o sketch, para que, haja uma comutação no funcionamento dos componentes (enquanto uma mini bomba está ativada outra esteja desativada de acordo com um tempo pré-determinado) e haja o controle pelo teclado porque o arduino ainda não enxerga o que é feito no loop().

Depois que reinstalar tudo novamente no Pc e refazer o sketch que estava construindo irei posta-lo.

*/

Comentário de Tiago de França Queiroz em 18 setembro 2013 às 10:08

Então, internamente o delay usa o timer, eu nunca olhei a implementação, mas  ideia é essa:
1 - Seta o timer para o delay que vc quer, se o delay for maior do que o timer pode contar, coloca a callback pra contar um número X de interrupções.

2 - Coloca o timer pra dar
3 - Coloca uma espera ocupada onde vc quer o delay. Algo como "while(!flag);"
4 - Desativa o timer
5 - Continua o programa.


Eu não entendi direito o que vc fazer.... especialmente essa parte: "Que interrompa uma determinada função, porém permitir que outras ocorram".

O arduino só pode executar uma função por vez. O máximo que da pra conseguir é usar interrupções de software (como os timers) e hardware. O efeito disso vai ser interromper o que está sendo executado, executar a função relacionada com a interrupção e depois retomar a execução que foi interrompida.

Normalmente em aplicações com teclado e coisas do tipo vc tem que ficar lendo as teclas o tempo todo até detectar que uma foi pressionada.

Explica com mais detalhes o que vc quer fazer que eu tento te dar uma resposta melhor.

Destaques

Registre-se no
Lab de Garagem
Clicando aqui

Convide um
amigo para fazer
parte

curso gratis de arduino

© 2020   Criado por Marcelo Rodrigues.   Ativado por

Badges  |  Relatar um incidente  |  Termos de serviço