Experimento 36 – Arduino com sensor de distância

Material necessário:

  • Módulo sensor de distância ultrassônico HC-SR04
  • 5 LEDs
  • 5 resistores (150 ou 220 ohms dependendo da cor do LED)
  • 1 Arduino Nano
  • 1 Alto-falante de 8 ohms

Neste experimento usaremos um sensor de distância ultrassônico para reagir à distância de objetos colocados diante do sensor. O HC-SR04 consegue detectar distância de um centímetro a alguns metros. O objetivo será fazer LEDs diferentes acenderem e soar um som diferente no alto-falante à medida em que a distância mudar. O esquema está abaixo:

Esta é uma possível montagem com o protoboard.

Antes de acionar LEDs ou alto-falantes, vamos testar o HC-SR04 imprimindo os valores lidos no monitor serial. O HC-SR04 tem 4 terminais. Dois são usados para fornecimento de energia (VCC e GND), e devem ser ligados em 5V e GND. Os outros dois são a entrada TRIGGER, usado para solicitar que o sensor faça uma medição, e a saída ECHO, que contém a resposta da medição. O sensor é controlado através do envio de pulsos, que devem ter uma duração especificada, e a resposta é recebida também como um pulso, cuja duração varia conforme a distância medida. O pulso é uma onda quadrada, com estado LOW em 0 e HIGH em 5V.

Para solicitar uma medição, o Arduino precisa enviar um pulso (HIGH) de 10 microssegundos para a entrada TRIGGER do sensor. Quando o sensor recebe esse pulso, ele devolve um pulso (também HIGH) de duração variável na saída ECHO. O programa precisa então medir a duração deste pulso, e multiplicar por um valor fixo para obter a distância em centímetros.

O gráfico abaixo ilustra graficamente o pulso que precisamos enviar para a entrada TRIGGER que irá disparar uma medição:

Este outro gráfico mostra um pulso de resposta recebido em ECHO. A duração deste pulso é o tempo em que ele permanece na posição HIGH.

A linguagem do Arduino possui uma função especial para medir a largura de um pulso. A função pulseIn() recebe dois (ou três) argumentos: o primeiro é o pino onde o pulso será recebido e o segundo é o nível do pulso  (HIGH ou LOW). Por exemplo, a instrução abaixo interrompe o código até que um pulso de valor HIGH seja recebido no pino 8. Quando ele terminar (ou seja, quando o sinal voltar para LOW), a duração será gravada na variável duracao:

long duracao = pulseIn(8, HIGH);

Se o pulso nunca chegar, o programa fica esperando para sempre. Uma maneira de evitar isto é estipular um timeout, que é o terceiro argumento. A instrução abaixo espera até 1 minuto (60 segundos) pelo pulso:

long duracao = pulseIn(8, HIGH, 60000);

Para enviar o pulso de disparo, não podemos usar delay() pois a sua resolução mínima é de apenas 1 milissegundo. Neste caso precisamos usar a função delayMicrosseconds(). O pulso de disparo dura 10 microssegundos no estado HIGH, portanto precisamos começar com o estado LOW, esperar um tempo (pode ser também uns 5 a 10 microssegundos) e depois mudar o estado para HIGH, esperar 10 microssegundos, e baixar para LOW:

 digitalWrite(TRIGGER, LOW);
 delayMicroseconds(5);
 digitalWrite(TRIGGER, HIGH);
 delayMicroseconds(10);
 digitalWrite(TRIGGER, LOW);

Por fim, precisamos converter a duração do pulso recebido em ECHO na distância em centímetros. De acordo com a especificação do HC-SR04 é preciso apenas dividir por 58.

O programa abaixo pode ser usado para testar o funcionamento do sensor, imprimindo os dados recebidos no monitor serial. Faça o upload, abra o monitor serial e aproxime uma superfície plana do sensor e veja os valores recebidos na tela.

const int TRIGGER = 3;
const int ECHO = 4;

void setup() {
   pinMode(TRIGGER, OUTPUT);
   pinMode(ECHO, INPUT);
   Serial.begin(9600);
}

void loop() {
   float distance = pulse();
   Serial.println(distance); // imprime a distancia em cm
   delay(500); // must be over 60ms
}

