segunda-feira, 14 de fevereiro de 2011

Dimmer Digital Futurístico com Arduíno (Parte 2)



Neste post falaremos sobre a teoria do funcionamento do dimmer digital com Arduino desenvolvido pelo Vinicius Senger e como implementar um controle de intensidade preciso utilizando um sensor de distância IR.

A rede elétrica que temos disponível na maioria das residências no Brasil é senoidal com 127 volts, sendo que a freqüência de oscilação é de 60 Hz (sessenta ciclos por segundo). Isso significa que temos um ciclo a cada 16,66 ms.

Para implementar o controle de luminosidade de uma lâmpada em AC, normalmente utilizamos um componente chamado TRIAC.

TRIAC ou Triode for Alternating Current é um componente eletrônico equivalente a dois retificadores controlados de silício (SCR ou tiristores) ligados em antiparalelo e com os terminais de disparo “gate“ ligados juntos.
Este tipo de ligação resulta em uma chave eletrônica bidirecional que pode conduzir a corrente elétrica nos dois sentidos. O TRIAC faz parte da família de tiristores.





Um TRIAC pode ser disparado tanto por uma corrente positiva quanto negativa aplicada no terminal de disparo (gate). Uma vez disparado, o dispositivo continua a conduzir até que a corrente elétrica caia abaixo do valor de corte.
Isto torna o TRIAC ideal para o controle de circuitos de corrente alternada, permitindo acionar grandes potências com circuitos acionados por correntes da ordem de miliamperes.

Podemos controlar o início da condução do dispositivo, aplicando um pulso em um ponto pré-determinado do ciclo de corrente alternada, o que permite controlar a percentagem do ciclo que estará alimentando a carga (também chamado de controle de fase).

Quando usado com cargas indutivas, como motores elétricos, é necessário que se assegure que o TRIAC seja desligado corretamente no final de cada semi-ciclo.
Para circuitos de maior potência, podemos utilizar dois SCRs ligados em antiparalelo, o que garante que cada SCR estará controlando um semi-ciclo independente, não importando a natureza da carga.

Para que seja possível disparar o TRIAC no momento correto, precisamos sincronizar esse disparo com a forma de onda da rede elétrica, para isso utilizamos um circuito detector de passagem por zero volts, no nosso projeto esse circuito é formado pelos componentes R1 a R4 , OK1 (4N35) e R5 (ver diagrama elétrico) .

A cada passagem da onda por zero volts, é gerada uma interrupção (INT0) no ATMEGA, ou seja , temos uma interrupção a cada 8,33 ms. Devemos então gerar um pulso no gate do TRIAC dentro de um intervalo entre 150 microsegundos até 8 milisegundos após a interrupção, assim ele conduzirá corrente elétrica em parte do semi-ciclo atual, seja ele positivo ou negativo.
Quanto mais antecipado for o disparo do TRIAC maior será a intensidade da lâmpada.

obs: não devemos disparar o TRIAC exatamente após a interrupção, pois a tensão ainda estará baixa e não haverá corrente suficiente para manter o TRIAC conduzindo, por isso demos uma margem de 150 microsegundos.

Neste projeto o circuito responsável pelo acionamento do TRIAC é formado pelos componentes OK2 (MOC3021), R7 e TRIAC-1.
Lembrando que tanto OK1 como OK2 tem também as funções de isolar o circuito AC do circuito DC.

Para controlarmos o instante de disparo do TRIAC, quando o ATMEGA recebe uma interrupção externa (INT0), iniciamos o TIMER1 com um valor em us (microsegundos) que é inversamente proporcional a ultima leitura válida do sensor de distância, sendo que transcorrido esse período será gerada uma interrupção indicando o momento do disparo do TRIAC.
O momento do disparo definirá a intensidade luminosa da lâmpada.
A implementação da interrupção utilizando o TIMER1 é feita utilizando a biblioteca TIMER1.

