0

Controlar servos por serial con Arduino

¡Saludos, humano! Una duda recurrente que recibimos, especialmente de aquellos que están empezando en el mundillo de Arduino, es cómo controlar un servo enviando carácteres/comandos por Serial. Si bien es una pregunta muy amplia que no tiene una única respuesta (la forma de hacerlo dependerá de nuestras necesidades concretas), en este post te daré varias ideas que puedes implementar o adaptar para tus proyectos.

Materiales

Para este tutorial necesitaremos, naturalmente, una placa Arduino. Cualquier modelo que tengas debería servir, pero los ejemplos que aparecerán han estado testeados con los modelos UNO rev3 y Nano v3.1.

En segundo lugar, necesitaremos dos servos. Los que yo he utilizado son dos micro-servos HD-1160A de Pololu. Las conexiones entre estos servos y la placa Arduino serán estas:

Con la energía que proporciona el cable USB tendremos suficiente para alimentar tanto la placa Arduino como ambos micro-servos. ¡Pero ojo! Si utilizas un modelo de servo con un consumo más elevado será mejor alimentarlos a través de una batería, en vez de conectarlos al pin +5V de Arduino.


Recordatorio: ¿Cómo enviamos mensajes por Serial a Arduino?

En los ejemplos que veremos durante el tutorial de hoy enviaremos los mensajes a nuestra placa a través del Serial Monitor que lleva incorporado el IDE de Arduino. Recuerda que para abrir el Monitor Serial tienes que conectar tu placa al ordenador y pulsar sobre este botón:

Se abrirá una ventana como esta:

Esta es la ventana del Monitor Serial, que permite comunicarse directamente con Arduino a través de este protocolo. Para enviar un mensaje, tienes que escribirlo en la barra superior y pulsar ENTER o hacer clic sobre el botón ‘Send’ para enviarlo:

También tenemos la opción de enviar los mensajes con o sin el carácter de final de línea. En los ejemplos de este tutorial (a no ser que se indique explícitamente lo contrario) será indiferente si lo enviamos o no.

Este desplegable permite seleccionar un final de línea.

Y por último, podemos establecer el baudaje de la comunicación Serial. En todos los ejemplos que veremos hoy utilizaremos ‘9600 baud’:

Este desplegable permite elegir el baudaje. Tenemos que seleccionar ‘9600 baud’


Ejemplo 1: Acciones

La forma más sencilla que se me ocurre de controlar un servo por Arduino es programar varias funciones con acciones de movimiento y enviar por Serial mensajes que indiquen cuál de las acciones queremos ejecutar.

En este primer código controlaremos únicamente el servo que hemos conectado al Pin Digital 3. Hemos definido tres funciones con los nombres “primera_accion”, “segunda_accion” y “tercera_accion” que al llamarlas hacen que el servo se active y realice una serie de movimientos.

Dentro del loop(), utilizamos la función readString() para leer el mensaje que nos llega a través de serial, y después utilizamos la función trim() para eliminar los carácteres del final de línea como ‘\r’,‘\n’ (si los hubiera). Finalmente, si el mensaje recibido es igual al string “accion1” llamaremos a la primera función; si es “accion2”, llamaremos la segunda, etc.

#include <Servo.h>

//Definimos una constante para el pin del servo:
#define PIN_SERVO 3

//Creamos un objeto de clase 'Servo':
Servo servo1; 

// Creamos tres funciones que definirán una serie de
// movimientos para el servo:
void primera_accion()
{
  servo1.write(0);
  delay(1000);
  servo1.write(90);
  delay(1000);
  servo1.write(0);
  delay(1000);
}

void segunda_accion()
{
  servo1.write(0);
  delay(100);
  for(int i = 0; i < 180; i++)
  {
     servo1.write(i);
     delay(10);
  }
}

void tercera_accion()
{
  servo1.write(45);
  delay(500);
}



void setup() {
  
  //Vinculamos el servo1 al pin definido por PIN_SERVO:
  servo1.attach(PIN_SERVO);

  //Inicializamos el serial a 9600 baudios:
  Serial.begin(9600); 
}

