Sketch funcional para uso com interruptores pulsadores

Neste post vou mostrar um sketch para dar utilidade prática aos interruptores pulsadores utilizados com o Arduino e relés para o controle de lampadas.

Este post é uma continuação do post anterior, portanto, se ainda não leu, não deixe de ler este post(Automação Residencial – Ambiente de Testes), pois todo o código será baseado no layout mostrado anteriormente.

Agora que já leram o post anterior, devem saber que dois pontos negativos até então do ambiente de testes era o pobre sketch, e a bagunça geral. Bem, a bagunça continua, mas já adquiri conectores para “crimpar” os fios do cabo de rede, e amanha vou comprar chapas de compensado para montar um local adequado para as instalações. Os conectores que adquiri (espero que não demorem para chegar) são os abaixo:

capa

Capa plástica para os conectores (tanto o macho quanto o femea)

conector femea

Conector Fema

conector macho

Conector Macho

Com a maquete o ambiente ficará sem dúvida mais gostoso de trabalhar, e estes conectores serão utilizados para arrumar a bagunça de conexões, analisando ainda o seu uso pela casa.

Bem, vamos ao que interessa, que é o sketch. Como puderam ver no post anterior, tínhamos que manter o pulsador pressionado para que a lampada ficasse acesa. Isso não é muito legal né? Abaixo o sketch que acende a lampada e a mantem acesa, bem como apaga e mantem apagada, bastanto um toque no pulsador. O sketch é o abaixo:

// Atribuindo nomes aos pinos de entrada, conectados aos pulsadores
const int pulsa1 = 2;
const int pulsa2 = 3;
const int pulsa3 = 4;

// Atribuindo nomes aos pinos de saida, conectados aos relés correspondentes as lampadas
const int spot1 = 8;
const int spot2 = 9;
const int led = 10;

// Variaveis para armazenar o estado das lampadas
boolean spot1_estado = false;
boolean spot2_estado = false;
boolean led_estado = false;

// Variaveis para armazenar o estado dos pulsadores
boolean pulsa1_estado = false;
boolean pulsa2_estado = false;
boolean pulsa3_estado = false;

// Variavel que controla o tempo até uma detecção confiavel
const int debounceTime = 20;

// Declarando função que detecta por quanto tempo o pulsador foi pressionado;
// pino é o pino sendo verificado;
// &state é o estado do pino (HIGH ou LOW);
// &inicio é quando ele foi pressionado
unsigned long pulsadorTempo(int pino, boolean &state, unsigned long &inicio);


void setup(){
 // Definindo os pinos conectados aos pulsadores como entrada
 pinMode(pulsa1,INPUT);
 pinMode(pulsa2,INPUT);
 pinMode(pulsa3,INPUT);
 // Ativando o resistor PULL-UP
 digitalWrite(pulsa1,HIGH);
 digitalWrite(pulsa2,HIGH);
 digitalWrite(pulsa3,HIGH);
 // Definindo os pinos conectados aos relés como saida
 pinMode(spot1,OUTPUT);
 pinMode(spot2,OUTPUT);
 pinMode(led,OUTPUT);
 // Usando a serial para algum debug
 Serial.begin(9600);
}

void loop(){
 int tempoPulsa1 = pulsador1Tempo();
 if (tempoPulsa1 > debounceTime){
 pulsa1_estado = true;
 }
 else {
 if (pulsa1_estado == true ) {
 comutaRele(spot1, spot1_estado);
 pulsa1_estado = false;
 }
 }
 
 int tempoPulsa2 = pulsador2Tempo();
 if (tempoPulsa2 > debounceTime){
 pulsa2_estado = true;
 }
 else {
 if (pulsa2_estado == true ) {
 comutaRele(spot2, spot2_estado);
 pulsa2_estado = false;
 }
 }
 
 int tempoPulsa3 = pulsador3Tempo();
 if (tempoPulsa3 > debounceTime){
 pulsa3_estado = true;
 }
 else {
 if (pulsa3_estado == true ) {
 comutaRele(led, led_estado);
 pulsa3_estado = false;
 }
 }
}