float pulse() {
   digitalWrite(TRIGGER, LOW);
   delayMicroseconds(10);
   digitalWrite(TRIGGER, HIGH);
   delayMicroseconds(10);
   digitalWrite(TRIGGER, LOW);

   long duration = pulseIn(ECHO, HIGH);
   return duration / 58.0;
}

Agora que testamos o sensor, podemos usar esses dados para  controlar dispositivos de saída. No circuito temos LEDs e um alto-falante. O programa abaixo utiliza o arquivo do experimento 32, que define frequências de notas musicais, e seleciona notas de frequências mais altas à medida em que a distância aumenta. O aumento também causa o acendimento de mais LEDs. Aproximar um objeto a uma distância menor que 5 centímetros deve desligar o som e os LEDs:

#include "notas.h"

const int TRIGGER = 3;
const int ECHO = 4;
const int SPEAKER = 5;
const int LED_R = 6,
          LED_Y = 7,
          LED_G = 8,
          LED_B = 9,
          LED_W = 10;

const int notas[] = {
   ZZ, C4, D4, E4, F4, G4
};

void setup() {
   pinMode(TRIGGER, OUTPUT);
   pinMode(SPEAKER, OUTPUT);
   pinMode(LED_R, OUTPUT);
   pinMode(LED_Y, OUTPUT);
   pinMode(LED_G, OUTPUT);
   pinMode(LED_B, OUTPUT);
   pinMode(LED_W, OUTPUT);
   pinMode(ECHO, INPUT);
}

void luz(boolean acender, int led) {
   if (acender) {
      digitalWrite(led, HIGH);
   } else {
      digitalWrite(led, LOW);
   }
}

void loop() {
   float distance = pulse();

   // Som: idx = indice do array notas[] (0 a 5)
   int idx = (int)(distance / 5);
   if(idx > 5) { // se idx resultar em numero > 5, faça idx = 5
      idx = 5;
   }
   // soar um tom na nota correspondente por 500ms
   tone(SPEAKER, notas[ idx ], 500);

   // Luz(condição para acender, led)
   luz( distance >= 5, LED_R);
   luz( distance >= 10, LED_Y);
   luz( distance >= 15, LED_G);
   luz( distance >= 20, LED_B);
   luz( distance >= 25, LED_W);

   delay(200);
}

float pulse() {
   // envia o pulso para TRIGGER, solicitando a medição
   digitalWrite(TRIGGER, LOW);
   delayMicroseconds(10);
   digitalWrite(TRIGGER, HIGH);
   delayMicroseconds(10);
   digitalWrite(TRIGGER, LOW);

   // espera até 50 segundos por um pulso de resposta
   long duration = pulseIn(ECHO, HIGH, 50000);
   // retorna a distância em cm (calculada)
   return duration / 58.0;
}

O sensor é muito sensível e interferências são frequentes. Neste exemplo o próprio som do alto-falante pode interferir no sinal. Há várias maneiras de contorná-las e algumas usando software. Em vez de construir você mesmo os pulsos como fizemos neste exemplo, você pode usar uma biblioteca criada especificamente para facilitar o uso do sensor. Uma boa bibliioteca como a NewPing não apenas oferece funções como também lida com diferentes sensores de outros fabricantes e busca otimizar a medição, reduzindo interferências e melhorando a precisão.

 

Alteração 35.1 – Termômetro com display de 7-segmentos

Este experimento é uma mistura do experimento anterior, que demonstra o uso de um display de 7 segmentos com Arduino, e do experimento 29, que implementa um termômetro com o LM35DZ.

Material adicional:

  • Circuito integrado LM35DZ (termômetro digital analógico).

O esquema consiste apenas em acrescentar o LM35, que será conectado a uma entrada analógica. Mudamos para a entrada A3, já que o display está usando as primeiras três entradas analógicas como saídas digitais:

Uma possível implementação com o protoboard está ilustrada abaixo:

Para simplificar o programa e facilitar o reuso, transferimos o mapeamento dos dígitos e segmentos para uma biblioteca local (chamamos de seven_segment_display.h) contendo o código abaixo:

static int digit[10][7] = {
    {HIGH,HIGH,HIGH,HIGH,HIGH,HIGH,LOW},
    {LOW, HIGH,HIGH,LOW, LOW, LOW, LOW},
    {HIGH,HIGH,LOW, HIGH,HIGH,LOW, HIGH},
    {HIGH,HIGH,HIGH,HIGH,LOW, LOW, HIGH},
    {LOW, HIGH,HIGH,LOW, LOW, HIGH,HIGH},
    {HIGH,LOW, HIGH,HIGH,LOW, HIGH,HIGH},
    {HIGH,LOW, HIGH,HIGH,HIGH,HIGH,HIGH}, 
    {HIGH,HIGH,HIGH,LOW, LOW, LOW, LOW}, 
    {HIGH,HIGH,HIGH,HIGH,HIGH,HIGH,HIGH}, 
    {HIGH,HIGH,HIGH,HIGH,LOW, HIGH,HIGH}
};

const int DISPLAY_LEDS = 7;
const int DISPLAY_DIGITS = 10;

void showNumber(int* disp, int dig) {
    for(int i = 0; i < DISPLAY_LEDS; i++) {
        digitalWrite(disp[i], digit[dig][i]);
    }
}

Para criar o arquivo, clique na seta que fica abaixo do ícone do monitor serial(canto superior direito do IDE do Arduino) e selecione “New Tab”, depois escolha o nome (seven_segment_display.h) e grave.

Para usar é preciso apenas incluir o arquivo no código-fonte principal usando #include, e chamar a função showNumber() passando como argumento o display e o dígito a exibir. No programa abaixo criamos uma função exibir que recebe o número e mostra nos dois displays. Chamamos a função exibir assim que lemos a temperatura a cada 2 segundos:

#include "seven_segment_display.h"

#define TERMOMETRO A3

int unidades[] = {14, 15, 2, 3, 4, 5, 6};
int dezenas[] = {7, 8, 9, 10, 11, 12, 16};
 
void setup() {
    for(int i = 0; i < DISPLAY_LEDS; i++) {
        pinMode(dezenas[i], OUTPUT);
        pinMode(unidades[i], OUTPUT);
    }
    // Usando a referência interna de 1,1V para maior precisão
    analogReference(INTERNAL);
}

void exibir(int numero) {
    showNumber(unidades, numero % 10);
    showNumber(dezenas, numero / 10);
}

void loop() {
    int leitura = analogRead(TERMOMETRO);
    float volts = (leitura / 1024.0) * 1.1;
    float celsius = (volts) * 100.0;
 
    int temperatura = (int) round(celsius); // arredonda
    exibir(temperatura);
 
    delay(2000);
}

Este programa possui uma diferença em relação ao utilizado no experimento 29. Em vez de usar a referência padrão de 5V para leituras analógicas (via analogRead), usamos a referência interna calibrada do Arduino através do comando

analogReference(INTERNAL);

Isto significa que o valor 0 a 1024 não corresponde mais a 0 a 5V, mas a 0 a 1,1V. Isto permite maior precisão na leitura. Assim, para obter a tensão entre o terminal central e GND, dividimos o valor lido por 1024 e depois multiplicamos por 1,1V. Cada grau Celsius corresponde a 0,01 V de diferença, então multiplicamos por 100 para obter a temperatura.

Experimento 35 (extra) – Usando displays de 7 segmentos

Material:

  • 2 displays de 7 segmentos de catodo comum (HS 5101A). Pode-se usar displays de anodo comum também (HS5101B), neste caso é preciso inverter a lógica dos pinos no Arduino e ligar o anodo comum no positivo (5V).
  • 14 resistores de 220 ohms
  • 1 Arduino Nano
  • Protoboard, fios e jumpers

Podemos exibir as informações numéricas obtidas por sensores usando um display LCD ou de LEDs. Existem vários tipos. Entre os mais populares em projetos Arduino estão displays OLED (exibem desenhos e cores) displays Nokia 5110, displays LCD com 2 ou 4 linhas e 16 colunas, e displays de LED com 7 segmentos (foto).

