Curso Arduino – #9 – Sensor de Distância Ultrassónico HC-SR04 e Novas Funções

 

 

Neste artigo, vamos introduzir o Sensor de Distância Ultrassónico HC-SR04, que é muito comum nos projetos Arduino. Também, está na hora de começar a escrever suas próprias funções. No final, vamos apresentar um outro elemento – buzzer.

Esperamos que esta parte seja interessante para os construtores de robots e para todos aqueles que gostariam de usar um sensor de distância nos seus projetos. Porém, antes de introduzirmos o sensor, vamos abordar novos truques de programação, que vão tornar os seus programas ainda melhores!

Funções sem argumentos

Até agora, incluímos o nosso código nas funções setup () {} ou loop () {}. A primeira define configurações, e a segunda é um loop infinito, realizado o tempo todo. Por exemplo, para colocarmos o LED do pino 13 a piscar, escrevemos um programa deste género:

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
}

void loop() {
digitalWrite(13, HIGH); //Liga o LED
delay(1000); //Espera 1 segundo
digitalWrite(13, LOW); //Desliga o LED
delay(1000); //Espera 1 segundo
}

Imagine uma situação em que o seu programa é muito extenso e quer usar este piscar como confirmação de operações selecionadas. Vai rapidamente reparar que a repetição do código responsável por ligar/desligar o LED consome muito do seu tempo. O que é pior é que também torna todo o programa muito mais difícil de analisar.

Ao declarar uma variável, podemos utilizá-la inúmeras vezes de uma forma muito simples. Se pudéssemos escrever sequências de operações sob um nome fácil, os programas seriam muito mais legíveis. Quaisquer alterações que pretendessemos fazer, também seriam mais fáceis de realizar. As funções ajudam neste aspeto, já que podemos criá-las nós próprios.

Vamos voltar ao exemplo anterior do LED a piscar. O fragmento de código seguinte é responsável por essa operação:

digitalWrite(13, HIGH); //Liga o LED
delay(1000); //Espera 1 segundo
digitalWrite(13, LOW); //Desliga o LED
delay(1000); //Espera 1 segundo

Podemos retirar este fragmento da função loop e criar uma função própria com ele. Como é que o fazemos? No início, à semelhança de uma variável, é necessário declarar o tipo e o nome da função. Fazemo-lo depois da função loop () {}.

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
}

void loop() {
}

void piscarLED() {
//Conteúdo da função
}

Como pode ver, antes do nome da função (piscarLED) vem o tipo de função. Não é nada mais do que informação acerca da função, se envia algum valor/mensagem/etc. após o término da atividade. Se fosse esse o caso, então deveria ser colocado o tipo apropriado de função (de acordo com os tipos de variáveis). Por exemplo, um inteiro (int), um caracter (char), um número não inteiro (float), etc. Neste caso, o tipo de função é void. Isto significa que a função não retorna qualquer valor/mensagem/etc. Porque é que escolhemos este tipo? O objetivo da função é piscar o LED, esta operação não envia nenhum resultado, além do que é visível.

Após o nome da função deverá aparecer parênteses. Para já, vamos assumir que não há nada dentro deles. Depois abrimos chavetas e colocámos o código pretendido. Fechámos chavetas e está pronto. Na prática será assim:

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
}

void loop() {
piscarLED();
}

void piscarLED() {
digitalWrite(13, HIGH); //Liga o LED
delay(500); //Espera meio segundo
digitalWrite(13, LOW); //Desliga o LED
delay(500); //Espera meio segundo
}

Por favor, note que na função loop () {} inserimos o nome da nossa função (sem o prefixo void). Isto é intitulado chamada de função. Faça o upload do programa e verifique se funciona conforme a seguinte imagem:

Curso Arduino - #9

Graças a esta função, se pretendermos alterar algum aspeto no código em questão, só o precisamos de fazer uma vez e num só sítio.

Trabalho de Casa nº28

