Postagem em destaque

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte I

Este post é o primeiro de uma série de seis que escrevi tratando de controle de potência e PID (controle proporcional, integral e derivativo...

sábado, 8 de dezembro de 2012

Tutorial: interrupções de tempo (timer interruptions) no Arduino

Introdução

Hoje, no Parque, apareceu o ex-aluno Mackson, com o seu projeto de TCC, o qual foi desenvolvido com várias passagens pelo Automação no Parque. Já foi até objeto de post anterior, aqui. Agora, nos "finalmentes" ele trouxe a maquininha para a gente apreciar.



O trabalho dele consiste desenvolver um sistema para operação automática de um biodigestor. Ele montou uma caixa com o diagrama do equipamento no campo e colocou o circuito dentro da caixa, com LEDs, chaves e potenciômetros para ilustrar o funcionamento da bagaça.
Basicamente o digestor fornece gás metano, que é conduzido através de uma válvula (LED amarelo, aceso qdo a válvula está aberta, bombeado por um compressor (LED verde) e queimado num queimador por uma chama (LED azul). A luz verde indica que o sistema está operando em modo Normal.
Aí alguém deu o palpite: mas a chama não é azul, ela muda de cor. E se a gente colocasse um LED RGB e fizesse um efeito chama com ele mudando de cor? Imediatamente a galera se mobilizou para botar a coisa rodando.
A dificuldade residia em que, ao mesmo tempo em que a rotina da chama deveria ficar "rodando", o sistema teria que monitorar os outros indicadores (temperatura, pressão etc), ou seja, durante o tempo em que a chama estiver ligada, seria interessante que a rotina da chama rodasse "em paralelo" com o resto do sistema, de maneira a criar o efeito desejado.
É aí que entra o recurso de time interruption.

Teoria

Para quem programa há vinte anos ou mais: interrupção do Arduino é igual a interrupção do DOS. Pode pular a teoria.
Para os "menos experientes", interrupção é um mecanismo que permite a um processador parar uma rotina, executar uma outra tarefa e depois voltar à execução da tarefa principal. "Mas então é como uma sub-rotina, um sub-programa?", perguntaria alguém.
Sim, é. A diferença é que o desvio do processo de execução para essa sub-rotina se dá não por decisão do programa que chamou a sub-rotina, mas sim por um outro evento qualquer, por exemplo, um intervalo fixo de tempo entre as chamadas. Assim, a cada "n" microssegundos, o Arduino para o que estiver fazendo, roda a rotina e depois retoma o que estava fazendo antes.

Exempo

Vamos a um exemplo. Primeiro o resultado, um LED RGB com o tal "efeito chama" funcionando.



O LED RGB é uma espécie de 3 em 1. São três LED, um (R)vermelho, outro (G)verde e um (B)azul, tudo no mesmo encapsulamento, com 4 conectores: um para cada cor e um comum. Normalmente o comum é o terra, mas eu já vi muitos onde o comum é o +. Nesse caso, vc tem que ligar ele no 5V ou 3.3V e escrever 1 na porta correspondente para apagar e 0 para acender. Uma dica do Alex, frequentador do Automação no Parque e dono do projeto MariaMole: para "misturar" melhor as cores do LED RGB, lixe o bicho com BomBril, de maneira a que fique um pouco opaco.

O jeito óbvio de fazer uma aplicação dessa seria:

int rPin = 11;
int gPin = 10;

void setup()
{
  pinMode(rPin,OUTPUT);
  pinMode(gPin,OUTPUT);
}

void loop()
{
  for (int i=0; i<100; i++)
  {
    analogWrite(gPin,i);
    analogWrite(rPin,200);
    delay(5);
  }
  for (int i=100; i>0; i--)
  {
    analogWrite(gPin,i);
    analogWrite(rPin,200);
    delay(5);
  }
}


No código acima, os pinos 10 e 11 estão ligados aos terminais verde e vermelho do LED, respectivamente.

No loop(), temos dois laços: um varia a intensidade do verde entre 0 e 100 crescendo, o outro varia a velocidade do LED decrescendo. O vermelho é mantido num valor constante. Moleza.

Só que... imagine que o seu programa tem que fazer algum outro serviço além de ficar comandando o LED. Aí complica, porque se vc colocar, por exemplo, um comando delay(1000), o LED vai "congelar" ou apagar, dependendo de como seu código for escrito. Seria interessante que o código pudesse rodar e o efeito LED continuasse acontecendo, até que vc resolvesse desligá-lo. É a aí que entra a interrupção. Veja o código abaixo, agora usando interrupção de tempo, abaixo:

#include <TimerOne.h>
int rPin = 11;
int gPin = 10;

volatile byte _i = 0;
volatile byte _j = 1;

void chama()
{
    analogWrite(gPin,_i);
    analogWrite(rPin,200);
    _i+=_j;
    if (_i == 100)
      _j=-1;
    else
      if (_i == 0)
        _j=1;
}
void setup()
{
  Timer1.initialize(5000);
  Timer1.attachInterrupt(chama); 
  Serial.begin(9600);
  Serial.begin(9600);
  pinMode(rPin,OUTPUT);
  pinMode(gPin,OUTPUT);
}

void loop()
{
}


Agora temos:
O # include da biblioteca TimeOne.h.
Temos uma rotina chama(), que faz o efeito de chama. Ela é um pouquinho mais complicada:
Temos duas variáveis _j, _j, do tipo inteiro, que são usadas pela rotina mas não ficam definidas dentro dela. Isso porque o seu conteúdo deverá ser preservado a cada chamada da rotina, quer dizer, depois que a rotina terminar esses valores tem que permanecer para ser usados a cada chamada. A palavra "volatile", colocada antes da declaração dessas variáveis basicamente diz ao compilador para cuidar para que essas variáveis tenham seus conteúdos preservados da chamada de uma rotina para outra.
Dentro da rotina agora temos o seguinte: o valor da cor vermelha é sempre 200 (valor arbitrário que vc tem que descobrir para o seu LED qual o melhor para obter o efeito que deseja), e o da cor verde é o valor de _i.
A cada chamada da rotina, _j é somado a _i. Em seguida o valor de _i é testado, de maneira que, quando _i for 100, _j = -1, ou seja, _i passará a ser decrementado. Quando _i for 0, _j = 1, o que fará com que no próximo loop _i assuma o valor 1 e assim por diante.
Em seguida vem o setup(). Lá, a instrução   Timer1.initialize(5000); define que a cada 5000 microssegundos a rotina de timer será executada. Em seguida, a instrução Timer1.attachInterrupt(chama); define a rotina a ser chamada a cada interrupção A nossa chama() anteriormente descrita.

Observem que não existe nenhuma rotina na instrução loop(), ou seja, ela está livre para que vc escreva seu código sem se preocupar com a rotina chama(), que estará sendo executada e dando o efeito desejado normalmente.

Não é legal esse troço de interrupção? Agora vejam o efeito funcionando no TCC do Mackson.


E aí se foi mais um sábado de Automação no Parque, o último com a minha participação esse ano. Se o mundo estiver por aí daqui a alguns dias, estaremos de volta em 2013.