// Função para detectar por quanto tempo o pulsador foi pressionado
unsigned long pulsadorTempo(int pino, boolean &state, unsigned long &inicio){
 if(digitalRead(pino) != state) {
 state = !state;
 inicio = millis();
 }
 if (state == LOW)
 return millis() - inicio;
 else
 return 0;
}

// Funções abaixo são especificas de cada pulsador
long pulsador1Tempo(){
 static unsigned long inicio = 0;
 static boolean state;
 return pulsadorTempo(pulsa1, state, inicio);
}

long pulsador2Tempo(){
 static unsigned long inicio = 0;
 static boolean state;
 return pulsadorTempo(pulsa2, state, inicio);
}

long pulsador3Tempo(){
 static unsigned long inicio = 0;
 static boolean state;
 return pulsadorTempo(pulsa3, state, inicio);
}

// Função que comuta o relé
void comutaRele(int pino, boolean &estado){
 estado = !estado;
 digitalWrite(pino, estado);
 Serial.println("Mudou");
}

Bem, o sketch esta razoavelmente grande, porem simples. Nada de uso de arrays ou estruturas mais sofisticadas de controle. O importante neste momento é entender perfeitamente o sketch, pois é a partir dele que desenvolveremos as outras funcionalidades. A partir de uma compreensão exata do código, poderemos fazer alterações que tragam mais confiabilidade e performance, e introduzir o uso de uma programação mais refinada. Alguns pontos que podem observar no código, e que tentaremos ser o mais fiel possível, são:

  • Uso de variáveis para pinos. Todos os pinos utilizados recebem algum nome. Assim alterações futuras serão simplificadas, como por exemplo qual pulsador acende qual lampada.
  • Uso de variáveis para armazenar estados. Embora possamos ler o estado de uma lampada(Acesa ou apagada) lendo diretamente o pino com digitalRead(x), ler uma variável é mais prático “programaticamente” falando, e mais eficiente também em termos de tempo. Fiz um sketch que lê todos os pinos e imprime o tempo gasto e também lê o mesmo número de variáveis e imprime o tempo gasto. Na média ler todos os pinos gasta 52 microsegundos, e ler todas as variáveis gasta 4 microsegundos. Microsegundos é um tempo curtissimo, eu sei, mas estamos apenas começando nosso sketch, e todo microsegundo que economizarmos vai ser útil.
  • Restrição ao uso da função delay(); Toda vez que utilizamos a função delay(); , o Arduino trava durante o intervalo de tempo especificado, não processando mais nada. Imagine que você pressiona o interruptor e nada acontece por que o Arduino esta esperando a função delay() terminar, sem condições certo? Evitaremos ao máximo utilizar a função delay(); preferindo verificar se passou o tempo desejado utilizando a função millis(); Com isso teremos muito mais trabalho “programaticamente” falando, mas sem sombra de dúvida que a solução ficará bem mais eficiente.
  • Uso de resistores internos PULL-UP. Em setup(){} definimos os pinos conectados aos pulsadores como entrada, com pinMode(x,INPUT); e então utilizamos digitalWrite(x,HIGH); para ativar os resistores internos PULL-UP. Se não utilizarmos resistores PULL-UP, teríamos que conectar o pino ao GND através de um resistor externo, para que a leitura do estado LOW fosse precisa. A vantagem do uso do resistor PULL-UP é menos conexões e componentes externos, a desvantagem é que lemos o pino “ao contrário”, ou seja, se o pulsador não estiver pressionado, a leitura do pino produz HIGH, se o pulsador estiver pressionado, a leitura do pino produz LOW, já que estará conectado ao GND
  • Nada se cria, tudo de copia. As funções que determinam por quanto tempo o interruptor foi pressionado, pulsadorTempo(); , pulsador1Tempo(); , pulsador2Tempo();pulsador3Tempo(); foram copiadas exatamente igual a exemplos do livro “Arduino Cookbook”, da editora O’Reily.
  • Uso de funções. Na medida do possível procuraremos colocar nosso código dentro de funções. A vantagem é que cada função realiza uma tarefa especifica, e podemos fazer o reuso do código simplesmente chamando a função desejada. Embora estejamos trabalhando apenas com pulsadores neste momento, o objetivo é fazer com que determinada ação(simples toque, toque duplo, longo toque, etc) chamem uma função específica, com isso, ao inserirmos bluetooth e ethernet em nosso projeto, será simples questão de ler a requisição e chamar a função correspondente.