Para os displays mais simples, como o de 7 segmentos, a conexão ao Arduino é geralmente feita em paralelo, individualmente para cada LED e requer muitos fios (usa quase todos os pinos digitais). A solução é usar um circuito intermediário (multiplexador, registrador de deslocamento, etc.) para codificar os dados de forma serial, diminuindo o uso dos pinos.

Neste experimento vamos simplesmente conectar dois displays de LED de sete segmentos ao Arduino e fazê-los exibir números. Para isto, acessaremos cada LED do display individualmente e usaremos 14 pinos. O Arduino possui 14 pinos digitais, mas três deles devem ser evitados:

  • O pino 0, porque é também usado para receber dados (RX). Ele pode ser usado, mas para que o upload seja possível ele precisa ser desligado (apenas durante o upload). Este pino também possui um LED ligado diretamente a ele, que irá roubar parte da corrente que alimenta o LED do display.
  • O pino 1, porque é usado para transmitir dados (TX). Ele pode ser usado, mas se houver transmissão de dados (por exemplo, uso do monitor Serial) ele também irá roubar corrente do display (fazendo o LED acender mais fraco ou piscar durante a transmissão). Ele também tem um LED na placa que acende durante a transmissão.
  • O pino 13, porque ele tem um LED na placa diretamente ligado a ele que precisará compartilhar corrente com o LED do display.

Uma vez programado o Arduino, e desligado do computador, os pinos 0 e 1 podem ser usados normalmente, mas para simplificar este projeto, iremos usar outros pinos.

O Arduino Nano possui 8 pinos analógicos (A0 a A7), 6 dos quais podem ser usados como pinos digitais (A0 a A5) referenciados pelos números 14 a 19. Portanto, neste projeto, em vez de usar os pinos 0 e 1, usaremos 14 e 15, respectivamente, e trocaremos o pino 13 pelo pino 16.

Os pinos a-g do display correspondem ao anodo de cada LED, que é ligado ao positivo (ligamos aos pinos de saída do Arduino, que acenderão cada LED com um nível lógico HIGH). O último pino do display é o ponto decimal. Os catodos de todos os LEDs estão interligados e podem ser ligados ao negativo através do pino central em qualquer um dos lados do display. Experimente acender LEDs individuais ligando uma mini bateria CR2032 (3V) entre o catodo comum e outro terminal do display.

Neste experimento não usaremos o LED do ponto decimal. O esquema das conexões está ilustrado abaixo, usando resistores de 220 ohms para ligar cada LED.

O display usado (HS-5101) consome no máximo 80mA (isto varia bastante e depende do tamanho do display – displays gigantes consomem vários amperes), mas consideramos os valores nominais de cada LED para calcular os resistores (a corrente recomendada é de 12mA, e máxima de 20mA). Assim, para displays de LEDs vermelhos ou amarelos (HS5101AS ou AY), a tensão direta em cada LED é de 2,0V e valor seria 250 ohms. Para displays de LEDs verdes ou azuis (HS5101AG ou AB) a tensão é 3,0V e usaríamos 170 ohms. Usamos um valor médio (220 ohms) que garante uma corrente abaixo da máxima e próxima da recomendada independente do modelo usado.

Pode-se calcular apenas um resistor para o catodo comum, mas isto irá fazer causar alterações de brilho entre dígitos diferentes (1, que usa apenas 2 LEDs e 8 que usa 2 LEDs, por exemplo). Isto foi feito no experimento 23.

Uma possível implementação com o protoboard está ilustrada abaixo. A maior complexidade deste circuito são a grande quantidade de fios. Verifique as conexões com cuidado, e se preferir, posicione os displays mais afastados. Use o esquema para verificar as ligações.

Teste das conexões

Para testar cada LED digite o programa abaixo no IDE do Arduino e faça upload. Ele configura 14 pinos como OUTPUT e depois faz com que cada LED acenda em sequência.

void setup() {
   // Define cada pino (2 a 16, exceto 13 )como saida
   for(int i = 2; i < 17; i++) {
      if(i != 13) {
          pinMode(i, OUTPUT);
      }
   }
   teste();  // roda o teste abaixo uma vez
}

