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

  • Rubens de Andrade Neto

    Legal

    Não sabia porque não conseguia fazer funcionar os motores (biblioteca servo) com o timer 1 para fazer o controle.

    Tive que usar o Timer 2 no uno.

    Segue link para utilizar o Timer 2:

    http://arduino.cc/playground/Main/MsTimer2


    abs

  • Eduardo castellani

    Oi Luiz, eu preciso controlar uma chave, que desligará uma resistencia eleltrica apos uns 10 min, mas não sei como fazer isso, tenho a chave de contato, o arduino, etc, mas não tenho o conhecimento.

    Pode me ajudar?

  • Tiago de França Queiroz

    Olá Eduardo, existem várias formas de fazer isso, algumas mais simples e outras mais complicadas.

    Além de controlar essa chave o arduino vai fazer mais alguma coisa (receber dados, monitorar sensores)?

    Essa chave de contato seria um relé (http://pt.wikipedia.org/wiki/Rel%C3%A9), certo?

  • Eduardo castellani

    Sim, Tiago, acho que um relé  seria necessario, mas postei um pedido de ajuda no forum e ate uma proposta, veja se tiver tempo. abraço

    http://labdegaragem.com/forum/topics/pedido-de-ajuda-para-um-timer-...

  • Wilkerson Willame

    Olá, estou tendo um problema com o "TimerFive" ! O programa (arduino) não está reconhecendo ele como biblioteca. Quando eu digo isso, digo que o nome <TimerFive.h> não está ficando laranjinha... rsrs'. Algum palpite de problema?

  • Diogo Santos Silva

    Opa!

    Não sei se nesse post posso abordar dessa forma o assunto, mas...... teriam como me dá uma força em como utilizar a biblioteca "timer"!?

    Primeiro ao vou abordar o problema!

    O delay() é um comando extremamente prático e simples de manusear, porém tem suas limitações. E está dando trabalho para achar um comando alternativo ou algo que possa desempenhar a mesma função, de forma prática.

    Aí entra a utilização da biblioteca "timer"!

    Eu ingressei recentemente na programação(é ainda sou nub nisso) logo não está sendo simples enxergar/construir um programa sem o delay(), para executar atividade de simples como manter componentes ativados e desativados!

    Seria possível utilizar a biblioteca "timer", de forma a, desempenhar a mesma função que o delay() para a função manter ligado e/ou desligado por um tempo?

    Usando o exemplo mais básico para o arduino (http://playground.arduino.cc/Portugues/LearningBlink) é possível fazer a mesma atividade utilizando a biblioteca "timer"?

    Desculpe se eu estiver fugindo do tópico, mas ainda estou aprendendo na base de exemplos + tentativas.

  • Tiago de França Queiroz

    Diego, a função delay() e timer são bem diferentes. Para coisas bem simples como o blink elas parecem iguais, mas não são.

    Basicamente o delay() para a execução do programa por dado tempo enquanto que os timers executam uma função a cada intervalo de tempo.

    Quando uma interrupção do timer é gerado o loop() é interrompido e a função registrada com attachInterrupt é executada e depois o loop() continua sendo executado.

    O objetivo do timer (no caso do exemplo desse post) é executar uma função em um intervalo regular de tempo independente do que está sendo executado no loop().


    Eu consegui esclarecer sua dúvida?

  • Diogo Santos Silva

    Certo, mas... então não existe "nada" que possa servir de alternativa para o delay()?

    Estava olhando o millis()(quando vi o post sobre a biblioteca "timer"), que também possibilita o controle por ciclos de tempo, porém para o que pretendo(ações do componentes serem ativadas por teclado de acordo com o momento) sua aplicação não prática.

    Existe algo que se aproxime desse tipo de ação?

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

    E valeu pela explicação Tiago!

  • Tiago de França Queiroz

    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.

  • Diogo Santos Silva

    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.

    */

  • Diogo Santos Silva

    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!

  • Rafael Poletti

    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

  • Thiago Magalhães

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

    ?

  • Thiago Magalhães

    #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 ?

    }



    }

  • João Estevão

    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.

  • João Estevão

    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.