Análise da lógica

Vamos ver a lógica do sketch, analisando apenas um pulsador.  Os outros dois funcionam de maneira idêntica, portanto podemos nos ater somente ao primeiro para uma melhor compreensão. Vamos lá:

 1  - int tempoPulsa1 = pulsador1Tempo();
 2  - if (tempoPulsa1 > debounceTime){
 3  - pulsa1_estado = true;
 4  - }
 5  - else {
 6  - if (pulsa1_estado == true ) {
 7  - comutaRele(spot1, spot1_estado);
 8  - pulsa1_estado = false;
 9  - }
 10 -}
 11 - unsigned long pulsadorTempo(int pino, boolean &state, unsigned long &inicio){
 12 - if(digitalRead(pino) != state) {
 13 - state = !state;
 14 - inicio = millis();
 15 - }
 16 - if (state == LOW)
 17 - return millis() - inicio;
 18 - else
 19 - return 0;
 20 - }
 21 - 
 22 - long pulsador1Tempo(){
 23 - static unsigned long inicio = 0;
 24 - static boolean state;
 25 - return pulsadorTempo(pulsa1, state, inicio);
 26 - }
 27 - void comutaRele(int pino, boolean &estado){
 28 - estado = !estado;
 29 - digitalWrite(pino, estado);
 30 - Serial.println("Mudou");
 31 - }

Identifiquei com números as linhas de código para facilitar a explicação. Para cada pulsador, este trecho de código é executado em cada loop, acompanhe abaixo a lógica do sketch:

1 – Iniciamos criando uma variável tempoPulsa1, atribuindo a ela o valor retornado pela função pulsador1Tempo(). Essa variável é recriada a cada loop, portanto seu valor é significativo apenas em cada execução do loop. Como chamamos a função pulsador1Tempo(), nosso código segue para a linha 22;

22 – Criamos a função como sendo do tipo long, ou seja, a função vai retornar um valor do tipo long. Nenhum parâmetro é passado, já que existe uma função para cada pulsador.

23 – Declaração de uma variável inicio, do tipo long. Notaram o “static” no início? Variáveis declaradas como static só podem ser modificadas pela função onde foram declaradas. Iniciamos a variável com o valor 0 (zero), e essa linha não fará mais efeito nas outras execuções do loop. O último valor atribuído à variável permanece entre os loops, até que a função seja novamente chamada e venha a modificar seu valor, ou não. Apenas prestem atenção que essa linha de código não será mais executada, a não ser que desligue o Arduino e ligue-o novamente.

24 – Declaração da variável state, do tipo boolean e também static. Lembrando, o valor da variável passa entre as execuções do loop, apenas essa função pode alterar seu valor, e essa linha de código não será executada novamente enquanto o Arduino estiver energizado. Como não atribuímos um valor para a variável, ela por padrão terá o valor true, ou 1.

25 – Aqui a função retorna um valor, obtido através de uma chamada a outra função, no caso a pulsadorTempo(). notem que passamos como argumentos o pulsador sendo verificado, o estado, e o inicio. A execução do código segue para a linha 11

11 – Criamos uma função do tipo long, e passamos como parâmetros o pino sendo analisado, o estado e o inicio. Notem que state e inicio possuem um & (E comercial) na frente, o que isso significa? Quando passamos parâmetros com o & na frente, estamos passando um valor por referência, isso quer dizer que qualquer manipulação que fizermos na variável dentro da função, estaremos na verdade alterando o valor da variável passada como referência.

12 – Verificamos se a leitura do pino informado é diferente do seu estado. Inicialmente a variável state possui o valor true. Como estamos utilizando resistores PULL-UP, se o pulsador não estiver sendo pressionado o valor do pino sera HIGH( ou 1, ou true, é tudo a mesma coisa 😉 ) e será LOW(ou false, ou 0 ) caso esteja pressionado. Com isso, temos duas possibilidades para a verificação, vamos dividir a explicação considerando cada uma delas:

Interruptor não pressionado

