Medindo altura de objetos com trigonometria e Arduino

Desenho exemplificando o funcionamento do protótipo. Desenho feito pela Bruna

O Problema

Uma das coisas mais chatas que há de se fazer quando não se tem vontade de aprender, é programar. Isso é uma realidade, programar por programar é muito chato para quem não ama isso. A paixão tem o poder de mudar toda uma vida de um indivíduo. Ela é capaz de “fazer acontecer” aquilo que parecia inalcançável.

Motivação

O curso de Engenharia Florestal que minha irmã mais nova faz, a Bruna Elisa Trentin, como todo bom curso de engenharia, tem uma matéria de programação. A pergunta é: como ensinar programação para alguém que lida com a natureza e, por padrão, não quer nem saber de ficar digitando texto puro em uma interface não muito amigável? Sem falar quando se trata de aprender a programar em C!

Esse desafio me chamou muito a atenção, e então comecei a bolar alguma coisa que pudéssemos fazer, com Arduino, para ensinar-lhe a programar. Inicialmente pensei em desenvolver um medidor de água para o bebedouro de seu hamster. Seria simples, porém o problema é que mesmo tendo água, esta deve ser trocada, afinal ele não bebe tanta água assim. Então não teria tanta emoção. A segunda ideia foi desenvolver um elevador para o bixano, sempre quis fazer isso! Porém ao fazer os primeiros esboços mentais me deparei com o primeiro grande problema: que material leve e forte eu iria usar para que o rato não roesse todo o elevador? Bom, a ideia do elevador basicamente é essa:

RATO Peg entra no elevador
Bixinho roedor ativa o elevador
Por fim, usufrui de todos os recursos da gaiola
Peg come elevador e a brincadeira acaba :(

Como podemos perceber, o projeto não iria durar muito tempo e o Arduino, após ingerido, poderia causar alterações nas funções básicas do bixinho roedor, o que acredito eu, minha maninha não iria gostar.

O papel do Arduino

Atualmente não tem, é moda, Arduino pegou e veio para ficar (essa afirmação não é necessariamente válida, vendo que minha experiência com eletrônica e programação embarcada é mínima). O que quero dizer é que, como já escrevi em outro tutorial, Arduino é capaz de tirar suas ideias do papel e isso sim eu garanto!

Devido a facilidade em comprar, programar, e montar dispositivos, o padrão Arduino de desenvolvimento mostra-nos que você não precisa ser um expert em eletrônica e programação para conseguir fazer algo. Você pode simplesmente conectar um led e fazer um programa que interaja com ele diretamente, em menos de 5 minutos! A “temível” programação em C, vira brincadeira com essa plataforma, pois com ela, você pode finalmente enteder variáveis, a lógica booleana, controles de fluxo, funções e tudo mais.

A Proposta

Nossa aplicação encontrada para estudar C com Arduino, foi desenvolver um medidor de árvores. Não necessariamente árvores, pois também pode ser usado para medir quaisquer objetos com alguns metros de altura. Portanto, o foco do nosso medidor é ser capaz de medir um objeto na sua vertical. Para tal, foi usado um chip Atmega328, um acelerômetro, um sensor de distância, toda a parafernália de fios para ligar tudo isso e claro, um suporte improvisado.

Como Funciona

O segredo de tudo isso é a trigonometria, ramo da matemática qual é responsável por estudar as relações de dois lados de um triângulo retângulo (triângulo que possui um ângulo de 90º)[1]. Na prática, o que precisamos é saber a distância de nós até o objeto (a árvore) e depois o ângulo de inclinação até o topo do objeto (árvore feliz), como podemos ver em um de nossos rascunhos feitos:

Display lcd 128x64 com Arduino Mega
Esboço inicial do funcionamento do medidor. Desenho original: Paulo e anotações para cálculo: Bruna
Desenho exemplificando o funcionamento do protótipo
Desenho feito pela Bruna exemplificando o funcionamento do protótipo.

Para lermos a distância entre o medidor e a árvore (objeto), usamos o sensor HC-SR04, que segundo este manual,ele é capaz de medir até 5 metros de distância. Na prática porém, conseguimos medir até 3,30 metros.

O ângulo de inclinação é lido a partir do eixo X do acelerômetro. Usamos o módulo DC-SS009 que é um circuito completo e pronto para usar seu chip A7260, que é um acelerômetro capacitivo.

Todo o processamento de informações é feito através do nosso querido amigo Atmega328 que foi montado diretamente numa protoboard de forma simples e prática. Futuramente desenvolverei um tutorial que explique passo-a-passo como isso foi feito, por hora o video pode servir de grande ajuda. Caso deseje colocar em prática o que foi feito aqui, entre em contato comigo por email, que terei prazer em lhe ajudar!

Após ler a distância entre o objeto e o sensor, aplicamos o cálculo da tangente do ângulo, de acordo com a tabela de razões trigonométricas. Esse cálculo nada mais é que a multiplicação entre a distância (em CM) e a tangente do ângulo. O ângulo de 45º, por exemplo, possui tangente 1. Isso indica que à 45º a altura do objeto será o mesmo que a distância entre este e o sensor de distância.

Testando sensor de distância

Aqui estamos o sensor de distância, para garantir que está funcionando.

Abaixo o código fonte usado neste teste. Ele foi obtido em http://fristec.blogspot.com/2011/01/14-aplicacao-sensor-de-distancia-hc.html:

#include "Ultrasonic.h"
Ultrasonic ultrasonic(12,13); // TRIG = pino 12     ECHO = pino 13

void setup() {
  pinMode(10, OUTPUT);
   Serial.begin(9600);
}

void loop()
{
   digitalWrite(10, HIGH);
   Serial.print("distancia em centimetros : ");
   Serial.println(ultrasonic.Ranging(CM));
   digitalWrite(10, LOW);
   delay(100);
}

Testando o Acelerômetro

Aqui vemos um teste simples com o acelerômetro, que infelizmente, não dispõem de muita precisão

Acelerômetro retorna valores com até 5 pontos de variação, o que gera um erro na medida do ângulo

Na maioria das aplicações acredito que esta imprecisão não causará maiores problemas, porém aqui ela faz grande diferença, afinal, podemos ter uma variação de 3 ângulos (-1, 0 e 1). Isso significa que à 3.2 metros de distância do objeto com um ângulo de 70 graus, teremos um metro de erro!

Seguindo os dados feitos com testes de medidas, para garantir precisão, o ângulo máximo de inclinação deveria ser em torno de 35º. Isso à 3.2 metros do objeto nos permite medir objetos de no máximo 2.2 metros.

Porém, com margem de erro de 50CM, podemos usar ângulos de até 60º, o que nos permite medir, à 3.2 metros de distância, objetos de até 5.5 metros.

Tentamos corrigir o erro visto na tabela ao lado lendo esse post, e este, porém, ambos apenas citam correções na leitura de acordo com o ângulo. E o problema visto aqui é que mesmo parado, o leitor apresenta essa variação de  5 pontos. E esse problema continua ainda sem solução.

Abaixo o software usado para fazer a medida do eixoX, mostrada acima.

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

void loop (){
  int eixoX = analogRead(A3);

  //  Mapeia a escala do sensor para um angulo entre 0 e 90 graus
  int novoX = map(eixoX, 332,167,0,90);
  Serial.print ("Valor do eixo X: ");
  Serial.print (eixoX);
  Serial.print (" valor convertido: ");
  Serial.println(novoX);
  delay (500);
}

Funcionamento do programa

Inicialmente vamos recorrer ao fluxograma desenvolvido.

Rascunho do Fluxograma desenvolvido pela Bruna
Fluxograma final
Fluxograma final

Seguindo o fluxograma, percebemos que a primeira coisa a se fazer ao ligar o dispositivo, é ler o botão. Enquanto não for pressionado o programa ficará num “looping infinito”.

Após pressionado, será então medida a distância entre o medidor e o objeto. Enquanto não conseguir verificar a distância, ficará alertando o usuário através do led vermelho e um “beep”.

Ao fazer a leitura da distância, é acionado o led verde indicando sucesso e um “beep” um pouco mais suave que o anterior. O próximo estágio novamente é aguardar o pressionamento do botão, pois nesse momento o software está aguardando o usuário mirar no topo do objeto.

Assim que o usuário pressiona esse botão, o software faz então a leitura da inclinação do aparelho, calcula a alutra do objeto de acordo com a tabela de valores das tangentes, informa ao usuário a altura calculada e por fim, gera 3 “beeps” informando o término do processo, possibilitando então que o usuário faça uma nova medida.

Fotos do desenvolvimento

[nggallery id=4]

Video apresentando o projeto

Código fonte do programa

  #include "Ultrasonic.h"

  Ultrasonic ultrasonic(12,13); // TRIG = pino 12     ECHO = pino 13
  float tangentes[] = {  0.017455, 0.034921, 0.052408, 0.069927, 0.087489, 0.105104, 0.122785, 0.140541,
                        0.158384, 0.176327, 0.19438, 0.212557, 0.230868, 0.249328, 0.267949, 0.286745,
                      0.305731, 0.32492, 0.344328, 0.36397, 0.383864, 0.404026, 0.424475, 0.445229,
                    0.466308, 0.487733, 0.509525, 0.531709, 0.554309, 0.57735, 0.600861, 0.624869,
                  0.649408, 0.674509, 0.700208, 0.726543, 0.753554, 0.781286, 0.809784, 0.8391,
                0.869287, 0.900404, 0.932515, 0.965689, 1, 1.03553, 1.072369, 1.110613,
              1.150368, 1.191754, 1.234897, 1.279942, 1.327045, 1.376382, 1.428148,
            1.482561, 1.539865, 1.600335, 1.664279, 1.732051, 1.804048, 1.880726,
          1.962611, 2.050304, 2.144507, 2.246037, 2.355852, 2.475087, 2.605089,
        2.747477, 2.904211, 3.077684, 3.270853, 3.487414, 3.732051, 4.010781,
      4.331476, 4.70463, 5.144554, 5.671282  };

int pinoBotao = 9;
int ledVerde = 3;
int ledVermelho = 2;
int pinoX = A3;

void setup (){
 Serial.begin (9600);
 Serial.println("Pronto para uso. Mire no objeto e pressione o botao.");
 pinMode(pinoBotao, INPUT);
 pinMode (ledVermelho, OUTPUT);
 pinMode (ledVerde, OUTPUT);
}

void loop (){
  /*
    Calcula altura da árvore (ou objeto) de acordo com valores dos sensores
  */

  //  Enquanto o usuário não apertar o botão, fica aguardando
  aguardaBotao();

  //  Lê a distância
  int distancia = verificaDistancia();
  Serial.print("Distancia lida: ");
  Serial.print(distancia);
  Serial.println("cm");

  //  Aguarda o usuário mirar no topo do objeto e apertar novamente o botão
  Serial.println("Mire na ponta do objeto e pressione o botao novamente.");
  aguardaBotao();

  //  Calcula o ângulo
  int anguloInclinacao = verificaAngulo();
  Serial.print("Angulo de inclinacao lido: ");
  Serial.print(anguloInclinacao);
  Serial.println(" graus");

  /*
    Finalmente, calcula a altura. Para isso, pega a tangente
    correspondente do ângulo e multiplica pela distância lida
  */
  float tangente = tangentes[anguloInclinacao - 1];
  Serial.print("tangente obtida: ");
  Serial.println(tangente);
  float altura = distancia * tangente;

  //  Exibe a altura
  Serial.print("Altura do objeto: ");
  Serial.print(altura);
  //  Exibe outras informaçoes
  Serial.print("cm, baseada na distancia de ");
  Serial.print(distancia);
  Serial.print(" CM e no angulo de ");
  Serial.print(anguloInclinacao);
  Serial.println(" graus.");
  Serial.println();

  //  Informa fim de leitura com 3 bips
  geraBipOk();
  delay(100);
  geraBipOk();
  delay(100);
  geraBipOk();
}

/*
  Lê o sensor do ângulo do eixo X do acelerômetro.

  Faz uma média de leituras e retorna o valor delas
*/
int verificaAngulo()
{
  //  Define a quantidade de leituras para uma média
  byte quantiaLeituras = 10;
  //  Cria um vetor para alocar as médias
  int leiturasAngulo[quantiaLeituras];
  //  Cria variável de controle do laço (for)
  int i;

  //  Lê-se: Para i = 1, enquanto o valor de i for menor que
  //  a quantiaDeleituras, incremente i e faça
  for (i = 0; i < quantiaLeituras; i = i + 1){
    //  Lê o valor do sensor e coloca-o na variável eixoX (esse valor vai de 0 à 1023)
    int eixoX = analogRead(pinoX);
    //  Mapeia o valor lido que normalmente é de 332 à 165 para 0 e 90 (Graus)
    int novoX = map(eixoX, 332,167,0,90);
    //  Aloca no vetor leiturasAngulo os valores
    leiturasAngulo[i] = novoX;
  }

  //  Calcula a média aritmética do ângulo
  int somaAngulos = 0;

  for (i = 0; i < quantiaLeituras; i++){
    somaAngulos = somaAngulos + leiturasAngulo[i];
  }

  //  Retorna o valor do cálculo (arredonda a média)
  return somaAngulos / quantiaLeituras;
}

/*
  Lê o botão usado para interagir com o usuário
  Enquanto a leitura retornar 0, fica lendo o botão
*/
void aguardaBotao()
{
  /*
    O ! é usado para negar o resultado da leitura.
    Se o resultado for 0 ele vira 1
    Se o resutlado for 1 ele vira 0

    Lê-se: enquanto o botão não estiver pressionado (negação de 0 é 1) fique lendo
  */
  while(! digitalRead(pinoBotao));

  /*
  //  A mesma coisa que a linha de cima
  boolean botaoNaoPressionado = true;

  while(botaoNaoPressionado){
    if (digitalRead(pinoBotao)){
      botaoNaoPressionado = false;
    }
  }
  */

  //  Aguarda 1 segundo para evitar varios acionamentos pelo botao
  delay(1000);
}

/*
  Lê o sensor ultrassom até receber 3 valores e gerar a média
  entre eles.

  Após lido os três valores, a luz verde irá ligar e um "bip"
  soará indicando a correta leitura

  Por fim, retorna a distância em CM
*/
int verificaDistancia()
{
  int quantiaLeituras = 10;
  int leiturasUltrassom[quantiaLeituras];
  int i;
  boolean lido;

  for (i = 0; i < quantiaLeituras; i++){
    lido = false;

    while(!lido){
      int valorUltrassom = ultrasonic.Ranging(CM);
      int valorAngulo = verificaAngulo();

      if (valorUltrassom < 1000){
        lido = true;
        leiturasUltrassom[i] = valorUltrassom;
      }else{
        delay(500);
        geraBipFalha();
        ativaLedVermelho();
      }
    }
  }

  //  Gera o "BIP" e ativa o led
  ativaLedVerde();
  geraBipOk();

  //  Calcula a média aritmética da distância
  int somaDistancias = 0;

  for (i = 0; i < quantiaLeituras; i++){
    somaDistancias = somaDistancias + leiturasUltrassom[i];
  }

  Serial.print("soma das distancias: ");
  Serial.print(somaDistancias);
  Serial.print(" quantiaLeituras: ");
  Serial.println(quantiaLeituras);
  //  Retorna o valor do cálculo
  return somaDistancias / quantiaLeituras;
}

void ativaLedVerde()
{
  digitalWrite(ledVerde, HIGH);
  desativaLedVermelho();
}

void desativaLedVerde()
{
  digitalWrite(ledVerde, LOW);
}

void ativaLedVermelho()
{
  digitalWrite(ledVermelho, HIGH);
  desativaLedVerde();
}

void desativaLedVermelho()
{
  digitalWrite(ledVermelho, LOW);
}

void geraBipOk()
{
  tone(8,256,50);
}

void geraBipFalha()
{
  tone(8,104,50);
}

Agradecimentos

A construção desse projeto no tempo esperado não teria sido possível se o Herculano não tivesse emprestado o acelerômetro e o sensor de distância, valeu professor!

Conclusão

É evidente no video que o projeto precisa de melhorias em relação à precisão da leitura, exceto se for para medir a cabeça de um gato hehe. Essas melhorias basicamente estão nos sensores usados.
Acreditamos, no entando, que a ideia foi validada, visto que comprovamos que é possível medir objetos com Arduino um acelerômetro e um sensor de distância.

10 Comments

  1. Cara, voce me ajudou muito, estudo engenharia e estou ajudando a divulgar teu blog, ja que me ajudou muito!

  2. Sou apaixonado por ELETRÔNICA e gostei muito do que vi no seu titorial criado para ajudar sua irmã. Rsss…
    Gostei muito do ratinho gordinho apos comer os componentes. Rsss…
    Mais adorei mesmo a sua abordagem simples e himilde para apredizes como eu.
    Responda meu e-mail.
    Boa noite. Obrigado!

Leave a Reply

%d