// testa os leds acendendo-os e depois apagando-os
void teste() {
   for(int i = 2; i < 17; i++) {
       if(i != 13) {
           digitalWrite(i, HIGH);
           delay(500);
       }
   }
   for(int i = 2; i < 17; i++) {
       if(i != 13) {
           digitalWrite(i, LOW);
           delay(500);
       }
   }
}

void loop() {}

O próximo passo é configurar os LEDs de maneira a desenhar números.

Contando até 99

Para desenhar os números precisamos acender determinados LEDs ao mesmo tempo. O Arduino precisa fornecer nível HIGH para cada LED que deve acender. A tabela abaixo relaciona o estado (HIGH ou LOW) nos pinos a-g para desenhar cada dígito de 0-9:

dígito a b c d e f g
0 HIGH HIGH HIGH HIGH HIGH HIGH LOW
1 LOW HIGH HIGH LOW LOW LOW LOW
2 HIGH HIGH LOW HIGH HIGH LOW HIGH
3 HIGH HIGH HIGH HIGH LOW LOW HIGH
4 LOW HIGH HIGH LOW LOW HIGH HIGH
5 HIGH LOW HIGH HIGH LOW HIGH HIGH
6 HIGH LOW HIGH HIGH HIGH HIGH HIGH
7 HIGH HIGH HIGH LOW LOW LOW LOW
8 HIGH HIGH HIGH HIGH HIGH HIGH HIGH
9 HIGH HIGH HIGH HIGH LOW HIGH HIGH

O programa abaixo declara uma matriz 10 x 7 (vetor bidimensional) contendo cada um dos 10 dígitos associados a uma lista de segmentos contendo seu estado (HIGH ou LOW). Através dela poderemos mapear pinos do Arduino a cada LED individualmente. Contém também duas listas de 7 elementos (vetores) contendo os valores dos pinos Arduino usados em cada display.

int unidades[] = {14, 15, 2, 3, 4, 5, 6};
int dezenas[] = {7, 8, 9, 10, 11, 12, 16};

static int digito[10][7] = {
   {HIGH,HIGH,HIGH,HIGH,HIGH,HIGH,LOW},
   {LOW,HIGH,HIGH,LOW,LOW,LOW,LOW},
   {HIGH,HIGH,LOW,HIGH,HIGH,LOW,HIGH},
   {HIGH,HIGH,HIGH,HIGH,LOW,LOW,HIGH},
   {LOW,HIGH,HIGH,LOW,LOW,HIGH,HIGH},
   {HIGH,LOW,HIGH,HIGH,LOW,HIGH,HIGH},
   {HIGH,LOW,HIGH,HIGH,HIGH,HIGH,HIGH}, 
   {HIGH,HIGH,HIGH,LOW,LOW,LOW,LOW}, 
   {HIGH,HIGH,HIGH,HIGH,HIGH,HIGH,HIGH}, 
   {HIGH,HIGH,HIGH,HIGH,LOW,HIGH,HIGH}
};

const int DISPLAY_LEDS = 7;
const int DISPLAY_DIGITS = 10;
 
void setup() {
    for(int i = 0; i < DISPLAY_LEDS; i++) {
       pinMode(dezenas[i], OUTPUT);
       pinMode(unidades[i], OUTPUT);
    }
}

void acende(int* disp, int dig) {
    for(int i = 0; i < DISPLAY_LEDS; i++) {
       digitalWrite(disp[i], digito[dig][i]);
    }
}

void loop() {
    for(int j = 0; j < DISPLAY_DIGITS; j++) {
       acende(dezenas, j);
       for(int i = 0; i < DISPLAY_DIGITS; i++) {
          acende(unidades, i);
          delay(500);
       } 
    }
}

O setup() configura cada pino de cada display como OUTPUT, para que o Arduino possa fornecer saída de 0 a 5V.

A função acende() recebe como argumentos um display (array unidades ou dezenas) e um dígito e passa por cada LED gravando HIGH ou LOW, dependendo da linha da matriz 10×7 correspondente ao dígito a desenhar. O loop() chama acende para o display dezenas e unidades, contando de 00 a 99.

Para reiniciar a contagem, aperte o botão reset do Arduino.