Transmitiendo paquetes de datos entre Galileo y Processing

Por: fjibarra

 

 

Introducción:

 

Hasta el momento hemos establecido comunicación entre la tarjeta Galileo y Processing en ambos sentidos, pero intercambiando un solo dato de información. Sin embargo, a veces es necesario transmitir más información, sobre todo hacia nuestra interfaz, lo que nos puede parecer una tarea un tanto más compleja ya que nuestra interfaz debe de reconocer de donde proviene o que representa cada dato que recibe.

GAL_PRO.png

 

Si tuviésemos únicamente datos digitales, una solución práctica podría ser el transmitir un carácter en ASCII distinto para cada botón digital, sin embargo, el problema viene cuando además queremos transmitir valores analógicos, por lo que ya no nos funcionaría lo planteado anteriormente.

 

La solución para este problema es realizar una especie de transmisión “handshake”, en la cual ambos dispositivos (emisor/receptor) deberán de estar sincronizados para enviar y recibir un paquete de datos, por lo que el receptor deberá indicarle al emisor que se encuentra listo para recibir más datos y posteriormente indicarle que ya los ha recibido y que ya puede recibir otro paquete.

 

Equipo requerido:

 

  • Tarjeta Intel® Galileo
  • Fuente de poder para Galileo.
  • Cable microUSB
  • Joystick shield

 

El Joystick Shield:

 

Joystick Shield v1.2 _01.jpg

 

El Joystick shield que usaremos contiene 7 botones digitales y 2 ejes analógicos para las posiciones en (x,y) de la palanca, los cuales, al conectar el joystick a la tarjeta Galileo, tienen la siguiente distribución de terminales:

 

BOTÓNPIN
Botón AD2
Botón BD3
Botón CD4
Botón DD5
Botón ED6
Botón FD7
Botón KD8
Eje XA0
Eje YA1

joystickbuttons.png

*El botón "K" se activa al presionar la palanca hacia abajo

 

Diagrama Esquemático:

 

Para este ejercicio, el diagrama esquemático es simple ya que usaremos el joystick shield, por lo que lo único que necesitamos es conectarlo directamente a las terminales de la tarjeta Galileo:

 

schematic.png

Código para la tarjeta Galileo:

 

Como lo comentamos anteriormente, es necesario establecer primero la comunicación entre Galileo y Processing para después comenzar a enviar paquetes de datos. Para esto, se utiliza la siguiente función, la cual tendremos que llamar desde la función “setup”:

 

void establishContact() {

while (Serial.available() <= 0) {

Serial.write('A'); // enviar letra "A"

   delay(100);

  }

}

 

Cuando sea llamada a esta función, la ejecución del programa se quedará dentro de ésta, transmitiendo letras “A” por el puerto serial. La ejecución de esta función se detendrá hasta que se reciba un dato por el puerto serial. Este dato deberá ser enviado por la interfaz de Processing.

 

Una vez que se recibe un dato, quiere decir que Galileo ha establecido contacto con la interfaz de Processing, y la ejecución del programa continúa hacia la función “loop”, en donde lo primero que se hace es checar que exista un dato en el bus del puerto serial para ser leído. Después, se lee el valor de todas las entradas, tanto digitales como analógicas, y se almacenan en un arreglo, para posteriormente transmitirlos todos, uno por uno, en una especie de paquete de datos:

paquetededatos.png

 

Y aquí tienes el código completo, donde se incluye además, la inicialización de las entradas y del puerto serial:

 

int sensorValue[9];

int inByte = 0;

int i;

 

void setup(){

for(i=2;i<=8;i++){

pinMode(i, INPUT);  //configurar botón conectado al pin "i" como ENTRADA

    digitalWrite(i, HIGH); //Habilitación de la resistencia pull-up interna.

  }

  Serial.begin(9600);  //INICIALIZAR COMUNICACION SERIAL A 9600 BAUDIOS:

  establishContact();  //Función para establecer el primer contacto con la interfaz.

}

 

