Bem-vindo!
Nesta publicação vamos aprender a programar e a controlar um PrintBot a partir de um terminal com a ajuda do Protocoder. Protocoder é uma ferramenta de prototipagem de aplicações móveis, que permite criar interfaces e programas rapidamente. Protocoder foi desenvolvido por Victor Díaz e podes encontrar informações ou descarregar o Protocoder aqui.
Utilizaremos o Protocoder como o interface que utilizaremos para comunicar com a placa ZUM BT-328 através do Bluetooth, e assim podermos mover o PrintBot Evolution livremente ou utilizar cada um dos modos automáticos: segue-linhas, segue-luz e esquiva-obstáculos.
Para controlar o PrintBot Evolution utilizaremos dois programas:
- Código Protocoder: armazenará a interface gráfica e as funções encarregadas de enviar os dados para a placa Zum através do Bluetooth do terminal.
- Código Zum BT-328: armazenará a programação do PrintBot Evolution, como por exemplo, os modos de funcionamento ou a comunicação Bluetooth.
Podes descarregar ambos os códigos no final desta entrada. Lembra-te que para executar o código do Protocoder, deverás ter instalado, o Protocoder no teu terminal (podes descarrega-lo aqui).
Código Protocoder
Para ver e modificar o código do Protocoder, em primeiro lugar, acede à aplicação Protocoder a partir do teu terminal e introduz o endereço IP que aparece na parte inferior da janela do navegador web do teu computador. Descarrega o código no final desta entrada e passa o arquivo printbot_evolution_protocoder.proto para o teu terminal. No teu terminal, com um explorador de arquivos, abre a pasta onde guardaste o arquivo (normalmente está em downloads), clica sobre o código do Protocoder e aparecerá um diálogo. Selecciona open with Protocoder (abrir com Protocoder). Vai abrir-se uma janela. Selecciona a opção Install (instalar). Agora já podes utilizar e modificar o código.
Uma vez que tenhas importado o arquivo .proto, verás o projecto na aba do navegador MY PROJECTS. Clica sobre o ícone e verás o código. Dentro do código podemos distinguir varias partes:
- Criar botão de texto: cria um botão com texto dentro dele. É útil para dar mais informação sobre o uso que se tem do botão. Para cria-los, utilizamos um código como este:
123var btnSiguelineas = ui.addButton("Sigue lineas",240, 350, w, h).onClick(function(){ // Criamos o botão. Parâmetros: ("texto a mostrar",posição X, posição Y, largura, altura)send("siguelineas"); // Passamos como parâmetro à função <em>send</em> (enviar em inglês) a palavra <em>siguelineas</em> (segue-linhas em espanhol), que será recebida completamente no Arduino.});
Dentro do código, com var btnSiguelinas estamos a declarar o botão. Teremos que dar um nome distinto a cada botão para que não hajam conflitos. Depois, com ui.addButton dizemos que a variável btnSiguelineas será um botão. Entre parêntesis escrevemos, por ordem, o texto que aparecerá no botão, a posição em X e Y, a largura e a altura do botão. Depois indicamos a função que se activará ao pressionar o botão. Neste caso, envia por Bluetooth a palavra “siguelinas”. (segue-linhas)
- Criar botão com imagem
123var btn180 = ui.addImageButton(x+2*w, y+3*w, w, w, "180.png",false).onClick(function(){ // Criamos um botão com imagem. Parâmetros: (posição X,posição Y,altura,"endereço da imagem",verdadeiro/falso)send("180");});
Com var btn180 estamos a declarar o botão, mas não como no anterior. Neste caso, utilizamos uma imagem em vez de um texto. Depois, com ui.addImageButton dizemos que a variável btn180 será um botão de imagem. Entre parêntesis escrevemos, por ordem, a posição em X e em Y, a largura e a altura do botão e o nome da imagem que queremos que apareça no botão. Depois indicamos a função que se accionará ao pressionarmos o botão (neste caso, envia por Bluetooth a palavra 180). Para adicionar uma imagem ou qualquer outro arquivo, basta arrastar o ficheiro do explorador de ficheiros do teu computador até ao quadro inferior direito. Lembra-te que quanto mais simples for o nome, mais fácil será introduzi-lo no código sem erros. - Criar botões para o Dashboard: Para inclui-los, fazemos do mesmo modo que os botões para o dispositivo, mas em vez de utilizar o prefixo ui, utilizamos o prefixo dashboard. Por defeito, o Dashboard não aparecerá, mas se quiseres que apareça, modifica a línha dashboard.show(false) para dashboard.show(true);.
- Funcões para conexão e desconexão do Bluetooth: Estas são as funções incluídas dentro das variables btnConectar e btnDesconectar. Vamos também, criar uma variável com o nome btClient, que utilizaremos para “interagir” através do Bluetooth.
- Conectar: Para conectar, em primeiro lugar criamos um botão com o texto “Conectar Bluetooth” e dentro da função que se activará ao pressionarmos o botão, incluímos a conexão para Bluetooth.
12345678910111213var btnConectar = ui.addButton("Conectar bluetooth", 240, 150,300,100).onClick(function() { // Criamos o botão do bluetoothbtClient = network.bluetooth.connectSerial(function(status) { // Conectamos o btClient -- Mostrará uma lista dos dispositivos emparelhados previamenteconsole.log("connected " + status); // Debugif (status === true){ // Se estiver conectadoconsole.log(status); // Guardamos o estadobluetoothOn = 1; // Alteramos o estado da variável para <em>conectado (1)</em>ui.jump(btnConectar); // Fazemos com que o botão <em>salte</em>ui.alpha(btnConectar, 0); // Tornamos o botão completamente transparentemedia.playSound("Drago"); // Reproduzimos o som armazenado previamente.device.vibrate(750); // Fazemos com que vibre o dispositivo}});});
Dentro desta função, além de conectarmos o Bluetooth, dizemos que a variável bluetoothOn (que utilizamos como “sinal” de conexão/desconexão para o bluetooth) tem o valor 1 e alteramos a transparência do botão para 0, que significa, completamente transparente. Vamos também incluir algumas animações, como jump e fazemos com que o dispositivo vibre quando se conectar. - Desconectar: Para desconectar, em primeiro lugar, criamos um botão com o texto Desconectar e dentro da função que se activará ao pressionarmos o botão, incluímos a desconexão para o Bluetooth.
123456var btnDesconectar = ui.addButton("Desconectar", 540, 150,200,100).onClick(function() { // Criamos o botão <em>Conectar</em>btClient.disconnect(); // Desconectamos se for pressionadobluetoothOn=0; // Alteramos a o estado da variável para <em>desconectado (0)</em>ui.alpha(btnConectar, 255); // Alteramos a transparência do botão para opacoui.jump(btnDesconectar); // Fazemos com que "salte" o botão <em>Desconectar</em>});
Dentro desta função, além de desconectarmos o Bluetooth, dizemos que a variable bluetoothOn (que utilizamos como “sinal” de conexão/desconexão do Bluetooth) terá o valor 0 e alteramos a transparência do botão para 255, que significa, opaco.
- Conectar: Para conectar, em primeiro lugar criamos um botão com o texto “Conectar Bluetooth” e dentro da função que se activará ao pressionarmos o botão, incluímos a conexão para Bluetooth.
- Função para enviar e receber dados por Bluetooth: A função para a transmissão de dados por Bluetooth é a seguinte:
123456function send(str) { // Função que utilizamos para enviar uma string (cadeia de caracteres)if (bluetoothOn == 1) { // Se o bluetooth estiver conectado, o código das seguintes linhas será executadobtClient.send("="+str+"+"); // Enviamos a cadeia de caracteres entre os símbolos = e +console.log("="+str+"+"); // Mostramos na consola, a cadeia de caracteres que estamos a enviar.}}
Esta função não possui nenhum botão, já que se chamará esta função cada vez que um botão for pressionado (excepto para conectar e desconectar o Bluetooth). Para enviar dados por Bluetooth, utilizamos o comando btClient.send(“=”+str+”+”). Esta função envia, por exemplo, um comando para mover o carro para a frente: =avanzar+ (avançar). Neste caso, realizámos a codificação desta maneira, mas podes experimentar com outro tipo de código. Utilizam-se os caracteres = e + como delimitadores do inicio e do final das transmissões.- Envio de dados por Bluetooth: Para enviar dados através do Bluetooth utilizamos a função send() com um texto entre aspas (por exemplo, se queremos enviar a palavra retroceder utilizaríamos: send(“retroceder”);
Código Arduino
Para ver e modificar o código do Arduino, descarrega o pacote no final desta entrada e pressiona sobre o arquivo printbot_evolution_arduino.ino. Se não tiveres instalado o IDE do Arduino ou outro IDE com o qual possas ler o arquivo, podes descarrega-lo visitando o Curso de programação para makers com Arduino e Protocoder, para veres diferentes IDEs e descarregares o teu preferido. Além disso, poderás aprender mais sobre o Arduino e o Protocoder. Uma vez o ficheiro aberto, é possível distinguir as várias partes do código dentro dele.
- Declaração de variáveis e inclusão de arquivos: Declaramos as diferentes variáveis que utilizaremos durante o programa:
1234567891011121314151617181920212223242526272829303132333435//Para controlar os servos, utilizamos o arquivo Servo.h#include "Servo.h"// Declaramos os servosServo servoR, servoL; // Servos dos motores direito e esquerdoServo servoBat; // Servo que controla o movimento do sensor Bat (ultrassons)// Declaramos as variáveis relativas ao sensor Bat// TP corresponde ao "trigger pin" (pino que envia o sinal de ultrassons)// e EP corresponde ao "Echo pin" (pino que recebe o sinal de ultrassons)// Medindo o tempo que passa desde que se envia, até que se recebe o sinal de ultrassons// podemos calcular a distância.int TP = 4, EP = 5;//Declaramos os pinos correspondentes aos sensores LDR (resistências dependentes da luz)int ldrIzq = A2, ldrDrch = A3; //pines ldr//Declaramos os pinos correspondentes aos sensores infravermelhosint irIzq = 2, irDrch = 3; // pinos infravermelhos//Tempos por defeito para os Delays.// Um delay é um atraso, ou seja, é um determinado período de tempo em que se pára a execução// do programa até terminar esse tempo.// Por exemplo, é utilizado aqui para conseguir que o PrintBot gire durante um certo tempo.int defaultDelay = 200, lineasDelay = 0;//Atrasos para conseguir que o PrintBot gire nos diferentes ângulos.//É possível que tenhas que modificar estes tempos para ajustá-los ao teu PrintBot.int giro_180 = 1028, giro_90 = 514, giro_45 = 257, giro_30 = 200;//Declaramos os pinos do buzzer (cigarra em Português)int pinBuz = 12;// A partir do Protocoder receberemos uma cadeia de caracteres (string)que utilizaremos para// determinar qual é o movimento ou modo de funcionamento do PrintBotString inString = "";
Dentro destas variáveis, utilizaremos algumas para dizer ao Arduino , por exemplo, em que pinos estão conectados as fichas, ou outras coisas como os delays, para modificar a execução do programa. Se os movimentos de rotação não se realizarem correctamente ou quiseres modifica-los para que girem noutros ângulos, podes modificar as variáveis giro_180, giro_90, giro_45 y giro_30. - Setup: Na função setup, uma das que somos obrigados a utilizar no Arduino (tal como a função loop) vamos iniciar o programa e os componentes que o compõem. O código da função setup é:
1234567891011121314151617181920212223242526272829void setup() {//Iniciamos a comunicação série. O Bluetooth da placa ZUM BT 328// trabalha a 19200, portanto se modificamos este valor, ela não funcionará.Serial.begin(19200);Serial.flush(); // Apagamos o conteúdo da porta sérieservoL.attach(6); // Dizemos ao Arduino que o servo Esquerdo (Left) está no pino 6servoR.attach(9); // Dizemos ao Arduino que o servo Direita (Right) está no pino 9servoBat.attach(11); // Dizemos ao Arduino que o servo que controla o sensor Bat (ultrassons) está no pino 11servoR.write(90); // Escrevemos o valor 90 graus no servo da direita (90 equivale a <em>parado</em>)servoL.write(90); // Escrevemos o valor 90 graus no servo da esquerda (90 equivale a <em>parado</em>)servoBat.write(90); // Colocamos o servo do sensor Bat no centro// Dizemos ao Arduino, quais os pinos que serão utilizados como entrada (INPUT) e como saída (OUTPUT)// Pinos do sensor BatpinMode(EP, INPUT);pinMode(TP, OUTPUT);//pinos ldrpinMode(ldrDrch, INPUT);pinMode(ldrIzq, INPUT);//pines infravermelhospinMode(irDrch, INPUT);pinMode(irIzq, INPUT);}
Dentro desta função, iniciamos a porta série a uma velocidade de 19200 bauds/s, que corresponde à velocidade a que funciona o módulo Bluetooth da placa ZUM BT-328.
A seguir, precisamos de: dizer onde está conectado cada servo, parar os servos e colocar o servo com o sensor Bat (sensor de ultrassons) na sua posição central. Por último, com o comando pinMode(pin,mode), definimos os diferentes pinos como entradas (INPUT) ou saídas (OUTPUT) conforme necessário. - Loop: Dentro da função loop (a outra função do Arduino que somos obrigados a utilizar), lidamos com a recepção de dados:
1234567void loop() {if (Serial.available() > 0) { // Se existirem dados na porta série (0 indica que existe)readFromAndroid(); // Chamamos a função <em>readFromAndroid</em> para ler os dados recebidos por Bluetooth.}writeData(); // Uma vez lidos os dados recebidos via Bluetooth, chamamos a função// <em>writeData</em> que "decidirá" que modo activar, em função dos dados recebidos.}
Dentro desta função, vemos o comando if (Serial.available() >0) que traduzido para linguagem “humana” seria: Se existirem dados na porta série, então prossegue com os seguintes comandos. Neste caso, as ordenes são chamar a función readFromAndroid().. Esta função encarrega-se de ler os dados enviados pela aplicação Protocoder. O outro comando que aparece no loop é writeData().. Esta função encarrega-se de activar os diferentes modos de controlo (tanto os manuais, como os externos), assim que os dados da função anterior tenham sido lidos. - readFromAndroid();:
12345678910111213void readFromAndroid() {char inChar; // Criamos uma variável que seja um caracterwhile (Serial.available() > 0) {//Realiza os comandos entre parêntesis, até que não hajam mais dados na porta série(bluetooth)// com o comando <em>While</em>inChar = (char) Serial.read(); // Armazena na variável <em>inChar</em>, os dados lidos através da porta sérieSerial.flush(); // apaga o conteúdo da porta sérieif (inChar == '=') // = é o caracter de final de envioinString = ""; // Se chegar até ao caracter final de envio,apaga os dados armazenados na variável <em>inString</em>else if (inChar != '+') // Se o caracter não for <strong>= </strong>ou +, então, concatena os caracteres lidos na variável <em>inString</em>inString += inChar;}}
Em vez desta função, poderíamos ter utilizado a função readString(), mas a leitura seria feita mais lentamente, causando que os comandos enviados do Protocoder (no nosso terminal) até ao PrintBot Evolution, chegassem com atraso.. - writeData();: Esta função está encarregue de controlar o fluxo do programa, lê os dados que foram recebidos na função readFromAndroid() e realiza um comando ou outro, conforme esses dados recebidos.
- Funções de movimento: Estas funções estão encarregues de mover o PrintBot Evolution. Estas funções são: avançar, recuar, parar, esquerda, direita, girar_180_graus, esquerdaLinhas e direitaLinhas. Podemos utilizar estas funções em qualquer momento: apenas temos que chama-las. Por exemplo, para utilizar a função girar_180_graus utilizamos o comando:
1girar_180_graus(); - Funções de modos: Estas funções usam-se para que o PrintBot Evolution entre nos modos automáticos.
- Segue linhas: Neste modo automático, utilizam-se os sensores infravermelhos para detectar se o sensor está em cima de uma linha. Neste modo utilizam-se as funções especiais de movimento, esquerdaLinhas e direitaLinhas que movem apenas uma roda, em vez das duas em direcções contrárias, para realizar o movimento.
- Esquiva obstáculos: Neste modo automático, utiliza-se o sensor de ultrassons para detectar objetos e evitar chocar contra eles. O código desta função é:
123456789101112131415161718192021222324252627282930313233343536373839void obstaculos() {// Os seguintes valores utilizam-se para determinar as posiciones// do sensor bat (através do servoBat).int izq = 140, drch = 50, cent = 90;// Confirma se existem obstáculos nas três posições. Se existir um obstáculo, pára.boolean obsIzq = buscaObstaculo(izq); //Chamamos a função, que devolve verdadeiro (true) se existir um obstáculoif (obsIzq) {parar();}boolean obsCent = buscaObstaculo(cent);if (obsCent) {parar();}boolean obsDrch = buscaObstaculo(drch);if (obsDrch) {parar();}// Realiza uma série de confirmações e move-se na direcção livre de obstáculos.if (!obsCent) { // Se não existir um obstáculo no centroavanzar();} else if (!obsDrch) { // Se não existir um obstáculo na direita e existir no centroderecha();delay(giro_45);parar();} else if (!obsIzq) { // Se não existir um obstáculo na esquerda e existir no centro e na direitaizquierda();delay(giro_45);parar();} else { // Se existir um obstáculo no centro, direita e esquerda, deve girar 180ºretroceder(); // Fazemos com que o printbot retroceda durante um certo tempodelay(giro_30); // Utilizamos o mesmo atraso (delay) que se utilizaría para girar 30ºgiro_180_grados(); // Por último, giramos o printbot 180 graus.}}
Dentro desta função, em primeiro lugar, encontramos as variables izq (esquerda),drch (direita) e cent (centro), que determinam a posição do miniservo que move o sensor de ultrassons. As posições têm que apontar à esquerda, direita e centro respectivamente, mas podes ajustar as posições a outras diferentes ou criar novas posições. A seguir, confirmamos se existem obstáculos na direcção em que aponta o servo, utilizando a função buscaObstaculos() para todas as posições, transmitindo o ângulo que o servo deve apontar, como informação. O código desta função é:
1234567891011121314151617181920212223boolean buscaObstaculo(int angulo) {// Utiliza-se esta função para procurar obstáculos.// Para utilizá-la, precisamos de transmitir as seguintes informaçõesint distancia_max = 25; // Distância máxima para determinar se o sinal se converte num obstáculo ou nãoint distancia; // Armazenaremos a distância lida aquiservoBat.write(angulo); //Movemos o servo para a posição de leitura que passámos como um argumentodelay(200); // Paramos a execução para dar ao servo, tempo de chegar.distancia = Distance(); // Guardamos a distância que devolve a função <em>Distance()</em> na variável <em>distancia</em>if (distancia != 0 && distancia < distancia_max) {// Se a distância não é 0 e a distância é menor que a distância máxima// fazemos soar um tom no buzzer e devolve true (verdadeiro)// indicando que existe um obstáculotone(pinBuz, 261, 100);return true;} else {// Se a distância é 0 ou a distância é maior que a distância máxima// a função devolve false (falso) indicando que não existem obstáculos.return false;}}
Dentro desta função podes modificar a distância máxima que utilizaremos para detectar obstáculos, ou seja, qual a distância a partir da qual se encontrarmos um objeto, se considera um obstáculo ou não. Para calcular a distância, utilizam-se as funções auxiliares Distance e TP_init que se encarregam de activar o sensor Bat, enviar um sinal de ultrassons, calcular o tempo que demora a ler o sinal, depois de ter refletido num obstáculo, e depois, de acordo com esse tempo, calcular a distância a que este obstáculo se encontra. Assim que soubermos se temos ou não um obstáculo à nossa frente em cada uma das três posições, podemos mover o PrintBot para uma zona livre. - Segue luz: Este modo automático utiliza-se para mover o PrintBot até uma zona onde a intensidade da luz, seja maior. Para isso, lemos o valor de cada um dos pinos onde estão conectados os módulos LDR (Light-Dependant Resistors, ou em português, Fotoresistência), comparamos os valores, giramos para o lado onde a intensidade da luz seja maior, e movemos o PrintBot em linha recta durante 1 segundo.
Podes ver esta entrada em execução, no seguinte vídeo:
- Modifica os ícones da aplicação Protocoder para incluir imagens em vez de texto nos botões.
- Utiliza os sensores de infravermelhos para detectar precipícios. Podes ver um exemplo de como fazê-lo aqui.
- Podes criar um botão para que o teu PrintBot mova a cabeça e emita um som.
Se quiseres aprender mais sobre o Arduino, o Protocoder e robótica, podes espreitar o Curso de programação para makers com Arduino e Protocoder