En este artículo vamos a ver cómo controlar un joystick con Arduino utilizando entradas analógicas.
Desde que tengo uso de razón soy fan de los videojuegos. No sé si te gusta jugar, pero seguro que alguna vez has visto un mando de juegos. En estos te encuentras principalmente dos tipos de controles: los pulsadores y los joysticks.
El joystick es la palanca que traen algunos mandos de consolas y son utilizados para controlar el movimiento de nuestro personaje o la interfaz de videojuego.
Hoy no pretendo enseñarte a jugar o a utilizar un mando de PlayStation. En lugar de eso puedes aprender a utilizar un joystick con Arduino utilizando las entradas analógicas.
Ya decides tú después si quieres hacer un videojuego o controlar un brazo robótico.
Pero antes de llegar a eso verás:
- Cómo funciona un conversor analógico digital y que tipos existen.
- Las principales características de los conversores.
- Cómo utilizar la función analogRead().
- Finalmente, cómo controlar un servomotor con un joystick con Arduino UNO.
Indice de contenidos
¿Qué es un ADC o convertidor analógico digital?
Antes de explicarte que es un convertidor analógico digital es necesario que aprendas a diferenciar las señales analógicas de las digitales.
Ya anteriormente se ha publicado un artículo sobre el convertidor ADS1115 donde se explica todo esto. Si no lo has leído te recomiendo que le des un vistazo. Tendrás una mejor perspectiva para entender como funciona un joystick con Arduino.
Lo importante es que comprendas que vivimos en un mundo analógico, ya que todas las señales físicas que eres capaz de percibir son analógicas:
- La temperatura.
- Los sonidos.
- Los colores.
- La iluminación.
Por otra parte, los microcontroladores son dispositivos digitales. Eso indica que no son capaces de interpretar señales analógicas.
Entonces, ¿Cómo hacer para trabajar con señales analógicas utilizando un microcontrolador?
Aquí es donde entra en acción el convertidor analógico digital (ADC, por sus siglas en inglés Analog-to-Digital Converter). Este es el encargado de “traducir” las señales del mundo analógico al mundo digital.
Esto permite realizar proyectos donde tu Arduino o tu ESP es capaz de procesar variables físicas como la temperatura o la iluminación. Puede ser para encender las luces del jardín por la noche, manejar la temperatura de una nevera o controlar un joystick con Arduino.
Tipos de conversores analógico-digitales
Existen varios tipos de conversores analógico-digital (ADC). Los más conocidos y utilizados son:
- Paralelo (también conocidos como flash)
- Aproximaciones sucesivas
Convertidores paralelos
Los convertidores tipo paralelo son los más rápidos que existen en el mercado y su principio de funcionamiento es relativamente simple.
Se basa en el uso de un grupo de resistencias colocadas en serie de forma que se obtienen varios voltajes a partir de un voltaje de referencia.
Es similar a cuando utilizas un divisor de voltaje. Solo que en este caso se emplean varias resistencias.
Las resistencias son conectadas a un grupo de comparadores tal y como se muestra en la figura siguiente.
Los comparadores tienen dos entradas denotadas + y -, además de una salida. Si el voltaje en el pin + es superior al del pin – la salida tendrá un estado alto (HIGH). En caso contrario la salida tendrá un estado bajo (LOW).
Como puedes ver, todos los pines + de los comparadores se han conectado juntos al voltaje analógico a medir (VAA). Por otro lado, los pines – se han conectado a los divisores resistivos. De esta forma, dependiendo del voltaje en VAA será la cantidad de comparadores que se ponen en estado alto.
Por ejemplo, si se aplica un voltaje de 3.5 voltios en VAA solo se activará el último comparador.
En caso de aplicar un voltaje entre 3.75 y 6.25 se activarán los dos últimos y así sucesivamente.
Ojo, ten en cuenta que el convertidor toma cualquier valor entre 1.25 y 3.75 voltios como si fueran 1.25 V, eso se debe a la baja resolución de este ejemplo.
Para obtener un resultado mejor es necesario emplear muchas más resistencias y comparadores. Esto hace que su proceso de fabricación sea complejo y costoso. Por ejemplo, el AD9208 es un conversor tipo paralelo de excelentes prestaciones pero cuesta casi 2000 euros.
Convertidores de aproximaciones sucesivas
Estos son los más comunes y económicos. La mayoría de los microcontroladores modernos traen integrado un conversor de este tipo en su interior.
Se llaman así porque, al determinar un valor analógico utilizan un algoritmo iterativo que en cada paso realiza una mejor aproximación al valor real. Sería algo así:
- Se toma el intervalo de voltajes completo que soporta el convertidor. Por ejemplo, en un Arduino UNO es de 0 a 5 voltios.
- Se toma el voltaje central del intervalo y se compara con la señal de entrada.
- Si el voltaje central es menor que la señal de entrada
- Se toma el intervalo superior como nuevo intervalo.
- Caso contrario
- Se toma el intervalo inferior como nuevo intervalo.
- Si el voltaje central es menor que la señal de entrada
- Volver al paso 2.
En la siguiente figura puedes ver las 4 primeras repeticiones de este proceso para una señal de entrada de 4.5 voltios en un Arduino UNO.
Como puedes ver, en el cuarto ciclo ya se ha obtenido un valor bastante próximo al valor analógico. Los microcontroladores que poseen este tipo de conversores realizan una cantidad de iteraciones igual a su resolución (luego te cuento que significa este concepto).
Tanto los ESP8266 como la mayoría de los microcontroladores que emplean las placas Arduino tienen un conversor analógico digital de aproximaciones sucesivas. Esto nos permite trabajar con señales analógicas sin necesidad de añadir ningún componente extra a nuestro sistema.
Por lo tanto, puedes inferir que para manipular el joystick con Arduino uno se utiliza un conversor de aproximaciones sucesivas.
Voltaje de referencia de un convertidor analógico digital
El rango de entrada analógico o voltaje de referencia determina el intervalo de voltajes que puede interpretar el conversor, es decir, indica cuál es el máximo voltaje que se le puede aplicar a las entradas analógicas.
La mayoría de las placas Arduino utilizan la propia alimentación del microcontrolador como voltaje de referencia. Esto es un problema cuando se utiliza una fuente de alimentación imprecisa como puede ser el puerto USB de un ordenador.
Cuando digo fuente imprecisa, me refiero a que el voltaje puede variar significativamente. Por ejemplo, en un puerto USB el voltaje puede variar desde 4.8 hasta 5.3 voltios.
Para salvar este inconveniente es posible utilizar la función analogReference() . Esta función permite seleccionar la referencia analógica a utilizar mediante un parámetro:
1 |
analogReference(ref); |
Donde ref determina el tipo de referencia a utilizar y puede tomar uno de los siguientes valores:
- DEFAULT: utiliza la alimentación del microcontrolador como referencia. Típicamente 5 V o 3.3 V.
- INTERNAL: utiliza como referencia un voltaje interno de 1.1 V (solo disponible en placas basadas en ATmega328 como el Arduino UNO)
- INTERNAL1V1: utiliza como referencia un voltaje interno de 1.1V (solo disponible en Arduino MEGA)
- INTERNAL2V56: utiliza como referencia un voltaje interno de 2.56V (solo disponible en Arduino MEGA)
- EXTERNAL: utiliza el voltaje del pin AREF como referencia.
Es importante que cuando se utilice el pin AREF se le aplique un voltaje estable o de lo contrario las mediciones realizadas serán imprecisas o erróneas.
El voltaje de referencia suministrado al pin AREF tiene que ser menor al voltaje de alimentación del microcontrolador.
Resolución de un convertidor analógico digital
La resolución de un convertidor analógico digital se expresa en bits e indica la cantidad de niveles en que se divide el rango de entrada analógico. Para determinar la cantidad de niveles es necesario elevar 2 al número de bits.
Por ejemplo, un Arduino MEGA tiene una resolución de 10-bits, por lo tanto, tendrá:
Esto quiere decir que el convertidor analógico digital devuelve un valor entre 0 y 1023 que representa un voltaje entre 0 y 5 voltios.
Conociendo la resolución y el voltaje de referencia del conversor es posible calcular la variación mínima de voltaje que se puede medir. Para esto es necesario dividir el rango de entrada entre 2 elevado al número de bits.
Tomando como ejemplo un Arduino UNO, donde la resolución es de 10 bits y el rango es de 5 voltios se obtiene:
Lo que nos indica que no podemos medir variaciones de voltaje inferiores a 4.9 mV en una entrada analógica.
Otro caso interesante es el ESP8266. Este presenta una resolución de 10 bits y un rango de tan solo 1 voltio, con lo cual se obtiene:
En este caso se pueden obtener variaciones de voltajes realmente pequeñas. El inconveniente es que solo se pueden convertir señales con un voltaje máximo de 1 voltio.
Por desgracia, en la mayoría de placas basadas en ESP8266 como NodeMCU o WeMOS su entrada analógica está conectada a un divisor resistivo. Esto nos permite medir señales de hasta 3.3 voltios, pero a costa de una pérdida de precisión significativa. La pérdida de exactitud, se debe a que no siempre las resistencias empleadas para el divisor resistivo son resistencias de precisión.
Es por eso que no se recomienda utilizar el conversor analógico digital del ESP8266 en aplicaciones donde se requiera una buena precisión.
En la siguiente tabla puedes ver un resumen con las resoluciones y voltajes de referencia de las placas Arduino más conocidas.
PLACA | VOLTAJE DE REFERENCIA | RESOLUCIÓN |
---|---|---|
Uno | 5 V | 10 bits |
Mini, Nano | 5 V | 10 bits |
Mega, Mega2560, MegaADK | 5 V | 10 bits |
Micro | 5 V | 10 bits |
Leonardo | 5 V | 10 bits |
Zero | 3.3 V | 12 bits |
Due | 3.3 V | 12 bits |
MKR Family boards | 3.3 V | 12 bits |
ESP8266 | 1 V (3.3 V en las placas) | 10 bits |
Lectura de valores analógicos con analogRead
Realizar una conversión analógico-digital con Arduino es muy simple utilizando la función analogRead(). Por ejemplo, en el código de nuestro joystick con Arduino puedes notar que esta función admite un único parámetro:
1 |
int D = analogRead(pin); |
Donde:
- pin: es el pin analógico del que se realizará la lectura.
- D: es el valor obtenido de la conversión. Este es un número entre 0 y 2n, donde “n” es la resolución del convertidor.
El pin utilizado como parámetro tiene que ser uno de los pines analógicos de la placa Arduino. Esos pines por lo general están etiquetados como Ax en la mayoría de las placas.
A pesar de que el microcontrolador cuenta con varios pines de entrada analógicos solo posee un convertidor analógico digital. Esto implica que no es posible realizar dos conversiones al mismo tiempo.
Por lo tanto, al implementar el joystick con Arduino se observa que la función analogRead() internamente conecta el pin seleccionado a la entrada del convertidor, y luego inicia la conversión. Una vez terminada la conversión retorna el valor generado por el convertidor. En total ese proceso toma cerca de 100 us.
Entonces, si planeas utilizar más de una entrada analógica es necesario realizar una lectura primero y al terminar, realizar la próxima, es decir que no es posible realizar más de una lectura simultánea.
1 2 3 |
int analog1 = digitalRead(A0); int analog2 = digitalRead(A5); int analog3 = digitalRead(A2); |
Es importante que comprendas que la función analogRead() no guarda ninguna relación con analogWrite(). Incluso se puede decir que son funciones complementarias, ya que realizan operaciones inversas.
Si te interesa conocer sobre la función analogWrite() te recomiendo dar un vistazo al artículo sobre señales PWM con Arduino y analogWrite publicado en el blog.
Antes de manejar el joystick con Arduino, veamos otro ejemplo simple, que permita comprender lo analizado.
Control de iluminación con potenciómetro
En este ejemplo te muestro como utilizar un potenciómetro y la función analogRead() para controlar la iluminación de un LED a partir de la posición del potenciómetro.
Para este proyecto necesitarás:
- 1x Arduino Nano *
- 1x resistencia de 330
- 1x LED
- 1x Potenciómetro
* También te vale un Arduino MEGA o un Arduino UNO.
En las siguientes figuras puedes ver el esquema a montar.
Se ha colocado un potenciómetro rotatorio al Arduino de forma tal que es posible variar el voltaje en el pin A2 desde 0 hasta 5 voltios.
El LED se conecta al pin digital 3 utilizando una resistencia de 330 ohmios. Se emplea este pin para poder utilizar la función analogWrite() y controlar la iluminación del LED.
Ahora vamos a analizar el código por partes.
Lo primero es declarar las constantes. En este caso he declarado dos constantes, una con el pin del potenciómetro y otra con el pin del LED.
1 2 3 4 |
#define PIN_LED 3 // Conectada al led #define PIN_POT A2 // Conectada al potenciometro |
Lo siguiente son las variables que se emplean en el código. En este caso se emplean dos.
La variable raw es utilizada para almacenar las lecturas analógicas y out para especificar la iluminación del LED.
1 2 3 |
int raw, out; |
En la función setup() se inicializa el puerto serie a una frecuencia de 9600 baudios y se configura el pin del LED como salida.
1 2 3 4 |
void setup(){ Serial.begin(9600); pinMode( PIN_LED, OUTPUT ); } |
En la función loop() inicialmente se realiza la lectura del potenciómetro y se almacena en la variable raw. Luego la variable raw es enviada al monitor serie para comprobar que todo esté funcionando bien.
1 2 3 4 5 |
void loop(){ // obtener valor analogico en el pin A2 int raw = analogRead(PIN_POT); // Enviar el valor por puerto Serie Serial.println(raw); |
Como la función analogRead() retorna un valor entre 0 y 1023 es necesario adaptar el valor de raw a un rango entre 0 y 255 que es el empleado por la función analogWrite(). Para esto se utiliza la función map y el nuevo valor es almacenado en la variable out.
1 2 |
// Ajustar el valor obtenido al rango de analogWrite out = map( raw, 0, 1023, 0, 255 ); |
Finalmente, se ejecuta la función analogWrite() para modificar el brillo del LED y se esperan unos 50 ms antes de repetir todo el proceso.
1 2 3 4 5 6 |
// Ajustar la iluminacio'n del led utilizando analogWrite analogWrite( PIN_LED, out ); // esperar 50 ms delay(50); } |
Una vez hayas cargado el código, el sistema estará listo para funcionar. En la siguiente figura se muestra la salida obtenida en el monitor serie para una rotación completa del potenciómetro.
De modo general el código te permite variar la intensidad del LED a partir el voltaje aplicado a un pin analógico. No tiene por qué ser un potenciómetro, bien puede ser una LDR o cualquier otro sensor analógico.
En cualquier caso, aquí tienes el código completo. Siéntete libre de modificarlo a tu gusto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void loop(){ // obtener valor analogico en el pin A2 int raw = analogRead(PIN_POT); // Enviar el valor por puerto Serie Serial.println(raw); // Ajustar el valor obtenido al rango de analogWrite out = map( raw, 0, 1023, 0, 255 ); // Ajustar la iluminacion del led utilizando analogWrite analogWrite( PIN_LED, out ); // esperar 50 ms delay(50); } |
Relación entre el voltaje y el valor del convertidor
En el ejemplo anterior logramos obtener los valores proporcionados por el convertidor, sin embargo, no se llegó a calcular el voltaje real del pin.
Conocer el voltaje real en el pin es muy útil cuando necesitas conocer el estado de carga de una batería o trabajas con sensores analógicos. Esto es porque los sensores analógicos “convierten” una magnitud física en un voltaje analógico.
Un ejemplo típico es el sensor de temperatura LM35 que da un voltaje de salida que depende de la temperatura. Específicamente su salida aumenta en 10 mV por cada grado celsius de temperatura. Por lo tanto, si somos capaces de obtener el voltaje de su salida podemos determinar la temperatura ambiente.
Volviendo al tema, calcular el voltaje real del pin no es complicado una vez hemos obtenido el valor digital del conversor. Para esto basta con aplicar una simple fórmula:
Donde:
- VAN: es el voltaje en el pin analógico seleccionado.
- VREF: es el voltaje de referencia que utiliza el conversor. Este depende de la placa empleada.
- RES: resolución del convertidor analógico digital.
- D: código obtenido en la conversión.
Para un Arduino UNO la fórmula quedaría:
Eso significa que si al utilizar la función analogRead() obtenemos un valor de 276 en realidad en el pin hay unos:
Como ejercicio te propongo que intentes deducir la fórmula para un ESP8266.
Joystick con Arduino
Veamos ahora como utilizar un joystick con Arduino. La idea básica que propongo es obtener la posición del joystick mediante las entradas analógicas del Arduino y cambiar el ángulo de inclinación de un servomotor en consecuencia.
Una vez comprendas como usar un joystick con Arduino puedes dar rienda suelta a tu imaginación.
Para este proyecto necesitarás:
- 1x Arduino UNO *
- 1x Módulo Joystick
- 1x Servomotor
- Cables
* Puedes utilizar cualquier placa Arduino siempre y cuando tenga al menos dos entradas analógicas.
Estructura de un joystick analógico
Un joystick analógico no es más que una palanca conectada a dos potenciómetros. Los potenciómetros están ubicados de forma tal que uno permite conocer la inclinación de la palanca en el eje x mientras el otro permite conocer la inclinación en el eje y. Adicionalmente, algunos joysticks incluyen un pulsador, usualmente llamado selector.
Si no tienes experiencia trabajando con potenciómetros puedes ver el artículo del blog donde se muestra como utilizar un potenciómetro con Arduino.
La mayoría de los módulos que utilizan un joystick con Arduino cuentan con un total de 5 pines:
- VCC o +5V: alimentación del módulo.
- GND: Conexión a tierra.
- VRx o HORZ: permite conocer la posición de la palanca con el eje x (movimiento horizontal).
- VRy o VERT: permite conocer la posición de la palanca con el eje y (movimiento vertical).
- SW o SEL: pulsador
En algunos módulos de joystick con Arduino. Sus nombres o las posiciones de los pines pueden ser diferentes, pero por lo general son fáciles de identificar.
Los pines VRx y VRy están conectados a los divisores de tensión que forman los potenciómetros. Por lo tanto, cuando inclinamos la palanca los potenciómetros giran y cambia el voltaje de los pines. En la figura siguiente, puedes ver el esquema equivalente a un joystick.
Conexión de un joystick con Arduino
El pin VCC se debe conectar al voltaje de operación del microcontrolador. Por ejemplo, para Arduino UNO se conecta al pin de 5V pero en un Arduino Zero o un NodeMCU se debe conectar al pin de 3.3 voltios.
Los pines VRx y VRy se tienen que conectar a una entrada analógica cada uno. El pin SW por su parte debe ser conectado a un pin digital.
El circuito para un joystick con Arduino UNO sería el siguiente.
En caso de que tu placa este basada en ESP8266 solo podrás conectar uno de los pines, ya que el ESP solo posee una entrada analógica (A0).
Conexión del servomotor
Un servomotor es un motor eléctrico que nos permite mantener el ángulo que le indiquemos. Esto se realiza mediante una señal PWM.
Si tienes dudas sobre su funcionamiento puedes revisar el tutorial paso a paso de servomotores con Arduino que está en el blog.
Un servo usualmente cuenta con tres cables. Uno va conectado a la alimentación de 5 voltios, otro irá a tierra y el tercero a un pin de PWM.
Agregando el servomotor, al joystick con Arduino quedaría como se muestra en la siguiente figura.
Aquí puedes ver el esquema eléctrico.
Programación de joystick con Arduino
Con el circuito para nuestro joystick con Arduino ya listo es hora de darle vida al proyecto programando la placa Arduino. Veamos el código paso a paso.
En primer lugar, es necesario incluir la librería Servo que nos permitirá controlar el servomotor.
1 |
#include <Servo.h> |
Después se declaran las constantes necesarias. En este caso se emplean 4 constantes para definir los pines de las tres señales del joystick y el pin del servo.
1 2 3 4 5 6 |
#define PIN_VRx A0 #define PIN_VRy A1 #define PIN_SW 2 #define PIN_SERVO 6 |
Se declara una variable tipo Servo para controlar el motor. También se declaran variables para almacenar las lecturas analógicas del joystick y los ángulos correspondientes.
1 2 3 |
Servo motor; int x, y; int x_ang, y_ang; |
En la función setup() en primer lugar se inicializa el puerto Serial a 9600 baudios para enviar información al monitor serie. Luego se configura el pin del pulsador como entrada con resistencia de pullup. Por último, se especifica el pin al que se ha conectado el servomotor y se pone en la posición de 0 grados.
Es importante configurar el pin del pulsador como entrada con pullup porque el módulo no tiene resistencia de pullup.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void setup() { // inicializar monitor serie a 9600 baudios Serial.begin(9600); // configurar el pin del pulsador como entrada con pullup pinMode( PIN_SW, INPUT_PULLUP ); // inicializar el servo para trabajar con el pin 6 motor.attach(PIN_SERVO); // colocar el servo en la posición de 0 grados motor.write(0); } |
En la función loop() inicialmente se obtiene los valores analógicos del joystick.
1 2 3 4 |
void loop() { // leer posición del joystick con arduino x = analogRead(PIN_VRx); y = analogRead(PIN_VRy); |
Luego se mapean los valores obtenidos a ángulos utilizando la función map.
1 2 3 |
// mapear los valores a grados x_ang = map( x, 0, 1023, 0, 180 ); y_ang = map( y, 0, 1023, 0, 180 ); |
Los valores de ángulos obtenidos y el estado del pulsador son enviados al monitor serie para verificar que todo esté funcionando correctamente.
1 2 3 4 5 6 7 8 9 10 |
// Imprimir datos al monitor serie Serial.print( "X_ang:" ); Serial.print(x_ang); Serial.print(" "); Serial.print( "Y_ang:" ); Serial.print(y_ang); Serial.print( " SW:"); Serial.print( digitalRead(PIN_SW) ); Serial.println(); |
Por último, se actualiza la posición del servomotor a partir del ángulo en el eje x. Finalmente, se esperan 250 milisegundos antes de repetir la operación.
1 2 3 4 5 6 |
// actualizar ángulo del motor motor.write( x_ang ); // esperar 250 ms delay(250); } |
Eso es todo, aquí te dejo el código completo para que lo personalices a tu gusto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <Servo.h> #define PIN_VRx A0 #define PIN_VRy A1 #define PIN_SW 2 #define PIN_SERVO 6 Servo motor; int x, y; int x_ang, y_ang; void setup() { // inicializar monitor serie a 9600 baudios Serial.begin(9600); // configurar el pin del pulsador como entrada con pullup pinMode( PIN_SW, INPUT_PULLUP ); // inicializar el servo para trabajar con el pin 6 motor.attach(PIN_SERVO); // colocar el servo en la posición de 0 grados motor.write(0); } void loop() { // leer posición del joystick con Arduino x = analogRead(PIN_VRx); y = analogRead(PIN_VRy); // mapear los valores a grados x_ang = map( x, 0, 1023, 0, 180 ); y_ang = map( y, 0, 1023, 0, 180 ); // Imprimir datos al monitor serie Serial.print( "X_ang:" ); Serial.print(x_ang); Serial.print(" "); Serial.print( "Y_ang:" ); Serial.print(y_ang); Serial.print( " SW:"); Serial.print( digitalRead(PIN_SW) ); Serial.println(); // actualizar ángulo del motor motor.write( x_ang ); // esperar 250 ms delay(250); } |
Como ejercicio puedes probar a conectar otro servo y controlarlo con el ángulo del eje y del joystick con Arduino.
Conclusiones
Hemos visto cómo funcionan las entradas analógicas de un Arduino. Cómo utilizar la función analogRead() para interactuar con componentes analógicos. Por ejemplo, un joystick con Arduino.
Estoy seguro que tienes alguna idea innovadora para usar el joystick con Arduino. Tal vez ahora te animas a implementar tu propia versión del Snake o del Tetris.
Aunque, Puedes usar otro microcontrolador ¿Por qué no?
Cualquier duda o sugerencia la puedes dejar en los comentarios.