Lego 9398 com iluminação e Arduino
Que tal pegar um kit da Lego bem legal e colocar luzes funcionais de farol, lanterna, setas, freios e luz de ré como um veículo real? Pois é bem isso do que se trata este projeto, apenas conseguir reproduzir em escala tudo aquilo que um veículo real faz. A princípio me pareceu fácil, mas confesso que consumiu muitas horas de montagem da eletrônica e outras tantas de programação para conseguir chegar a este resultado.
O Lego 9398
O Lego 9398 é um utilitário 4×4 para escalar terrenos difíceis. Tem grande curso em sua suspensão, diferencial nos eixos dianteiros e traseiros, motores independentes na tração e controle remoto sem fio. Apesar de ser um brinquedo, ele é muito bem feito e rende boas horas de diversão. Na sua configuração original tem controle remoto por infravermelho que até é interessante para ambientes internos, mas inviável para uso externo devido a luz solar. Você pode pesquisar na internet e encontrará vários vídeos sobre ele.
Conceito principal do projeto
Instalar luzes totalmente funcionais no carro e que ele tenha um sistema de controle remoto por rádio frequência ao invés de infravermelho. Para fazer o projeto ganhar vida vamos utilizar novamente o Arduino e o XBee.
Principais funções do nosso projeto
Acendimento automático dos faróis e lanternas (sensor crepuscular)
Acionamento do farol baixo e das lanternas a qualquer momento pelo controle remoto
Acionamento do farol alto a qualquer momento pelo controle remoto
Setas de direção que funcionam automaticamente ao mudar de direção
Função pisca-alerta com desarme automático ao voltar a se movimentar
Luz de freio que se acende toda vez que o carro pára
Luz de ré que se acende quando ele vai para trás
Compatibilidade total com o nosso já conhecido Controle Remoto DualShock
Tenho certeza que vocês não esperavam por tudo isso…não é mesmo?
Primeiro desafio: deslocamento
Eleger uma alternativa para substituir o receptor IR original foi o primeiro desafio, porque ele tem toda a unidade de controle dos motores de tração e direção. Para a tração eu optei por uma ponte H L298N que é barata e faz bem a função de controlar a velocidade e direção dos motores. A L298N ainda permite alimentação direta da bateria de LiPo o que facilita bastante o projeto e prevê a saída de 5V para o Arduino.
Aqui uma dica de extrema importância. Como optei pelo Arduino FIO devido a receber o XBee sem necessidade de shield, temos de tomar cuidado porque o FIO trabalha com 3,3V. Assim coloquei dois diodos retificadores 1N400X em série com positivo para ter uma queda de tensão de 1,4V. Cada diodo tem uma queda de 0,7V resultando em 3,6V na saída que, com a carga de funcionamento, atende bem ao FIO sem riscos.
O ponto negativo é que tive de cortar os conectores originais do fio e por isso o processo não é tão fácil assim de reverter ao original
Segundo desafio: direção
Como já tinha usado este kit antes em outros projetos como o Garden Explorer tendo danificado o “servo motor” do conjunto de direção e aí tive de pensar em uma alternativa para identificar o ponto central da barra de direção, caso contrário não iria conseguir fazer o carro andar em linha reta.
Depois de algumas tentativas frustradas, a melhor solução foi utilizar um potenciômetro de micro servo com engrenagens quebradas. Fiz uma adaptação na barra de direção dianteira e li com um multímetro a posição central. Por fim, foi só ligar o potenciômetro em uma entrada analógica do Arduino e incluir a rotina no código principal.
Terceiro desafio: 4 funções em um mesmo Led
O utilitário tem apenas uma luz de cada lado na parte de trás e eu decidi não mudar o desenho da traseira. Esta decisão foi a razão de muitas horas de pensamento para resolver o seguinte desafio: como fazer um único led ficar apagado, acender fraco, acender forte e piscar em diferentes momentos.
Com certeza vocês devem estar pensando que isso é fácil, mas na prática definir o momento de acender a lanterna, luz de freio e pisca durante a seta é um grande desafio. Depois de definir as prioridades e ajustar a lógica do código tudo deu certo e tenho certeza que foi um dos maiores prazeres que já tive com meus projetos.
A luz de ré foi simples e não exigiu nenhuma habilidade extra para seu funcionamento.
Quarto desafio: Piscar o Led sem usar “delay”
A grande maioria dos iniciantes em Arduino começa piscando um led, normalmente o da placa que fica no pino 13. Para isso alterna entre ligado e desligado utilizando um atraso através do comando Delay. O ponto negativo é a “parada” para a execução do programa durante o tempo definido e isto é inviável para um veículo que está se movimentando por controle remoto. Imagine você chegando na ponta de uma escada e o comando de parar não estar sendo aceito porque o código está aguardando o tempo durante um Delay…
Nestas horas é que você entende a importância de um programa livre e da importância de compartilhar conhecimento. Consultando na internet vi alguns códigos que utilizavam o piscar do Led com a variável tempo lendo a função millis
Quinto desafio: Instalação dos Leds
As peças utilizadas pela Lego no modelo para as luzes são translúcidas o que permitiu utilizar Leds de 3mm que se encaixam perfeitamente em seu interior. Como hoje temos Leds de alto brilho deste tamanho e com invólucro incolor fez com que a montagem tivesse um bom acabamento.
Entretanto, passar o nosso “chicote” por entre a carroceria é uma tarefa trabalhosa que me consumiu um sábado inteiro. Para o conjunto ótico dianteiro preparei uma placa de circuito impresso universal onde instalei os leds, resistores e um transistor BC557. O transistor foi utilizado como chaveador para os 4 leds ligados em paralelo e desta forma sem comprometer a porta digital do Arduino.
No centro da capota foi instalado o LDR para ler a luminosidade do ambiente e ser os “olhos” do nosso sensor crepuscular. Também foi instalado em uma placa de circuito impresso universal bem pequena e fixada com uns pingos de cola quente.
Na traseira temos os 3 leds divididos em 2 para lanternas e 1 para luz de ré. Os fios foram passados e se juntaram aos vindos da frente, do LDR e da alimentação vindos da ponte H. Todos eles foram ligados a um conector e depois no Arduino para permitir a retirada do Arduino sem ter de desfazer a instalação elétrica. A ligação do acionamento dos motores do Arduino a ponte H também foi feita através de conectores.
Para facilitar foi colocada uma chave para ligar e desligar o positivo da bateria até a ponte H. Assim tudo fica muito mais simples do que ter de plugar e desplugar o conector da bateria
O produto final
Depois de tanta transpiração esta versão hi-tech do Lego 9398 ficou show! Manteve as características de mobilidade da versão original com toda a funcionalidade da iluminação e facilidade de controle através de rádio frequência.
Estou preparando o vídeo dele que será colocado aqui, mas enquanto não fica pronto você pode curtir um pouco mais através de outras fotos dele.
O código
Estou colocando abaixo o código utilizado no programa para vocês poderem analisar e utilizar. Quero apenas lembrar que, devido possível diferença de plataformas e browsers, alguns caracteres podem ficar diferentes durante o processo de copiar-colar. Assim cuidado com as aspas e outros carácteres especiais que poderão sair diferentes e causar erro na compilação.
/*
*******************************************************
Programa de recepcao de comandos Via XBee – DualShock
Acionador de motores com Ponte H
Autor Magneto – Outubro 2016
*******************************************************
*/
// *** Definicao dos pinos para movimento e direcao ***
const int mB1 = 4; // sentido do Motor B – deslocamento
const int mB2 = 6; // sentido do Motor B – deslocamento
const int mA1 = 8; // sentido do Motor A – de direcao
const int mA2 = 7; // sentido do Motor A – de direcao
const int vMA = 9; // velocidades do Motor A – PWM
const int vMB = 5; // velocidades dos Motor B – PWM
const int sensorCentro = 2; // sensor de centro do motor de direcao
const int ledCentro = 13; // led indicador do centro do motor de direcao
const int pot1 = A0; // potenciometro de direcao
// *** Definicao das variaveis para movimento e direcao ***
int cEnergyMA = 6; // coeficiente de energia dado pelo range do driver do motor A (0 a 255)
int cEnergyMB = 8; // coeficiente de energia dado pelo range do driver do motor B (0 a 255)
int cEnergyCentro = 50; // coeficiente de energia de retorno ao centro
int scaleJoy2 = 0; // escala de deslocamento
int scaleJoy3 = 0; // escala de direcao
byte trimJoy2 = 98; // trimagem do joystick 2 conforme programa de teste do TX
byte trimJoy3 = 157; // trimagem do joystick 3 conforme programa de teste do TX
byte motorCentral = 0; // posicao central do motor de direcao
int valorDirecao = 0; // valor do potenciometro da direcao
// *** Definicao dos pinos para luzes ***
const int frenteEsquerda = 12; // seta da frente do lado esquerdo
const int frenteDireita = A5; // seta da frente do lado direito
const int farois = 3; // farois
const int atrasEsquerda = 10; // luz traseira do lado esquerdo
const int atrasDireita = 11; // luz traseira do lado direito
const int re = A3; // luz de re
const int ldr = A4; // sensor crepuscular
// *** Definicao das variaveis para luzes ***
int valorLDR = 0; // valor do LDR
int gatilhoLDR = 250; // ponto onde o sensor crepuscular eh ativado
int gatilhoLDR2 = 90; // ponto onde o sensor crepuscular eh ativado
byte luzesAuto = 0; // sensor crepuscular ativado
byte lanterna = 0; // luzes de lanterna e farol baixo
byte farolAlto = 0; // farol alto
byte farolBaixo = 0; // farol baixo
byte luzRe = 0; // luz de re
byte piscaAlerta = 0; // pisca alerta
byte freio = 0; // freio para as luzes traseiras
byte piscaDireito = 0; // pisca do lado direito
byte piscaEsquerdo = 0; // pisca do lado esquerdo
byte flipFarol = 0; // alterna entre farol alto e baixo ou desligado
byte valorBaixo = 50; // PWM para farol baixo e lanterna
byte ctrlSeta = 0; // controle da ativacao das setas
byte ctrlFreio = 0; // controle da ativacao do freio
byte ctrlFarolAlto = 0; // controle da ativacao do farol Alto
byte ctrlFarolBaixo = 0; // controle da ativacao do farol Baixo
// *** Definicao de outras variaveis ***
int incomingByte; // Variavel de entrada de dados pela Serial
int valorFrente = LOW; // ultimo valor das setas frontais
int valorAtras = 0; // ultimo valor das setas de tras
long previousMillis = 0; // will store last time LED was updated
long interval = 100; // interval at which to blink (milliseconds)
void setup() {
// inicializa Serial
Serial.begin(9600);
// Inicializa pinos
pinMode(mB1,OUTPUT);
pinMode(mB2,OUTPUT);
pinMode(mA1,OUTPUT);
pinMode(mA2,OUTPUT);
pinMode(vMA,OUTPUT);
pinMode(vMB,OUTPUT);
pinMode(sensorCentro,INPUT);
pinMode(frenteEsquerda,OUTPUT);
pinMode(frenteDireita,OUTPUT);
pinMode(farois,OUTPUT);
pinMode(atrasEsquerda,OUTPUT);
pinMode(atrasDireita,OUTPUT);
pinMode(re,OUTPUT);
pinMode(ledCentro,OUTPUT);
}
void loop() {
// Lendo os valores dos sensores de direcao e luzes
motorCentral = digitalRead(sensorCentro);
valorDirecao = analogRead(pot1);
valorLDR = analogRead(ldr);
if(motorCentral == 1)
digitalWrite(ledCentro, HIGH);
else digitalWrite(ledCentro, LOW);
if(valorLDR < gatilhoLDR)
luzesAuto = 1;
else luzesAuto = 0;
// Rotinas de setas de direcao
if(piscaAlerta == 1) {
ligaPiscaAlerta();
ctrlSeta = 1;
}
else if(piscaAlerta == 0 && piscaDireito == 1) {
ligaPiscaDireito();
ctrlSeta = 1;
}
else if(piscaAlerta == 0 && piscaEsquerdo == 1) {
ligaPiscaEsquerdo();
ctrlSeta = 1;
}
// Rotina de Freio
if(freio == 1 && ctrlSeta == 0)
acendeFreio();
else if(freio == 0 && lanterna == 0 && ctrlSeta == 0)
apagaFreio();
else if(freio == 0 && lanterna == 1 && ctrlSeta == 0)
apagaFreio1();
// Rotina de Farol Alto
if(farolAlto == 1)
acendeFarolAlto();
else if(farolAlto == 0 && farolBaixo == 0 && luzesAuto == 0)
apagaFarolAlto();
else if(farolAlto == 0 && farolBaixo == 1 && luzesAuto == 1)
apagaFarolAlto1();
else if(farolAlto == 0 && farolBaixo == 0 && luzesAuto == 1)
apagaFarolAlto1();
// Rotina de Farol Baixo
if(farolBaixo == 1 && farolAlto == 0)
acendeFarolBaixo();
else if(farolBaixo == 0 && farolAlto == 0 && luzesAuto == 1)
acendeFarolBaixo();
else if(farolBaixo == 0 && farolAlto == 0 && luzesAuto == 0)
apagaFarolBaixo();
// Rotina de Lanterna
if(lanterna == 1 && ctrlSeta == 0 && freio == 0)
acendelanterna();
else if(lanterna == 0 && ctrlSeta == 0 && freio == 0 && luzesAuto == 1)
acendelanterna();
else if(lanterna == 0 && ctrlSeta == 0 && freio == 0 && luzesAuto == 0)
apagalanterna();
// Rotina luz de re
if(luzRe == 1)
acendeRe();
else if(luzRe == 0)
apagaRe();
// Restabelece os controles ao nivel inicial
ctrlSeta = 0;
luzesAuto = 0;
// Verificando se tem entrada de dados na Serial
if (Serial.available() > 0) {
incomingByte = Serial.read(); // Lendo o ultimo dado recebido
// Identificar J2UpDown – Joystick da Esquerda
if(incomingByte >= 68 && incomingByte <= 127) {
scaleJoy2 = (incomingByte – trimJoy2);
if(scaleJoy2 > 0) {
digitalWrite(mB1,HIGH);
digitalWrite(mB2,LOW);
freio = 0;
luzRe = 1;
piscaAlerta = 0;
}
else if(scaleJoy2 < 0) {
scaleJoy2 = scaleJoy2 * -1;
digitalWrite(mB1,LOW);
digitalWrite(mB2,HIGH);
freio = 0;
luzRe = 0;
piscaAlerta = 0;
}
else if(scaleJoy2 == 0) {
freio = 1;
luzRe = 0;
}
analogWrite(vMB, scaleJoy2 * cEnergyMB);
}
// Identificar J3LeftRight – joystick da Direita
if(incomingByte >= 128 && incomingByte <= 187) {
scaleJoy3 = (incomingByte – trimJoy3);
if(scaleJoy3 > 0) {
digitalWrite(mA1,LOW);
digitalWrite(mA2,HIGH);
piscaDireito = 1;
}
else if(scaleJoy3 < 0) {
scaleJoy3 = scaleJoy3 * -1;
digitalWrite(mA1,HIGH);
digitalWrite(mA2,LOW);
piscaEsquerdo = 1;
}
else if(scaleJoy3 == 0) {
piscaDireito = 0;
piscaEsquerdo = 0;
}
analogWrite(vMA, scaleJoy3 * cEnergyMA);
// retorno automatico para centro da direita – corrigindo para a esquerda
if(scaleJoy3 == 0 && valorDirecao <= 547 && scaleJoy2 != 0 && motorCentral == 0) {
digitalWrite(mA1,HIGH);
digitalWrite(mA2,LOW);
analogWrite(vMA, cEnergyCentro);
}
// retorno automatico para centro da esquerda – corrigindo para a direita
if(scaleJoy3 == 0 && valorDirecao >= 577 && scaleJoy2 !=0 && motorCentral == 0) {
digitalWrite(mA1,LOW);
digitalWrite(mA2,HIGH);
analogWrite(vMA, cEnergyCentro);
}
}
// ligar farol Alto
if(incomingByte == 3)
farolAlto = 1;
// Desligar Farol Alto
if(incomingByte == 4)
farolAlto = 0;
// Ligar lanterna e farol baixo
if(incomingByte == 248) {
lanterna = 1;
farolBaixo = 1;
}
// Desligar lanterna, farol baixo, pisca alerta e alarme
if(incomingByte == 251) {
lanterna = 0;
farolBaixo = 0;
piscaAlerta = 0;
}
// Pisca Alerta
if(incomingByte == 250)
piscaAlerta = 1;
}
// temporizacao das setas
unsigned long currentMillis = millis();
if(currentMillis – previousMillis > interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (valorFrente == LOW && valorAtras == 0) {
valorFrente = HIGH;
valorAtras = 255;
}
else if(valorFrente == HIGH && valorAtras == 255) {
valorFrente = LOW;
valorAtras = 0;
}
if(piscaEsquerdo == 0 && piscaDireito == 0 && piscaAlerta == 0) {
valorFrente = LOW;
valorAtras = 0;
ligaPiscaEsquerdo();
ligaPiscaDireito();
}
}
//Debug das informacoes do motor
//Serial.println(incomingByte);
//Serial.print(“Sensor de centro da direcao: “);
//Serial.println(motorCentral);
//Serial.print(“Valor do Potenciometro: “);
//Serial.println(valorDirecao);
//Serial.print(“Valor LDR: “);
//Serial.println(valorLDR);
//delay(500);
}
// *** Rotinas para as luzes ***
void ligaPiscaEsquerdo() {
digitalWrite(frenteEsquerda, valorFrente);
analogWrite(atrasEsquerda, valorAtras);
}
void ligaPiscaDireito() {
digitalWrite(frenteDireita, valorFrente);
analogWrite(atrasDireita, valorAtras);
}
void ligaPiscaAlerta() {
digitalWrite(frenteEsquerda, valorFrente);
analogWrite(atrasEsquerda, valorAtras);
digitalWrite(frenteDireita, valorFrente);
analogWrite(atrasDireita, valorAtras);
}
void acendeFreio() {
analogWrite(atrasEsquerda, 255);
analogWrite(atrasDireita, 255);
}
void apagaFreio() {
analogWrite(atrasEsquerda, 0);
analogWrite(atrasDireita, 0);
}
void apagaFreio1() {
analogWrite(atrasEsquerda, valorBaixo);
analogWrite(atrasDireita, valorBaixo);
}
void acendeFarolAlto() {
analogWrite(farois, 255);
}
void apagaFarolAlto() {
analogWrite(farois, 0);
}
void apagaFarolAlto1() {
analogWrite(farois, valorBaixo);
}
void acendeFarolBaixo() {
analogWrite(farois, valorBaixo);
}
void apagaFarolBaixo() {
analogWrite(farois, 0);
}
void acendelanterna() {
analogWrite(atrasEsquerda, valorBaixo);
analogWrite(atrasDireita, valorBaixo);
}
void apagalanterna() {
analogWrite(atrasEsquerda, 0);
analogWrite(atrasDireita, 0);
}
void acendeRe() {
digitalWrite(re, HIGH);
}
void apagaRe() {
digitalWrite(re, LOW);
}
/* Disposicao dos botoes e comandos
j1LeftRight = 8 a 67
j2UpDown = 68 a 127
j3LeftRight = 128 a 187
j4UpDown = 188 a 247
joyBLeft = 1
joyBRight = 2
frontTopLeft = 3
frontBotLeft = 4
frontTopRight = 5
frontBotRight = 6
upperLeft = 248;
upperRight = 249;
upperTop = 250;
upperDown = 251;
Potenciometro de direcao
Esquerda: 720/759
Direita: 345/377
Centro: 548/576
*****************************
** Limites dos Joysticks **
** J1 Left: 8 **
** J1 Center: 36 **
** J1 Right: 67 **
** **
** J2 Up: 68 **
** J2 Center: 98 **
** J2 Down: 127 **
** **
** J3 Left: 128 **
** J3 Center: 157 **
** J3 Right: 187 **
** **
** J4 Up: 188 **
** J4 Center: 217 **
** J4 Dowm: 247 **
*****************************
*/