Raspberry Pi & Arduino. Un nuevo ecosistema?
mayo 18th, 2012
Afortunadamente (para mí) he sido uno de los que han recibido la Raspberry Pi en los primeros envíos de Farnell, creo que en la segunda tanda.
Como es de bien nacidos ser agradecido, quiero hacer lo propio con l@s chic@s de @FarnellES en twitter que me han tenido al tanto siempre de como iba la distribución de las Raspberry Pi
.
Bueno, pues a lo que iba, esto hoy va a ser más un artículo de opinión personal con mi experiencia (muy poca todavía) con la placa y su entorno.
Lo que me ha llevado a escribir esta entrada, es un post de Liz (Raspberry Pi Foundation), en el que mostraba su tristeza por la actitud (quizás demasiado apasionada si esa puede ser la descripción) de alguna gente de la comunidad de Arduino que se mostraba a la defensiva dando a entender que la Raspberry venía a ocupar el nicho que actualmente posee Arduino en el mundo geek.
Personalmente, pienso como ella y no creo que eso llegue a pasar, pues son dos cosas completamente diferentes y que a mi entender se complementan o pueden hacerlo muy bien.
No podemos comparar la capacidad de cálculo de la Raspberry Pi con la de Arduino, como tampoco podemos hacerlo en la Raspberry Pi con la versatilidad I/O y adaptabilidad que nos ofrece Arduino. Son cosas completamente diferentes e incluso conceptualmente tambien lo son.
He querido hacer una prueba que me demostrase una de las principales diferencias entre una placa y la otra, y nada mejor que un control de dos servos, que implica la generación de dos señales PWM.
Usé un Pan-tilt con dos servos de 7grs que responden a una señal de 3,3v como pulso PWM de control para poder conectarla a los GPIO de la Rasperry Pi directamente. Los servos van alimentados con 5v desde una FA.
La primera prueba la vamos a hacer conectando las señales de los dos servos a los GPIO de la Raspberry (ojo! son conexiones directas, sin buffers intermedios ni ningún tipo de protección. Un mal uso quemaría el GPIO de la Raspberry o hasta el SoC) en este caso a las patillas 11 y 16 de P1, que se corresponden con GPIO 21 y 22 de la librería para python RPi.GPIO-0.1.0.zip de Ben Croston.
Es necesario instalarla para poder ejecutar el programa.
# servo_GPIOs.py
#
# Copyright 2012 Raspberry Pi User pi@raspberrypi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import RPi.GPIO as GPIO
import time
import consola_io
#-- Servo positions
ESC = '\x1B'
servo1=90
servo2=90
# Move servos routine
# servos are attached at GPIO22 and GPIO21
# on raspberry Pi
def mover_servo(grados,servo):
if servo==1: GPIO_servo=22
elif servo==2: GPIO_servo=21
# create PWM pulse
pos_servo=(0.0000122*grados)+0.0002
GPIO.output(GPIO_servo, True)
# Using time.sleep is a worst idea
time.sleep(pos_servo)
GPIO.output(GPIO_servo, False)
# Another bad idea.
time.sleep(0.0025-pos_servo)
# Asimple menu with some info
def menu():
print """
W - Arriba
S - Abajo
A - Izquierda
D - Derecha
ESC - Terminar
@radikaldesig 2012 RaspberryPi
Control de servos
"""
# set up the GPIO channels 21 & 22 as output
GPIO.setup(22, GPIO.OUT)
GPIO.setup(21, GPIO.OUT)
menu()
# main loop
while 1:
c = consola_io.getkey()
if c=="w" or c=="W":
servo1=servo1+1
if servo1>180 : servo1=180
mover_servo(servo1,1)
elif c=="s" or c=="S":
servo1=servo1-1
if servo1<0 : servo1=0
mover_servo(servo1,1)
elif c=="a" or c=="A":
servo2=servo2-1
if servo2<0 : servo2=0
mover_servo(servo2,2)
elif c=="d" or c=="D":
servo2=servo2+1
if servo2>180: servo2=180
mover_servo(2,2)
elif c==ESC:break
print "Goodbye"
Como se puede ver en el código, la generación del pulso PWM es “manual”, usando como generador de retardos el procedimiento sleep de la librería time de python.
Es una solución chabacana que nos va a dar problemas y va a hacer que el pulso sea irregular y que varíe o que se interrumpa, con lo que no podremos conseguir una señal estable que nos permita mover los servos en condiciones y luego veremos porqué pasa esto.
Este video muestra el movimiento de los servos y en él se aprecian los saltos producidos por las pérdidas de pulsos o la rotura de los mismos:
Si nos paramos a pensar detenidamente, no es una buena idea generar los pulsos PWM para los servos desde los GPIOs de la Raspberry:
Hagamos un ejemplo en C para ver como se producen retardos entre procesos por la planificación de éstos en el sistema:
#include signal.h
#include time.h
main ()
{
int a,b;
b=0;
if ((a=fork())==0)
{
while (1)
{
printf("iter:%d\n",b);
usleep(100);
b++;
}
}
usleep(100000);
printf("Terminacion del proceso con pid= %d e iter:%d\n",a,b);
kill(a,SIGTERM);
}
Tenemos que un servo se mueve aproximadamente con un pulso de entre 0,5ms y 2,5ms a una frecuencia de unos 50Hz (20ms) algo, como esto:

Así que para una precisión de 1 grado necesitaríamos poder ajustar la longitud del pulso en intervalos de 11,1 uS.
Con este pequeño programa de prueba, lo que vamos a hacer es crear un proceso hijo cada 100 uS durante un tiempo máx de 100mS.
Ponemos una variable a contar los procesos hijos creados y comprobamos en varias ejecuciones que estos difieren en número en cada ocasión.
Es muy simple, el núcleo del sistema atiende otros procesos a la vez además del nuestro, y esto hace que el nivel de prioridad del ejemplo descienda en la tabla de planificación y que nuestro pulso de 0,8ms sea realmente de 1,1ms o 0,9ms o 0,83ms provocando desviaciones de hasta 90 grados ( WoW! ), dependiendo de la carga de procesos del sistema.
Y claro, se nos viene a la cabeza -Esto con Arduino no pasa, el uC ejecuta un solo proceso unicamente interrumpible por una IRQ y que yo tengo que habilitarle su permiso… -
Pues sí, basta el ejemplo de cualquier dispositivo o máquina industrial, ninguna CPU o sistema embebido realiza la maniobra de un servomotor, actuador o electroválvula… de todo ello se encarga un sistema periférico, bien un inversor, un regulador de giro o un sistema controlador de servos comunicados por CAN, profibus, RS485, etc.
Entonces, ¿porqué no complementar la capacidad de cálculo de la Raspberry Pi con las posibilidades I/O de Arduino estableciendo una comunicación a través del propio USB, que para eso disponemos de él?
Además establecemos una barrera perfecta entre nuestros motores, servos, Triacs o lo que sea y nuestra Raspberry, que no ha sido diseñada para eso.
Ese es el paso siguiente. Conectemos el Arduino al USB y busquemos a ver que tenemos:
Ya tenemos localizado entre los dispositivos serie la id de nuestro Arduino. En Debian debería de crearnos un nuevo dispositivo del tipo /dev/ttyACM0.
Podemos olvidarnos por ahora del Arduino IDE en nuestra Raspberry, ya que la máquina de java es muy pesada y tenemos la limitación de 256Mb de RAM de la placa, así que nos vamos a limitar a comunicarnos con Arduino a través de la conexión serie del USB.
Cargamos nuestro Arduino con ServoControl.pde:
/*
* Servo control with Arduino
* using an USB comunication
*/
#No olvideis poner los angulos en el include
#include Servo.h
// Create objects
Servo servo1;
Servo servo2;
// Common servo setup values
int minP = 600; // minimum servo position, us (microseconds)
int maxP = 2400; // maximum servo position, us
// User input for servo and position
int inputData[3]; // raw input from serial buffer, 3 bytes
int byteIni; // start byte, begin reading input
int servo; // which servo to pulse?
int pos; // servo angle 0-180
int i; // iterator
void setup()
{
// Attach each Servo object to a digital pin
servo1.attach(2, minP, maxP);
servo2.attach(3, minP, maxP);
// Open the serial connection, 9600 baud
Serial.begin(9600);
}
void loop()
{
// Wait for serial input (min 3 bytes in buffer)
if (Serial.available() > 2) {
// Read the first byte
byteIni = Serial.read();
// If it's really the byteIni (255) ...
if (byteIni == 255) {
// ... then get the next two bytes
for (i=0;i<2;i++) {
inputData[i] = Serial.read();
}
// First byte = servo to move?
servo = inputData[0];
// Second byte = which position?
pos = inputData[1];
// Packet error checking and recovery
if (pos == 255) { servo = 255; }
// Assign new position to appropriate servo
switch (servo) {
case 1:
servo1.write(pos); // move servo1 to 'pos'
break;
case 2:
servo2.write(pos);
break;
}
}
}
}
Conectamos las señales de control de los servos a Digital2 y Digital3 del Arduino y ya tenemos nuestro módulo periférico totalmente preciso al que vamos a enviarle las órdenes de movimiento de la forma:
[start byte 0xFF] [servo a mover] [posicion en grados]
Usamos la librería servo incluida en el entorno de Arduino y ahora ya podemos dejar los cálculos y el control de las posiciones para la Raspberry.
Hagamos un ejemplo en Python:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# servos.py
#
# Copyright 2012 Raspberry Pi User pi@raspberrypi
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
import time
import serial
import consola_io
import random
# Assign Arduino's serial port address
# Windows example
# usbport = 'COM3'
# Linux example
# usbport = '/dev/ttyACM0'
# MacOSX example
# usbport = '/dev/tty.usbmodem621'
usbport = '/dev/ttyACM0'
# Set up serial baud rate
ser = serial.Serial(usbport, 9600, timeout=1)
# servo's position
servo1=90
servo2=90
# move servo routine that send data to Arduino
def mover_servo(grados,servo):
ser.write(chr(255)) # start transmission
ser.write(chr(servo)) # Select servo
ser.write(chr(grados)) # degrees to move
# A little menu with info
def menu():
print """
W - Arriba
S - Abajo
A - Izquierda
D - Derecha
ESC - Terminar
@radikaldesig 2012 RaspberryPi
Control de servos
"""
menu()
while 1:
c = consola_io.getkey()
# Move UP
if c=="w" or c=="W":
servo1=servo1+5
if servo1>180 : servo1=180
mover_servo(servo1,1)
# Move down
elif c=="s" or c=="S":
servo1=servo1-5
if servo1<0 : servo1=0
mover_servo(servo1,1)
# Move left
elif c=="a" or c=="A":
servo2=servo2-5
if servo2<0 : servo2=0
mover_servo(servo2,2)
# Move right
elif c=="d" or c=="D":
servo2=servo2+5
if servo2>180: servo2=180
mover_servo(servo2,2)
# move to 20 random pos
elif c=="v" or c=="V":
for i in range(20):
servo1=random.randint(0,180)
servo2=random.randint(0,180)
mover_servo(servo1,1)
mover_servo(servo2,2)
time.sleep(0.5)
elif c==ESC:break
print "Goodbye"
Necesitaremos la librería consola_io.py en el mismo directorio donde lancemos el programa y además instalar las librerías de comunicación serie de python pyserial-2.5
Tambien debemos configurar el dispositivo creado por el Arduino para dirigir nuestra comunicación, en mi caso es usbport = ‘/dev/ttyACM0′
Nuestro porgrama es simple, dependiendo de la pulsación de las teclas WASD, mueve los servos a intervalos de 5 grados, desde la posición de 0 grados hasta la de 180 grados, enviando 3 bytes al Arduino:
Startbyte, Nservo, Posicion
Le he añadido una pequeña feature al pulsar V y es que mueve los 2 servos a 20 posiciones aleatorias cada 0,5 segundos.
Ahora si que no hay saltos y los pulsos son perfectos.
Verdad que el Arduino y la Raspberry se complementan bien?
Ya resumiendo, es evidente que no necesitamos una Raspberry Pi para controlar un Arduino que mueva dos servos, pero sí podemos usarla para montarla sobre una base móvil (robot, orugas,…) y poder implementar un sistema de visión artificial con OpenCV corriendo en Python usando una cámara USB, enviando la señal de la imagen por wifi, y a su vez datos de telemetría, sensores de temperatura, giroscopios, acelerómetros y dotarla de una IA que entonces sí gestione un Arduino encargado de mover los motores, el pan-tilt de la cámara, leer los sensores y manejar infinidad de dispositivos I/O que para eso Arduino tiene mucho músculo y rodaje.
Aún podemos pensar que esto no puede ser un ecosistema maravilloso???
Os leo en el twitter…
Droky
@radikaldesig
DESCARGAS



