Com a utilização do TIMER1 da forma descrita acima, liberamos o ATMEGA para executar outras funções durante o período entre a detecção de passagem por zero e o momento do disparo do TRIAC, ou seja , ganhamos até 8 ms por semi-ciclo. Esse tempo é aproveitado por exemplo para fazer a multiplexação dos displays de 7 segmentos que indicam a % da potência aplicada à lâmpada (método Loop), evitando assim a necessidade de mais componentes para controlar esses displays.

A figura abaixo mostra as formas de onda da rede AC, da saída do detector de passagem pelo zero volts, do momento de disparo do gate do TRIAC e da forma de onda aplicada na lâmpada com 3 níveis diferentes de intensidade (100%, 50% e 10%).

Vejam que quanto mais cedo for disparado o TRIAC, maior será a área do ciclo aplicado a lâmpada (em vermelho) , e consequentemente maior será a intensidade luminosa.



Para definir o momento de leitura do sensor de distância, utilizamos a biblioteca MS_Timer2 de forma que seja gerada uma interrupção a cada 200ms. No tratamento dessa interrupção efetuamos a leitura do sensor de distância e definimos os dígitos de cada display 7 segmentos de acordo com a leitura efetuada.

Como pode ser observado no código disponibilizado , o método loop( ) deste projeto, fica o tempo todo atualizando a informação nos displays de 7 segmentos, e todas as demais funções são tratadas a partir de interrupções externa (INT0 “zerocross detection”) e internas(Timer1 “disparo do TRIAC” / TIMER2 “leitura do sensor distância IR”).

O resultado dessa implementação é um dimmer preciso e com indicação visual da potência aplicada na lâmpada.

Nas figuras abaixo temos o diagrama elétrico completo e a arte final em formato de shield.





Lista de Componentes:

R1 a R4 : resistores de 47K
R5 : resistor de 10K
R6 e R7 : resistores de 330ohm
OK1: fotoacoplador 4N35
OK2: MOC3021
TRIAC-1: BTA16
Display de 7 segmentos com 3 digitos (anodo comum).
Protoshield
sensor de distância analógico (IR)

O protótipo foi montado em uma protoshield que atendeu muito bem, possibilitando a montagem de todos os componentes com bastante facilidade. (foto abaixo)



abaixo temos o código completo do dimmer:

#include <TimerOne.h>   // inclui biblioteca Timer1
#include <MsTimer2.h>   // Inclui biblioteca MsTimer2
//define portas associadas ao display 7seg
//int displaySeg[] = {a, b, c, d, e, f, g, pd};
int displaySeg[] = {6, 1, 17, 15, 14, 5, 18, 12};
//int displayPorts[] = {Anodo1, Anodo2, Anodo3};
int displayPort[] = {7, 4, 3};

// define padroes do display 7SEG, de 0 - 9 e apagado
// 1 = segmento apagado, 0 = segmento aceso
byte seven_seg_digits[11][8] =  {
{ 0,0,0,0,0,0,1,1 },  // = 0
{ 1,0,0,1,1,1,1,1 },  // = 1
{ 0,0,1,0,0,1,0,1 },  // = 2
{ 0,0,0,0,1,1,0,1 },  // = 3
{ 1,0,0,1,1,0,0,1 },  // = 4
{ 0,1,0,0,1,0,0,1 },  // = 5
{ 0,1,0,0,0,0,0,1 },  // = 6
{ 0,0,0,1,1,1,1,1 },  // = 7
{ 0,0,0,0,0,0,0,1 },  // = 8
{ 0,0,0,1,1,0,0,1 },   // = 9
{ 1,1,1,1,1,1,1,1 }   // = blank
};

int sensorDistancia = 2;  // porta associada ao sensor distancia IR
int triacGatePin = 10 ;  // porta associada ao gate do TRIAC

