Arduino e dimmer com Triac: criando um amanhecer artificial

Módulo dimmer permite controlar dinamicamente a luminosidade de lâmpadas incandescentes.

Dizem que provocar um amanhecer artificial com uma lâmpada incandescente de luz amarela pode levar a um despertar bem mais tranquilo para quem dorme com cortinas fechadas, pois imita o nascer do sol. Não sei se é verdade, mas acho plausível.

Esse amanhecer artificial pode ser produzido com uma lâmpada incandescente comum, em uma montagem que permita que sua luminosidade vá aumentando aos poucos, suavemente, ao longo de um período relativamente longo (meia hora, por exemplo), simulando em ambientes de metrópoles a experiência do amanhecer rural, ou de locais em que seja confortável e seguro dormir de janela entreaberta, e as luzes e sons da cidade não perturbem o sono durante a noite.

O experimento que veremos hoje implementou um amanhecer artificial com lâmpada incandescente comum, controlado por um Arduino e um circuito simples de montar (mas exige cuidados especiais, pelos riscos inerentes à conexão à rede elétrica residencial!), baseado em módulo dimmer pré-pronto.

No processo de explicar como eu o construí, vou apresentar um pouco da prática com interrupções programáveis no Arduino, e também sobre como funciona o controle de cargas AC usando Triacs, no estilo dimmer.

O importante é não perder o sono com a implementação ;-)

Dica: em um artigo posterior, usarei o mesmo módulo e aplicação como exemplos para um uso mais avançado das interrupções de temporização do Arduino. Entretanto, no artigo que você está lendo, esse uso será reduzido ao mínimo.


Índice das seções:

  1. Componentes e montagem
  2. A interrupção INT0 do Arduino
  3. Dimmer com Arduino e Triac - Breve visão teórica
  4. O programa
  5. O problema com o tratamento de interrupções deste programa
  6. Patrocínio: Usinainfo

Componentes e montagem

Nosso projeto irá controlar uma carga AC (mais especificamente, uma lâmpada incandescente comum) conectada à instalação elétrica residencial, de 110V ou 220V (dependendo da região).

O Arduino não opera diretamente com cargas AC e nem com essa faixa de tensão, o que nos leva a recorrer a um circuito externo para complementá-lo – no caso, um Módulo Dimmer para Arduino, que recebi da Usinainfo (que patrocina o experimento deste artigo - obrigado!).

Trata-se de uma placa de cerca de 6x3cm, com 2 blocos de 4 terminais cada, sendo um deles voltado às conexões AC (o par de fios de entrada que chega da tomada e o par de fios de saída que segue para a lâmpada), e outro às conexões DC.

As identificações e descrições das conexões no lado DC do módulo são as que seguem:

  • GND: Terra, a ser conectado ao pino GND do Arduino.
  • DIM: Gatilho de temporização que define a intensidade da iluminação, a ser conectado ao pino 9 do Arduino. Quando ele fica HIGH, o módulo permite a passagem de eletricidade (recebida da tomada) para a lâmpada, e continuará permitindo até que a tensão do ciclo AC passe novamente pelos 0V, o que ocorre 120 vezes por segundo. Veremos detalhes a seguir, na fundamentação teórica.
  • ZC: Detector de zero crossing, ou seja, do momento em que a tensão recebida da tomada AC passa pelos 0V, o que ocorre 120 vezes por segundo em condições normais. Também veremos detalhes a seguir. A ser conectado ao pino 2 do Arduino.
  • VCC: Entrada de tensão, a ser conectada ao pino VCC do Arduino.

O módulo precisa ser adequado à tensão da sua instalação elétrica residencial: há modelos de 110V e 220V. A diferença mais evidente entre as 2 variações disponíveis na Usinainfo é o valor do resistor grande, montado na vertical: na versão para 110V ele é de 15KΩ , e na de 220V ele é de 33KΩ.

A foto acima foi tirada com uma lente macro, e permite observar que o módulo é baseado no Triac BT137, com apoio de 2 CIS optoacopladores, o MOC3021 e o 4N25, encarregados de isolar da tensão elevada (veja detalhes em “Arduino e acoplador óptico”) as funcionalidades dos pinos DIM e ZC, respectivamente. Uma descrição básica de seu funcionamento (em especial quanto ao comportamento nas interfaces) é apresentada adiante.

