miércoles, 9 de mayo de 2012

Arduino - Conexión Serial

Gracias a la inspiración de mi compañero David Sosa, me dio una fuerte sensación de intentar realizar una conexión serial con mi Arduino UNO de alguna manera. Inicialmente creía que la única forma de hacerlo era mediante Processing(lenguaje de programación y entorno de desarrollo integrado de código abierto basado en Java), pero al investigar me di cuenta que existían múltiples formas de realizarlo, y la que me parecio más interesante fue usando pySerial, una librería de python.


En este post veremos un ejemplo de uso de pySerial de forma básica con un script sencillo, para después ir mejorandolo hasta ser usado desde un sitio web usando CGI.


Conexión Serial


Se utiliza para la comunicación entre la placa Arduino y un ordenador u otros dispositivos. Todas las placas Arduino tienen al menos un puerto serie (también conocido como UART o USART): Serial. Se comunica a través de los pines digitales 0 (RX) y 1 (TX), así como con el ordenador mediante USB. Por lo tanto, si utilizas estas funciones, no puedes usar los pines 0 y 1 como entrada o salida digital.


Demostración


En las primeras demostraciones se utilizará un simple LED para visualizar como se controla su encendido y apagado mediante una comunicación serial. Las conexiones son como se aprecia en la imágen:




Conexión mediante Serial Monitor


Para poder realizar una conexión serial con el Arduino UNO, primero es necesario programarlo para que inicie una conexión serial y este listo para recibir los datos desde la computadora. Para esto podemos usar el ejemplo que tiene por default Arduino(con unas pequeñas modificaciones), con el código siguiente:


const int ledPin = 13; // the pin that the LED is attached to
int incomingByte;      // a variable to read incoming serial data into

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // initialize the LED pin as an output:
    Serial.println("Hello world!");

  pinMode(ledPin, OUTPUT);
}

void loop() {
  // see if there's incoming serial data:
  if (Serial.available() > 0) {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
    // if it's a capital H (ASCII 72), turn on the LED:
    if (incomingByte == 'H') {
      digitalWrite(ledPin, HIGH);
        Serial.println("Encendiendo LED");
    } 
    // if it's an L (ASCII 76) turn off the LED:
    if (incomingByte == 'L') {
      digitalWrite(ledPin, LOW);
      Serial.println("Apagando LED");
    }
  }
}


El código es bastante sencillo, define una constante que será usada para definir el pin donde conectaremos el LED que se encenderá y apagará. Y además un byte para almacenar el byte que se recibe en el buffer serial.

Se inicializa la conexión serial, y también se define el pin 13 como salida en el setup(). Después en el loop() se verifica que haya tráfico serial entrante y se lee el byte más viejo del buffer, almacenandolo en la variable incomingByte(es int, y almacenará el número ASCII del carácter que se envió). Se compará el valor recibido con 'H' para saber si encender el LED, o 'L' para apagarlo, y además se envía a la computadora mediante el puerto serial un mensaje para cada caso.


Ahora para probar el funcionamiento de la comunicación serial, podemos usar el Serial Monitor, una herramienta del IDE de Arduino para enviar datos mediante el puerto serial(Tools>Serial Monitor).
En ella simplemente ingresamos una 'H' para encender el LED, y una 'L' para apagarlo:



Conexión mediante un script con pySerial


La siguiente tarea es realizar el mismo procedimiento pero mediante pySerial.


Qué es pySerial?


La biblioteca pySerial provee muchos módulos con los cuales se puede tener casi total control del puerto Serial de una computadora. Ya que las computadoras pueden tener más de un puerto Serial configurado, pySerial utiliza números del 0 al 255 para cada uno de los puertos, de tal forma que se pueda invocar cada puerto de manera individual y que se asegure la compatibilidad con muchas plataformas.


Esta biblioteca tiene una gran gama de aplicaciones, qeu van desde la comunicacion con dispositivos móviles como celulares y miniordenadores, hasta el control de plantas industriales automatizadas. Las funciones que este módulo provee, posibilitant, por ejemplo, establecer comunicacion con los bancos o registros de memorias de muchos dispositivos y poder manejar sus datos. En el área de la robotica, actualmente muchas plantas automatizadas en las que se utilizan robots o maquinaria industrial, utilizan el protocolo RS-232 y puerto seriales para poder comunicarse con dichos dispositivos, y por ejemplo, enviar instrucciones de funcionamiento o recibir la retroalimentacion de estos dispositivos, estableciendo una comunicacion bilateral o Full-duplex. Todo esto se realiza a través del puerto Serial y pySerial tiene la capacidad de realizar esto

