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