void loop(){

  //Si se recibe un dato se comienza el proceso de lectura y transmisión:

  if (Serial.available() > 0){

inByte = Serial.read();

//Lectura del estado de todos los botones

    for(i=0;i<=6;i++){

      sensorValue[i]=digitalRead(i+2);

    }

    //Lectura de la posicón de los ejes

    sensorValue[7]=analogRead(0)/4;

    sensorValue[8]=analogRead(1)/4;

    //Transmisión del estado de los botones y los ejes

    for(i=0;i<=8;i++){

      Serial.write(sensorValue[i]);

    }

  }

}

 

void establishContact(){

while (Serial.available() <= 0){

Serial.write('A'); // enviar letra "A"

   delay(100);

  }

}

 

Como podrás notar, los valores leídos de las terminales analógicas son divididos entre 4; esto se debe a que estas terminales proporcionan valores analógicos representados por una serie de bits, es decir, funcionan como convertidores de analógico a digital (ADCs) de 10 bits. Entonces, como la longitud de los datos para comunicación serial es de 8 bits, es necesario realizar esta división para ajustar los valores obtenidos por las terminales analógicas de 10 bits, a datos de 8 bits.

 

Probando el Código para la tarjeta Galileo:

 

Para verificar el correcto funcionamiento del código para Galileo, modificarémos temporalmente un poco el código, para imprimir en consola la información de los botones y los ejes. Lo único que modificarémos será el ultimo ciclo "for" que se encuentra dentro de la función "loop", tal como se muestra a continuación:

 

//Transmisión del estado de los botones y los ejes

    for(i=0;i<=8;i++){

      Serial.print(sensorValue[i]);

      Serial.print(',');

    }

    Serial.println(' ');

 

Cargaremos este código en la tarjeta y abriremos el monitor serial. Lo que deberíamos de ver es que se están transmitiendo letras “A” y esto continuará así hasta que utilicemos la función de enviar del monitor serial, con lo cual, la tarjeta Galileo detectará que ha llegado un dato, lo que iniciará el proceso de transmisión del estado de los botones, y cada que enviemos un dato a Galileo, éste nos transmitirá un nuevo paquete de datos con el estado de los botones y los ejes:

serialmonitor.png

No olvidemos que al final de comprobar el funcionamiento del código, necesitamos cambiar el ciclo "for" como se muestra originalmente en el código completo, para que no tengamos problemas al utilizar el código para la interfaz de processing que se muestra en la siguiente sección.

 

Código para Processing:

 

El siguiente código es el encargado de crear una interfaz para Processing, la cual es una representación virtual del joystick.

JoystickProcessing.pngDentro de las funciones “drawButtons” y “drawAxis” se dibujan los botones y los ejes, pero siempre tomando en cuenta el estado físico de estos. Esta información se encuentra almacenada en el arreglo de variables “serialInArray”, el cual es actualizado en la función “serialEvent”.

 

import processing.serial.*;

Serial myPort;

 

boolean firstContact = false;

int texto_size=32;

int[] serialInArray = new int[9];

int serialCount = 0;

 

void setup(){

char i;

size(700, 400);

myPort = new Serial(this, "COM6", 9600);

textSize(texto_size);

background(200);

  for(i=0;i<=8;i++){

serialInArray[i]=1;

  }

}

 

void draw(){

background(100,0,0);

drawButtons();

drawAxis();

}

 

void serialEvent(Serial myPort){

int inByte = myPort.read();

if (firstContact == false){ //Detecta que se ha establecido el primer contacto

   if (inByte == 'A'){

     myPort.clear();

     firstContact = true; // indica que se estableció el primer contacto

     myPort.write('A'); // Solicitar el primer paquete de datos.

     }

}

else{ //Almacenamiento de los estados de los botones y los ejes

serialInArray[serialCount] = inByte;

serialCount++;

if (serialCount > 8 ){

     myPort.write('A'); //Solicita un nuevo paquete de datos

     serialCount = 0;

    }

  }

}

 