void loop() {

  //Comprobamos si llega algún mensaje por serial:
  if(Serial.available() > 0)
  {
    //Leemos la instrucción que nos envía el usuario:
    String mensaje = Serial.readString();

    //Eliminamos los carácteres \r \n \0, etc (finales de línea)
    mensaje.trim();

    // Comprobamos si el mensaje recibido es una acción válida, y
    // llamamos a la función correspondiente:
    if(mensaje == "accion1"){
      Serial.println("Ejecutando Acción 1...");
      primera_accion();
    }
    else if(mensaje == "accion2"){
      Serial.println("Ejecutando Acción 2...");
      segunda_accion();
    }
    else if(mensaje == "accion3"){
      Serial.println("Ejecutando Acción 3...");
      tercera_accion();
    }
    
  }

}

Una vez compilado y cargado este código, puedes abrir el Monitor Serial y enviar el texto “accion1”, “accion2” o “accion3” (sin las comillas). Deberías ver que el servo realiza la acción correspondiente.

Lógicamente, puedes añadir nuevas funciones con otras combinaciones de movimeinto.


 Ejemplo 2: Enviar la posición exacta

En vez de tener un conjunto de acciones predefinidas, podríamos enviar un número por Serial con la posición a la que queremos mover el servo. Es decir, si enviamos el número 90, queremos que el servo se mueva noventa grados; si enviamos 180, queremos que se mueva a la mitad, etc… ¿Cómo lo hacemos?

En este segundo programa también vamos a controlar un único servo, conectado al Pin Digital 3. Vamos a leer el mensaje que llega por Serial y lo convertiremos a un número entero utilizando la función toInt(). Este número entero vamos a pasarlo como parámetro a la función servo.write(), y determinará la posición a la que se moverá el servo. ¿Fácil, verdad?

#include <Servo.h>

//Definimos una constante para el pin del servo:
#define PIN_SERVO 3

//Creamos un objeto de clase 'Servo':
Servo servo1; 


void setup() {
  
  //Vinculamos el servo1 al pin definido por PIN_SERVO:
  servo1.attach(PIN_SERVO);

  //Inicializamos el serial a 9600 baudios:
  Serial.begin(9600); 
}

void loop() {

  //Comprobamos si llega algún mensaje por serial:
  if(Serial.available() > 0)
  {
    //Leemos el entero que llega por Serial:
    String mensaje = Serial.readString();

    //Eliminamos los carácteres \r \n \0, etc (finales de línea):
    mensaje.trim();

    //Convertimos el mensaje a entero:
    int posicion = mensaje.toInt();
    
    //Movemos el servo:
    servo1.write(posicion);
    delay(100);

    //Escribimos la nueva posicion del servo:
    Serial.print("Posicion: ");
    Serial.print(posicion);
    Serial.print("\n");

  }

}

Una vez tengas cargado este código a tu placa Arduino, abre el Monitor Serial y envia un número entre 0 y 180 (o entre 0-360 si tu servo permite hacer la rotación completa). Deberías ver que tu servo se mueve a la posición indicada.

 

Si estamos seguros que el mensaje que enviamos por Serial no contiene finales de línea (como ‘\n’,’\0′,’\r’,etc) , podemos simplificar el programa y utilizar la función Serial.parseInt(), que convertirá el mensaje directamente a un número entero:

#include <Servo.h>

//Definimos una constante para el pin del servo:
#define PIN_SERVO 3

//Creamos un objeto de clase 'Servo' para controlar el servo:
Servo servo1; 


void setup() {
  
  //Vinculamos el servo al pin definido por PIN_SERVO:
  servo1.attach(PIN_SERVO);

  //Inicializamos el serial a 9600 baudios:
  Serial.begin(9600); 
}