Antes de prosseguir, um alerta importante:

Atenção: o projeto deste artigo utiliza conexão direta à rede elétrica residencial, o que causa riscos de CHOQUE ELÉTRICO e INCÊNDIO, entre outros, com danos pessoais, morais e materiais sobre os quais este autor não se responsabiliza. Ele é apresentado como descrição de um experimento realizado, e não como proposta de implementação. Só execute um projeto como este caso você tenha experiência e habilitação com instalações elétricas. Sempre siga as normas de segurança aplicáveis, e certifique-se de que todos os equipamentos e infraestrutura associada também as seguem. Use equipamentos de proteção individual, e sempre acrescente os componentes de segurança (fusíveis, resistores, isolamento, etc.) adequados a cada projeto, sabendo que eles não estão representados neste artigo e devem ser selecionados de acordo com as condições de cada execução.

A montagem realizada está representada no diagrama a seguir:

Note que o módulo está conectado à tomada, à lâmpada e a um conjunto de 4 pinos de um Arduino Uno, e não há outros componentes em uso.

Já fiz o alerta sobre os riscos inerentes ao uso da rede elétrica residencial, mas vou acrescentar: vários dos conectores e componentes do módulo dimmer estarão energizados com a eletricidade recebida da tomada da sua casa. Tocá-los diretamente, ou encostá-los em qualquer material condutor (incluindo suas ferramentas) é análogo a encostar diretamente na tomada. Operar esse circuito exige atenção e cautela especiais.

Uma observação: por sua própria natureza, o circuito do Triac esquenta bastante durante o uso. A Usinainfo registra em sua documentação que ele é voltado especificamente à função de dimmer de lâmpadas incandescentes, e que há necessidade de usar dissipador de calor. Sugiro consultar as datasheets dos componentes, para estimar a demanda disso de acordo com a carga e as condições ambientais.

A interrupção INT0 do Arduino

Embora não seja multitarefa, o microcontrolador ATmega328 que é o cérebro do seu Arduino Uno tem uma série de recursos que permitem reagir a eventos, suspendendo brevemente a atividade rotineira que estava em execução, executando rapidamente uma função correspondente ao evento ocorrido, e retornando transparentemente à atividade rotineira anterior.

Esse mecanismo é chamado, genericamente, de interrupção, e pode reagir a uma série de eventos, incluindo um que será especialmente útil para nosso artigo de hoje: a mudança de estado no pino digital 2, que recebe o nome de INT0.

O ciclo AC no Brasil ocorre 60 vezes por segundo, e a cada uma delas o Arduino executará duas vezes o código que definirmos para a INT0.

A instalação elétrica AC (corrente alternada) da minha casa segue o padrão brasileiro de ciclo de 60Hz, no qual o sinal da tensão fica positivo 60 vezes por segundo e negativo outras 60 vezes por segundo.

Ao alternar o sinal (por exemplo, passar de +110V para -110V), a tensão chega a ser 0V por um breve instante, e esse momento é sinalizado pelo módulo, com um pulso HIGH no seu pino ZC (zero crossing ou passagem pelo zero)

Conectando o pino ZC do módulo ao pino 2 do Arduino, podemos usar a INT0 para definir um trecho de código a ser executado automaticamente (independentemente do que o programa "normal" estiver fazendo) sempre que a tensão da rede elétrica passar pelo ponto dos 0V, interrompendo a função loop() (ou qualquer outra função chamada por ela, diretamente ou não), e depois retornando transparentemente ao ponto em que ela parou.

Para isso, usei a função attachInterrupt() (padrão do Arduino) para associar a INT0 a uma função definida no meu programa (o nome dela é zeroCross()), ativando-a no modo RISING, que gera acionamentos sempre que o pino 2 passar para o estado HIGH.

Simplificando o efeito, isso significa que a cada vez que o estado do pino 2 mudar para HIGH (indicando que o pino ZC avisa que o ciclo AC acaba de passar pelo valor 0), o Arduino vai parar o que estiver fazendo, executar a minha função zeroCross() e em seguida retornar ao que estava fazendo.