void drawButtons(){

//Boton A

if(serialInArray[0]==1){

stroke(255,255,0);

fill(255,255,0);

ellipse(550, height/4, 80,80);

}

else{

stroke(255,0,0);

fill(255,0,0);

ellipse(550, height/4, 60,60);

}

//Boton B

if(serialInArray[1]==1){

stroke(0,0,255);

fill(0,0,255);

ellipse(650, height/2, 80,80);

}

else{

stroke(255,0,0);

fill(255,0,0);

ellipse(650, height/2, 60,60);

}

//Boton C

if(serialInArray[2]==1){

stroke(255,255,0);

fill(255,255,0);

ellipse(550, height-height/4, 80,80);

}

else{

stroke(255,0,0);

fill(255,0,0);

ellipse(550, height-height/4, 60,60);

}

//Boton D

if(serialInArray[3]==1){

stroke(0,0,255);

fill(0,0,255);

ellipse(450, height/2, 80,80);

}

else{

stroke(255,0,0);

fill(255,0,0);

ellipse(450, height/2, 60,60);

}

//Boton E

if(serialInArray[4]==1){

stroke(0,0,0);

fill(0,0,0);

ellipse(width/2+50, height-height/6, 60,60);

}

else{

stroke(255,0,0);

fill(255,0,0);

ellipse(width/2+50, height-height/6, 50,50);

}

//Boton F

if(serialInArray[5]==1){

stroke(0,0,0);

fill(0,0,0);

ellipse(width/2-50, height-height/6, 60,60);

}

else{

stroke(255,0,0);

fill(255,0,0);

ellipse(width/2-50, height-height/6, 50,50);

}

//TEXTO

fill(255, 255, 255); //Letras Blancas

text("B", 650-texto_size/4, height/2+texto_size/2);

text("D", 450-texto_size/4, height/2+texto_size/2);

text("E", width/2+50-texto_size/4, height-height/6+texto_size/2);

text("F", width/2-50-texto_size/4, height-height/6+texto_size/2);

fill(0, 0, 0);

text("C", 550-texto_size/4, height-height/4+texto_size/2);

text("A", 550-texto_size/4, height/4+texto_size/2);

}

 

void drawAxis(){

  if(serialInArray[6]==1){ //Dibuja la palanca cuando el botón K NO se encuentra presionado

  stroke(0);

fill(0);

ellipse(width/8+serialInArray[7]/2,height/2-serialInArray[8]/2,130,130);

}

  else{ //Dibuja la palanca cuando el botón K es presionado

  stroke(255,0,0);

fill(255,0,0);

ellipse(width/8+serialInArray[7]/2,height/2-serialInArray[8]/2,120,120);

fill(255, 255, 255);

text("K", width/8+serialInArray[7]/2-texto_size/4, height/2-serialInArray[8]/2+texto_size/2);

  }

}

 

La función “serialEvent” también es la encargada de sincronizar los datos enviados desde Galileo, y solicitar un nuevo paquete de datos cada que ha recibido los 9 estados de los botones y los ejes.

 

Video:

 

 

En resumen:

 

En este tutorial hemos expandido las capacidades de la interacción entre Galileo y Processing, lo que nos permitirá realizar cualquier tipo de interfaz sin tener limitaciones por la cantidad de datos con distintos tipos de información. El límite lo pone nuestra imaginación.

 

Y qué tal un reto? Serías capaz de diseñar un juego muy sencillo en Processing (el que se te ocurra) y que sea controlado con el Joystick Shield conectado a una tarjeta Galileo? Te invito a realizar este desafío y a compartir tus resultados con la Comunidad Intel Galileo!!!

 

Para cualquier duda puedes preguntarnos a través de los foros de nuestra Comunidad: Foros