Projeto Atual:

Programando Aplicativos com Javascript

20/01/2020

Programação e Robótica II - Parte 11

Controle Remoto II

Com o programa do carro já migrado para C/C++, inserimos parte da lógica do teste do controle remoto. Para tornar o código mais legível, transformamos os códigos das teclas em uma tabela de constantes.

Para o teste inicial, usamos comandos das teclas 1 e 2 do controle remoto para acender e apagar o farol, respectivamente.

No código adicionado, perdemos os destaques coloridos que conseguíamos com o Ardublockly. O texto do programa é todo em letras pretas.

Seguem abaixo fragmentos do programa onde houve alterações:

// Carro Automato usando controle remoto quando encontra obstaculo

// Declarações de dados relativas ao receptor infravermelho de comandos.
#include <IRremote.h> 

// Receptor IR ligado ao pino 11 do Arduino   
int RECV_PIN = 11;
  
unsigned long valor_da_tecla;  

const unsigned long tecla_0 = 0x1BC0157B;
const unsigned long tecla_1 = 0xC101E57B;
const unsigned long tecla_2 = 0x97483BFB;
const unsigned long tecla_3 = 0xF0C41643;
const unsigned long tecla_4 = 0x9716BE3F;
const unsigned long tecla_5 = 0x3D9AE3F7;
const unsigned long tecla_6 = 0x6182021B;
const unsigned long tecla_7 = 0x8C22657B;
const unsigned long tecla_8 = 0x488F3CBB;
const unsigned long tecla_9 = 0x449E79F;

const unsigned long tecla_ok        = 0xD7E84B1B;
const unsigned long tecla_para_cima = 0x511DBB;

IRrecv irrecv(RECV_PIN);  

decode_results results;    


// Código gerado automaticamente pelo Ardublockly
int velocidade;

.
.
.
// Fim do código gerado pelo Ardublockly

// Ajustes iniciais, que preparam as portas do Arduino e
// Atribuem valores a algumas variáveis.
void setup() {
  pinMode(A1, INPUT);   // Sensor IR da esquerda (seguidor de faixa)
  pinMode(A0, INPUT);   // Sensor IR da direita (seguidor de faixa)
  pinMode(A5, INPUT);   // Sensor de luz
  pinMode(2, OUTPUT);   // Pulso do ultrassom (sensor de distancia)
  pinMode(3, INPUT);    // Eco do ultrassom (sensor de distancia)
  pinMode(4, OUTPUT);   // Pinos de controle do motor
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(12, OUTPUT);  // Buzina
  pinMode(13, OUTPUT);  // Farol

  velocidade = 120;     // Velocidade do carro
  tempo_giro = 270;     // Tempo para girar 90 graus
  tempo_ajuste = 60;    // Tempo para girar 12 graus

  digitalWrite(2, LOW); // Prepara o pino de pulso do ultrassom.

  // Porta serial transmitindo em 9600 bits/segundo
  Serial.begin(9600);

  // Aguarda 1 segundo (1000 milissegundos) para Arduino se estabilizar.
  delay(1000);

  irrecv.enableIRIn();  // Ativa o receptor IR.
}


// Loop principal. Repetido ate o Arduino ser desligado.
void loop() {
  if (irrecv.decode(&results))    {
    // Recebeu algum comando no receptor IR
    // Imprime o valor no monitor serial do Arduino
    Serial.print("Valor lido : ");
    valor_da_tecla = (results.value);
    Serial.println(valor_da_tecla, HEX);
    // Executa o comando, se for para acender ou apagar o farol.
    if (valor_da_tecla == tecla_1) //Verifica se a tecla 1 foi acionada
      acender_farol();
    if (valor_da_tecla == tecla_2) //Verifica se a tecla 2 foi acionada
      apagar_farol();
     irrecv.resume(); //Le o próximo valor
  } 
  // Código anterior que tratava do farol, desativado (com comentários)
  // por enquanto
  // if (tem_luz()) {
  //   apagar_farol();
  // } else {
  //   acender_farol();
  // }
  if (distancia() < 30) {
    parar();
    delay(3000);
  } else if (faixa_a_direita()) {
    // Faixa dos dois lados é uma transversal.
    // Chegou ao destino!
    if (faixa_a_esquerda()) {
      parar();
      delay(3000);
    } else {
      ajuste_direita();
    }
  } else if (faixa_a_esquerda()) {
    ajuste_esquerda();
  } else {
    avancar();
  }
  // Pequena pausa antes de recomeçar o loop principal
  delay(200);
}