Para instalar pySerial, podemos hacer una de varias cosas:

1. Instalarlo desde el código fuente. Para esto descargamos el código de: http://pypi.python.org/pypi/pyserial , extraemos los archivos, entramos a la carpeta y corremos el comando:
python setup.py install
2. Si contamos con pip, podemos simplemente correr el comando:
pip pyserial
3. O si contamos con easy_install:
easy_install -U pyserial
Ya instalado podemos escribir un pequeño script para mandar el estado del LED mediante la línea de comandos, como el siguiente:

#!/usr/bin/python                                                                                                       
import serial, time, sys
#Correr: 
#./arduino_serial.py "Estado del LED" "Puerto Serial"
#Ejemplo:
#./arduino_serial.py H ttyACM1

#Intentamos recoger los argumentos de la linea de comandos
try:
 puerto = sys.argv[2]
 estado = sys.argv[1]
#Si no podemos, damos un mensaje de error y salimos
except IndexError:
 print "Error al ingresar los argumentos.\nDebe ser: python arduino_serial.py 'Estado del LED' 'Puerto Serial'"
 sys.exit()
#Intentamos abrir la comunicacion serial y realizar algunas
#operaciones en ella
try:
 ser = serial.Serial('/dev/%s'%sys.argv[2], 9600, timeout = 1)
 time.sleep(2)
 message = ser.read(100)
 print message
 ser.write('%s'%sys.argv[1])
 message = ser.read(100)
 print message
#Si no podemos, mostramos un mensaje de error
except serial.SerialException as e:
 print "%s"%e

