Finalizamos nesta semana um incrível projeto de integração do Sun Spot com Arduino. Costumo dizer que o Sun Spot é o primo rico do Arduino e como não conseguimos encontrar acelerômetros em estoque nas lojas dos Estados Unidos, acabamos por topar o desafio de fazer o Arduino pegar os dados do acelerometro do Sun Spot, um tipo de "mashup" de hardware, ou no clássico termo: um hacking.
Para ter uma "implementação de referência" de uso do Sun Spot com Arduino, pegamos um controle de remoto infravermelho de um bicho bem esquisito que trouxemos da Maker Faire: o Mechamo.
O resultado esta no vídeo abaixo onde controlamos esta aranha usando o acelerômetro do Sun Spot:
Com o mesmo conceito você pode usar qualquer recurso do Sun Spot no Arduino e vice-versa. Vamos avançar agora transformando o código em bibliotecas para o Arduino com C++ e Jars para o Sun Spot.
O projeto contou com inúmeras colaborações do Paulos Carlos Ferreira dos Santos e Benedicto Franco Junior. Dois grandes mentores e parceiros Globalcode.
A seguir o descritivo completo incluindo código-fonte do projeto.
Etapa 1: abrindo e interceptando o controle remoto
Abrimos o controle remoto dela e para nossa sorte seu controle é o mais básico possível: com 4 botões comuns. Com isso usando o multimetro descobrimos qual era o pino de sinal de cada botão e soldamos 5 fios: 1 no terra e 1 para cada botão, esquerda, direita, frente e traz. Feito.
Etapa 2: testando o controle remoto com Arduino
Logo que terminamos testamos controlar o controle remoto puramente com Arduino e funcionou bem tranquilo o que nos deu uma segurança para prosseguir para a próxima fase: integrar o Arduino com Sun Spot, transferir dados do acelerometro do Sun Spot e depois comandar o controle remoto.
Etapa 3: integrando Arduino com Sun Spot método 1
A primeira solução que implementamos foi a mais ansiosa, diria assim. Foi com a técnica de polling, usamos 3 digitais: uma para dados, uma para polling e uma para acknowledge. Polling é basicamente ficar laço infinito lendo o sinal e assim que mudar de estado, 0 para 1 ou inverso, você reage e inicia a leitura dos dados. Funcinou! Mas deu um trabalhão para sincronizar os hardwares, na real tivemos que ir tentando o melhor delay após mudar o estado de uma porta digital. Enfim, mas funcionou gastando processamento de polling e também com custo de delays bem alto.
Etapa 4: mudando para usar interrupções no Arduino
Apresentando a engenhoca para o real engenheiro Paulo Carlos Ferreira dos Santos, tivemos uma mudança siginificativa na técnica: usar uma das 2 interrupções disponíveis do Arduino. UAU. Show de técnica. Basicamente mantivemos o uso de 3 digitais, porém uma das digitais ligamos do Sun Spot no Arduino Digital 2 que representa a interrupção 0, a digital 3 a interrupção 1. O que muda radicalmente é o código, pois fica assíncrono e a programação de serviços de interrupção (IRS) requer algumas práticas.
Se você é programador Java como eu, logo vai querer pensar com Threads, o que não acontece com Arduino pois ele é real-time. Xii, e as váriáveis que são alteradas pelos serviços de interrupção? Se temos duas interrupções? Pois é, a boa prática é quando entrar em uma interrupção, bloquear a outra, que no caso de só termos duas fica tranquilo. Bem, viagens a parte, seguimos adiante com a implementação de interrupção, que requer o uso das seguintes funções do Arduino:
attachInterrupt(0, suaFuncaoDeInterrupcao, RISING);
Esta função esta vinculando na interrupção 0, porta digital 2, a função "suaFuncaoDeInterrupcao", quando o sinal mudar de 0 para 1, e apenas de 0 para 1. Poderia ser falling (contrário), CHANGE qualquer mudança ou ainda LOW, ou seja, sempre que 0. Enfim, fácil de usar.
Aprendemos também que devemos fazer o mínimo necessário dentro da função de serviço de interrupção, pois em primeiro tentar interromper uma vez e transferir um byte por vez, não deu. Ai mostramos novamente a engenhoca para o engenheiro que diagnosticou com precisão: tem que ser uma interrupção por bit... Dito e feito e sincronizado.
Outro detalhe é que as variáveis que alteramos nas função de serviço de interrupção devem ser volatile (se lembram deste nome no Java??) volatile é uma diretiva de compilação que faz o Arduino alocar esta variável na RAM e não na stack e seu único uso é realmente para funções de interrupção.
Sei que ficou grande o post mas foram muitos aprendizados que queremos documentar e compartilhar com todos. Vejam o código completo do Arduino:
Podemos perceber que o método loop fica totalmente independente da recepção dos dados do Sun Spot. Isso acontece assincronamente pela interrupção. Lembrando que apesar de termos apenas 2 no Arduino convencional, o Arduino Mega oferece 4 interrupções. Se fossemos criar um sistema de threads com para ATMega sem dúvida metade do caminho esta andado com este projeto.
Bem, vamos analisar agora o lado do Sun Spot. Lá é nossa praia (pelo menos minha), pois é Java. Programamos o Sun Spot com NetBeans e Midlets e uma agradável API no leva para os objetos que representam componentes do Sun Spot, como acelerômetro e portas digitais / analógicas.
Com este código obtemos as portas digitais de comunicação com Arduino:
Aqui configuramos no startup se as portas serão de input ou output além do estado inicial delas:
O código completo ficou assim:
Para ter uma "implementação de referência" de uso do Sun Spot com Arduino, pegamos um controle de remoto infravermelho de um bicho bem esquisito que trouxemos da Maker Faire: o Mechamo.
O resultado esta no vídeo abaixo onde controlamos esta aranha usando o acelerômetro do Sun Spot:
Com o mesmo conceito você pode usar qualquer recurso do Sun Spot no Arduino e vice-versa. Vamos avançar agora transformando o código em bibliotecas para o Arduino com C++ e Jars para o Sun Spot.
O projeto contou com inúmeras colaborações do Paulos Carlos Ferreira dos Santos e Benedicto Franco Junior. Dois grandes mentores e parceiros Globalcode.
A seguir o descritivo completo incluindo código-fonte do projeto.
Etapa 1: abrindo e interceptando o controle remoto
Abrimos o controle remoto dela e para nossa sorte seu controle é o mais básico possível: com 4 botões comuns. Com isso usando o multimetro descobrimos qual era o pino de sinal de cada botão e soldamos 5 fios: 1 no terra e 1 para cada botão, esquerda, direita, frente e traz. Feito.
Etapa 2: testando o controle remoto com Arduino
Logo que terminamos testamos controlar o controle remoto puramente com Arduino e funcionou bem tranquilo o que nos deu uma segurança para prosseguir para a próxima fase: integrar o Arduino com Sun Spot, transferir dados do acelerometro do Sun Spot e depois comandar o controle remoto.
Etapa 3: integrando Arduino com Sun Spot método 1
A primeira solução que implementamos foi a mais ansiosa, diria assim. Foi com a técnica de polling, usamos 3 digitais: uma para dados, uma para polling e uma para acknowledge. Polling é basicamente ficar laço infinito lendo o sinal e assim que mudar de estado, 0 para 1 ou inverso, você reage e inicia a leitura dos dados. Funcinou! Mas deu um trabalhão para sincronizar os hardwares, na real tivemos que ir tentando o melhor delay após mudar o estado de uma porta digital. Enfim, mas funcionou gastando processamento de polling e também com custo de delays bem alto.
Etapa 4: mudando para usar interrupções no Arduino
Apresentando a engenhoca para o real engenheiro Paulo Carlos Ferreira dos Santos, tivemos uma mudança siginificativa na técnica: usar uma das 2 interrupções disponíveis do Arduino. UAU. Show de técnica. Basicamente mantivemos o uso de 3 digitais, porém uma das digitais ligamos do Sun Spot no Arduino Digital 2 que representa a interrupção 0, a digital 3 a interrupção 1. O que muda radicalmente é o código, pois fica assíncrono e a programação de serviços de interrupção (IRS) requer algumas práticas.
Se você é programador Java como eu, logo vai querer pensar com Threads, o que não acontece com Arduino pois ele é real-time. Xii, e as váriáveis que são alteradas pelos serviços de interrupção? Se temos duas interrupções? Pois é, a boa prática é quando entrar em uma interrupção, bloquear a outra, que no caso de só termos duas fica tranquilo. Bem, viagens a parte, seguimos adiante com a implementação de interrupção, que requer o uso das seguintes funções do Arduino:
attachInterrupt(0, suaFuncaoDeInterrupcao, RISING);
Esta função esta vinculando na interrupção 0, porta digital 2, a função "suaFuncaoDeInterrupcao", quando o sinal mudar de 0 para 1, e apenas de 0 para 1. Poderia ser falling (contrário), CHANGE qualquer mudança ou ainda LOW, ou seja, sempre que 0. Enfim, fácil de usar.
Aprendemos também que devemos fazer o mínimo necessário dentro da função de serviço de interrupção, pois em primeiro tentar interromper uma vez e transferir um byte por vez, não deu. Ai mostramos novamente a engenhoca para o engenheiro que diagnosticou com precisão: tem que ser uma interrupção por bit... Dito e feito e sincronizado.
Outro detalhe é que as variáveis que alteramos nas função de serviço de interrupção devem ser volatile (se lembram deste nome no Java??) volatile é uma diretiva de compilação que faz o Arduino alocar esta variável na RAM e não na stack e seu único uso é realmente para funções de interrupção.
Sei que ficou grande o post mas foram muitos aprendizados que queremos documentar e compartilhar com todos. Vejam o código completo do Arduino:
int SUN_SPOT = 9;
int SUN_SPOT_ACK = 10;
int SUN_SPOT_INTERRUPT = 0; //PORTA digital 2
int FORWARD=7; //fio branco
int BACKWARD=6; //fio laranja
int LEFT=4; //azul
int RIGHT=5; //verde
int interrupt=0;
volatile int data;
volatile boolean bit_array[16];
volatile int contador;
volatile int x;
volatile int y;
void setup() {
setupSunSpot();
//Setup do controle remoto da Aranha
setupMechamo();
attachInterrupt(0, receiveSunSpot, RISING);
Serial.begin(9600);
}
void setupSunSpot() {
pinMode(SUN_SPOT,INPUT);
pinMode(SUN_SPOT_ACK,OUTPUT);
}
void setupMechamo() {
pinMode(FORWARD, OUTPUT);
pinMode(BACKWARD, OUTPUT);
pinMode(LEFT, OUTPUT);
pinMode(RIGHT, OUTPUT);
digitalWrite(SUN_SPOT_ACK,LOW);
digitalWrite(FORWARD,HIGH);
digitalWrite(BACKWARD,HIGH);
digitalWrite(LEFT,HIGH);
digitalWrite(RIGHT,HIGH);
}
void receiveSunSpot() {
digitalWrite(SUN_SPOT_ACK,LOW);
bit_array[contador++]=digitalRead(SUN_SPOT);
if(contador==16) {
contador=0;
x = BtoI(0,7,bit_array);
y = BtoI(8,15,bit_array);
}
digitalWrite(SUN_SPOT_ACK,HIGH);
}
int BtoI(int start,int end, volatile boolean bits[]){
boolean negative=bits[start];
start++;
unsigned long integer=0;
unsigned long mask=1;
int r;
for (int i = end; i >= start; i--) {
if(negative) {
if (!bits[i]) integer |= mask;
}
else {
if (bits[i]) integer |= mask;
}
mask = mask << 1;
}
r = (int) integer;
if(negative) r= ~r;
return r;
}
void loop() {
digitalWrite(RIGHT, !(x>30 && x<90));
digitalWrite(LEFT, !(x<-30 && x>-90));
digitalWrite(FORWARD, !(y>30 && y<90));
digitalWrite(BACKWARD, !(y<-30 && y>-90));
Serial.print("x = ");
Serial.println(x);
Serial.print("y = ");
Serial.println(y);
delay(300);
}
Podemos perceber que o método loop fica totalmente independente da recepção dos dados do Sun Spot. Isso acontece assincronamente pela interrupção. Lembrando que apesar de termos apenas 2 no Arduino convencional, o Arduino Mega oferece 4 interrupções. Se fossemos criar um sistema de threads com para ATMega sem dúvida metade do caminho esta andado com este projeto.
Bem, vamos analisar agora o lado do Sun Spot. Lá é nossa praia (pelo menos minha), pois é Java. Programamos o Sun Spot com NetBeans e Midlets e uma agradável API no leva para os objetos que representam componentes do Sun Spot, como acelerômetro e portas digitais / analógicas.
Com este código obtemos as portas digitais de comunicação com Arduino:
private IIOPin arduinoData = EDemoBoard.getInstance().getIOPins()[EDemoBoard.D0];
private IIOPin arduinoStrobe = EDemoBoard.getInstance().getIOPins()[EDemoBoard.D2];
private IIOPin arduinoAck = EDemoBoard.getInstance().getIOPins()[EDemoBoard.D1];
Aqui configuramos no startup se as portas serão de input ou output além do estado inicial delas:
protected void startApp() throws MIDletStateChangeException {
arduinoData.setAsOutput(true);
arduinoStrobe.setAsOutput(true);
arduinoAck.setAsOutput(false);
arduinoData.setHigh(false);
arduinoStrobe.setHigh(false);
arduinoLoop();
}
O código completo ficou assim:
package br.com.globalcode.arduino;
import com.sun.spot.sensorboard.io.IIOPin;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.sensorboard.peripheral.IAccelerometer3D;
import com.sun.spot.util.Utils;
import java.io.IOException;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
/*
* ArduinoIntegration.java
*
* This simple Sun Spot Midlet reads accelerometer X and Y axis and send
* to Arduino using interruption.
*
* author: Vinicius Senger - vinicius@globalcode.com.br
* Paulo Carlos dos Santos - paulosantos@globalcode.com.br
* date: July, 2009
*/
public class ArduinoIntegration extends MIDlet {
private IAccelerometer3D accel = EDemoBoard.getInstance().getAccelerometer();
private ITriColorLED[] leds = EDemoBoard.getInstance().getLEDs();
private IIOPin arduinoData = EDemoBoard.getInstance().getIOPins()[EDemoBoard.D0];
private IIOPin arduinoStrobe = EDemoBoard.getInstance().getIOPins()[EDemoBoard.D2];
private IIOPin arduinoAck = EDemoBoard.getInstance().getIOPins()[EDemoBoard.D1];
public void arduinoLoop() {
for (int i = 0; i < 8; i++) {
leds[i].setOff(); // turn off all LEDs
leds[i].setColor(LEDColor.BLUE); // set them to be blue when lit
}
int oldX = 0;
int oldY = 0;
while (true) {
try {
int tiltX = (int) Math.toDegrees(accel.getTiltX()); // returns [-90, +90]
int tiltY = (int) Math.toDegrees(accel.getTiltY()); // returns [-90, +90]
if (tiltX != oldX || tiltY != oldY) {
sendBitsToIO(getBits((byte) tiltX), getBits((byte) tiltY));
oldX = tiltX;
oldY = tiltY;
}
changeLeds(tiltX);
} catch (IOException ex) {
System.out.println("Error reading accelerometer: " + ex);
}
}
}
private void sendBitsToIO(boolean bitsX[], boolean bitsY[]) {
for (int x = 0; x < bitsX.length; x++) {
send(bitsX[x]);
}
for (int y = 0; y < bitsY.length; y++) {
send(bitsY[y]);
}
}
private void send(boolean bit) {
arduinoData.setHigh(bit);
arduinoStrobe.setHigh();
arduinoStrobe.setLow();
while (arduinoAck.isLow());
}
private boolean[] getBits(byte value) {
//Depois de escrever o método minha
//querida esposa lembrou to Integer.toBinaryString :)
// Agora fica assim mesmo.
boolean result[] = new boolean[8];
byte displayMask = (byte) (1 << 7);
for (int c = 0; c < 8; c++) {
result[c] = (value & displayMask) != 0;
value <<= 1;
}
return result;
}
private void changeLeds(int tiltX) {
int offset = -tiltX / 15;
if (offset < -3) {
offset = -3;
}
if (offset > 3) {
offset = 3;
}
leds[3 + offset].setOn();
leds[4 + offset].setOn();
Utils.sleep(20);
leds[3 + offset].setOff();
leds[4 + offset].setOff();
}
protected void startApp() throws MIDletStateChangeException {
arduinoData.setAsOutput(true);
arduinoStrobe.setAsOutput(true);
arduinoAck.setAsOutput(false);
arduinoData.setHigh(false);
arduinoStrobe.setHigh(false);
arduinoLoop();
}
protected void pauseApp() {
}
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
for (int i = 0; i < 8; i++) {
leds[i].setOff();
}
}
}
Comentários
Dica: http://developertips.blogspot.com/2007/08/syntaxhighlighter-on-blogger.html
Qual irá ser o seu próximo desafio? Que tal integrar o SunSpot com esta aranha?
Quero um brinquedinho desses para mim hehe..
Alguém já pensou em fazer experiências com o Lego Mindstorms e o Arduino, ou o Sun Spot,, deve ser no minimo interessante ..
Abraço,
Agora estou neste mesmo projeto integrando o GPS Garmin que eu tenho para fazer a Aranha andar em rotas traçadas por GPS. Minha idéia é fazer ela aprender a chegar na praia sozinha, afinal de contas, é a inteligência mais digna de um robô filho de peixe.RS.
O mais legal disso é poder ver a eletrônica no baixo nível e praticar deslocamento de bits como nunca.
Isso é muito bom pois todos os fundamentos de lógica e algoritmos são praticados.
[]s
Vinicius
Lembre-se que o GPS comum, nas melhores condições, tem precisão variando em torno dos 15 metros. Com o serviço de correção WAAS disponível apenas na América do Norte, a precisão melhora para algo em torno de 3 metros.
[]s,
Bene.