¿Qué vamos a hacer?
En esta ocasión, mis queridos aprendices, os traigo un nuevo ejemplo de las maravillosas posibilidades de la comunicación vía puerto Serie entre nuestra tarjeta Arduino y nuestro PC vía puerto Serie. Como en el anterior artículo en el que imitábamos (más o menos) a la videoconsola Wii moviendo una pelota en pantalla con el solo gesto de nuestra mano, en esta ocasión vamos a interactuar con nuestro videojuego moviendo dos cuadraditos en pantalla, tratando de mantenernos dentro de sendas carreteras a derecha e izquierda girando las ruedecitas de dos potenciómetros.
A los profesores de Tecnología algo más veteranos les sonará de sobra el aspecto del juego: es la misma prueba de reflejos a la que debemos someternos todos los conductores cuando necesitamos renovar nuestro carnet de conducir. De ahí el nombre del juego, TEST DE CONDUCTORES.
¿Cómo funciona?
Es muy fácil: vamos a conectar dos potenciómetros al convertidor Analógico/Digital (A/D) de nuestra Arduino. Para quien no lo sepa, esta parte de la tarjeta puede leer valores analógicos entre 0 y 5 V y devolver un valor digital entre 0 y 1023. Así, la Arduino leerá las posiciones de los potenciómetros (que enviarán dichos valores analógicos según la posición de su rueda), y los convertirá a un valor digital entre 0 y 1023). Así, por ejemplo, si hemos girado la rueda exactamente a un cuarto de vuelta, la resistencia enviará a la patilla A0 o A1 1,25 V, que el convertidor A/D traducirá al valor digital 255.
La Arduino enviará estos valores al ordenador a través del puerto serie, donde estará escuchando nuestro programa en Processing.
Importante para estudiantes avanzados: antes de enviar dichos valores por el puerto Serie, el código en Arduino utilizará la instrucción map para convertir cada valor obtenido a su equivalente en un rango de 0 a 255. Esto es así porque queremos enviar cada valor de modo que pueda ser contenido en un único byte, y ahorrarnos así problemas de lectura en Processing.
Processing, por su lado, estará escuchando en el puerto Serie que le hayamos dicho (explicamos esto más abajo), y convertirá el valor enviado por cada potenciómetro en la posición del vehículo correspondiente (izquierda o derecha) en pantalla. Si el vehículo queda fuera de la pantalla, pasa del color blanco al rojo (PWEEET).
El código en Processing es un poco complejo para explicarlo aquí, aunque los más avezados en programación pueden analizarlo más abajo. Baste decir que trabajamos de modo procedimental, por eventos, de los cuales el más importante (obviamente) es el SerialEvent, que avisa de que llegan nuevos datos por el puerto Serie, por lo que habrá que parar un momento para recalcular y reposicionar los cuadraditos que representan ambos vehículos. Los caminos se calculan mediante la función actualizacarretera(), que asigna valores fijos en y para todos los píxeles que forman los cuatro bordes de los caminos, y va cambiando de modo ordenado las coordenadas x de acuerdo a valores aleatorios que van marcando si viene una curva a la izquierda o a la derecha.
¡Vamos a montarlo!.
Como dicen en mi casa, el montaje físico del juego tiene las letras muy gordas. Como ves en la ilustración, sólo hay que montar dos potenciómetros en tu protoboard. Las patillas extremas de ambas resistencias variables se conectan entre 5 V y GND de nuestra Arduino. Las patillas centrales se conectarán a las patillas A0 y A1 del convertidor A/D de nuestra tarjeta (si luego ves que los vehículos van al revés, tendrás que intercambiar dichas conexiones e ir probando hasta que te salga):
Programando el invento (Arduino)
Esta es la parte más sencilla. Volcar el código. Aquí tienes el código para Arduino:
/* Test Conductores Arduino Este código lee y convierte los valores analógicos de dos potenciómetros en las patillas A0 y A1 y las transforman a un rango entre 0 y 255, para a continuación enviar dichos valores por el puerto Serie. This example code is in the public domain. modificado el 10 de diciembre de 2016 by Antonio Gómez García */ int sensorPin = A0; // select the input pin for the potentiometer int sensorPin2=A1; int sensorValue = 0; // variable to store the value coming from the sensor int sensorValue2=0; int inByte=0; void setup() { Serial.begin(9600); establishContact(); } void loop() { if (Serial.available() > 0) { // get incoming byte: inByte = Serial.read(); // read first analog input, divide by 4 to make the range 0-255: // read the value from the sensor: sensorValue = analogRead(sensorPin); delay(10); sensorValue2 = analogRead(sensorPin2); Serial.write(sensorValue/4); Serial.write(sensorValue2/4); delay(10); } } void establishContact() { while (Serial.available() <= 0) { Serial.print('A'); // send a capital A delay(300); } }
Programando el invento (Processing)
Aquí tienes el código para Processing.
Importante: para establecer la comunicación vía puerto Serie, necesitamos indicar a Processing cuál de ellos vamos a utilizar, siguiendo un orden continuo que parte de cero. Si ves que el programa no funciona, es porque no encuentra a Arduino en el puerto Serie especificado. En la línea resaltada en azul, tendrás que ir cambiando el 0 de Serial.list()[0] a 1, 2, 3... e ir probando.
/*
Test Conductores Processing
Para utilizar con Arduino programada con el código
'Test Conductores Arduino'
This example code is in the public domain.
modificado el 10 de diciembre de 2016
by Antonio Gómez García
*/
import processing.serial.*;
Serial miPuerto;
int[] valorSerie=new int[2];
int seriesContados=0;
boolean firstContact=false;
int valor1,valor2;
int contador=0;
int contador2=10;
int ancho=80;
int [] xPos, xPos2, yPos;
//0 recto, 1 izquierda, 2 derecha
int sentido, sentido2;
int velocidadbase=2;
int velocidad=velocidadbase;
int velocidad2=velocidadbase;
int xCoche1;
int xCoche2;
void setup(){
println(Serial.list());
// Abre el puerto que se este usando a la velocidad deseada
//Tener en cuenta que hay que elegir el numero correspondiente
//al puerto COM que estemos usando en Arduino, y ponerlo
//en Serial.list[x]
miPuerto = new Serial(this, Serial.list()[0], 9600);
noStroke();
size (800,600);
yPos=new int[height];
xPos=new int[height];
xPos2=new int[height];
xCoche1=(width/4)-25;
xCoche2=(width*3/4)-25;
inicia();
}
void draw(){
background(0,255,255);
leeMandos();
actualizacarretera();
actualizacoche();
}
void inicia(){
for (int i=0;i<xPos.length;i++){
xPos[i]=width/4;
xPos2[i]=width*3/4;
}
for (int i=0;i<yPos.length;i++){
yPos[i]=i;
}
}
void actualizacarretera(){
contador++;
contador2++;
if (contador==20){
contador=0;
sentido=(int)random(3);
}
if (contador2==20){
contador2=0;
sentido2=(int)random(3);
}
//println(sentido);
if((xPos[xPos.length-1]-ancho)<0){
// sentido=1;
xPos[xPos.length-1]=ancho;
}
if((xPos[xPos.length-1]+ancho)>width/2){
//sentido=1;
xPos[xPos.length-1]=(width/2)-ancho;
}
if (sentido==0){velocidad=-velocidadbase;
}
if(sentido==1){velocidad=0;}
if (sentido==2){velocidad=velocidadbase;
}
//println(sentido);
if (sentido2==0){velocidad2=-velocidadbase;
}
if(sentido2==1){velocidad2=0;}
if (sentido2==2){velocidad2=velocidadbase;
}
if(xPos2[xPos2.length-1]-ancho<width/2){
//sentido2=1;
xPos2[xPos2.length-1]=(width/2)+ancho;
}
if(xPos2[xPos2.length-1]+ancho>width){
//sentido2=1;
xPos2[xPos2.length-1]=width-ancho;
}
xPos[xPos.length-1]= xPos[xPos.length-1]-velocidad;
xPos2[xPos2.length-1]= xPos2[xPos2.length-1]-velocidad2;
for (int i=0;i<xPos.length-1;i++){
xPos[i]=xPos[i+1];
}
//Este último bucle podría reducirse justamente al anterior
//(es el mismo número de puntos)
for (int i=0;i<xPos2.length-1;i++){
xPos2[i]=xPos2[i+1];
}
fill(255,255,255);
for (int i=0;i<yPos.length;i++){
ellipse(xPos[i]-ancho,yPos[i],10,10);
ellipse(xPos[i]+ancho,yPos[i],10,10);
ellipse(xPos2[i]-ancho,yPos[i],10,10);
ellipse(xPos2[i]+ancho,yPos[i],10,10);
}
}
void actualizacoche(){
xCoche1=valor1*width/512;
xCoche2=width/2+valor2*width/512;
if (xCoche1<xPos[height/2]-ancho||xCoche1+20>xPos[height/2]+ancho){
fill(255,0,0);}
else {fill(255,255,255);}
rect(xCoche1,(height/2)-25,20,20);
if (xCoche2<xPos2[height/2]-ancho||xCoche2+20>xPos2[height/2]+ancho){
fill(255,0,0);}
else {fill(255,255,255);}
rect(xCoche2,(height/2)-25,20,20);
}
void keyPressed(){
if (key == 'a' || key=='A'){
xCoche1=xCoche1-5;
if (xCoche1<0){
xCoche1=0;
}
}
if (key == 'd' || key=='D'){
xCoche1=xCoche1+5;
if (xCoche1>(width/2)-50){
xCoche1=(width/2)-50;
}
}
if (key == 'j' || key=='J'){
xCoche2=xCoche2-5;
if (xCoche2<width/2){
xCoche2=width/2;
}
}
if (key == 'l' || key=='L'){
xCoche2=xCoche2+5;
if (xCoche2>width-50){
xCoche2=width-50;
}
}
}
void leeMandos(){
if (miPuerto.available() > 0) {
}
}
void serialEvent(Serial myPort) {
// read a byte from the serial port:
int inByte = myPort.read();
// if this is the first byte received, and it's an A,
// clear the serial buffer and note that you've
// had first contact from the microcontroller.
// Otherwise, add the incoming byte to the array:
if (firstContact == false) {
if (inByte == 'A') {
myPort.clear(); // clear the serial port buffer
firstContact = true; // you've had first contact from the microcontroller
myPort.write('A'); // ask for more
}
}
else {
// Add the latest byte from the serial port to array:
valorSerie[seriesContados] = inByte;
seriesContados++;
// If we have 3 bytes:
if (seriesContados > 1 ) {
valor1 = valorSerie[0];
valor2 = valorSerie[1];
// print the values (for debugging purposes only):
println(valor1 + "\t" + valor2);
// Send a capital A to request new sensor readings:
myPort.write('A');
// Reset serialCount:
seriesContados = 0;
}
}
}
¡A jugar!
Nada más que añadir por mi parte. A ver qué te parece el invento.
Tienes, como siempre, toda la documentación en mi Github: https://github.com/agomezgar/tutoriales
(en la carpeta testConductores).
Por otro lado, aquí te dejo un vídeo resumen de los principios de programación y montaje de este sistema:
Y por mi parte, creo que eso es todo. ¡Sed felices!. ¡Hasta la próxima, makers!.
POSTDATA: Mi amigo y compañero Félix Villanueva no sólo ha probado este montaje, sino que hasta se ha currado un cuadro de mandos algo más definitivo para conectar al ordenador. Como novedades, hemos generado dos manillas con la impresora 3D que se adaptan a la rueda de los potenciómetros, sino que incluso hemos aprovechado la librería Minim para Processing para reproducir un archivo WAV (rescatado de un antiquísimo videojuego) que chirría sonoramente cada vez que nos salimos de la pista.
![]() |
![]() |
![]() |