/* Fim do Arquivo */


Este código funcionou mal. Diversos comandos emitidos com o controle remoto eram perdidos, possivelmente pelo fato de o programa estar utilizando chamadas à função delay(). Acontece que o tratamento dos códigos de teclas recebidos tem que ser feito imediatamente (o que chamamos de 'em tempo real'). A função delay() deixa o Arduino inativo por longos períodos (para ele), e os dados são perdidos. Precisamos alterar esta lógica do loop principal.


Tratamento do receptor IR em tempo real

Para resolver o problema com o uso da função delay() no loop principal do programafizemos alterações para usar a função millis(), que devolve o número de milissegundos desde que o Arduino foi ligado (ou reiniciado). 

Controlamos as pausas com variáveis que guardam o instante de tempo em que algum evento ocorreu (tempo_parado, por exemplo), e guardamos a ação que deve ser realizada em uma variável booleana (acende_farol). Também precisamos saber se estamos em movimento ou parados. Esta pode ser considerada uma variação da técnica de programação chamada máquina de estados.

Seguem fragmentos de código com as alterações:

// Guarda tempo em que foi feito o último teste de condições.
unsigned long tempo_teste = 0;

// Guarda tempo em que parou os motores.
unsigned long tempo_parado = 0;

// Controla se os motores estão parados.
bool parado = false;

// Controla se farol deve estar aceso ou apagado.
bool acende_farol = false;

// Loop principal. Repetido ate o Arduino ser desligado.
void loop() {
  // Tem tecla apertada no controle remoto?
  if (irrecv.decode(&results))    {
    // Sim! Recebeu algum comando no receptor IR.
    valor_da_tecla = (results.value);
    // Trata o comando, se for para acender ou apagar o farol.
    // Verifica se a tecla 1 foi acionada.
    if (valor_da_tecla == tecla_1) {
      // Não acende o farol ainda! Apenas anota o que tem que ser feito.
      acende_farol = true;

    // Verifica se a tecla 2 foi acionada.
    } else if (valor_da_tecla == tecla_2) {
      acende_farol = false;
    } else {
      // Código sem tratamento! Apresentar no Monitor Serial.
      Serial.print("Valor lido : ");
      Serial.println(valor_da_tecla, HEX);
    }
    // Prepara para ler o próximo valor.
    irrecv.resume();
  } 
  
// Testes de estado a cada 80 milissegundos.
  if (millis() > tempo_teste + 80) {
    // O que está dentro desse bloco só é executado após um pequeno
    // intervalo, deixando mais tempo para o bloco anterior,
    // que trata os comandos recebidos no receptor IR em tempo real.
    tempo_teste = millis();

    // Acende farol, se necessário.
    if (acende_farol) {
      acender_farol();
    } else {
      apagar_farol();
    }
    // Se está parado, espera 3000 milissegundos sem fazer (quase) nada.
    if (parado) {
      // Passaram três segundos desde que parou?
      if (millis() > tempo_parado + 3000) {
        // Voltamos a nos movimentar
        parado = false;
      }
    } else {
      // Estamos em movimento...
      // Temos que evitar colisão com obstáculos a menos de 30 cm.
      if (distancia() < 30) {
        parar();
        // Guarda o estado de que está parado.
        parado = true;
        // Guarda o instante em que parou.
        tempo_parado = millis();
      } else {
        // Lógica do seguidor de linha
        if (faixa_a_direita()) {
          if (faixa_a_esquerda()) {
            // Faixa encontrada dos dois lados é sinal para parar.            
            parar();
            parado = true;
            tempo_parado = millis();
          } else {
            // Se encostamos na faixa pela direita, precisamos ajustar.
            ajuste_direita();
          }
        } else if (faixa_a_esquerda()) {
          // Se encostamos na faixa pela esquerda, precisamos ajustar.
          ajuste_esquerda();
        } else {
          // Estamos no caminho! Basta avançar.
          avancar();
        }
      }
    }
  }
}

/* Fim do Arquivo */

Com o código acima, o controle remoto passou a funcionar como esperado. Continuamos as alterações para adicionar comandos que ajudem o carro a sair de situações em que está bloqueado por algum obstáculo. Adicionamos os códigos de mais algumas teclas, e modificamos novamente a lógica do laço principal. Alteramos também a função voltar(), para que volte apenas uma pequena distância.

const unsigned long tecla_para_tras = 0xA3C8EDDB;
const unsigned long tecla_para_esquerda = 0x52A3D41F;
const unsigned long tecla_para_direita = 0x20FE4DBB;