El código es igualmente simple. Tomamos desde la línea de comando dos argumentos, uno: el estado que deseamos dar al LED del arduino, y dos: el puerto serial con el que nos vamos a comunicar(este lo podemos encontrar en el IDE de Arduino en la pestaña Tools>Serial Ports.


Con el string del puerto serial inicializamos la conexión con dicho puerto serial, dando un timeout de 1 segundo. Ahora es importante también dar un retraso de dos segundos, debido a que al conectar el puerto serial, el Arduino se resetea, lo que significa que si no ponemos este delay, el reseteo interferirá con nuestro intento de envíar el caractér 'H' o 'L' al Arduino, y no ocurrirá nada.


Volviendo al código, leemos lo que el arduino envío inicialmente al correr y lo almacenamos en una variable para luego imprimirla. Escribimos el estado que se ingreso en la línea de comando por el puerto serial para cambiar el estado del LED, y de nuevo leemos un mensaje que haya enviado el led, confirmando el cambio de estado.


El resultado en la línea de comando es el siguiente:




Conexión mediante un script CGI y pySerial 


Para concluir, haremos algo similar a lo anterior, pero ahora mediante un formulario web, utilizando python y CGI.


¿Qué es CGI?


El CGI (Por sus siglas en inglés “Common Gateway Interface” es de las primeras formas de programación web dinámica.
En sí, es un método para la transmisión de información hacia un compilador instalado en el servidor. Su función principal es la de añadir una mayor interacción a los documentos web que por medio del HTML se presentan de forma estática.
Entonces usando CGI, hice un pequeño y simple formulario web, el cual simplemente cuenta con dos botones de radio, para poder seleccionar entre los dos estados del LED, y un botón para confirmar la selección.
Inicialmente este método no funcionaba, ya que era necesario dar permisos al archivo para poder abrir una comunicación serial, esto lo arregle de la siguiente forma(no es la recomendada, pero igualmente funciona):
chmod o+rw /dev/ttyACM1
(Nota: Hay que cambiar ACM1 por el respectivo puerto serial, de nuevo, este puede ser encontrado al buscar en la pestaña Tools>Serial Port del IDE de Arduino)
Con esto podemos utilizar el formulario web para enviar los estados por el puerto serial, el script de python es el siguiente:
#!/usr/bin/python                                                                                                       
import serial,time,cgi, sys
print "Content-type: text/html\n"
print "<html><head><meta http-equiv='refresh' content='0;url=http://localhost/'/><title>LED Test</title></head><body><p>\n"
datos = cgi.FieldStorage()
if datos.has_key("estado") \
    and datos["estado"].value != "":
try:
 ser = serial.Serial('/dev/ttyACM1', 9600)
 time.sleep(2)
 ser.write("%s"%datos["estado"].value)
 ser.close()

except serial.SerialException as e:
 print "no device connected - printing to terminal only "%s 
 sys.exit()
print "</p></body></html>"


Sin entrar mucho en detalles, el código lee todo el contenido del cgi, busca si existe la llave "estado", correspondiente a los radio button, y si estos no estan vacios(se selecciono alguno), intenta iniciar la conexión serial y manda el estado seleccionado al arduino. 


Para hacer funcionar esto, es necesario tener instalado apache, y mover el código a /usr/lib/cgi-bin, además de colocar el respectivo index.html en /var/www, que se puede descargar de aquí:


La página es muy simple, y se ve así:


Los archivos, tanto el html como el .py pueden descargarse de AQUÍ.

Imágen del circuito:



Lo mismo pero ahora con un Display

Ahora para mostrar el control de algunas salidas más, utilizaré los pines 13-10 para dar una salida BCD,  que represente los números del 0 al 9, para poder mostrarlos en un display BCD a 7 segmentos.

Para hacer esto hay que reprogramar el Arduino, usando el siguiente código:

const int A = 13;
const int B = 12;
const int C = 11;
const int D = 10;

int incomingByte; 

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // initialize the LED pin as an output:
  Serial.println("Hello world!");

  pinMode(A, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(C, OUTPUT);
  pinMode(D, OUTPUT);
}

void loop() {
  // see if there's incoming serial data:
  if (Serial.available() > 0) {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
    
    switch(incomingByte){
     case '1':
       digitalWrite(A, HIGH);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
     
     case '2':
       digitalWrite(A, LOW);
       digitalWrite(B, HIGH);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
     
     case '3':
       digitalWrite(A, HIGH);
       digitalWrite(B, HIGH);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
     
     case '4':
       digitalWrite(A, LOW);
       digitalWrite(B, LOW);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '5':
       digitalWrite(A, HIGH);
       digitalWrite(B, LOW);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '6':
       digitalWrite(A, LOW);
       digitalWrite(B, HIGH);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '7':
       digitalWrite(A, HIGH);
       digitalWrite(B, HIGH);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '8':
       digitalWrite(A, LOW);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, HIGH);
      break;
     
     case '9':
       digitalWrite(A, HIGH);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, HIGH);
      break;
     
     case '0':
      digitalWrite(A, LOW);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
    }
  }
}

Este código es similar al anterior, en el sentido que leerá un byte mediante la conexión serial, y dependiendo de que caracter sea, mostrará una salida diferente. Pero ahora se darán múltiples salidas con las combinaciones binarias del número correspondiente al caracter. Para controlarlo mediante el Serial Monitor y el script sencillo en python, se pueden usar los códigos anteriores, pero para manipularlo mediante CGI, se requiren algunos cambios, para lo cual añadi una página en el apartado http://localhost/display, donde aparece una ventana como la siguiente:

Los archivos pueden descargarse AQUÍ.

Los cambios son mínimos, simplemente cambie los radio buttons por una lista desplegable con los números del 0 al 9 en el html, y el identificador de ésta en el .py y el html.


Referencias:

3 comentarios:

  1. Súperbien. 12 pts lab integrados.

    ResponderEliminar
  2. hola, un saludo y excelente la información, hay problema o alguna modificación si esto se pretendiera realizar en Windows???

    ResponderEliminar
  3. Hola Enmanuel, tengo una consulta, estuve probando el ejemplo, con la terminal y con python no hay problema, pero al tratar de ejecutarlo desde el navegador, se abre la ventana de descarga de archivo, para abrir/guardar el archivo "arduino_serie.py" en vez de simplemente ejecutarlo.
    1)La secuencia que hago es ejecutar index.html
    2) selecciono opción
    3) click en entrar, esto apertura la opción de abrir /descargar del navegador Mozilla

    ResponderEliminar