int distancia = 0 ;  // variavel para armazenar distancia
int power= 0 ;  //inicializa variavel que controla potencia na lampada com 0 (lampada apagada)
int centena = 10 ;  // armazena informacao a ser apresentada no display de centena (inicializa com 10 = apagado)
int dezena = 10 ;  // armazena informacao a ser apresentada no display de dezena (inicializa com 10 = apagado)
int unidade = 10 ;  // armazena informacao a ser apresentada no display de unidade (inicializa com 10 = apagado)

void setup() {  
//Serial.begin(9600);
pinMode(triacGatePin, OUTPUT);
for (int i=0 ; i<=3 ; i++) {  
pinMode(displayPort[i], OUTPUT);  
}
for (int j=0 ; j<=7 ; j++) {  
pinMode(displaySeg[j], OUTPUT);  
}
attachInterrupt(0, zeroCrossInterrupt, CHANGE);  //associa interrupcao INT0 com funcao "zeroCrossInterrupt"

MsTimer2::set(200, leSensor); // inicializa Timer2 com 200 milisegundos (tempo entre leituras do sensor de dintancia)
MsTimer2::start();  // start do timer2
}

void loop() {
atualizaDisplay(unidade, dezena, centena);  // atualiza informacoes nos displays de 7 segmentos
}

void atualizaDisplay(int uni, int dez, int cen) {
for (int k = 0; k < 8; ++k) {
digitalWrite(displaySeg[k], seven_seg_digits[uni][k]);
}
digitalWrite(displayPort[2], HIGH);
delay (1);
digitalWrite(displayPort[2], LOW);

for (int k = 0; k < 8; ++k) {
digitalWrite(displaySeg[k], seven_seg_digits[dez][k]);
}
digitalWrite(displayPort[1], HIGH);
delay (1);
digitalWrite(displayPort[1], LOW);

for (int k = 0; k < 8; ++k) {
digitalWrite(displaySeg[k], seven_seg_digits[cen][k]);
}
digitalWrite(displayPort[0], HIGH);
delay (1);
digitalWrite(displayPort[0], LOW);

}

void zeroCrossInterrupt(){  // trata interrupcao INT0 
if(power > 0) {
long dimtime = int(map(power,0,100,8000,150));  // calcula o tempo de delay para o disparo do TRIAC
Timer1.initialize (dimtime);  // inicializa o TIMER1 com o delay calculado
Timer1.attachInterrupt(gateTRIAC);  //associa a funcao gateTRIAC com  Interrupcao do TIMER1
Timer1.start();  // inicia contagem TIMER1
}  else {
digitalWrite(triacGatePin, LOW);   // mantem gate do TRIAC desativado.  
Timer1.stop();
}
}

void gateTRIAC () {  // trata interrupcao do TIMER1 gerando pulso no gate do TRIAC
digitalWrite(triacGatePin, HIGH);  // dispara o Triac
delayMicroseconds(15);  // aguarda 15 microsegundos para garantir disparo do TRIAC
digitalWrite(triacGatePin, LOW);   // desabibilta gate do TRIAC 
Timer1.stop();
}

void leSensor() {
distancia = analogRead(sensorDistancia);  // le sensor de distancia
//Serial.println(distancia);
distancia = map(distancia,70,600,120,0);  // mapeia distancia lida para uma faixa de potencia entre 0 e 120
if (distancia < 0) {
distancia = 0;
}

if (distancia >=0 && distancia <= 100) {  // se distancia <= a 100 atualiza potencia
power=distancia;
}

centena = int(power / 100);  // atualiza variaveis que contem informacoes dos displays
dezena = int((power - (centena*100)) / 10);
unidade = int(power - (centena*100)-(dezena*10));
if (centena == 0 && dezena == 0){ 
centena = 10;
dezena = 10;
}
if (centena == 0){
centena = 10;
}
}



Segue um vídeo demonstrando o funcionamento do dimmer.