12 – Caso o interruptor não esteja pressionado, temos HIGH no pino, e então a avaliação do IF resultará em FALSE, pois if(HIGH diferente de HIGH) é mentira, falso. Nessa hipótese a execução do código vai para a linha 16

16 – Outro IF, desta vez comparando se o valor da variável state é LOW. Se lembrem que na linha 24 a declaração da variável produz um valor true por padrão, então esse IF também resulta em FALSE e a execução do código vai para a linha 19;

19 – Aqui a função retorna o valor 0, e, como podemos ver na linha 25 explicado acima, esse valor por sua vez é usado como o retorno da função pulsador1Tempo(). Voltando ainda a linha 1, vemos que a variável tempoPulsa1 recebe por sua vez esse valor zero. A execução do código continua na linha 2;

2 – Verificamos se o valor da variável é maior que o debounceTime, que é 20. Como 0 não é maior que 20, o IF retorna FALSE e a execução do código vai para a linha 6;

6 – Verificamos se o valor da variável pulsa1_estado é igual a true. Como declaramos a variável como false, e não modificamos esse valor, o IF retorna FALSE e mais nada acontece neste loop, o próximo loop se inicia e acontece tudo de novo exatamente como até aqui, considerando que o pulsador não foi pressionado

Interruptor pressionado.

12 – Caso o interruptor esteja pressionado, temos LOW no pino, e então a avaliação do IF resultará em TRUE, pois if(LOW diferente de HIGH) é verdade, true. Nessa hipótese a execução do código vai para a linha 13;

13 – Atualizamos o valor da variável da state, simplesmente atribuindo a ela o oposto do seu valor atual. Lembrem se que !(interrogação) inverte um valor, ou seja, era HIGH, e agora estamos atribuindo a ela seu valor oposto, ou seja, LOW;

14 – Atribuímos à variável inicio o valor retornado pela função millis(), que é uma função interna do Arduino e simplesmente retorna os milisegundos decorridos desde que o Arduino foi ligado.

16 – Outro IF, desta vez comparando se o valor da variável state é LOW. Se lembrem que na linha 13 invertemos o valor, que agora é LOW, então esse IF  resulta em TRUE e a execução do código vai para a linha 17;

17 – A função retorna o valor de millis() – inicio, ou seja, retorna os milisegundos que passaram entre a chamada anterior (na linha 14) e a chamada atual de millis(); esse valor por sua vez é usado como o retorno da função pulsador1Tempo(). Voltando agora a linha 1, vemos que a variável tempoPulsa1 recebe por sua vez esse valor. A execução do código continua na linha 2;

2 –  Verificamos se o valor da variável é maior que o debounceTime, que é 20. Caso não seja, o IF retorna FALSE e a execução do código vai para a linha 6, caso o valor da variável seja maior que o debounceTime, a execução do código vai para a linha 3;

Pulsador pressionado por tempo MENOR que debounceTime

6 – Verificamos se o valor da variável pulsa1_estado é igual a true. Como declaramos a variável como false, e não modificamos esse valor, o IF retorna FALSE e mais nada acontece neste loop, o próximo loop se inicia e acontece tudo de novo, considerando que o pulsador não foi pressionado(já que o debounceTime é justamente para confirmar o pressionamento do pulsador) e considerando também os novos valores atribuídos a state e inicio;

Pulsador pressionado por tempo MAIOR que debounceTime

3 – Como o pulsador foi considerado como pressionado, atribuimos o valor true à variável que armazena o estado do pulsador. Neste ponto nenhum código a mais é executado neste loop, e o próximo loop se inicia, considerando que o pulsador foi pressionado, e considerando ainda os valores atribuídos às variáveis state e inicio.

 

Bem, já comentei muito sobre a lógica do sketch, e creio que vocês podem continuar a análise, verificando como a lógica segue nos próximos loops. Uma dica é não esquecer os valores das variáveis entre os loops, e notem que a função que comuta a lampada efetivamente só é chamada quando DEIXAMOS de pressionar o pulsador, ou seja, enquanto estivermos pressionando o pulsador, nada acontecerá. A razão disso é que pretendo fazer uso do tempo que mantemos o pulsador pressionado para uma funcionalidade especifica, mas isso é assunto para outro post.