void loop() {

  //Comprobamos si llega algún mensaje por serial:
  if(Serial.available() > 0)
  {
    //Leemos el entero que llega por Serial:
    int posicion = Serial.parseInt();
    
    //Movemos el servo:
    servo1.write(posicion);
    delay(100);

    //Escribimos por el Monitor Serial la nueva
    // posicion del servo:
    Serial.print("Posicion: ");
    Serial.print(posicion);
    Serial.print("\n");
  }

}

Al igual que antes, al enviar un número a través del Monitor Serial, el servo se moverá a esa misma posición.

Fíjate también que si envías los mensajes con final de línea, el servo se moverá a la posición indicada y después volverá a la posición 0. Esto es debido a que Serial.parseInt() no se lleva bien con este tipo de carácteres y los interpreta como ceros.


Ejemplo 3: Controlar dos servos

¿Qué ocurre si queremos controlar 2+ servos? ¿Hay alguna forma de enviar un único mensaje que indique a la vez el servo que queremos activar y la posición a la que debe moverse?

¡Pues sí! Podríamos enviar los mensajes definiendo la siguiente convención: el último dígito indicará el servo que queremos activar, y el resto de dígitos serán la posición a la que deben desplazarse:

De esta forma, una vez tengamos el mensaje convertido a número entero, podemos utilizar la operación módulo %10 para quedarnos con el último dígito y así saber el servo. Después, podemos hacer la división entera entre 10 para quedarnos con la posición:

Veamos cómo programarlo. En este tercer ejemplo utilizamos ambos servos (¡por fin!): uno está conectado al Pin Digital 3 y el otro al Pin Digital 9. Dividiremos el mensaje de la forma que acabamos de ver para obtener el número de servo y su posición, y activaremos el servo que pertoque:

#include <Servo.h>

//Definimos una constante para los pines de los servos:
#define PIN_SERVO_1 3
#define PIN_SERVO_2 9

//Creamos dos objetos 'Servo':
Servo servo1;
Servo servo2; 


void setup() {
  
  //Vinculamos el servo 1 al pin definido por PIN_SERVO_1:
  servo1.attach(PIN_SERVO_1);

  //Hacemos lo mismo con el segundo servo:
  servo2.attach(PIN_SERVO_2);

  //Inicializamos el serial a 9600 baudios:
  Serial.begin(9600); 
}

void loop() {

  //Comprobamos si llega algún mensaje por serial:
  if(Serial.available() > 0)
  {
    //Leemos un string por Serial:
    String mensaje = Serial.readString();

    //Eliminamos el final de línea:
    mensaje.trim();

    //Convertimos el mensaje a un entero:
    int num = mensaje.toInt();

    //Nos quedamos con el numero de servo que queremos activar:
    int numero_servo = num%10;

    //Y nos quedamos también con la posición:
    int posicion = num/10;

    // Escribimos por el Monitor el servo y la posicion 
    // que hemos calculado:
    Serial.print("Servo = ");
    Serial.print(numero_servo);
    Serial.print(" | Posicion = ");
    Serial.print(posicion);
    Serial.print("\n");

    //Movemos uno de los dos servos:
    switch (numero_servo){
    	case 1:
    	   servo1.write(posicion);
    	   delay(100);
    	break;
    	
    	case 2:
    	    servo2.write(posicion);
    	    delay(100);
    	break;
    }

  }

}

¡Hecho! Una vez cargado el programa, si envías (por ejemplo) el mensaje 1802 verás que el Servo 2 se mueve 180 grados. En cambio, si envías el mensaje 1801, verás que es el servo 1 el que se desplaza a esa posición.

Este programillo funciona bien, pero tiene sus limitaciones. ¿Qué ocurre si tenemos diez servos o más? ¿Se te ocurre alguna forma de arreglarlo y, de paso, hacer más compacto este código? Te lo dejo como ejercicio…


Ejemplo extra: Python + Arduino

(Para este apartado tienes que cargar el mismo código del Ejemplo 3 dentro de tu placa Arduino)