Para uma rede elétrica de 60Hz, os pulsos HIGH no pino ZC vão ocorrer a cada 8,33 milissegundos, aproximadamente – e a cada um deles vai ser gerada uma INT0, e o ATmega328 vai parar o que estiver fazendo e executar a minha função zeroCross().

O efeito dessa série de interrupções a ser gerada é uma precisa temporização de envio (pela zeroCross()) de um pulso HIGH para o pino DIM do módulo, que faz a lâmpada acender (e ficar acesa até a próxima passagem pelo 0V).

O tempo (que chamaremos de t1) entre o pulso do ZC ser recebido e o pulso no DIM ser enviado é o que define a luminosidade produzida: se demorar pouco, a lâmpada ficará acesa durante mais tempo, e perceberemos mais luminosidade; se demorar muito, a lâmpada ficará menos tempo acesa, e perceberemos menos luminosidade. Se demorar mais do que 8,33 milissegundos, a lâmpada nem chegará a acender.

Nota: Para saber mais sobre as interrupções do Arduino Uno, recomendo as seções 7.7 e 12 da datasheet do ATmega328.

Dimmer com Arduino e Triac - Breve visão teórica

Este dimmer baseado em Arduino baseia-se em controlar com precisão o momento de apagar e acender a lâmpada, 120 vezes por segundo. Devido à frequência de repetição, nossa visão não percebe isso como piscadas, e sim como redução da luminosidade.

Veremos a seguir os elementos principais que fundamentam esse efeito. Este resumo fará uso de conceitos sobre o funcionamento de CPUs e da própria eletricidade residencial, que não necessariamente serão apresentados aqui.

No Arduino geralmente trabalhamos com tensões DC (corrente contínua), em valores relativamente baixos, tais como 5V ou 3,3V. Mas a rede elétrica das nossas casas é em AC (corrente alternada) e, além de operar em valores bem mais altos (110V ou 220V), tem a característica própria que a diferencia da corrente contínua: quando operamos em AC, o sinal da tensão varia várias vezes por segundo, alternando entre positivo e negativo.

No Brasil a rede elétrica é padronizada em 60Hz, o que significa que um ciclo completo (de -110V a 110V e de volta a -110V, ou de -220V a 220V e de volta a -220V, dependendo da região), acontece 60 vezes por segundo. Para alternar entre o valor positivo e o valor negativo, há um momento crucial em que a tensão chega a ser 0V, que o módulo dimmer sinaliza por meio de um pulso em seu pino ZC.

O módulo dimmer usado no projeto oferece duas funções básicas: sincronização e permitir o controle de uma carga AC pelos modestos 5V DC do Arduino.

O Arduino não opera diretamente sobre AC, e é por isso que recorremos a um módulo dimmer, baseado em um componente chamado Triac, para realizar essa operação. Nosso módulo dimmer tem 2 funções básicas: avisar ao Arduino a cada vez que a tensão AC passa pelo valor 0V (o que ocorre duas vezes a cada ciclo, ou seja, 120 vezes por segundo), e permitir que a pequena tensão DC controlada diretamente pelo Arduino seja usada para ligar e desligar a lâmpada.

Com base nessas capacidades do Triac, vamos fazer com que o Arduino desligue brevemente a lâmpada, durante uma parte de cada um dos ciclos. Isso acontece 120 vezes por segundo (ou 2 vezes por ciclo), e nossos olhos não conseguem perceber que a luz está piscando: o que vemos é apenas uma redução da luminosidade, equivalente à que acontece quando há uma queda de tensão da rede elétrica do seu bairro, embora causada por outro princípio (o mecanismo aqui descrito não modifica a tensão, apenas o tempo durante o qual ela fica disponível).

O gráfico a seguir mostra o que acontece durante cada um dos ciclos AC (ou seja: a senóide apresentada se repete 60 vezes por segundo):

O eixo X é o tempo, e o gráfico inteiro se repete 60 vezes por segundo, ou seja, a linha horizontal representada no gráfico equivale a 1/60 de segundo.