Escreva uma função que faça o LED brilhar e desligar gradualmente. Verifique novamente o artigo sobre sinais PWM.

Funções com argumentos

Vamos agora descobrir novos segredos das funções. Lembra-se de uma das funções mais comuns que utilizamos na programação com Arduino? É, provavelmente, a mudança de estado num determinado pino, por exemplo:

digitalWrite(13, HIGH);

Em contraste com a nossa função piscarLED (); , existem dados dentro dos parênteses. Podem ser um número de pino, um estado ou duty cycle do sinal PWM. Essas informações são usadas posteriormente na função, na execução de determinadas operações.

Agora vamos escrever a nossa própria função, com um argumento. Vamos supor que queremos editar a função piscarLED de tal forma que, durante a chamada, seja possível alterar a velocidade do piscar. Na declaração da função, devemos a informação pretendida para o argumento. Fazemo-lo dentro dos parênteses da função:

//Versão original
void piscarLED(){

//Nova versão
void piscarLED(int tempo){

Na prática, a função terá este aspeto:

void piscarLED(int tempo){
digitalWrite(13, HIGH); //Ligar o LED
delay(tempo); //Esperar determinado tempo
digitalWrite(13, LOW); //Desligar o LED
delay(tempo); //Esperar determinado tempo
}

Vamos agora inserir um último elemento. Não é nada surpreendente. Simplesmente colocámos um número entre parênteses, que vai indicar o tempo que o LED vai estar ligado e desligado. Vejamos o código:

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
}

void loop() {
piscarLED(50);
}

void piscarLED(int tempo){
digitalWrite(13, HIGH); //Ligar o LED
delay(tempo); //Esperar determinado tempo
digitalWrite(13, LOW); //Desligar LED
delay(tempo); //Esperar determinado tempo
}

Verifique como consegue alterar a frequência do piscar, ao alterar o valor entre parênteses.

Exemplo nº2

Até agora, o LED piscava constantemente, porque fizemos a chamada da função na função loop. Se transferimos a chamada da função para a função setup, veremos apenas um flash após o início do programa.

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
piscarLED(50);
}

void loop() {

}

void piscarLED(int tempo){
digitalWrite(13, HIGH); //Ligar LED
delay(tempo); //Esperar determinado tempo
digitalWrite(13, LOW); //Desligar LED
delay(tempo); //Esperar determinado tempo
}

É hora de transformar a função de tal forma que, além de alterar o tempo que o LED pisca, também é possível influenciar o número de piscas. Para isso, temos de alterar a declaração da função:

//Versão anterior
void piscarLED(int tempo){

//Nova versão
void piscarLED(int tempo, int quantidade){

Adicionamos o argumento “quantidade”, que será responsável pelo número de piscas. Como pode verificar, foi inserido após a vírgula, precedido, obviamente, pelo seu tipo (int). Este parâmetro pode ser de um tipo diferente, não há qualquer problema. O importante é que a declaração seja apropriada.

Esperamos que já saiba qual loop usar para controlar a quantidade de piscas. Se não, aconselhamo-lo a visitar o nosso artigo #8 que aborda o assunto. No entanto, vamos presumir que já domina este conteúdo e apresentar a nova função piscarLED:

void piscarLED(int quantidade, int tempo){
for (int i=0; i < quantidade; i++) {
digitalWrite(13, HIGH); //Ligar LED
delay(tempo); //Esperar determinado tempo
digitalWrite(13, LOW); //Desligar LED
delay(tempo); //Esperar determinado tempo
}
}

Agora vejamos o código completo:

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
piscarLED(100, 5);
}

void loop() {
}

void piscarLED(int tempo, int quantidade){
for (int i=0; i < quantidade; i++) {
digitalWrite(13, HIGH); //Ligar o LED
delay(tempo); //Esperar determinado tempo
digitalWrite(13, LOW); //Desligar o LED
delay(tempo); //Esperar determinado tempo
}
}

Tudo deverá funcionar na perfeição. Agora, após o início do programa, o LED deve piscar exatamente 5 vezes. Mas como é que o Arduino sabe distinguir, das informações contidas entre parênteses, qual o tempo e qual a quantidade de piscas? Bem, é muito simples – valores subsequentes separados por vírgulas, são atribuídos aos argumentos seguintes descritos na declaração da função. No caso acima, o primeiro número será sempre tratado como tempo e o segundo como a quantidade de vezes que o LED pisca.

Exercício prático – Funções com argumentos

O pino ao qual o LED está ligado é um número no código. Costumamos usá-lo em dois sítios: na declaração do pino como saída/entrada e na definição do estado high/low. Portanto, esse número pode ser um outro argumento da nossa função!

Material necessário:

Primeiramente, conecte o segundo LED a outro pino (exemplo: 8):

Curso Arduino - #9

O código será o seguinte:

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
piscarLED(100, 5, 13);
piscarLED(150, 4, 8);
}

