En el artículo de hoy te cuento cómo controlar un servomotor desde tu ordenador, a tu gusto, y a través de una interfaz gráfica sencilla y atractiva,
¿Cómo? Pues haremos uso de Arduino y de la comunicación serie que te expliqué en este artículo. Y es que encender y apagar LEDs o un display de 7 segmentos está muy bien, pero qué te parece si empezamos a mover cosas.
¿Qué es un servomotor?
Un servomotor, comúnmente llamado servo, se diferencia de un motor en que, además del propio motor, integra en el mismo dispositivo un sensor de posición, de modo que nos permite situar el eje del motor en una posición determinada, en lugar de girar indefinidamente mientras disponga de alimentación eléctrica.
Podemos decir, por tanto, que un servo es un sistema de bucle cerrado con retroalimentación.
Si quieres más detalles al respecto, puedes encontrar numerosísimas referencias en la web. Si ya conoces la teoría, vamos adelante con el proyecto.
Objetivo del proyecto
En la mayoría de tutoriales que encontrará por intenet te enseñarán los very basics de control de un servomotor. En jjosemayorga te voy a mostrar un proyecto más complejo (no mucho más complejo, no estamos construyendo una central nuclear).
En este proyecto vamos a desarrollar una aplicación con Java desde la que podremos enviar en tiempo real la posición que deseamos en nuestro servomotor y, para que sepas desde ya el resultado que vas a obtener, te enseño la demo del proyecto.
Como ves, la aplicación contará con una interfaz gráfica muy atractiva en la que podremos:
- Seleccionar el puerto serie y el baudrate que queremos emplear en la comunicación. Esto nos permitirá conectar fácilmente con el dispositivo que queramos desde la propia aplicación sin tener que modificar el código.
- Introducir la posición deseada para el servo a través de un slider.
- Ver de manera gráfica la posición del servo.
Montaje físico
Para este proyecto necesitamos:
- 1 x Arduino UNO rev 3
- 1 x servomotor
- 3 x cables «jumper»
- 1 x protoboard
El montaje es muy sencillo, ya que el servo sólo necesita tres conexiones: una para la tensión de alimentación, otra para la tierra GND y otra para la señal de mando.