Note que o eixo Y tem 2 escalas: a da esquerda, de -10V a +10V, vale apenas para a linha vermelha, que indica a tensão ativada pelo Arduino no pino DIM do nosso módulo, e a da direita, que vai de -150V a +150V, e vale para a linha pontilhada (que indica a tensão recebida da tomada pelo módulo) e para a linha azul (que indica a tensão fornecida à lâmpada pelo módulo).

A linha pontilhada é visível apenas em 2 pequenos trechos, porque nos demais ela é sobreposta pela linha azul.

Observe que a linha vermelha geralmente está fixa em 0 volts, exceto em 2 breves momentos, que ocorrem após o tempo t1 (que começa a contar sempre que a tensão da tomada passa pelo 0V) e cuja duração equivale ao tempo t2, que correspondem aos pulsos positivos (HIGH) que o Arduino envia para o pino DIM do nosso módulo, indicando que ele pode liberar a passagem entre a tomada e a lâmpada.

Note também que, no mesmo momento em que a linha vermelha começa a estar HIGH, a linha azul passa de 0V (indicando que até então a lâmpada estava apagada) para o mesmo valor da tensão recebida da tomada (linha pontilhada) naquele momento: é o momento em que a lâmpada acende.

Quando a linha vermelha sai do 0, a linha azul também sai – mas quando a linha vermelha volta ao 0, a azul não a acompanha.

Entretanto, logo após, quando a linha vermelha volta a ser 0V, a linha azul não volta junto com ela. Ela continua a sobrepor a linha pontilhada até que esta cruze novamente a linha de 0V, e é ali que elas se separam: como o pino DIM não está mais HIGH, o TRIAC corta a alimentação da lâmpada assim que a tensão da tomada volta a passar pelo 0V.

Agora estamos no lado negativo da senóide, e a contagem recomeça: após passar o mesmo tempo t1 que aconteceu no lado positivo, o pino DIM vai brevemente a 5V e, a partir daí, a linha azul volta a se encontrar com a linha pontilhada, até ambas passarem novamente pelo 0V, reiniciando o ciclo.

A duração de t1 é o elemento mais importante para definir a luminosidade percebida: quanto mais longa for a duração de t1, mais tempo a lâmpada ficará apagada a cada ciclo, e menos luminosidade será percebida.

O limite prático para o crescimento de t1 é pouco abaixo de 1/120 de segundo (8,33 milésimos de segundo, ou meio ciclo AC), caso contrário a lâmpada nem chegará a acender.

A duração de t2 é menos importante para a nossa aplicação: o pino DIM é um gatilho, e não uma chave. Depois que o Triac é acionado, manterá o estado da alimentação da lâmpada até a chegada da tensão AC ao 0V.

No nosso programa, a seguir, t2 tem um valor fixo (6 milissegundos), e t1 é um valor inversamente proporcional à variável volátil luminosidade.

Adaptei o gráfico acima1 de um disponibilizado no artigo AC Phase Control, do site do Genuino (que infelizmente não mencionou seu autor), que apresenta um programa bem mais complexo do que o que veremos a seguir, para gerenciar as mesmas temporizações.

Os detalhes técnicos que apresentei nesta seção são simplificados e superficiais. Para uso como referência sugiro recorrer a uma fonte mais solidamente amparada na descrição dos elementos da Física presentes, e não apenas em uma visão baseada em interfaces de um módulo.

O programa

Existe uma variedade de bibliotecas prontas para o controle de cargas AC com Triacs, algumas melhor testadas, outras nem tanto, e você pode optar livremente entre elas.

Para minha própria prática de conceitos referentes às interrupções do Arduino, optei por uma implementação em um nível arquitetural um pouco mais baixo, sem referência direta a bibliotecas, baseando-se em simplesmente definir a INT2 e usar a função delayMicroseconds() para a temporização2.

O resultado você vê a seguir, com explicações logo após.

#define PINO_ZC 2
#define PINO_DIM 9

volatile long luminosidade = 0;  // 0 a 100 
 
