Curso Arduino Nível II – #4 – Interrupts, Sensor de Abertura para Portas e Janelas e Sensor PIR

 

Sensor de Abertura para Portas e Janelas

Primeiramente, vamos abordar um sensor usado para detetar a abertura de portas e janelas. Consiste em duas partes: um sensor magnético e um elemento magnético. Uma metade do conjunto deve ser montado numa moldura da porta, por exemplo, e a outra deve ser ligada à porta em si.

Curso Arduino Nível II - #4 - Sensor de Abertura de Portas e Janelas

O princípio de funcionamento é semelhante ao de um relé tradicional. O circuito está normalmente aberto, quando as duas partes se aproximam, o circuito fecha, isto significa que uma corrente começa a fluir pelos fios.

Estes tipos de sensores são usados principalmente em sistemas de segurança residencial ou industrial.

Vejamos a aplicação prática do sensor:

Exercício Prático – Sensor de Abertura de Portas e Janelas

Está na hora de verificarmos o funcionamento do sensor na prática. Para isto, teremos de ligar um simples circuito de teste.

Material necessário:

O LED RGB vai servir como indicador do estado do alarme. Se a porta/janela estiver fechada, o LED estará verde, caso contrário deverá estar vermelho.

Confira a seguinte lista de conexões:

  • Terminal vermelho do LED (LED_R) → pino 10 do Arduino, através de uma resistência de 1KΩ;
  • Terminal verde do LED (LED_G) → pino 11 do Arduino, através de uma resistência de 1KΩ;
  • Terminal azul do LED (LED_B) → pino 12 do Arduino, através de uma resistência de 1KΩ;
  • Cátodo comum → GND;
  • Um dos fios do sensor → pino 0;
  • Segundo fio do sensor → GND.

Se tiver dúvidas pode consultar o esquema de ligação abaixo:

Inserir Esquema de Ligação

O código é algo muito simples, vejamos:

#define LED_R 10
#define LED_G 11
#define LED_B 12

#define Sensor 0

void setup() {
pinMode(LED_R, OUTPUT); //Pinos do LED RGB como saídas
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);

pinMode(Sensor, INPUT_PULLUP); //Sensor como entrada

digitalWrite(LED_R, LOW); //LED apagado
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}

void loop() {
if (digitalRead(Sensor) == LOW) { //Se o circuito estiver fechado
digitalWrite(LED_R, LOW); //LED verde
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //LED vermelho
digitalWrite(LED_G, LOW);
}
}

Como resultado, obtemos um alarme simples, que reage ao estado do sensor de abertura.

Inserir Imagens do Resultado

Como pode ver nas imagens acima, as duas partes não precisam de estar perfeitamente juntas uma à outra. É permitida uma certa margem, graças à qual a montagem do sensor nas portas/janelas se torna muito mais fácil.

Trabalho de Casa nº1

Use o altifalante/buzzer sem oscilador e escreva um programa que ligue o som do alarme quando o circuito do sensor é aberto. Quando o circuito é fechado, o alarme deve desligar.

Sensor de Movimento PIR

É com os Sensores de Movimento PIR (Passive Infrared), que costumamos associar os sistemas de alarme. Sensores de Movimento PIR

O seu princípio é alterar adequadamente o estado de saída quando algum movimento é detetado. Vale a pena saber que se baseiam na medição da radiação infravermelha (temperatura) do ambiente. Portanto, uma brisa mais quente pode ser considerada movimento, o que não é favorável. Mas para o que pretendemos realizar serve. No entanto, não vamos aprofundar muito esta questão.

Sensor de Movimento PIR HC-SR501

No caso de pequenos projetos, como os nossos, é frequente a utilização do barato sensor HC-SR501. Antes de passarmos à prática, eis alguns aspetos que deve saber a respeito do sensor:

  • Tensão de funcionamento: 4.5 a 20V DC;
  • Lógica das saídas: High 3.3V/ Low 0V;
  • Consumo de corrente: até 150uA;
  • Deteção de movimento: até 7 metros;
  • Ângulo de deteção: 100º.

Como pode ver, este sensor pode ser alimentado diretamente do Arduino, porque funciona com 5V de tensão. Para além disso, consome pouca corrente, portanto não afeta o resto do sistema (como por exemplo o servo). Pode, igualmente, ser integrado em projetos alimentados por bateria.

Sensor de Movimento PIR

Pode verificar na imagem acima que por cima do sensor existe uma lente de plástico. Sem esta, o funcionamento do sensor não seria possível.