const unsigned long tecla_nenhuma = 0x00;

.
.
.

// Tempo para voltar
const int tempo_voltar = 1000;

// Mover carro para trás.
void voltar() {
  direcao = -1;
  acionar_motores();
  delay(tempo_voltar);
  parar();
}

.
.
.

// Comandos para movimento do carro
unsigned long  comando_movimento = tecla_nenhuma;

// Loop principal. Repetido ate o Arduino ser desligado.
void loop() {
  // Tem tecla apertada no controle remoto?
  if (irrecv.decode(&results))    {
    // Sim! Recebeu algum comando no receptor IR.
    valor_da_tecla = (results.value);
    // Trata o comando, se for para acender ou apagar o farol.
    // Verifica se a tecla 1 foi acionada.
    if (valor_da_tecla == tecla_1) {
      // Não acende o farol ainda! Apenas anota o que tem que ser feito.
      acende_farol = true;

    // Verifica se a tecla 2 foi acionada.
    } else if (valor_da_tecla == tecla_2) {
      acende_farol = false;
    } else if (valor_da_tecla == tecla_para_esquerda) {
      comando_movimento = tecla_para_esquerda;
    } else if (valor_da_tecla == tecla_para_direita) {
      comando_movimento = tecla_para_direita;
    } else if (valor_da_tecla == tecla_para_tras) {
      comando_movimento = tecla_para_tras;

    } else if (valor_da_tecla == tecla_ok) {
      comando_movimento = tecla_ok;
    } else {
      // Código sem tratamento! Apresentar no Monitor Serial.
      Serial.print("Valor lido : ");
      Serial.println(valor_da_tecla, HEX);
    }
    // Prepara para ler o próximo valor.
    irrecv.resume();
  } 
  
  // Testes de estado a cada 80 milissegundos.
  if (millis() > tempo_teste + 80) {
    // O que está dentro desse bloco só é executado após um pequeno
    // intervalo, deixando mais tempo para o bloco anterior,
    // que trata os comandos recebidos no receptor IR em tempo real.
    tempo_teste = millis();

    // Acende farol, se necessário.
    if (acende_farol) {
      acender_farol();
    } else {
      apagar_farol();
    }
    // Se está parado, espera comandos para se desviar do obstáculo.
    if (parado) {
      if (comando_movimento == tecla_para_esquerda) {
        // Girar 90 graus a esquerda
        esquerda();
        comando_movimento = tecla_nenhuma;
      } else if (comando_movimento == tecla_para_direita) {
        // Girar 90 graus a direita
        direita();
        comando_movimento = tecla_nenhuma;
      } else if (comando_movimento == tecla_para_tras) {
        // Voltar
        voltar();
        comando_movimento = tecla_nenhuma;
      } else if (comando_movimento == tecla_ok) {
        // Reassumir o movimento.
        parado = false;
        comando_movimento = tecla_nenhuma;
    } else {
      // Estamos em movimento...
      // Temos que evitar colisão com obstáculos a menos de 30 cm.
      if (distancia() < 30) {
        parar();
        // Guarda o estado de que está parado.
        parado = true;
        // Guarda o instante em que parou.
        tempo_parado = millis();
      } else {
        // Lógica do seguidor de linha
        if (faixa_a_direita()) {
          if (faixa_a_esquerda()) {
            // Faixa encontrada dos dois lados é sinal para parar.            
            parar();
            parado = true;
            tempo_parado = millis();
          } else {
            // Se encostamos na faixa pela direita, precisamos ajustar.
            ajuste_direita();
          }
        } else if (faixa_a_esquerda()) {
          // Se encostamos na faixa pela esquerda, precisamos ajustar.
          ajuste_esquerda();
        } else {
          // Estamos no caminho! Basta avançar.
          avancar();
        }
      }
    }
  }
}

/* Fim do Arquivo */


Os novos comandos funcionaram como se esperava. Algumas vezes, falham, mas pode ser pela baixa qualidade do controle remoto. Um problema que não foi completamente resolvido foi a calibragem da velocidade e tempo de ajuste de direção. Se usamos uma velocidade mais baixa, para dar ao carro mais tempo, os motores travam. Com velocidade maior, os ajustes não são suficientes para manter o carro no caminho certo.


Esta foi a última tarefa realizada na oficina de Robótica II. Em um post final, vamos publicar o programa final completo, documentado e revisado.


Passar à Parte 12



Nenhum comentário:

Postar um comentário