Caso tenham alguma dúvida ou sugestão quanto ao sketch, não percam tempo, comentem que vamos discutir e melhorar o que for preciso.

Até a próxima.

8 thoughts on “Sketch funcional para uso com interruptores pulsadores

  1. Diego

    Muito o bom o projeto, estava procurando algo e não estava encontrando algum material serio, assim como você disse no primeiro post, parabéns!

    Reply
    1. Valdinei Rodrigues dos Reis Post author

      Fala Diego, tudo bem?

      A ideia aqui é relatar minha experiência, e também interagir com outros que tem um objetivo parecido. Afinal, juntos somos mais, e é justamente a troca de ideias que torna possível um projeto interessante.

      Se tiver sugestões, dúvidas, fique a vontade. Será muito bem vindo.

      Abs.

      Reply
  2. Eduardo

    Boa tarde Valdinei,
    Primeiramente gostaria de parabenizá-lo…

    Acho interessante a ideia de “digitalizar” a instalação através de 5V enviado pelo arduino.
    Porém,
    Toda a casa ficaria a merce do arduino. E nada funcionaria se a ATMEGA queimasse, por exemplo.
    Precisamos considerar não deixar a casa na mão de componentes digitais. Por isso, defendo a ideia de o digital complementar o analógico já existente no imóvel.

    Abaixo o link de um antigo vídeo que fiz:
    https://www.youtube.com/watch?v=bzhDR8_sljI

    Já avancei nesse projeto onde consigo identificar o estado das lâmpadas através dos sensores de corrente ACS712.

    Voltando ao seu projeto,
    Acho interessante que armazene o estado dos relés na EPROM, pois no caso de falta de energia, o arduino conseguirá voltar ao estado em que estava quando a luz voltar…

    att.

    Reply
    1. Valdinei Rodrigues dos Reis Post author

      Boa tarde Eduardo. Muito obrigado pela sua visita e considerações. Espero que continue visitando e contribuindo.
      Vamos lá.
      Quanto a “digitalizar” a instalação, eu AINDA estou com receios, porem as vantagens de ter tudo digital valem o risco que vou correr. Sem dúvida que misturar digital e analógico seria perfeito, mas é considerável as dificuldades nesta junção. Neste post eu já levantei essa preocupação, que se estendeu até os comentários.
      Alem da preocupação da queima do Arduino, veja que o relé também preocupa. Quanto tempo ele mantem uma lampada acesa sem queimar?
      Quanto a EEPROM, já adquiri duas pensando justamente no que falou. Logo vou postar a respeito, e incluí-las no projeto. Parece que nossas idéias são parecidas hein?
      Ví o seu vídeo, muito legal. Não é por inveja, mas depois de ver fiquei instigado e já vou preparar alguns vídeos meus também 😉 Ler um post é bom, mas ver o negócio funcionado e melhor ainda.

      Abs e espero que possamos trocar muitas outras idéias.

      Reply
  3. Erlon

    Tudo bem, Valdinei?!? Espero que sim!

    Bom, vi seu post e gostei muito do material, deu-me umas boas ideias! Estou tentando aprender sobre o arduino com foco na automação residencial, para meu próprio uso, mas confesso que o tempo disponível não tem sido muito.

    Uma das coisas que pretendo fazer é um mix do seu projeto com o que vi no site http://startingelectronics.com/tutorials/arduino/ethernet-shield-web-server-tutorial/ que é muito bom para quem pretende manter um webserver, mas quer economizar energia ou riscos, de ter que deixar seu computador ligado e por um pico de energia, corromper seus dados, por exemplo. Com a ajuda desse site, utilizo um webserver no próprio arduino, armazenado em um microSD de 256 MB (e sobra muuuuuuito espaço) para controlar lâmpadas de alguns cômodos (8 até agora), mas num sistema híbrido entre analógico e digital, como falou o Eduardo.

    Só para tranquilizá-los um pouco, meu sistema está rodando estes 8 cômodos (na realidade 7, mas um é um banheiro com a lâmpada principal e outro conjunto de 2 lâmpadas em cima do espelho, acionadas independentemente) há uns 8 meses, sem queimar nada até agora. Já tive problemas apenas quando a energia acaba por um tempo e às vezes é necessário resetar o arduino para recarregar o código. Não é sempre, mas acontece. Outro problema que tive foi na fase inicial, ao criar alimentação para o arduino, colocando 12V e mais os relês plugados nele, ele superaquecia e de vez em quando resetava. Identifiquei o aquecimento e individualizei a alimentação de uma protoboard com a ligação para os relês e sensores e a do arduino: uma fonte de 9V para cada. E acho que se desse para achar fácil (e barato) uma fonte de 7,5V para o arduino, seria o ideal, pois ele está com quase nada plugado e não esquentaria muito também. Mas, no geral, funciona bem legal o sistema! Tenho um mega e uma placa de 8 relês de reserva, tudo comprado baratinho do DX, apenas com paciência de chegar, para o caso de um dia (toc, toc, toc) dar algum problema em algum item.

    Ao contrário do Eduardo, ainda não consegui fazer o código do sensor de corrente (uso um outro, o TA17-03, não invasivo) funcionar em conjunto com o do webserver (até consigo identificar se uma lâmpada está ligada, mas não consigo juntá-la ao código que aciona as lâmpadas, ao menos de forma que funcione [:-;]), mas sei que é questão de tempo.

    Em alguns lugares aqui em casa, no entanto, será possível fazer como o seu, colocando o pulsador, para já ter um resultado instantâneo do acionamento ou desligamento de um relê, bem como adicionar outras funcionalidades pelo tempo de pressionamento, ou mesmo pela quantidade de toques no pulsador. As possibilidades são limitadas apenas… pelos recursos e criatividade! Muito boa sacada!

    Bom, pretendo caminhar para este lado, de juntar o seu código com o do site StartEletronics, mas fazendo ainda o acréscimo da detecção do estado da lâmpada com o sensor de corrente (que acho mais preciso que usar um LDR, por exemplo). Caso queira, posso trocar o código do que fiz até ver o seu site e quem sabe o projeto cresça ainda mais?!? E com quem mais quiser, como o Eduardo, por exemplo, pois assim podemos chegar a um bom produto, certo?

    Abraços!

    Reply
    1. Valdinei Rodrigues dos Reis Post author

      Ola amigo (não identifiquei o nome 😉 ).
      Obrigado pela visita e pelas considerações.

      Arduino com WebServer –> Eu preferi partir para o Raspberry. Como pretendo utilizar mysql e php para entregar páginas dinâmicas e armazenar informações de sensores, o Arduino torná-se inviável. Sei que há um Connector mysql para o Arduino e parece funcionar bem, mas parece que o PHP sem chance.

      Ufa, que bom ler que sua solução funciona a bastante tempo. A minha por enquanto só em lab, já que a reforma deu uma pequena pausa. Anotei suas observações quanto a alimentação, e vamos ver se não passo por isso também.
      Quanto ao problema de por algum motivo o Arduino desligar, vou utilizar EEPROM para armazenar o estado das coisas. Por enquanto só tenho lampadas no projeto, mas logo adicionarei várias outras coisas. Comprei duas EEPROMs para isso, mas o próprio Arduino possui 1K de EEPROM, e creio que isso vai ser suficiente.

      Estou muito interessado no seu código, se quiser pode enviar pelo email openflow.vrr @ gmail.com. Se quiser pode mesmo postar seu projeto aqui. Seria bem legal.

      Abs e desculpe a demora em responder, como vc citou o tempo disponível infelizmente é curto.

      Reply
      1. Valdinei Rodrigues dos Reis Post author

        Que vacilo, kkkkkkkkk.

        Seu nome na minha cara e eu não vi, desculpa Erlon.

        Reply
        1. Erlon Guimarães

          Sem problemas, Valdinei! Meu nome é pouco comum mesmo 😉

          Entendo perfeitamente o tempo: só agora percebi que você respondeu! Como fazia algum tempo, acabei entrando nos posts novos, que também me interessaram.

          Amanhã mandarei o código da página e do arduino para que você possa ver se lhe serve!

          Muito obrigado, amigo!

          Grande abraço,

          Erlon

          Reply

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

3 × cinco =