void zeroCross()  {
  if (luminosidade>100) luminosidade=100;
  if (luminosidade<0) luminosidade=0;
  long t1 = 8200L * (100L - luminosidade) / 100L;      
  delayMicroseconds(t1);   
  digitalWrite(PINO_DIM, HIGH);  
  delayMicroseconds(6);      // t2
  digitalWrite(PINO_DIM, LOW);   
}
 
void setup() {
  pinMode(PINO_DIM, OUTPUT);
  attachInterrupt(0, zeroCross, RISING);
}
 
void loop() {
  for (byte i=20; i<95; i++) {
    luminosidade=i;
    delay(150);     
  }
  delay(1000)
}

Dica: sugiro fortemente que você leia o texto acima do programa (a introdução às interrupções e a breve visão teórica) antes de ler a explicação do programa, a seguir.

A primeira característica especial do programa está presente na linha em vermelho, que define que a variável luminosidade será volatile, uma prática comum em variáveis que são compartilhadas entre o corpo "normal" do programa e as rotinas de interrupção. Com isso, o compilador as tratará de forma especial, permitindo que a rotina de interrupção as use e altere, e seu valor permanecerá disponível para uso pelas rotinas "normais", que também poderão alterá-las.

Antes de estudar a função zeroCross(), pule para a linha em cor roxa, no setup. Nela eu usei a já mencionada função attachInterrupt() para indicar que, sempre que acontecer o evento RISING (ou seja, sempre que o valor mudar para HIGH) no pino 2 (que é sempre o pino da interrupção 0, ou INT0), uma função chamada zeroCross deverá ser chamada.

Funções chamadas diretamente por interrupções têm uma série de limitações, e precisam terminar tão rapidamente quanto possível.

Vale destacar que funções usadas com interrupções têm uma série de limitações: não podem receber parâmetros (por isso optei por usar uma variável global volátil para transmitir um valor a ela), não podem retornar nada, não podem usar delay() (mas podem usar delayMicrosseconds()), não podem fazer uso do millis(), e mais. Além disso, elas podem suspender ou prejudicar o uso de outras interrupções usadas transparentemente pelo Arduino (para receber dados pela Serial, por exemplo), razão pela qual é importante que sejam tão rápidas quanto possível.

Voltemos agora à função zeroCross(), em marrom. Lembre do que vimos no gráfico: ela é chamada sempre que o pino ZC recebe um pulso HIGH, e sua responsabilidade é aguardar um tempo t1 (quanto maior, menos luminosidade), aí enviar um pulso HIGH ao pino DIM, que durará o tempo t2 (cuja duração pouco interfere no resultado, bastando ser longa o suficiente para que o Triac a identifique), e permitirá que a lâmpada acenda até a próxima passagem pelos 0V.

O primeiro passo é calcular, com base no valor da variável volátil luminosidade (que deve variar entre 0 e 100), qual será a duração do tempo t1 - quanto menor a luminosidade desejada, maior deve ser o tempo t1, até um limite próximo a 1/160 de segundos, ou aproximadamente 8333 microssegundos.

Calculei t1 pela fórmula a seguir:

t1 = 8200 × (100 - luminosidade) ÷ 100

Note que usei 8200 (e não 8333) como o limite máximo, para evitar que a soma de t1 + t2 (mais o tempo que leva para executar os comandos da zeroCross()) ultrapasse os 8333 microsegundos que temos entre cada chamada. Também fixei t2 (logo abaixo) em 6 microssegundos.

Tendo t1 e t2 em mãos, basta seguir a sequência:

  • Aguardar t1 microssegundos
  • Mover HIGH para o pino DIM
  • Aguardar t2 microssegundos
  • Mover LOW para o pino DIM

E assim termina a zeroCross(). Com isso, a interrupção INT0 se encerra (até que outra aconteça), e a execução retorna ao ponto em que o programa "normal" foi interrompido.

A inteligência do programa está toda aí. Fora isso, o que temos é uma função loop() que executa, a cada vez, um loop for que move valores de 20 até 95 para a variável volátil luminosidade, aguardando 150ms em cada valor, e depois aguardando 1 segundo inteiro (1000ms) antes de recomeçar.