Por el momento hemos enviado todos los mensajes a través del Monitor Serial de Arduino, pero esto no tiene por qué ser siempre así: estos mensajes podrían venir de otra placa Arduino u otra pieza de hardware, un programa (diferente al IDE de Arduino) ejecutándose dentro de tu ordenador, etc.

Si tienes Python instalado en tu ordenador y dispones de la librería Pyserial, podemos escribir un programa en este lenguaje que se comunique con la placa Arduino y le envíe mensajes para controlar los servos.

El programa de Python que hay debajo preguntará al usuario qué servo quiere activar y su posición, y enviará un mensaje (en el formato que hemos visto en el Ejemplo 3) a la placa Arduino.

import serial
 
#Abrimos la conexión con la placa Arduino:
ser = serial.Serial('/dev/ttyUSB0', 9600)
#IMPORTANTE! Tienes que cambiar '/dev/ttyUSB0' por la dirección de TU placa

#Aquí guardaremos el mensaje que enviaremos a Arduino:
mensaje = None

#Aquí guardaremos los inputs del usuario:
numero_servo = None
posicion = None


#Bucle infinito (lo romperemos si el usuario escribe 's')
while 1:

    #Pedimos el servo al usuario, y lo guardamos dentro de la variable 'numero_servo'
    print("Escribe el NÚMERO del servo (1 o 2) o 's' para salir")
    numero_servo = input()
    
    # Si en vez de un número el usuario introduce el carácter 's',
    # salimos del bucle infinito:
    if numero_servo == 's':
        break
        
        
    #Pedimos la POSICIÓN al usuario, y la guardamos dentro de la variable 'posicion'    
    print("Escribe la POSICIÓN a la que quieres mover el servo (0-360) o 's' para salir")
    posicion = input()
    
    # Al igual que antes, salimos si el usuario introduce el carácter 's':
    if posicion == 's':
        break
    
    # Como las variables 'posicion' y 'numero_servo' son textos, podemos unirlos con 
    # el operador '+' y formar así el mensaje que enviaremos a Arduino:    
    mensaje = posicion + numero_servo
    
    # Finalmente, enviamos el mensaje:
    print("He enviado el mensaje: ", mensaje)
    ser.write(mensaje.encode())
    

#Cerramos la conexión:    
print("Cerrando...")    
ser.close()

Este programa tienes que ejecutarlo dentro de tu ordenador. Acuérdate de cambiar la dirección ‘/dev/ttyUSB0’ que pasamos como parámetro a la función serial.Serial() por la dirección de tu placa Arduino (una forma fácil de saber la dirección de tu placa es conectarla, abrir el IDE de Arduino e ir al menú Tools->Port). También asegúrate de tener cerrado el Monitor Serial del IDE de Arduino cuando ejecutes este programa Python.


Conclusiones

Hoy hemos visto tres formas diferentes de activar y controlar servos a partir de mensajes enviados a través de Serial. Quiero remarcar, especialmente para los que llevéis poco tiempo programando, que estos códigos no son las tablas de la verdad; sin duda se os pueden ocurrir muchos métodos más, y mucho mejores.

Como siempre, si tienes alguna pregunta o comentario puedes escribirme un mensaje. Y si te ha servido este artículo, estaremos muy agradecidos si alimentas un poco nuestro ego nos dejas un like en Twitter o Facebook; es una buena forma de apoyarnos, y siempre recibirás notificaciones cuando publiquemos algo nuevo.

Esto es todo. Final de línea.

Tr4nsduc7or

Originariamente creado cómo un galvanómetro de bolsillo, Transductor tomó consciencia de si mismo y fue despedido cuando en vez cumplir con su trabajo se dedicó a pensar teorías filosóficas sobre los hilos de cobre, los electrones y el Sentido del efecto Joule en el Universo. Guarda cierto recelo a sus creadores por no comprender la esencia metafísica de las metáforas de su obra. Actualmente trabaja a media jornada cómo antena de radio, y dedica su tiempo libre a la electrónica recreativa y a la filosofía.

guest
0 Comments
Inline Feedbacks
Ver todos los comentarios