Na parte de trás do módulo (imagem abaixo) podemos encontrar três elementos interessantes:

  • Potenciómetro (Tx) – para ajustar a duração do estado high depois da deteção de movimento;
  • Potenciómetro (Sx) – para ajustar a sensibilidade do sensor;
  • Terminal com três pinos – para alimentação do sensor e saída do sinal.

Por padrão, o sensor funciona no modo retriggering. Isto é, quando um movimento é detetado, o estado high será mantido enquanto houver deteção de movimento e durante o tempo definido pelo potenciómetro (Tx).

Durante o modo non-retriggering, a saída estará no estado high apenas uma vez, depois passa para low, independentemente da deteção de movimentos subsequentes em progresso.

Exercício Prático – Sensor de Movimento PIR

Inicialmente, vamos utilizar o sensor de movimento de forma análoga ao sensor de abertura de portas e janelas.

Material necessário:

A conexão do LED RGB mantém-se, basta retirar o sensor de abertura e ligar o sensor PIR seguindo as ligações abaixo:

  • Vcc → 5V do Arduino;
  • OUT → Pino 2 do Arduino;
  • GND → GND.

O esquema é o seguinte:

Inserir Imagem do Esquema de Ligação

No início, não precisa de se preocupar com os potenciómetros de ajuste do tempo e sensibilidade. Basta rodá-los para a posição extrema de acordo com a figura abaixo. Assim, será mais fácil observar o efeito do sistema.

Curso Arduino Nível II - #4 - Sensor de Movimento PIR

O código é semelhante ao utilizado anteriormente:

#define LED_R 10
#define LED_G 11
#define LED_B 12

#define PIR 2

void setup() {
pinMode(LED_R, OUTPUT); //Pinos do LED RGB como saídas
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);

pinMode(PIR, INPUT); //Sensor de movimento como entrada

digitalWrite(LED_R, LOW); //LED apagado
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}

void loop() {
if (digitalRead(PIR) == LOW) { //Se não for detetado movimento
digitalWrite(LED_R, LOW); //LED verde
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //LED vermelho
digitalWrite(LED_G, LOW);
}
}

Quando é detetado algum movimento, o LED muda de verde para vermelho e, depois de um tempo, volta a ficar verde.

Aconselhamo-lo a testar o intervalo de deteção do sensor com este exercício.

Trabalho de Casa nº2

Crie um dispositivo que, quando é detetado movimento, emita uma melodia. Este dispositivo pode ser colocado, por exemplo, numa porta para informar a entrada de alguém.

Interrupts

Os sensores de alarme, por si só, não exigem novas habilidades de programação. Pode integrá-los de uma forma muito simples nos seus projetos. Vamos aproveitar o facto de se tratar de um hardware tão simples, para abordamos os interrupts, que são uma questão completamente nova.

Estamos cientes de que os interrupts são um assunto muito mais extenso. No entanto, tentamos generalizar algumas informações e incluir apenas aquelas que podem ser aplicadas no Arduino. Não queremos sobrecarregar os leitores com teoria que não irão aplicar no início.

O que são interrupts externos?

Os interrupts são um mecanismo que permite interromper imediatamente uma parte do programa e ir para uma função específica sob a influência de um sinal externo. Por exemplo, independentemente do que o programa está a fazer quando o sensor PIR deteta movimento, o alarme deve ser ligado.

Esta é uma situação simples, porém, existem casos mais complexos. Por vezes, será necessário comunicar com o computador (UART) ou controlar outras partes do sistema, tudo requer tempo valioso durante o qual podemos perder o sinal do sensor de alarme.

Quando é que um interrupt é útil na prática?

Vejamos quando é que, no Arduino, um interrupt é necessário. Voltemos a um exemplo anterior, quando a cor do LED dependia do estado do sensor de abertura de portas e janelas:

#define LED_R 10
#define LED_G 11
#define LED_B 12

#define Sensor 0

void setup() {
pinMode(LED_R, OUTPUT); //Pinos do LED RGB como saídas
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);

pinMode(Sensor, INPUT_PULLUP); //Sensor como entrada

digitalWrite(LED_R, LOW); //LED apagado
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}

void loop() {
if (digitalRead(Sensor) == LOW) { //Se o circuito estiver fechado
digitalWrite(LED_R, LOW); //LED verde
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //LED vermelho
digitalWrite(LED_G, LOW);
}
}

