Comunicando Arduino con otros sistemas
Ejercicio Básico:
Conectando tres potenciometros a una animación. Armas el circuito de tres potenciometros conectados al Arduino y luego subes el programa de la izquierda. Luego abres Processing y ejecutas el programa de la derecha.
Código Arduino:
int pot1;
int pot2;
int pot3;
int inByte = 0; // valor entrante de Processing
void setup()
{
Serial.begin(19200);
pot1 = 0;
pot2 = 0;
pot3 = 0;
digitalWrite(13, HIGH);
}
void loop()
{
if (Serial.available() > 0) {
inByte = Serial.read();
pot1 = analogRead(0)/4;
pot2 = analogRead(1)/4;
pot3 = analogRead(2)/4;
Serial.print(pot1, BYTE);
Serial.print(pot2, BYTE);
Serial.print(pot3, BYTE);
}
}
|
Código Processing:
import processing.serial.*;
Serial puerto; // contador
int PosX, PosY, PosZ; // posicion de un objeto 3D
bola pelota = new bola();
void setup() {
size(800, 800, P3D);
noStroke();
// asuntos seriales
println(Serial.list());
puerto = new Serial(this, Serial.list()[1], 19200);
puerto.write(65); // Envia el primer dato para iniciar el toma y dame
}
void draw() {
background(0);
lights();
fill(255,0,0,PosX);
rect(0,0,width/2,400);
fill(0,255,0,PosY);
rect(width/2,0,width/2,400);
fill(30,255,20, PosY);
translate(width/2, height/2, 0);
pelota.posX = PosX;
pelota.posY = PosY;
pelota.posZ = PosZ;
pelota.play();
if (puerto.available() == 0) puerto.write(65);
}
// esta función corre cada vez que llega un dato serial
void serialEvent(Serial puerto) {
if (puerto.available() > 2) {
// Lee el dato y lo añade al arreglo en su última casilla
PosX = puerto.read();
PosY = puerto.read();
PosZ = puerto.read();
println("Valores de los potenciometros: " + PosX + "," + PosY + "," + PosZ);
// y envía para pedir más
}
}
class bola{
int posX, posY, posZ;
bola(){
posX = 0;
posY = 0;
posZ = 0;
}
void play(){
pushMatrix();
translate(posX, posY, posZ);
sphere(40);
popMatrix();
}
}
|
Hoy en día la manera más común de comunicación entre dispositivos electrónicos es la comunicación serial y Arduino no es la excepción. A través de este tipo de comunicación podremos enviar datos a y desde nuestro Arduino a otros microcontroladores o a un computador corriendo alguna plataforma de medios (Processing, PD, Flash, Director, VVVV, etc.). En otras palabras conectar el comportamiento del sonido o el video a sensores o actuadores. Explicaré aquí brevemente los elementos básicos de esta técnica:
Funciones básicas
El mismo cable con el que programamos el Arduino desde un comptador es un cable de comunicación serial. Para que su función se extienda a la comunicación durante el tiempo de ejecución, lo primero es abrir ese puerto serial en el programa que bajamos al Arduino. Para ello utilizamos la función
beginSerial(19200);
Ya que solo necesitamos correr esta orden una vez, normalmente iría en el bloque void setup(). El número que va entre parentesis es la velocidad de transmisión y en comunicación serial este valor es muy importante ya que todos los dispositivos que van a comunicarse deben tener la misma velocidad para poder entenderse. 19200 es un valor estandar y es el que tienen por defecto Arduino al iniciar.
Una vez abierto el puerto lo más seguro es que luego queramos enviar al computador los datos que vamos a estar obteniendo de uno o varios sensores. La función que envía un dato es
Serial.print(data);
Una mirada en la referencia de Arduino permitirá constatar que las funciones print y println (println: lo mismo que la anterior pero con salto de renglón) tienen opcionalmente un modificador que puede ser de varios tipos:
Serial.print(data, DEC); // decimal en ASCII Serial.print(data, HEX); // hexadecimal en ASCII Serial.print(data, OCT); // octal en ASCII Serial.print(data, BIN); // binario en ASCII Serial.print(data, BYTE); // un Byte
Como puede verse, prácticamente todos los modificadores, menos uno, envían mensajes en ASCII. Explicaré brevemente:
Series de pulsos
En el modo más sencillo y común de comunicación serial (asincrónica, 8 bits, más un bit de parada) siempre se está enviando un byte, es decir un tren de 8 pulsos de voltaje legible por la máquina como una serie de 8, 1s ó 0s:
O sea que no importa cual modificador usemos siempre se estan enviando bytes. La diferencia esta en lo que esos bytes van a representar y sólo hay dos opciones en el caso del Arduino: una serie de caractéres ASCII o un número.
Si Arduino lee en un sensor análogo un valor de 65, equivalente a la serie binaria 01000001 esta será enviada, segun el modificador, como:
dato Modificador Envío (pulsos)
65 ---DEC---- (“6″ y “5″ ACIIs 54–55) 000110110–000110111 65 ---HEX---- (“4″ Y “1″ ACIIs 52–49) 000110100–000110001 65 ---OCT---- (“1″, “0″ y “1″ ACIIs 49–48–49) 000110001–000110000–000110001 65 ---BIN---- (“0″,”1″,”0″,”0″,”0″,”0″,”0″y”1″ ACIIs 49–48–49–49–49–49–49–48) 000110000-… 65 ---BYTE--- 01000001
No explico acá las conversiones entre los diferentes sistemas de representación numérica, ni la tabla ASCII (google), pero es evidente como el modificador BYTE permite el envío de información más económica (menos pulsos para la misma cantidad de información) lo que implica mayor velocidad en la comunicación. Y ya que esto es importante cuando se piensa en interacción en tiempo real es el modo que usaremos acá.
Un ejemplo sencillo
Enviar un solo dato es realmente fácil. En el típico caso de un potenciometro conectado al pin 24 del atmega:
int potPin = 2;
int ledPin = 13;
int val = 0;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH); // prendemos el pin para saber cuando arranco
}
void loop() {
val = analogRead(potPin); // lee el valor del Pot
Serial.println(val);
}
Si no utilizamos ningun modificador para el Serial.println es lo mismo que si utilizáramos el modificador DEC. Así que no estamos utilizando el modo más eficiente pero si el más fácil de leer en el mismo Arduino. Al correr este programa podremos inmediatamente abrir el monitor serial del software Arduino (último botón a la derecha) y aparecerá el dato leido en el potenciometro tal como si usaramos el println en Processing.
Envío a Processing (versión ultrasimple)
Para enviar este mismo dato a Processing si nos interesa utilizar el modo BYTE así que el programa en Arduino quedaría así:
int potPin = 2;
int ledPin = 13;
int val = 0;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, HIGH); // prendemos el pin para saber cuando arranco
}
void loop() {
val = analogRead(potPin)/4; // lee el Pot y lo divide entre 4 para quedar entre 0-255
Serial.println(val, BYTE);
}
En Processing tenemos que crear un código que lea este dato y haga algo con él:
import processing.serial.*;
Serial puerto;// Variable para el puerto serial
byte pot;// valor entrante
int PosX;
void setup() {
size(400, 256);
println(Serial.list()); // lista los puertos seriales disponibles
port = new Serial(this, Serial.list()[0], 9600); //abre el primero de esa lista
con velocidad 9600
fill(255,255,0);
PosX = 0;
pot = 0;
}
void draw() {
if (puerto.available() > 0) { // si hay algun dato disponible en el puerto
pot = puerto.read();// lo obtiene
println(pot);
}
ellipse(PosX, pot, 3, 3); // y lo usa
if (PosX < width) {
PosX++;
} else {
fill(int(random(255)),int(random(255)),int(random(255)));
PosX = 0;
}
}
Hasta aquí la cosa es bien sencilla.
Si ya se animó a intentar usar más de un sensor notará que no es tan fácil como duplicar algunas líneas.
Envío a Processing (toma y dame)
Cuando se envía más de un dato del Arduino a otro sistema es necesario implementar reglas de comunicación adicionales para poder distinguir a que dato coreesponde cada uno de los paquetes de bytes recibidos. Una manera simple y eficiente de hacer esto es jugando al toma y dame. Arduino no enviará los valores de los sensores hasta que Processing no le envíe también un valor por el puerto serial y Processing, a su vez, no enviara ese valor hasta no tener los datos que espera completos.
Este sería el codigo para Arduino usando tres potenciometros en los último tres pines análogos del atmega:
int pot1= 0; // valores de los sensores analogos
int pot2= 0;
int pot3= 0;
int inByte = 0; // valor entrante de Processing
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available() > 0) { // sólo si algo ha llegado
inByte = Serial.read(); // lo lee
// hace la lectura de los sensores en pines 3,4y5 (análogos)
pot1 = analogRead(3)/4;
pot2 = analogRead(4)/4;
pot3 = analogRead(5)/4;
// y los envía
Serial.print(pot1, BYTE);
Serial.print(pot2, BYTE);
Serial.print(pot3, BYTE);
}
}
Ahora viene lo interesante. Es processing quién empezará el toma y dame y deberá reconocer cada dato. Este es el código:
import processing.serial.*;
Serial puerto;
int[] datosEntrantes = new int[3]; // arreglo para recibir los tres datos
int cuantosDatos = 0; // contador
int posX, posY, posZ; // posicion de un objeto 3D
boolean hayDatos = false; // control de verdad
void setup() {
size(400, 400, P3D);
noStroke();
// asuntos seriales
println(Serial.list());
puerto = new Serial(this, Serial.list()[0], 9600);
puerto.write(65); // Envia el primer dato para iniciar el toma y dame
}
void draw() {
background(0);
lights();
fill(30,255,20);
translate(width/2 + posX, height/2 + posY, posZ);
sphere(40);
if (hayDatos == false) { //si no hay datos envia uno
puerto.write(65);
}
}
// esta función corre cada vez que llega un dato serial
void serialEvent(Serial puerto) {
if (hayDatos == false) {
hayDatos = true; // de ahora en adelante el dato de envío se dará por el toma y dame
}
// Lee el dato y lo añade al arreglo en su última casilla
datosEntrantes[cuantosDatos] = puerto.read();
cuantosDatos++;
if (cuantosDatos > 2 ) { // Si ya hay tres datos en el arreglo
posX = datosEntrantes[0];
posY = datosEntrantes[1];
posZ = datosEntrantes[2];
println("Valores de los potenciometros: " + posX + "," + posY + "," + posZ);
puerto.write(65); // y envía para pedir más
cuantosDatos = 0; // y todo empieza de nuevo
}
}