
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.