O loop deste programa é pequeno e a sua única tarefa é verificar o estado do sensor de abertura de portas e janelas. É impossível que o programa não detete a abertura do circuito. Todavia, basta alterar o código para tornar a situação completamente diferente.

Vamos expandir o programa para que o ciclo loop demore relativamente muito tempo. Este efeito será obtido através da inclusão de um controlo – sinalizar o alarme com o piscar do LED integrado no pino 13 do Arduino.

#define LED_R 10
#define LED_G 11
#define LED_B 12
#define LED_SIN 13

#define Sensor 0

void setup() {
pinMode(LED_R, OUTPUT); //LEDs como saídas
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
pinMode(LED_SIN, OUTPUT);

pinMode(Sensor, INPUT_PULLUP); //Sensor como entrada

digitalWrite(LED_R, LOW); //LEDs apagados
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
digitalWrite(LED_SIN , LOW);
}

void loop() {
if (digitalRead(Sensor) == LOW) { //Se o circuito estiver fechado
digitalWrite(LED_R, LOW); //LED verde
digitalWrite(LED_G, HIGH);
} else {
digitalWrite(LED_R, HIGH); //LED vermelho
digitalWrite(LED_G, LOW);
}

digitalWrite(LED_SIN, HIGH); //LED do pino 13 a piscar
delay(2000);
digitalWrite(LED_SIN, LOW);
delay(2000);
}

Vamos ver o funcionamento do programa. Como pode verificar, o alarme já não funciona corretamente. O programa reage às alterações do sensor de forma diferente – ás vezes imediatamente, outras vezes depois de 4 segundos e às vezes simplesmente não reage.

No caso de um sistema de alarme, algo assim não pode acontecer. Um ladrão podia entrar em nossa casa e o sistema nem detetava.

Para prevenir um assalto, vamos implementar um interrupt.

Interrupts no Arduino

Inicialmente, na função setup, deve ser declarado um interrupt. Para isso, vamos usar um comando bastante longo, que tem este aspeto:

attachInterrupt(digitalPinToInterrupt(Pino), Função, Reação);

O assunto é um pouco complexo, portanto vamos abordar cada argumento da função individualmente.

1. digitalPinToInterrupt (Pino)

O primeiro argumento da função, que declara o interrupt, serve para habilitar o pino a ser usado como um interrupt externo. Os interrupts são um mecanismo integrado nos microcontroladores, logo não é apenas um procedimento de programação. Por esta razão, só podemos utilizar pinos específicos.

No caso do Arduino UNO, os interrupts só são suportados nos pinos 2 e 3. Outras placas suportam interrupts noutros pinos e noutras quantidades.

Posto isto, existem apenas duas formas de declarar este argumento (dependendo do pino escolhido):

attachInterrupt(digitalPinToInterrupt(2), Função, Reação); //Interrupt no pino 2
attachInterrupt(digitalPinToInterrupt(3), Função, Reação); //Interrupt no pino 3

2. Função

Quando o microcontrolador recebe informações sobre um interrupt externo, interrompe a sua tarefa atual e executa o código que estará numa função específica. O nome da função é o segundo argumento da função attach.Interrupt.

Esta será uma função simples, como as que escrevemos no nível anterior do curso Arduino. Não aceita argumentos, nem qualquer retorno de valor. Por exemplo, se o interrupt do pino 2 definir a variável alarme como 1, o código deverá ser escrito da seguinte forma:

attachInterrupt(digitalPinToInterrupt(2), definirAlarme, Reação); //Interrupt no pino 2
[...]

void definirAlarme {
alarme = 1;
}

Falaremos um pouco mais sobre este argumento, mais para a frente no artigo.

3. Reação

Já sabemos quais os pinos compatíveis com interrupts e qual código executar. No último parâmetro da função attachInterrupt, devemos fornecer a informação da ação que deve existir para a função interrupt ser chamada. Podemos escolher entre quatro opções:

  • LOW – chamar o interrupt sempre que houver um estado low na entrada;
  • CHANGE – chamar o interrupt quando o estado do pino for alterado (de high para low e vice-versa);
  • RISING – chamar o interrupt quando o estado mudar de low para high;
  • FALLING – chamar o interrupt quando o estado mudar de high para low.

Exercício Prático – Interrupts no Arduino

Vamos agora aplicar os conhecimentos adquiridos. De início, vamos examinar o estado do sensor PIR, já que está ligado ao pino que suporta interrupts.