void loop() {
}

void piscarLED(int tempo, int quantidade, int pino){
for (int i=0; i < quantidade; i++) {
digitalWrite(pin, HIGH); //Ligar o LED
delay(tempo); //Esperar determinado tempo
digitalWrite(pin, LOW); //Desligar o LED
delay(tempo); //Esperar determinado tempo
}
}

Funciona? Nem por isso, certo? Apenas o LED do pino 13 pisca. O LED do pino 8 não quer dar sinal de vida. Mas, porquê? Bem, cometemos um erro muito simples, que é fácil de fazer ao escrever este tipo de funções. Não indicamos em lugar nenhum que, além do pino 13, podem existir outras saídas. Verifique a função setup:

void setup() {
pinMode(13, OUTPUT); //Configuração do pino 13 como saída
piscarLED(100, 5, 13);
piscarLED(150, 4, 8);
}

Podemos corrigir este problema facilmente. Basta mover a configuração do pino para dentro da nossa função, como pode ver abaixo:

void setup() {
piscarLED(100, 5, 13);
piscarLED(150, 4, 8);
}

void loop() {
}

void piscarLED(int tempo, int quantidade, int pino){
pinMode(pino, OUTPUT); //Configuração de saídas

for (int i=0; i < quantidade; i++) {
digitalWrite(pino, HIGH); //Ligar o LED
delay(tempo); //Esperar determinado tempo
digitalWrite(pino, LOW); //Desligar o LED
delay(tempo); //Esperar determinado tempo
}
}

Agora tudo deve funcionar como pretendido. No entanto, devemos admitir que esta não é uma solução propriamente elegante. Portanto, não aconselhamos a utilização deste tipo de solução em projetos profissionais. Como se trata de um projeto simples, não há qualquer problema, até nos facilita a vida.

Funções que enviam um resultado

Até aqui, apenas usamos funções que realizavam algumas operações, sem qualquer retorno de resultado. Agora vamos introduzir uma nova função que nos envia um valor. O objetivo da função será calcular a área de um quadrado, através do valor de um dos lados, e enviar o resultado. Para começar, é necessário declarar a função:

int area(int a) {

Como pode ver, existe a expressão int antes do nome da função. Isto significa que o resultado enviado será um número (área do quadrado).

A função deverá ficar desta forma:

int area(int a) {
int resultado = 0;
resultado = a * a;

return resultado;
}

Deve estar a perguntar-se para onde é enviado o resultado. Vamos verificar o exemplo seguinte:

void setup() {
Serial.begin(9600);
}

void loop() {
int resultado = area(4);
Serial.println(resultado); //Enviar resultado para monitor série
delay(500);
}

int area(int a) {
int resultado = 0;
resultado = a * a;

return resultado;
}

Execute o exemplo. O resultado da operação (16) deverá aparecer no monitor série.

À semelhança do que está no exemplo acima, podemos estabelecer o envio do resultado para o monitor série de uma outra forma:

//Versão anterior
int resultado = area(4);
Serial.println(resultado); //Enviar resultado para monitor série

//Nova versão
Serial.println(area(4)); //Enviar resultado para monitor série

A cereja no topo do bolo será enviar o valor do lado do quadrado para o Arduino via computador. Se já não se lembra como se faz, reveja o artigo do curso sobre comunicação via UART. O programa ficará assim:

String dadosRecebidos = ""; //Conjunto vazio de dados recebidos
void setup() {
Serial.begin(9600);
}

void loop() {
if(Serial.available() > 0) { //Se o Arduino receber dados
dadosRecebidos = Serial.readStringUntil('\n'); //Ler dados e salvá-los na variável
int resultado = area(dadosRecebidos.toInt());
Serial.println(resultado); //Enviar resultado para monitor série
}
}

int area(int a) {
int resultado = 0;
resultado = a * a;

return resultado;
}

A novidade aqui é a linha:

int resultado = area(dadosRecebidos.toInt())

Como se deve lembrar, durante a comunicação UART, todos os caracteres são enviados na forma de códigos ASCII. Assim, não podemos simplesmente enviar números para o Arduino. Terá de haver um conversor que transforme o número enviado como texto, num número tipo int. É exatamente isto que a linha anterior faz.

Trabalho de Casa nº29

Escreva funções que calculem a área de um retângulo, triângulo e círculo. Envie os resultados para o computador via UART!

Sensor de Distância Ultrassónico HC-SR04

Vamos passar agora à parte mais desejada do artigo: a introdução do sensor de distância HC-SR04. Este sensor consiste num transmissor, num recetor ultrassónico e em vários circuitos integrados. Graças a ele, tendo em conta a propagação e reflexão de uma onda sonora, podemos determinar, com precisão, a distância entre o sensor e um obstáculo. De forma semelhante à ecolocalização dos morcegos!

HC-SR04

Mas vamos voltar ao Arduino e ao sensor de distância. Para começar, vamos concentrar-nos nos seus quatro pinos. Dois deles são usados para alimentação (Vcc e GND) e os outros dois (trigger e echo) para realizar medições.

O Trigger é o pino que desencadeia a ação. Quando lhe atribuímos um estado high (por pelo menos 10 microssegundos), a medição da distância começa. No entanto, é a partir do pino Echo que vamos obter a distância medida. O alcance máximo deste sistema é 4 metros.

Exercício Prático – Medição de distância

Material necessário:

Vamos criar um sistema que mede distâncias em intervalos regulares e exibe as medições no monitor série. Para isso, é necessário montar o sistema de acordo com o seguinte diagrama:

curso arduino - #9 - hc-sr04

A descrição dos pinos do sensor está indicada no próprio diagrama. No entanto, para que não haja dúvidas, criamos uma tabela com a ordem dos pinos e com as conexões com o Arduino:

curso arduino - #9 - HC-SR04

Também pode conferir a imagem abaixo, que mostra as nossas ligações:

Como podemos verificar na tabela, o trigger está conectado ao pino 12 e o echo ao pino 11 do Arduino. Isso deve estar bem claro no código. Portanto, sugerimos que defina os pinos:

#define trigPin 12
#define echoPin 11

Vamos agora escrever o código que estabeleça o desencadeamento da medição da distância:

#define trigPin 12
#define echoPin 11

void setup() {
Serial.begin (9600);
pinMode(trigPin, OUTPUT); //Definição do pino 12 como saída
pinMode(echoPin, INPUT); //Definição do pino 11 como entrada
}

void loop() {
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

//Medição feita
}

Observe a nova função delayMicroseconds (). Esta é equivalente à função delay (). A diferença é óbvia:  a nova função conta o tempo em microssegundos. A função mais comum, conta o tempo em milissegundos.

A sequência que inicia a medição é muito simples. No início, definimos o estado do pino ligado ao trigger como low. Colocamos um atraso de dois microssegundos, que são suficientes, e definimos o estado high durante 10 microssegundos. O sensor realiza a medição e envia os resultados através do pino echo.

A questão é como é que vamos ler esse valor? Existe UART ou outra interface de comunicação para isso? Não, felizmente este sensor é muito simples e a distância medida é representada pelo pulso (estado high) no pino echo. O seu tamanho é proporcional à distância, ou seja, quanto maior for o pulso, maior é a distância medida.

Medição da duração de um pulso no Arduino

Felizmente, no Arduino existe uma função muito simples que consegue medir a duração do pulso em qualquer entrada. A sua duração deve ser compreendida entre 10 microssegundos e 3 minutos. O início da medição ocorre quando é detetada uma mudança de estado no pino.

A sua representação é muito simples:

int resultado = 0;
resultado = pulseIn(11, HIGH);

A função apenas aceita dois argumentos muito simples: o número do pino a ser verificado e o nível lógico (high/low) a ser medido.

Então, para medir a distância, temos de executar a seguinte operação:

#define trigPin 12
#define echoPin 11

void setup() {
Serial.begin (9600);
pinMode(trigPin, OUTPUT); //Definição do pino 12 como saída
pinMode(echoPin, INPUT); //Definição do pino 11 como entrada
}

void loop() {
long tempo;

digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

tempo = pulseIn(echoPin, HIGH);
Serial.print(tempo);

delay(500);
}

Ao executar este programa, vão aparecer números no monitor série. Quanto mais perto de um obstáculo o sensor estiver, menor será o valor que aparece. No entanto, são impercetíveis para nós. Para que a medição seja legível, o resultado deve ser dividido por um “número mágico”.

Verifique o funcionamento do seguinte programa.

#define trigPin 12
#define echoPin 11

void setup() {
Serial.begin (9600);
pinMode(trigPin, OUTPUT); //Definição do pino 12 como saída
pinMode(echoPin, INPUT); //Definição do pino 11 como entrada
}

void loop() {
long tempo, distancia;

digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

tempo = pulseIn(echoPin, HIGH);
distancia = tempo / 58;

Serial.print(distancia);
Serial.println(" cm");

delay(500);
}

Agora, o resultado deve estar apresentado em centímetros. É claro que o número (58) pelo qual dividimos o valor não é “mágico”. Resulta do tempo que o som viaja numa distância de 1cm e da distância padrão indicada pelo fabricante.

Função que envia a distância detetada pelo sensor em cm

Como pode ver, a parte do programa que permite obter uma medição é relativamente longa. Seria muito conveniente haver uma função que faz tudo isso. Vamos lá escrevê-la.

int medicaoDistancia() {
long tempo, distancia;

digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

tempo = pulseIn(echoPin, HIGH);
distancia = tempo / 58;

return distancia;
}

Agora, no local certo, coloque a chamada da função:

#define trigPin 12
#define echoPin 11

void setup() {
Serial.begin (9600);
pinMode(trigPin, OUTPUT); //Definição do pino 12 como saída
pinMode(echoPin, INPUT); //Definição do pino 11 como entrada
}

void loop() {
Serial.print(medicaoDistancia());
Serial.println(" cm");

delay(500);
}

int medicaoDistancia() {
long tempo, distancia;

digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

tempo = pulseIn(echoPin, HIGH);
distancia = tempo / 58;

return distancia;
}

Muito mais fácil de perceber, certo?

Exercício Prático – Alarme

Neste exercício, vamos adicionar um buzzer ao nosso sistema, ou seja, um elemento sonoro. Possui o seguinte aspeto:

curso arduino - #9 - HC-SR04

Este elemento possui duas entradas: VCC e GND. Quando ligamos as duas, o buzzer emite um som alto, não muito agradável.

Material necessário:

Para utilizar o buzzer, deverá fazer as ligações ao Arduino da seguinte forma:

curso arduino - #9 - HC-SR04

A imagem do buzzer utilizada no diagrama não é exatamente igual ao buzzer que indicamos acima. No entanto, isso não é relevante, o efeito será o mesmo. O importante é fazer a ligação dos fios (VCC e GND) corretamente!

Agora vamos escrever código cujo objetivo será verificar se um objeto se encontra a uma certa distância do sensor. Se se verificar, o buzzer irá emitir som.

Para tal, utilizamos o código anteriormente utilizado para medir a distância e acrescentamos a seguinte função:

void alcance(int a, int b) {
int limiteDistancia = medicaoDistancia();
if ((limiteDistancia > a) && (limiteDistancia < b)) {
digitalWrite(3, HIGH); //Ligar o buzzer
} else {
digitalWrite(3, LOW); //Desligar o buzzer quando objeto está fora do alcance
}
}

Como argumento da função, indicamos dois valores inteiros. De seguida, verificamos se a distância medida é maior do que o valor inicial da nossa faixa (a) e menor que o valor máximo (b).

Nota: O símbolo && combina duas condições numa.

O programa completo terá este aspeto:

#define trigPin 12
#define echoPin 11

void setup() {
Serial.begin (9600);
pinMode(trigPin, OUTPUT); //Definição do pino 12 como saída
pinMode(echoPin, INPUT); //Definição do pino 11 como entrada
pinMode(3, OUTPUT); //Definição do pino 3, ligado ao buzzer, como saída
}

void loop() {
alcance(10, 25); //Ligar o alarme se houver um objeto a uma distância de 10 a 25cm do sensor
delay(100);
}

int medicaoDistancia() {
long tempo, distancia;

digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

tempo = pulseIn(echoPin, HIGH);
distancia = tempo / 58;

return distancia;
}

void alcance(int a, int b) {
int limiteDistancia = medicaoDistancia();
if ((limiteDistancia > a) && (limiteDistancia < b)) {
digitalWrite(3, HIGH); //Ligar o buzzer
} else {
digitalWrite(3, LOW); //Desligar o buzzer quando objeto está fora do alcance
}
}

Verifique como é que o programa funciona na prática. Na nossa opinião, este é um ótimo primeiro passo na criação de um sistema de alarme.

Trabalho de Casa nº30

Escreva um programa que indique a distância de um obstáculo do sensor, através de uma linha de LEDs. Quanto mais perto o obstáculo estiver do sensor, mais LEDs deverão acender. Pode ter como exemplo a linha apresentada na imagem seguinte:

curso arduino - #9 - HC-SR04

Trabalho de Casa nº31

Verifique como a leitura do sensor muda dependendo do material a partir do qual o obstáculo é construído. Compare diferentes obstáculos, como por exemplo: parede, papel, pedra, plástico, etc…

Sumário

Depois deste artigo, será capaz de criar as suas próprias funções, assim como desenvolver um projeto utilizando o sensor de distância HC-SR04. As suas opções são infinitas, pode desenvolver um robot que evita obstáculos, um sistema de alarme, um sensor de estacionamento, etc… Ficámos à espera que nos mostre as suas invenções!

___________

O que achou deste artigo? Deixe o seu comentário abaixo, e partilhe nas Redes Sociais que certamente será útil e interessante para os seus amigos!

 

 

 

 Curso Arduino – #0 – Introdução

 Curso Arduino – #1 – O Básico do Arduino e o Software de Programação

 Curso Arduino – #2 – O Básico da Programação e as Portas I/O

 Curso Arduino – #3 – UART e Variáveis

 Curso Arduino – #4 – Conversor Analógico-Digital

 Curso Arduino – #5 – PWM, Servomecanismos e Bibliotecas

 Curso Arduino – #6 – UART (continuação) e Servos

 Curso Arduino – #7 – Displays

 Curso Arduino – #8 – Controlo de Motores DC

 Curso Arduino – #9 – Sensor de Distância Ultrassónico HC-SR04 e Novas Funções

 Curso Arduino – #10 – Gráficos, Números Aleatórios e Condições