Será mostrado neste tutorial como montar um carrinho com um Arduino Uno e um Wiimote que segue 2 leds infravermelhos. A partir da leitura feita pelo controle, as coordenadas dos leds são enviadas para o computador que interpreta e envia instruções para o Arduino, acoplado ao carrinho, movendo-o para frente, para trás ou para os lados.
Este projeto foi desenvolvido baseado no sistema operacional Ubuntu 14.04 mas o mesmo deveria funcionar tanto no Windows quanto no Mac OS X, feitas as devidas modificações nas instalações do protocolo de Bluetooth instalado (já que o BlueZ suporta apenas distribuições Linux) .
1 Hardware necessário
- Computador com sistema operacional Linux e dispositivo bluetooth
- Wiimote
- 2 pilhas AA
- 2 módulos led infravermelho
- Arduino uno
- Shield Motor L293D
- Módulo Bluetooth RS232 HC-05
- Kit chassi 4 rodas com 4 motores
- Fonte 5v 2000mA
Para montagem do carrinho, utiliza-se o Shield Motor para Arduino e o Arduino acoplados ao chassi. Os pinos dos 4 motores das rodas e do Módulo Bluetooth no Shield são indicados nas imagens abaixo
Shield Motor
Módulo Bluetooth
A placa com os LEDs é bem simples, somente observando o distanciamento entre os leds que influenciam no valor da distância que o Wiimote captura. Pois a distância do Wiimote pra os LEDs é calculada com base na distância entre os dois leds e quanto mais próximos os leds, mais longe o sensor vai achar que os leds estão.
2 Configuração dos softwares necessários
Para a execução deste projeto é necessária a instalação de:
- BlueZ – Implementação do stack de protocolo Bluetooth para Linux
- WiiUse – API para conexão com o wiimote
- arduino-serial – API para comunicação com o arduino via portas seriais
2.1 Instalando o BlueZ
A instalação do BlueZ pode ser feita a partir dos arquivos disponibilizados na sua página ou através dos pacotes disponibilizados pela própria distribuição, no Ubuntu 14.04 isso pode ser feito através do comando
sudo apt-get install bluez python-gobject python-dbus
ou através de algum frontend para o apt como o Synaptic
2.2 Instalando o WiiUse
Tanto os arquivos necessários quanto as instruções para instalação podem ser obtidos do seu repositório no GitHub. Em um sistema Ubuntu a instalação dos arquivos pode ser feita através do terminal desta maneira
git clone https://github.com/rpavlik/wiiuse.git
cd wiiuse
mkdir build
cd build
Para montar os arquivos pode ser utilizado tanto o ccmake quanto o cmake-gui
Utilizando o ccmake
ccmake ..
- Na próxima tela, pressione ‘c’ para configurar a build
- Caso desejar, altere as configurações do que ser instalado
- Para executar a build pressione ‘c’ e em seguida ‘g’ para gerar a build e sair
Para compilar todos os arquivos utilize o comando
make
Feito isso, poderá ser encontrado dentro do diretório build/example um programa de exemplo (wiiuseexample) enquanto no diretório build/src pode ser encontrado o arquivo libwiiuse.so que deve ser incluido/linkado ao projeto
2.3 Instalando o arduino-serial
Para instalar a biblioteca arduino-serial-lib utilize os comandos
git clone https://github.com/todbot/arduino-serial.git
cd arduino-serial
make
Executados estes comandos, no diretório atual poderão ser encontrados os códigos-fonte e compilados dos seguintes arquivos:
- arduino-serial — Programa com exemplo de uso da biblioteca
- arduino-serial-lib – Código da biblioteca que deve ser incluido no projeto
2.4 Pareando o bluetooth do carrinho com o computador
Durante a execução deste projeto, o sensor bluetooth só pode ser conectado após a remoção do pacote brltty, utilizado para a funcionalidade de leitura de tela (display “braile”), leia este post sobre o assunto
Primeiro verifique a existência de dispositivos pareados procurando por arquivos de dispositivos no diretório /dev
utilizando
ls /dev | grep 'rfcomm'
ou apenas digitando ls /dev/rf
, pressionando TAB
algumas vezes e observar o resultado do auto-complete.
Para fazer o ‘bind’ do device, primeiramente encontre o endereço do dispositivo a partir do terminal fazendo
hcitool scan
Caso encontrado o dispositivo de nome HC-05
, copie o endereço e execute o seguinte comando:
sudo rfcomm bind <numero_do_dispositivo> <endereco_do_dispositivo>
Supondo que nenhum arquivo de dispositivo rfcomm* tenha sido encontrado em /dev
sudo rfcomm bind 0 <endereco_do_dispositivo>
Caso este dispositivo tenha sido configurado, ele deverá aparecer ao se executar
ls /dev | grep 'rfcomm'
3 Implementação do código
Nesta seção serão explicadas as partes importantes da lógica do código do rastreamento e envio de comandos para movimentação, para obter o código-fonte completo incluindo o código do arduino ver seção 4.
Para referência, a documentação da API pode ser encontrada aqui.
3.1 Organização geral do código
O código do programa se divide entre três arquivos
- main.c — Inicia conexão com wiimotes, bluetooth e escuta eventos do wiimote.
- wiimote-handler.c — Lida com os eventos do wiimote, i.e., informações sobre o sensor IR e botões.
- robot-controller.c — Lida com as requisições para movimentação do carrinho.
3.2 Conexão dos dipositivos
A conexão com o wiimote e com o bluetooth do carrinho funciona da seguinte forma:
- Procure por wiimotes por 5 segundos.
- caso nenhum tenha sido encontrado, imprimir mensagem de erro e sair.
- Conecte-se com todos os wiimotes.
- caso nenhum wiimote tenha se conectado, imprimir mensagem de erro e sair.
- Configure wiimotes (leds, IR e acelerômetro).
- Conecte-se a porta serial do bluetooth do carrinho.
- caso não seja possível encontrar o dispositivo, imprimir mensagem de erro e sair.
- Enquanto houver wiimotes conectados, escutar eventos.
- caso não houver wiimotes conectados, imprimir mensagem de encerramento e sair.
Sabendo que
typedef enum {
MAX_WIIMOTES = 4,
SEARCH_TIMEOUT_SECONDS = 5
} WiiuseConstants;
No código de main.c podemos ver como funciona a lógica da conexão com os wiimotes:
wiimote** wiimotes;
int found, connected, fd;
/* Inicializar array de wiimotes */
wiimotes = wiiuse_init(MAX_WIIMOTES);
found = wiiuse_find(wiimotes, MAX_WIIMOTES, SEARCH_TIMEOUT_SECONDS);
if (!found) {
printf("Nenhum Wiimote encontrado!\n");
return 0;
}
/* Conectar wiimotes */
connected = wiiuse_connect(wiimotes, MAX_WIIMOTES);
if (connected) {
printf("Conectado a %d Wiimotes de um total de %d encontrados.\n",
connected, found);
setup_wiimote(wiimotes);
} else {
printf("Conexão com Wiimotes falhou!\n");
return 0;
}
E como funciona a lógica da conexão com o bluetooth do arduino:
/* iniciar conexão com o arduino */
fd = connect_to_robot();
if (fd == -1) {
printf("--A conexão com o arduino falhou!--\n");
return 0;
} else {
printf("--Conectado ao arduino--\n");
}
3.3 Escuta de eventos do wiimote
A escuta de eventos do wiimote funciona dentro de um laço:
- Verifique se existem wiimotes conectados
- caso não existam, saia do laço e encerre a conexão com a porta serial.
- Para cada um dos quatro possíveis wiimotes, observe o atributo ‘event’
- Caso ‘WIIUSE_EVENT’, execute ‘handle_event()’
- Caso ‘WIIUSE_STATUS’, execute ‘handle_status()’
- caso ‘WIIUSE_DISCONNECT’, execute ‘handle_execute()’
- volte ao primeiro passo
Ainda em main.c podemos ver como funciona este código:
/* enquanto existirem wiimotes conectados escutar eventos */
while (has_wiimote_connections(wiimotes, MAX_WIIMOTES)) {
if (wiiuse_poll(wiimotes, MAX_WIIMOTES)) {
int i;
/* Analisar cada um dos quatro slots */
for (i = 0; i < MAX_WIIMOTES; i++) {
switch (wiimotes[i]->event) {
/* eventos - botões, sensores de movimento ou IR*/
case WIIUSE_EVENT:
handle_event(wiimotes[i]);
break;
/* status - informações do dispositivo */
case WIIUSE_STATUS:
handle_status(wiimotes[i]);
break;
/* desconexão de dispositivo */
case WIIUSE_DISCONNECT:
handle_disconnection(wiimotes[i]);
default:
break;
}
}
}
}
/* fechar conexão bluetooth com o arduino */
disconnect();
return 0;
3.4 Rastreamento
O sistema de rastreamento dos LEDs funciona através de:
- Caso os LEDs se encontrem à esquerda, envie sinal rodar para a direita.
- Caso os LEDs se encontrem à direita, envie sinal rodar para a direita.
- Caso mais de um led estejam visíveis, faça:
- Se a distância z for menor que um certo valor, envie sinal mover para frente.
- Se a distância z for maior que um certo valor, envie sinal mover para trás
Em wiimote-handler.c podemos ver a implementação do código nesta lógica:
/**
* Caso o sensor IR esteja habilitado, interpreta os sinais do sensor e
* envia requisições para a movimentação do carro.
*/
if (WIIUSE_USING_IR(wm)) {
int i, visible_dot_count;
/**
* Margem de erro para alinhamento do carro, 20% para mais ou para menos
* do centro da tela, o valor '1023' é baseado na resolução do sensor IR
* do wiimote que é de 1024 por 768.
*/
float center_tolerance = 0.2;
float left_limit = 1023 * (0.5 - center_tolerance);
float right_limit = 1023 * (0.5 + center_tolerance);
/**
* Como a distâancia no eixo z (entre o wiimote e o ponto de referencia
* entre os leds) varia de acordo com a distância entre os leds, os
* valores 700 e 800 foram escolhidos arbitrariamente para que o 'zero'
* do carro se mantivesse a uma distância específica dos leds.
*/
int lower_distance_limit = 700;
int upper_distance_limit = 800;
/**
* Visita cada um dos 4 possíveis leds e imprime suas coordenadas x e y
* caso estejam visiveis.
*/
for (i = 0; i < 4; i++) {
if (wm->ir.dot[i].visible) {
printf("\nIR SOURCE %d: (rx: %d, ry: %d)\n",
i, wm->ir.dot[i].rx, wm->ir.dot[i].ry);
}
}
/**
* Executa correção de posicionamento apenas se existirem um ou mais
* leds visíveis.
*/
if (wm->ir.num_dots > 0) {
printf("IR cursor(a): (ax: %d, ay: %d)\n", wm->ir.ax, wm->ir.ay);
printf("IR cursor(i): ( x: %d, y: %d)\n", wm->ir.x, wm->ir.y);
printf("IR z-distance: %f\n", wm->ir.z);
/**
* Correção do alinhamento (eixo x).
*/
if (wm->ir.ax > right_limit) {
signal_right();
} else if (wm->ir.ax < left_limit) {
signal_left();
} else {
printf("MEIO\n");
/**
* Correção da distância (eixo z), necessita de pelo menos dois
* pontos de IR visíveis para o cálculo da distância.
*/
if (wm->ir.num_dots > 1) {
if (wm->ir.z < 700) {
signal_back();
} else if (wm->ir.z > 800) {
signal_forward();
}
}
}
} //Captura de um ou mais pontos
} //Interpretação do sinal IR
3.5 Envio de requisições para movimentação
Como a lógica para o rastreamento envia requisições tão rapidamente quanto o código puder ser executado, é necessário um meio para que a quantidade de requisições enviadas ao arduino não ultrapasse a sua taxa de execução, visto que essa é limitada pelo tempo de operação da movimentação dos motores, abaixo vemos como funciona esta lógica:
- Pegue o tempo de execução atual.
- Subtraia do tempo atual o tempo quando a última requisição foi enviada.
- Caso o intervalo de tempo do resultado seja superior ao limite estabelecido faça:
- Atualizar o tempo da última requisição para o tempo atual.
- Envie a requisição para movimentação.
- Imprima uma mensagem com a direção do movimento.
- Caso o intervalo seja inferior, apenas imprima uma mensagem com a direção do movimento.
- Caso o intervalo de tempo do resultado seja superior ao limite estabelecido faça:
Abaixo vemos o código para a verificação do tempo:
int has_enough_time(void)
{
clock_t now;
now = clock();
if ( ((double)(now - last_sent)) > limit ) {
last_sent = now;
return 1;
}
return 0;
}
E como essa verificação é utilizada no envio da requisição de movimentação para frente:
/**
* \brief Envia comando para mover para frente.
*
* Para fim de testes, caso não seja possível se utilizar a conexão com o
* bluetooth é possível imprimir no console a mensagem '<direcao>-E'
*/
void signal_forward()
{
if (fd == ERROR_CODE)
printf("FRENTE-E\n");
else {
if (has_enough_time()) {
serialport_write(fd, "w");
printf("FRENTE\n");
}
}
}
3.6 Conexão da porta serial
O código para conexão com a porta serial do bluetooth se encontra na sub-rotina connect_to_robot()
em robot-controller.c
, note que o nome do arquivo de dispositivo utilizado /dev/rfcomm0 é o do dispositivo configurado em 2.4
/**
* \brief Inicia uma conexão com a porta serial.
* \returns fd válido ou -1 caso contrário.
*/
int connect_to_robot(void)
{
return fd = serialport_init("/dev/rfcomm0", 9600);
last_sent = clock();
}
4 Código-fonte do projeto
O código completo dos arquivos main.c
, wiimote-handler.c
, robot-controller.c
e test_motor.ino
podem ser obtidos no repositório https://github.com/daniloBlera/ir-tracking.
Vídeo