Código Arduino
Para controlar el servo debemos hacer uso de la librería servo.h, la incluiremos al principio del código.
Con esta librería definiremos un objeto de la clase Servo y podremos hacer uso de susmétodos específicos como attach y write.
String readString;
#include <Servo.h>
Servo myservo; // creamos un objeto de la clase Servo
void setup() {
Serial.begin(115200);
myservo.write(90); // ponemos la posicion inicial en 90
myservo.attach(9); // la señal de control sale del pin 9
Serial.println("servo-test-22-dual-input");
}
void loop() {
while (Serial.available()) {
char c = Serial.read(); // lectura del puerto serio
readString += c;
delay(2);
}
if (readString.length() >0) {
Serial.println(readString);
int n = readString.toInt();
// verificamos si la entrada de señal es valida
if(n >= 500)
{
//Serial.print("writing Microseconds: ");
Serial.println(n);
myservo.writeMicroseconds(n);
}
else
{
//Serial.print("writing Angle: ");
Serial.println(n);
myservo.write(n);
}
readString="";
}
}
Código Java
El código de la aplicación Java cumple las premisas marcadas en Objetivo del proyecto. Se han integrado todas las funcionalidades con la interfaz gráfica.
package servomotorBT;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.metal.MetalSliderUI;
import java.io.*;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Enumeration;
public class servoBT implements SerialPortEventListener
{
int int_posicion=90;
String str_posicion="90";
JFrame marco=new JFrame("Control de servo");
JLabel titulo=new JLabel("<html><font size=10 color=blue>Control de servo por puerto serie</font></html>");
servomotor SV1=new servomotor(int_posicion);
JLabel lbl_puerto=new JLabel("Puerto de conexión:");
String[] str_puerto={"COM1","COM2","COM3","COM4","COM5","COM6","COM7","COM8","COM9","COM10","COM11","COM12","COM13","COM14","COM15","COM16","COM17","COM18","COM19","COM20","COM21","COM22","COM23","COM24","COM25","COM26"};
JComboBox cmb_puerto=new JComboBox(str_puerto);
JLabel lbl_baudrate=new JLabel("Baud Rate:");
String[] str_baudrate={"4800","9600","19200","38400","57600","115200","234000","460800","921600","1382400"};
JComboBox cmb_baudrate=new JComboBox(str_baudrate);
JButton btn_conectar=new JButton("Conectar");
JButton btn_desconectar=new JButton("Desconectar");
JLabel lbl_conectar=new JLabel(new ImageIcon("lightOFF.png"));
JTextField txt_posicion=new JTextField("90");
JSlider sld_posicion=new JSlider(0,179);
JTextField txt_noticias=new JTextField();
/** The output stream to the port */
private OutputStream output = null;
private BufferedReader input;
SerialPort serialPort;
private String PORT_NAME = "COM1";
/** Milliseconds to block while waiting for port open */
private static final int TIME_OUT = 2000;
/** Default bits per second for COM port. */
private static int DATA_RATE = 9600;
servoBT()
{
marco.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
marco.setIconImage(Toolkit.getDefaultToolkit().createImage("servom_png.png"));
marco.setResizable(false);
GridBagLayout gridbag=new GridBagLayout();
GridBagConstraints gbc=new GridBagConstraints();
marco.setLayout(gridbag);
gbc.insets=new Insets(5,20,5,20);
gbc.gridx=0;
gbc.gridy=0;
gbc.gridwidth=6;
gbc.gridheight=1;
marco.add(titulo,gbc);
gbc.gridx=0;
gbc.gridy=1;
gbc.gridwidth=2;
gbc.gridheight=4;
SV1.setBackground(Color.cyan);
SV1.setSize(400,400);
marco.add(SV1,gbc);
gbc.gridx=2;
gbc.gridy=1;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.weighty=0.5;
gbc.anchor = GridBagConstraints.SOUTH;
marco.add(lbl_puerto,gbc);
gbc.gridx=3;
gbc.gridy=1;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.fill=GridBagConstraints.HORIZONTAL;
cmb_puerto.addItemListener(new PORTlistener());
marco.add(cmb_puerto,gbc);
gbc.gridx=2;
gbc.gridy=2;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.fill=GridBagConstraints.NONE;
gbc.weighty=0.5;
gbc.anchor = GridBagConstraints.NORTH;
marco.add(lbl_baudrate,gbc);
gbc.gridx=3;
gbc.gridy=2;
gbc.gridwidth=1;
gbc.gridheight=1;
cmb_baudrate.addItemListener(new BRlistener());
marco.add(cmb_baudrate,gbc);
gbc.gridx=4;
gbc.gridy=1;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.anchor = GridBagConstraints.SOUTH;
gbc.fill=GridBagConstraints.HORIZONTAL;
btn_conectar.setName("conectar");
btn_conectar.addMouseListener(new Conectar());
marco.add(btn_conectar,gbc);
gbc.gridx=4;
gbc.gridy=2;
gbc.gridwidth=1;
gbc.gridheight=1;
gbc.anchor = GridBagConstraints.NORTH;
gbc.fill=GridBagConstraints.HORIZONTAL;
btn_desconectar.setName("desconectar");
btn_desconectar.addMouseListener(new Desconectar());
marco.add(btn_desconectar,gbc);
gbc.gridx=5;
gbc.gridy=1;
gbc.gridwidth=1;
gbc.gridheight=2;
gbc.fill=GridBagConstraints.NONE;
gbc.anchor = GridBagConstraints.CENTER;
marco.add(lbl_conectar,gbc);
gbc.gridx=2;
gbc.gridy=3;
gbc.gridwidth=4;
gbc.gridheight=1;
gbc.weighty=1.0;
txt_posicion.setPreferredSize( new Dimension( 200, 50 ) );
txt_posicion.setHorizontalAlignment(JTextField.CENTER);
txt_posicion.setEditable(false);
txt_posicion.setBackground(Color.white);
Font font1 = new Font("SansSerif", Font.BOLD, 30);
txt_posicion.setFont(font1);
marco.add(txt_posicion,gbc);
gbc.gridx=2;
gbc.gridy=4;
gbc.gridwidth=4;
gbc.gridheight=1;
sld_posicion.setMajorTickSpacing(20);
sld_posicion.setMinorTickSpacing(5);
sld_posicion.setPaintTicks(true);
sld_posicion.setPaintLabels(true);
sld_posicion.setValue(90);
gbc.fill=GridBagConstraints.HORIZONTAL;
sld_posicion.addChangeListener(new SliderListener());
sld_posicion.addMouseListener(new SliderMListener());
marco.add(sld_posicion,gbc);
gbc.gridx=0;
gbc.gridy=5;
gbc.gridwidth=6;
gbc.gridheight=1;
gbc.fill=GridBagConstraints.HORIZONTAL;
txt_noticias.setEditable(false);
txt_noticias.setBackground(Color.white);
marco.add(txt_noticias,gbc);
marco.pack();
marco.setLocationRelativeTo(null);
marco.setVisible(true);
}
class SliderListener implements ChangeListener
{
@Override
public void stateChanged(ChangeEvent e)
{
int_posicion=sld_posicion.getValue();
str_posicion=String.valueOf(int_posicion);
txt_posicion.setText(str_posicion);
SV1.renovar(int_posicion);
txt_noticias.setText("Posición enviada: "+str_posicion);
sendData(str_posicion);
}
}
class SliderMListener extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Point p = e.getPoint();
double percent = p.x / ((double) sld_posicion.getWidth());
int range = sld_posicion.getMaximum() - sld_posicion.getMinimum();
double newVal = range * percent;
int result = (int)(sld_posicion.getMinimum() + newVal);
sld_posicion.setValue(result);
}
}
class BRlistener implements ItemListener
{
@Override
public void itemStateChanged(ItemEvent evt)
{
// TODO Auto-generated method stub
JComboBox cb = (JComboBox) evt.getSource();
String str_DATA_RATE = (String) cb.getSelectedItem();
DATA_RATE=Integer.parseInt(str_DATA_RATE);
txt_noticias.setText("BaudRate cambiado a: "+str_DATA_RATE);
}
}
class PORTlistener implements ItemListener
{
@Override
public void itemStateChanged(ItemEvent evt)
{
// TODO Auto-generated method stub
JComboBox cb = (JComboBox) evt.getSource();
String str_PORT_NAME = (String) cb.getSelectedItem();
PORT_NAME=str_PORT_NAME;
txt_noticias.setText("Puerto cambiado a: "+str_PORT_NAME);
}
}
class Conectar extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Component aux=e.getComponent();
}
public void mouseReleased(MouseEvent e)
{
Component aux=e.getComponent();
{
if(aux.getName().equals("conectar"))
{
initialize();
}
}
}
}
class Desconectar extends MouseAdapter
{
public void mousePressed(MouseEvent e)
{
Component aux=e.getComponent();
}
public void mouseReleased(MouseEvent e)
{
Component aux=e.getComponent();
{
if(aux.getName().equals("desconectar"))
{
serialPort.removeEventListener();
serialPort.close();
lbl_conectar.setIcon(new ImageIcon("lightOFF.png"));
txt_noticias.setText("Conexión cerrada");
}
}
}
}
public void initialize()
{
CommPortIdentifier portId = null;
Enumeration portEnum = CommPortIdentifier.getPortIdentifiers();
// iterate through, looking for the port
while (portEnum.hasMoreElements())
{
CommPortIdentifier currPortId = (CommPortIdentifier)
portEnum.nextElement();
if (PORT_NAME.equals(currPortId.getName()))
{
portId = currPortId;
break;
}
}
if (portId == null)
{
System.out.println("Could not find COM port.");
txt_noticias.setText("Conexión fallida");
return;
}
try
{
// open serial port, and use class name for the appName.
serialPort = (SerialPort) portId.open(this.getClass().getName(),
TIME_OUT);
// set port parameters
serialPort.setSerialPortParams(DATA_RATE,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
// open the streams
input = new BufferedReader(new InputStreamReader(serialPort.getInputStream()));
output = serialPort.getOutputStream();
// add event listeners
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
lbl_conectar.setIcon(new ImageIcon("lightON.png"));
txt_noticias.setText("Conexión establecida");
}
catch (Exception e)
{
System.err.println(e.toString());
}
}
public synchronized void close()
{
if (serialPort != null)
{
serialPort.removeEventListener();
serialPort.close();
}
}
public synchronized void serialEvent(SerialPortEvent oEvent)
{
if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE)
{
try
{
}
catch (Exception e)
{
System.err.println(e.toString());
}
}
}
private void sendData(String data)
{
try
{
output.write(data.getBytes());
} catch (IOException e) {
showError("Error sending data");
//System.exit(ERROR);
}
}
private void showError(String errorMessage)
{
JOptionPane.showMessageDialog(marco,
errorMessage,
"Error",
JOptionPane.ERROR_MESSAGE);
}
public static void main(String[] args)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch(Exception e)
{
e.printStackTrace();
}
servoBT app=new servoBT();
}
}Para la creación del gráfico dinámico necesitaremos crear una clase adicional que herede de la clase Canvas.
package servomotorBT;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class servomotor extends Canvas
{
int pos;
public servomotor(int posicion)
{
pos=posicion;
}
void renovar(int posicion)
{
pos=posicion;
repaint();
}
public void paint(Graphics g)
{
g.setColor(Color.black);
g.fillOval(50, 50, 300, 300);
Graphics2D g2 = (Graphics2D)g;
g2.setStroke(new BasicStroke(16));
g.setColor(Color.white);
g.drawLine((int) (200-175*Math.cos(Math.toRadians(pos))), (int) (200-175*Math.sin(Math.toRadians(pos))), (int) (400-(200-175*Math.cos(Math.toRadians(pos)))), (int) (400-(200-175*Math.sin(Math.toRadians(pos)))));
}
}Recursos necesarios
Para que la aplicación se vea de la manera mostrada en el vídeo es necesario contar con los recursos gráficos adecuados. Ya que a veces resulta difícil optimizar el tamaño y los colores de imágenes, gráficos etc., te dejo por aquí los que he empleado yo, para que lo tengas más fácil.
Con esto ya tienes todas las indicaciones para poner en marcha este proyecto. Recuerda que si tienes alguna duda puedes consultarme por cualquiera de las vías que aparecen en la sección Contacto.
Demo
Para finalizar el artículo, os dejo la demo de la aplicación desarrollada.




0 comentarios