A função zeroCross() não é chamada diretamente por nenhuma outra parte do programa, apenas pelo gerenciamento de interrupções do microcontrolador.

Note que o loop for não faz qualquer chamada a nenhum recurso de controle do módulo ou da lâmpada: ele apenas atualiza o valor da luminosidade, porque sabemos que esse mesmo valor será consultado, 120 vezes por segundo, a cada vez que a INT0 chamar a função zeroCross().

Com essa interação contínua entre o loop() e a rotina de interrupção, a luminosidade da lâmpada vai variar entre 20% e 95% do valor de referência, em um amanhecer artificial bem rápido: pouco mais do que 10 segundos.

Aumentando o valor do delay originalmente definido como 150ms você pode fazer essa alvorada demorar bem mais, para ter o efeito luminoso desejado. E se você inserir na solução um módulo RTC (relógio de tempo real) externo, como o que vimos no artigo “Data, hora e memória extra no Arduino com o módulo Tiny RTC e o chip DS1307”, pode até planejar um despertador com amanhecer artificial.

Só não perca o sono implementando-o! ;-)

O problema com o tratamento de interrupções deste programa

Observe a fórmula definida na função zeroCross(), e note que, conforme o valor de luminosidade se aproximar de 0, o valor de t1 se aproximará de 8200.

Como a interrupção chama a função zeroCross() a cada 8333 microssegundos, se a execução dela demorar cerca de 8200 microssegundos, isso significará que (ao manter a lâmpada com baixa luminosidade), a maior parte do tempo do programa será passada executando o delayMicrosseconds() da nossa rotina de interrupção, violando a regra mencionada acima, de que rotinas de interrupção devem ser rápidas.

Uma rotina de interrupção muito frequente e que consome tempo demais acaba subvertendo e prejudicando o mecanismo.

Em outras palavras: se quiséssemos executar em paralelo alguma funcionalidade adicional ou complementar no nosso loop(), e o valor de luminosidade estiver baixo, quase não vai sobrar tempo para o loop() ser executado, porque o Arduino ficará tempo demais executando a zeroCross(), 120 vezes por segundo.

Uma solução para isso seria substituir os delays dentro da função de tratamento de interrupções por outras interrupções (baseadas em um timer interno, e não na mudança de um pino), e isso é algo que veremos em um próximo artigo.

Por enquanto, o conforto é saber que o programa "normal" (extra-interrupções) usado neste exemplo não faz tanta coisa assim...

Patrocínio: Usinainfo

Quero agradecer à UsinaInfo, que vem patrocinando alguns experimentos do BR-Arduino, inclusive este com o Módulo Dimmer para Arduino.

Além de receber material da empresa para os experimentos como parte do acordo de patrocínio, eu já fiz compras de componentes lá, aproveitando a variedade, o estoque bem suprido, as descrições detalhadas e a qualidade do seu sistema de comércio eletrônico. Recomendo sem ressalvas.

Agradeço também pela confiança que ficou expressa nos termos do patrocínio: a empresa me enviou os componentes necessários ao experimento combinado, mas não fez qualquer exigência sobre a forma e nem buscou exercer controle sobre o que eu fosse escrever. Obrigado, UsinaInfo!

 
  1.  Que poderia também se referir a tensões residenciais de 220V, aletrando apenas os limites do eixo Y à esquerda.

  2.  Vale mencionar que a delayMicroseconds() pode ser usada com segurança dentro de rotinas que manipulam interrupções, como a minha zeroCross() – o mesmo não pode ser dito da tradicional função delay().

Comentar

Dos leds ao Arduino, ESP8266 e mais

Aprenda eletrônica com as experiências de um geek veterano dos bits e bytes que nunca tinha soldado um led na vida, e resolveu narrar para você o que descobre enquanto explora esse universo – a partir da eletrônica básica, até chegar aos circuitos modernos.

Por Augusto Campos, autor do BR-Linux e Efetividade.net.

Recomendados

Livro recomendado


Artigos já disponíveis

Comunidade Arduino

O BR-Arduino é integrante da comunidade internacional de entusiastas do Arduino, mas não tem relação com os criadores e distribuidores do produto, nem com os detentores das marcas registradas.

Livros recomendados