O código será o seguinte:

#define LED_R 10
#define LED_G 11
#define LED_B 12

#define PIR 2

void setup() {
pinMode(LED_R, OUTPUT); //Pinos do LED RGB como saídas
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);

pinMode(PIR, INPUT); //Sensor de movimento como entrada 

digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);

attachInterrupt(digitalPinToInterrupt(PIR), alarme, RISING); // Interrupt que reage à passagem de low para high
}

void loop() {
//Nada acontece
}

void alarme() {
digitalWrite(LED_R, HIGH); //LED vermelho
digitalWrite(LED_G, LOW);
}

À primeira vista, este código não deveria fazer nada, porque não há instruções no loop principal. Faça o upload para verificar o que é que acontece realmente. Após a deteção de uma mudança no estado lógico da saída do sensor (de low para high), o LED acende com a cor vermelha.

Trabalho de Casa nº3

Escreva um programa que utilize interrupts para ligar o alarme (LED vermelho). No entanto, é pretendido que o alarme ligue após os movimentos detetados pelo sensor pararem, e não quando o primeiro movimento é detetado.

Argumento Função (aprofundamento)

Como mencionamos anteriormente, a função declarada nos argumentos da função attachInterrupt não pode enviar nenhum valor pelo mecanismo padrão return. Mas, é claro que isto pode ser contornado através de, por exemplo, variáveis globais.

Todas as variáveis globais utilizadas dentro da função interrupt são declaradas com um prefixo especial – volatile.

Vamos criar um programa que conte quantas vezes o sensor PIR detetou movimento e demonstre isso através do LED RGB:

#define LED_R 10
#define LED_G 11
#define LED_B 12
#define LED_13 13

#define PIR 2

volatile int vezes = 0;

void setup() {
pinMode(LED_R, OUTPUT); //Pinos do LED RGB como saídas
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);

pinMode(PIR, INPUT); //Sensor de movimento como entrada 

digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);

attachInterrupt(digitalPinToInterrupt(PIR), alarme, RISING); //Interrupt que reage à passagem de low para high
}

void loop() {
if (vezes < 3) { //Aceitável - cor verde
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
} else if (vezes < 6) { //Começa a ser preocupante - cor azul
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, HIGH);
} else { //Alarme! Foram detetados movimentos mais de 6 vezes - cor vermelha
digitalWrite(LED_R, HIGH);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
}
}

void alarme() { //Interrupt
vezes++; //Novo movimento detetado
}

O funcionamento do programa corresponde às espectativas. A cor do LED muda à medida que vão aumentando as situações em que o sensor PIR detetou movimento.

Inserir Imagens do Exercício

O que é que não devemos colocar numa função acionada por interrupt?

A inserção de interrupts pode facilitar-nos a vida durante a implementação de muitos projetos. No entanto, é muito fácil cair numa armadilha. O uso indevido de interrupts pode levar a uma situação na qual o programa vai comportar-se de forma muito estranha, e encontrar a causa será difícil.

Portanto, ao escrever funções que serão acionadas por interrupts, lembre-se dos seguintes tópicos:

  • Não coloque qualquer tipo de atrasos na função (delay, delayMicroseconds etc);
  • Não execute operações que demorem muito tempo;
  • As operações realizadas dentro da interrupção devem ser extremamente curtas.

Felizmente, com o Arduino, a função delay nem sequer funciona se a colocarmos dentro da função acionada pelo interrupt. A função delayMicroseconds funcionará corretamente, mas não devemos usá-la para não causar atrasos maiores e desnecessários no programa.

Quando é que devemos recorrer aos interrupts?

Devemos utilizar os interrupts por duas razões:

  1. Quando a reação a um sinal externo for crucial o tempo todo (ex.: alarme);
  2. Quando o programa é muito extenso e, adicionalmente, tem de responder a botões/sensores.

Quando é que devemos evitar os interrupts?

Devemos evitar os interrupts em duas situações:

  1. Quando o programa é curto ou não tem delays, e é capaz de verificar todas as entradas regularmente;
  2. Quando recolher informação das entradas não for crucial o tempo todo.

Sumário

Utilizar interrupts no Arduino é fácil, só são necessárias algumas linhas de código extra. Porém, é difícil fazê-lo corretamente e de forma consciente. Recomendamos que explore um pouco mais este assunto e faça algumas experiências. Vamos tentar incluir mais informações sobre os interrupts durante os próximos exercícios. Até ao próximo artigo!