Page 1 of 1

Robot +Camara +Sensores +Encoder +Servo +Matriz8x8 +Joystick

Posted: Tue Dec 29, 2015 1:34 am
by actkk2000
Hola Gente,

Y al final llego la hora de armar un robot... luego de comprar una Raspberry PI 2 (RPI2) y despues de un par de años de haber probado diferentes cosas con Raspberry, era tiempo de armar algo "movil" y que tuviera en lo posible algun indicio de "inteligencia" (artificial)...

Mis proyectos anteriores (viewtopic.php?f=76&t=73731 y
viewtopic.php?f=76&t=116466) hicieron que fuera conociendo Raspberry desde lo mas básico y avanzando hacia algo mas complejo.

Ya venia con la idea desde hacia un tiempo, especialmente cuando vi esto, que me dejo con las ganas: :)
viewtopic.php?f=76&t=37137
Pero no encontré mucha mas info al respecto en el foro en Español de Raspberry Pi, por lo que me decidí a escribir algo yo mismo.

Advertencia: esta nota intenta ser mas bien una guía y no un tutorial, e incluye muchos temas que me interesaban y que fui aprendiendo a lo largo del proyecto. No quiere decir que sea la única o mejor forma de hacerlo y además se trata de un prototipo de pruebas por lo que no es para nada definitivo y seguro que queda mucho mas por hacer y mejorar, pero no por ahora...
Tampoco me interesa ser criticado ya que como en todo proceso de aprendizaje se cometen errores y soy plenamente consciente de los que cometí y ya tomé debida nota para evitarlos en el futuro :oops:, y los voy a mencionar aquí por si sirve de ayuda a alguien.
Gracias por su comprensión.


Ir a "Actualización con Cargadores USB y Microswitch": viewtopic.php?p=871039#p871039
Ir a "Actualización con Camera Module (Raspicam) e Interfaz Web": viewtopic.php?p=871041#p871041
Ir a "Actualización con Luces Frontales y Sensores de obstáculos" : viewtopic.php?p=871110#p871110
Ir a "Actualización con Encoders y Módulo Sensor de Ruedas" : viewtopic.php?p=872858#p872858
Ir a "Actualización con Sensores de Línea y de Intensidad de luz" : viewtopic.php?p=875390#p875390
Ir a "Actualización con ServoMotor y Soporte para Cámara" : viewtopic.php?p=876466#p876466
Ir a "Actualización con Matriz de Leds y Backpack" : viewtopic.php?p=884329#p884329
Ir a "Actualización con Joystick Xbox 360 y Programas Python" : viewtopic.php?p=885757#p885757

Introducción:

Como no sabia por donde empezar comencé a buscar información al respecto, sobre cual seria la forma mas fácil y/o rápida de armar un rover y de ser posible económicamente accesible. Lamentablemente donde vivo esa segunda condición no es para nada fácil de lograr, pero eso es otra historia...

Encontré un articulo que intentaba listar los posibles kits de robots y placas controladoras que había en el mercado:
https://www.reddit.com/r/raspberry_pi/c ... lable_for/

Y al igual que el autor la que me pareció mas interesante por las posibilidades que ofrecía es Raspirobot de MonkMakes, ya que había visto muchos de los artículos publicados por Simon Monk y me suponían bastante fáciles de comprender y seguir, además que funciona con Python y podia conseguirla localmente:
https://github.com/simonmonk/raspirobotboard2
https://www.adafruit.com/products/1940

Se trata de esta placa (hacer click en las imágenes para ampliar):

ImageImage

Aunque se podría reclamar que la placa es un hat que ocupa todos los pines GPIO, por suerte RPI2 tiene pines extra que iban a quedar disponibles para conectar mas cosas, ya que Raspirobot fue originalmente diseñada para las Raspberry Pi de 26 pines. Además esta era version 2 que incluía varias mejoras respecto de la primera version.

Faltaba ahora una carrocería con los motores y sus ruedas, así que conseguí un kit de los mas comunes que se llama Dagu Magician Chassis, que es muy conocido por la facilidad que ofrece para el montaje de cualquier tipo de rover:
http://www.dagurobot.com/goods.php?id=53
Por suerte también lo conseguí localmente, junto con unos cables de conexión macho-hembra y hembra-hembra. De los proyectos anteriores me habían sobrado los de conexión macho-macho.

Cabe aclarar que por su precio obviamente solo incluye lo básico que son dos motores DC comunes con caja reductora de plástico, dos ruedas plásticas con cubiertas de goma y una ruedita metálica de apoyo y guía (castor). También tiene un portapilas con capacidad para 4, lo que presupone pensado para controladores que no requieren mucho voltaje, como podría ser por ejemplo una placa Arduino.

Nacia asi el proyecto "Norman", no es ninguna sigla, le puse ese nombre porque cuando empece estaba viendo la serie Bates Motel, por lo que lo nombré en honor a Norman Bates el protagonista, pero no es que sea un robot psicópata, jaja... :twisted:

Construcción:

Siguiendo las instrucciones de la pagina de Raspirobot comencé la aventura del ensamblaje.
Raspirobot demandaba un poco mas de voltaje ya que se encarga además de los motores, también de alimentar la propia RPI2.
Así que lo que hice fue agregar un segundo portapilas en serie para sumar mas voltios a la entrada.
También tome la oportunidad de agregar un sensor ultrasónico HC-SR04, uno de los mas comunes, ya que tiene para conectarlo directamente.

ImageImage
ImageImage

Una vez montada la Raspirobot sobre RPI era cuestión de adosarlas al chasis con los separadores:

ImageImage
ImageImage

Otra cosa a agregar seria un pequeño protoboard para usarlo como puente de conexiones entre componentes por la longitud de los cables, que simplemente estaría pegado con el pegamento que tiene debajo:

ImageImage

Luego conectar los cables para los puentes de conexión agregar las pilas:

ImageImage

Finalmente agregar el HC-SR04 al montaje:

ImageImage
ImageImage

Lo básico para hacer andar esto era cargar el sistema operativo Raspbian y configurarlo para usarlo preferentemente sin interfaz grafica con raspi-config y que se loguee automáticamente. Ya lo había mencionado en otro articulo pero aquí va:
http://www.opentechguides.com/how-to/ar ... start.html

Luego copiar el software del repositorio de Raspirobot.
En la carpeta "examples" se encontraba un "test.py" para hacer pruebas de la placa y uno llamado "rover_avoiding.py" que servía para probar el funcionamiento.
Lo que hace es mover el robot hacia adelante en forma automata y usando el sensor ultrasónico medir la distancia de objetos hacia el frente, y si se topa con algún obstáculo hacer un giro para avanzar en otra dirección.

Buscando en el repositorio del autor fui hacia el de la version 1 ya que había visto que era posible controlarlo directamente y no solo dejarlo que funcione automáticamente, pero ese programa no venia en la version 2.
Finalmente encontré uno llamado "rover.py" que me permitiría manejarlo con un teclado wireless, tal como había visto en varios videos:
https://www.youtube.com/watch?v=IO8a-2lrSns
https://www.youtube.com/watch?v=7oTjEB-Maeg
http://www.raspberrypitutorials.yolasit ... irobot.php

Era cuestión de hacer una modificación en la importación de librerías y definición de variables de la version y cambiar:

Code: Select all

from raspirobotboard import * 
rr = RaspiRobot()
por las de la version 2 y poner:

Code: Select all

from rrb2 import *
rr = RRB2()
El teclado que tengo es uno inalámbrico como este:

Image

Una cosa a tener en cuenta es que ese programa funciona con la librería pygame para mostrar en pantalla un grafico de distancia medida por el sensor ultrasónico. Asi que para que funcione sin estar conectado a la pantalla, primero hay que iniciar la Raspberry y luego de ejecutar el programa con "sudo python rover.py" recién entonces desconectar del monitor y empezar a utilizarlo.

A partir de allí era cuestión de empezar a estudiar esos programas en python para modificarlos y agregarles mejoras y mas funcionalidades, como por ejemplo usar un joystick como mando, quería algo asi:
https://www.youtube.com/watch?v=XJ7JGqFZcRU

El joystick que tengo es uno común USB como este:
Image

Por suerte encontré esto que sirvió de guía e investigando un poco mas sobre pygame pude para agregarlo al programa:
https://github.com/ric96/joypi/blob/master/joypi.py
http://sabia.tic.udc.es/gc/Contenidos%2 ... utton.html
https://www.pygame.org/docs/ref/joystick.html

Quedando asi para manejarlo con la palanca e incluso con los leds onboard como indicadores:

Code: Select all

while True:
    joystick_count = pygame.joystick.get_count() #Get Joystick Count
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()
    for event in pygame.event.get():
        if event.type == (pygame.JOYAXISMOTION):
            if (joystick.get_axis(1) < -0.5): #Forward
                rr.forward()
                rr.set_led1(True)
                rr.set_led2(True)
            elif (joystick.get_axis(1) > 0.5): #backward
                rr.reverse()
                rr.set_led1(True)
                rr.set_led2(True)
            elif (joystick.get_axis(0) < -0.5): #Left
                rr.left()
                rr.set_led1(True)
                rr.set_led2(False)
            elif (joystick.get_axis(0) > 0.5): #Right
                rr.right()
                rr.set_led1(False)
                rr.set_led2(True)
        if event.type == (pygame.JOYBUTTONDOWN):
            if (joystick.get_button(0) == True):
                rr.stop()
                rr.set_led1(False)
                rr.set_led2(False)
Para empezar entonces, este seria el listado de materiales:

1- Raspberry Pi 2 B (podría ser también el modelo B+, lo que conviene es que tenga un puerto GPIO de 40 pines)
2- Placa controladora Raspiboard V2 (podría ser la version 1, pero no tiene funcionalidades avanzadas)
3- Memoria MicroSD de al menos 4 GB (conviene que sea lo mas rápida posible, si es clase 10 mejor)
4- Fuente de alimentación 5V 2A (a utilizar durante la programación y pruebas para no gastar pilas)
5- Kit Dagu Magician Chassis (incluye 2 motores y sus cables, 2 ruedas y un portapilas para 4 pilas, una rueda direccional de metal, además de tornillos y separadores)(también puede ser cualquier plataforma 2WD)
6- Portapilas extra para 4 pilas (puede ser de 6 pilas y reemplazar los 2 portapilas en serie)
7- Separadores extra de metal (en caso de querer elevar la plataforma si uno de los portapilas no entra, mi caso)
8- 8 pilas (si se usan 2 portapilas en serie)(6 pilas si se usa un solo portapilas)(se recomiendan recargables)
9- Tornillos extra para ajustar el portapilas (también podrían ser bridas de plástico)
10- Protoboard (breadboard) de 400 puntos (mas grande no entraría)
11- Cables varios de conexión de toda variedad macho/hembra (pueden ser tipo Dupont)
12- Sensor ultrasónico HC-SR04 (no he probado si funcionaria adaptando otro modelo como el SR05)
13- Teclado wireless (puede ser un teclado USB pero el movimiento estaría limitado por el cable)
14- Monitor con conexión HDMI (para hacer la configuración inicial, luego se puede acceder por SSH)
15- Joystick USB (opcional, también tiene la limitación del cable, no probe joystick wireless)
16- Dongle wifi (hasta aquí opcional, se puede hacer conexión por cable para la programación)
17- Soldador y estaño (hasta aquí opcional, se pueden ajustar los cables de los motores enroscándolos en los conectores)

Nota: En lugar de una placa controladora Raspirobot se podrían utilizar componentes separados.
Con el chip L293D (como el de Raspirobot) se pueden controlar motores eléctricos y se puede usar un conversor switch step down para regular 5V. De lo contrario también se puede utilizar un driver de motores con chip L298N que es mas standard, maneja mas corriente e incluso tiene incorporado un regulador de 5V, por lo que es muy utilizado con Arduino.

Cantidad de errores cometidos: 2

- Se me perdieron un par de tornillos y tuercas porque hay que ajustarlos bien, de lo contrario se aflojan y se caen con el movimiento.
- Las pilas en el portapilas que venia con el kit cada tanto se saltaban debido a las vibraciones por movimiento y se apagaba el robot, hay que sujetarlas aunque sea con cinta o conseguir un mejor portapilas.

Re: [Proyecto] Robot Rover + Cargador USB

Posted: Tue Dec 29, 2015 1:38 am
by actkk2000
Actualización con Cargadores USB y Microswitch:

Lógicamente al empezar a utilizar un poco mas en profundidad el robot empezaron a surgir los inconvenientes.
Uno de los primeros que se presento fue el de la alimentación. Lamentablemente al comienzo utilice pilas alcalinas pero no recargables, por lo que luego de solo algunas horas de uso terminaron agotándose.

La primera alternativa entonces era comprar pilas recargables. Pero también me di con otro problema, que era por la disposición de los portapilas se hacia difícil cambiar o recargar las pilas a voluntad, ya que al estar en el medio para poder remover las pilas se hacia muy difícil, había que quitar la parte superior para poder acceder a las pilas, desatornillándola cada vez.

Así que se me ocurrió utilizar un par de Cargadores USB baratos de celulares (de 2600 mAh, no muy creible...) puestos en serie como la conexión de los portapilas cuyo acceso para recargar seria por la parte posterior o a los costados.
De esta forma deberían sumarse los voltajes y tener alrededor de 10V en la entrada. La única limitación seria la corriente que no se sumaria sino que se mantendría en el limite que supuestamente era de 1A.

Para unirlos debía poner un par de cables USB enlazados en un extremo donde quedarían los terminales positivo y negativo (la parte de datos del cable no me haría falta) y conectarlos a uno de los rail del protoboard para tomar alimentación desde allí.
Y para poder medir el voltaje y corriente en funcionamiento se me ocurrio utilizar un dispositivo que tenia llamado "USB Charger Doctor" que sirve como tester justamente de los puertos USB:

ImageImage
ImageImage

Por suerte las pruebas salieron bien, como se puede ver en este video (hacer click para reproducir):

Image

Una cuestión a considerar es que si el cable USB viene mallado, NO hay que unir esos extremos porque puede provocar un cortocircuito por la diferencia de voltaje que se forma.
Solamente unir el rojo de un cable con el negro del siguiente y usar los extremos libres rojo y negro para alimentación de Raspiboard y los cables blanco y verde tampoco unirlos ni usarlos.

Otra cuestión que se agregó es que la rueda direccional no funcionaba muy bien, provocaba bastante fricción y ruido y que el robot no se deslizara tan bien.
Así que decidí comprar otra rueda direccional pero como la de los carritos de supermercado, que se pudiera desplazar mas suavemente y no provocara mal funcionamiento, tal como había visto en la misma pagina de Raspirobot:

ImageImage

Conseguí además y pensando que podia llegar a necesitar mas adelante otros cargadores, un par de color azul que harían juego con el chasis. Los puse entonces en la parte posterior, emulando quizá un par de propulsores...

ImageImage
ImageImage

Pero la idea no fue tan bien ya que al tener mas peso en la parte posterior, al arrancar se elevaba haciendo un "willy", así que volví a la idea original de ponerlos adelante aunque sobresaliendo los conectores USB, lo que provocaba otro problema al pasar por lugares reducidos porque chocaban e incluso a veces se desconectaban...

Nueva idea: agregar un par de detectores para que si los USB llegaban a chocar, el robot girara hacia el lado contrario y siguiera. Para eso usaría unos microswitch, también como el que figura en la foto de la pagina de Raspirobot, pero atornillados a los conectores USB y conectados al los terminales SW1 y SW2 de la placa:

ImageImage

Con un par de funciones "if" agregadas al programa "rover_avoiding.py" bastaría para la detección y movimiento, las variables "reverse time" y "motor_speed" se deberían definir previamente, ajustando los valores para que el giro no sea muy brusco:

Code: Select all

reverse_time = 0.3
motor_speed = 0.4

if rr.sw1_closed():
        rr.set_led1(True)
        rr.set_led2(True)
        rr.reverse(reverse_time, motor_speed)
        rr.set_led1(False)
        rr.set_led2(True)
        rr.right(reverse_time, motor_speed)
if rr.sw2_closed():
        rr.set_led1(True)
        rr.set_led2(True)
        rr.reverse(reverse_time, motor_speed)
        rr.set_led1(True)
        rr.set_led2(False)
        rr.left(reverse_time, motor_speed)
Y ya que estaba se me ocurrio bajar el HC-SR04 y conectarlo con extensiones de cable al protoboard para acercarlo mas al frente y probarlo en una ubicación mas centrado:

ImageImage
ImageImage

Ahora se agregaron entonces estos materiales:

1- Cargadores USB para celular (2) de 5v 2600 mAh (pueden ser de menos mAh, pero la carga va a durar menos)
2- Cable USB con conectores macho-macho (no muy largo, cortarlo a medida si hace falta)
3- Rueda direccional giratoria (comúnmente conocida "para robot Arduino")
4- Separadores (2) y tornillos (4) para asegurar la rueda direccional (se pueden usar mas cantidad dependiendo del chasis)
5- Microswitch pulsador con palanca (2) (puede ser también de los de sensor de choque o fin de carrera)
6- Tornillos (4) para asegurar los microswitch al los conectores USB (tienen que ser muy chiquitos y algo largos)
7- Tester de puerto USB Charger Doctor (opcional, es para medir la corriente en funcionamiento si se tienen dudas)
8- Cinta aisladora para aislar los contactos del cable USB (puede ser espagueti termocontraíble)
9- Aisladores de goma (4) para evitar el roce o movimiento de los cargadores (opcional, se pueden usar bridas para ajustarlos)
10- Separadores de motherboard (4) para encastrarlos por dentro del chasis y sostener mejor el cable USB (opcional, se pueden usar bridas)

Nota: los cargadores USB se podrían reemplazar por una batería LiPo de 7.2V como minimo, tal como se menciona en la pagina de Raspirobot, pero son mas caras y necesitan un cargador especial.

Cantidad de errores cometidos: 3

- Utilizar pilas, lo que no da mucho tiempo de autonomía y encima de las comunes pensando que iban a durar mas.
- No aislar bien las conexiones, en un momento toqué sin querer un borde del mallado del cable USB y provoque un cortocircuito, aunque sin consecuencias permanentes.
- No estañar los terminales sueltos y al colocarlos en el protoboard no hacían buen contacto provocando desconexión de los motores.

Re: [Proyecto] Robot Rover + Cargador USB + Camara

Posted: Tue Dec 29, 2015 1:39 am
by actkk2000
Actualización con Camera Module (Raspicam) e Interfaz Web:

Habiendo probado algo de autonomía y un control básico, me pareció buena idea extender el alcance del control agregando una cámara para poder navegar incluso a distancia.
Eso presentaba entonces dos problemas: que cámara debería usar y como conectarme para manejar el robot remotamente.

Había visto varios proyectos que hablaban al respecto y que eran muy interesantes:
https://translate.google.com.ar/transla ... &sandbox=1
https://www.youtube.com/watch?v=ueci6U2N4ec
http://trouch.com/2013/03/04/webiopi-in ... -tutorial/
http://translate.google.com/translate?h ... pibot-b%2F
http://diyhacking.com/raspberry-pi-webcam-robot/
http://ianrenton.com/hardware/raspberry-tank/
http://timcorrigan.com/raspberry-pi-tra ... #more-1492

El primero y el ultimo eran de los que mas se aproximaban a lo que buscaba porque utilizan Raspirobot.
Así que los tome con la idea de utilizar una vieja webcam Logitech Quickcam que tenia, aunque había perdido la base le haría un soporte improvisado con un tubito de los que se usan para pasar cables por la pared:

ImageImage

Por desgracia ese modelo no esta plenamente soportado por Raspberry, de hecho las fotos que sacaba con raspistill (https://www.raspberrypi.org/documentati ... pistill.md) eran un desastre, si es que llegaba a tomarlas. Y ni hablar de obtener video...:

ImageImageImage

Así que viendo las opciones y teniendo en cuenta la versatilidad que ofrecería decidi que mejor iria por la Raspicam en vez de una Webcam HD o semejante que además del peso agregado y la dificultad de enrollar el cable USB, me iba a costar similar.
Siempre considere algo superfluo lo de la cámara porque no me dedico a la fotografía, pero en este caso iba a valer la pena.

La presentación del equipo es muy buena, haciendo juego perfectamente con la línea Raspberry Pi:

ImageImage

Ahora quedaba como agregarla al conjunto, lo mejor que pude improvisar fue tomar una de las partes de un kit Dagu Pan Tilt y usarlo como soporte, ya que por el tamaño y disposición de los componentes ya no me que daba mucho lugar para montarlo entero, además de tenerlo incompleto y no contar con los servos en ese momento:

ImageImage

Tome un par de separadores de plástico de los que se usan para motherboards de PC, los recorte un poco para adaptarlos y los use para sujetar con tornillos la cámara sobre el soporte metálico:

ImageImage

Luego moviendo a un costado la RPI2 quedo algo de lugar para poner al lado el soporte en forma vertical.
Tampoco es que tenia mucha mas opciones ya que el cable plano que trae por defecto la cámara es muy corto, y para que pudiera llegar a conectarlo debía poner la cámara lo mas cerca posible de la RPI2:

ImageImage

Respecto del software, lo mejor para transmitir una señal de video seria utilizar como se menciona en la mayoría de los links anteriores, mjpg-streamer pero en su version para Raspicam:
https://github.com/jacksonliam/mjpg-streamer

Como el cable era corto tuve que poner la cámara en forma "invertida", asi que había que agregar el parámetro "-rot 180" en la línea del programa para que quede del derecho. Tampoco le di mucha resolución ni fps para no consumir mucho ancho de banda:

Code: Select all

LD_LIBRARY_PATH=/opt/mjpg-streamer/ /opt/mjpg-streamer/mjpg_streamer -i "input_raspicam.so -fps 10 -q 80 -x 640 -y 480 -rot 180" -o "output_http.so -p 8080 -w /opt/mjpg-streamer/www"
Respecto de como controlar a distancia el robot, lo mejor que se ajustaba era Webiopi (http://webiopi.trouch.com/) que es una interfaz web que trabaja con Python.
En el enlace de Tim Corrigan que puse al comienzo, estaba el código que adaptaba los comandos de la librería de Raspirobot con Webiopi, así como también la interfaz en html para manejarlos desde el programa que llamé "cambot.py".
Con un par de modificaciones para poder controlar la velocidad y que se enciendan los leds en forma acorde con el movimiento quedo algo asi:

Code: Select all

motor_speed1 = 0.72
motor_speed2 = 0.7
motor_speed3 = 0.5
motor_speed4 = 0.5
motor_speed5 = 0.6
turn_time = 0
def reverse():
#	rr.reverse()
	rr.reverse(turn_time, motor_speed5)
	rr.set_led1(True)
	rr.set_led2(True)
def forward():
#	rr.forward()
	rr.set_motors(motor_speed2, 0, motor_speed1, 0)
	rr.set_led1(True)
	rr.set_led2(True)
def stop(): 
	rr.stop()
	rr.set_led1(False)
	rr.set_led2(False)
def left(): 
#	rr.left()
	rr.right(turn_time, motor_speed3)
	rr.set_led1(True)
	rr.set_led2(False)
def right(): 
#	rr.right()
	rr.left(turn_time, motor_speed4)
	rr.set_led1(False)
	rr.set_led2(True)
La diferencia entre la velocidad de los motores se debe a que no funcionan igual, y la corrección se debe hacer en este caso en forma manual, de lo contrario se inclina siempre hacia algún lado. El calibrado por lo tanto es de acuerdo a prueba y error...
También modifique el archivo "index.html" dejando solo el recuadro con el video y debajo las controles de dirección.
Como nota aclaratoria, solo funciono correctamente en el browser Chrome (hacer click para ampliar):

Image
Aquí ya se presentaba la necesidad imperiosa de utilizar un dongle Wifi para poder conectarse remotamente al robot sin necesidad de usar cable de red.
Yo tenia uno genérico con chip que supuestamente era compatible con Raspberry Pi, uno como este: http://www.diygadget.com/802-11b-g-n-na ... t5370.html, pero por algún motivo no me funcionaba bien con RPI2, se cortaba la conexión a cada rato.
Tuve que comprar uno nuevo pero que fuera compatible con la instalación Wifi que tenia, y encontré este barato y con las instrucciones y drivers de la pagina en el foro de Raspberry funciono perfectamente:
Image
viewtopic.php?f=28&t=62371&start=650

El listado de materiales para completar esta parte fue:

1- Raspberry Camera Module (Raspicam, incluye cable plano de conexion)
2- Soporte metalico de kit Dagu Pan Tilt para colocar la cámara (puede ser cualquier soporte, incluso de plástico)
3- Separadores (2) de motherboard y tornillos (2) (puede ser también cualquier separador o tornillos plásticos)
4- Tornillo metálico y tuerca para ajustar el soporte al chasis (se puede usar sino una brida de plástico)
5- Dongle Wifi TP-Link TL-WN752N-02 (puede ser cualquier dongle que soporte al menos 150 mbps)

Nota: En lugar de la Raspicam se puede utilizar cualquier cámara web que sea compatible con Rasperry Pi, aunque ocuparía mas espacio, especialmente el cable USB.
Para ver mas acerca de compatibilidad con webcams ir aquí: http://elinux.org/RPi_USB_Webcams

Cantidad de errores cometidos: 3

- Intentar utilizar una webcam que tenia tirada hace años y que ya no es compatible ni siquiera con Windows Vista.
- Intentar utilizar un dongle Wifi chino genérico que no permitía una buena conexión o la mayoría de las veces ni andaba.
- Comprar un par de motores extra para probar de cambiarlos y/o combinarlos para ver si funcionaban a la misma velocidad.
No resultó, el problema es que no hay dos motores DC baratos iguales...

Re: [Proyecto] Robot Rover + Cargador USB + Camara + Sensore

Posted: Tue Dec 29, 2015 5:43 am
by actkk2000
Actualización con Luces Frontales y Sensores de obstáculos:

Para aprovechar los conectores Open Collector (OC1 y OC2) de la placa Raspirobot, se me ocurrió agregarle un par de leds blancos para utilizarlos como iluminación en caso que la luz no fuera suficiente o incluso estuviera oscuro, conectados al frente en el protoboard.
Ya había visto eso mismo en la pagina de Tim Corrigan:
http://timcorrigan.com/raspberry-pi-tra ... headlight/

ImageImage

Para que enciendan hay que poner en 1 el estado de los OC y en 0 para apagarlos, y lo hice de esta forma en el programa "rover_avoiding.py" usando por ejemplo el horario entre las 20 y las 8 am:

Code: Select all

import datetime
now = datetime.datetime.now()
hour = now.hour
hour1 = int(hour / 10)
hour2 = hour % 10
   if (hour1 >= 2 and hour2 >= 0) or (hour1 == 0 and hour2 <= 8):
        rr.set_oc1(1)
        rr.set_oc2(1)
    else:
        rr.set_oc1(0)
        rr.set_oc2(0)
En el caso de querer agregarlo a la interfaz Web tenia que hacerlo como lo indicaba Tim Corrigan en el otro enlace, http://timcorrigan.com/raspberry-pi-tra ... #more-1492, salvo que como yo tenia 2 leds podia controlarlos independientemente e incluso hacerlos parpadear:

Code: Select all

def lightr_on():
        rr.set_oc1(1) 
def lightr_off():
        rr.set_oc1(0)
def lightr_flash():
        lightr_on()
        webiopi.sleep(0.25)
        lightr_off()
        webiopi.sleep(0.25)
def lightl_on():
        rr.set_oc2(1)
def lightl_off():
        rr.set_oc2(0)
def lightl_flash():
        lightl_on()
        webiopi.sleep(0.25)
        lightl_off()
        webiopi.sleep(0.25)
def lights_on():
        rr.set_oc1(1)
        rr.set_oc2(1)
def lights_off():
        rr.set_oc1(0)
        rr.set_oc2(0)
def lights_flash():
        lights_on()
        webiopi.sleep(0.25)
        lights_off()
        webiopi.sleep(0.25)
De paso agregue otras funciones para intentar iniciar el modo automático como estaba en este enlace: http://ianrenton.com/hardware/tank-day- ... rson-view/ donde se ve un modo "Autonomy" y también resetear y apagar la RPI2 desde allí:

Code: Select all

import os
def auto():
        os.system("sudo python /home/pi/rover_avoiding.py")
def restart():
        os.system("sudo reboot")        
def turnoff():
        os.system("sudo halt -p")
También había que modificar "index.html" para agregar los botones correspondientes y los agrande un poco para fueran mas fáciles de acceder desde la pantalla de un celular, que debería estar conectado por Wifi a la red. El celular tiene que funcionar con Android para que se pueda usar Chrome y poder acceder a la pagina web del robot.
Pero por desgracia el modo automático aunque funciona se superpone con el programa "cambot.py" que esta corriendo y lo que hace es que funcione mas lento el movimiento del robot, pero no lo considero error sino una limitación del programa porque no pueden anularse el uno al otro.

La interfaz resultante seria esta (hacer click para ampliar):

Image

Para seguir complicando las cosas, también agregué un par de sensores infrarrojos de obstáculos, lo que permitiría detectar objetos a cierta distancia según la calibración que se hiciera en los sensores con el potenciómetro incorporado.
Estos sensores debía colocarlos al frente pero apuntando hacia los costados a 45 grados aproximadamente, para tener una vision en diagonal que no podia abarcar con el sensor ultrasónico y aprovechando los soportes de la rueda direccional.

Como esperaba continuar agregando sensores y me imaginaba que iba a sobrecargar los cargadores, me vi forzado a agregar un cargador extra de los que tenia, o mejor dicho cambie el orden y puse los blancos para la alimentación principal (porque eran de mejor calidad y duraban mas tiempo) y uno de las azules sujeto al soporte de la cámara con una brida.
De esta forma no solo quedaba estéticamente bien, sino que además me permitiría intercambiar cargadores cuando se agotara el que estaba en funcionamiento:

Image Image Image

Para conectar el Cargador USB hacia falta otro cable USB, pero solamente un extremo, dejar libres solamente los terminales rojo y negro y conectarlos a otro de los rail del protoboard, y por supuesto aislar el resto de terminales y mallado.
Y aunque los sensores de obstáculos tienen una salida de 5V, medi la entrada de voltaje en los puertos GPIO y allí se detectaban unos 3.6V, por suerte dentro de los limites de tolerancia de GPIO...

Para que los detectores de obstáculos funcionen se deben definir un par de pines GPIO como entrada en el programa "rover_avoiding.py" y leer el estado para determinar el movimiento, utilizando las variables definidas anteriormente para velocidad.
Ademas con este seteo se evitan mensajes de conflicto en caso que el puerto ya este pre asignado: "GPIO.setwarnings(False)"

Code: Select all

GPIO.setmode(GPIO.BCM) # Set pin numbering to board numbering
GPIO.setwarnings(False) # turn off warning
GPIO.setup(06, GPIO.IN) # Setup pin 33 as an input
GPIO.setup(12, GPIO.IN) # Setup pin 32 as an input

    if not (GPIO.input(12)): # Setup an if loop to avoid obstacles
        rr.set_led1(True)
        rr.set_led2(True)
        rr.reverse(reverse_time, motor_speed5)
        rr.set_led1(False)
        rr.set_led2(True)
        rr.right(reverse_time, motor_speed3)
    if not (GPIO.input(06)): # Setup an if loop to avoid obstacles
        rr.set_led1(True)
        rr.set_led2(True)
        rr.reverse(reverse_time, motor_speed5)
        rr.set_led1(True)
        rr.set_led2(False)
        rr.left(reverse_time, motor_speed4)
Aquí un par de fotos con las luces apagadas y luego encendidas antes de comenzar la diversion a pleno:

ImageImage

Y estos videos mostrando tanto el funcionamiento de las luces, la interfaz Web mejorada desde celular y como robot automata evitando chocar con los objetos de alrededor.
Por fin "Norman" ya estaba mejor encaminado :lol: ...:

Image Image Image

Listado de materiales que se usaron para esta parte, además de los ya disponibles:

1- Leds blancos de 5 mm (2) (Se pueden utilizar también de 3 mm pero la iluminación sera un poco menor)
2- Resistencias de 220 ohm (2) para conectar los leds (pueden ser de mayor valor, dependiendo del brillo deseado)
3- Portaleds de 5 mm (2) para cubrir los leds (opcional)(también se pueden utilizar para fijarlos al chasis)
4- Sensores infrarrojos de obstáculos con salida digital (2) (No confundir con sensores infrarrojos de línea)
5- Cargador USB adicional de al menos 2000 mAh (puede ser de mayor carga si se quiere, pero no tamaño)
6- Cable USB con un conector macho (se puede cortar uno que tenga en el otro extremo otro tipo de ficha)
7- Brida plástica (precinto) para ajustar el Cargador USB al soporte (mejor comprar una bolsa que trae 100)

Nota: Si se utilizó una batería LiPo el cargador USB adicional no seria necesario, pero si un regulador o conversor de voltaje de 5V para alimentar los sensores.

Cantidad de errores cometidos: 4

- Al principio no tomé en cuenta las resistencias y conecté los leds de iluminación directamente a los colectores, funcionaron bien por un tiempo pero luego empezaron a fallar y tuve que reemplazarlos.
- Los leds quedaron alejados de la cámara por lo que no resultaron muy efectivos, salvo cuando están cerca de los objetos.
- El soporte para la cámara Web quedó del lado incorrecto de forma tal que al adosarle el cargador USB adicional, dificulta la vision para conectar el cable HDMI y la alimentación en el conector micro USB para hacer pruebas.
- Los sensores de obstáculos quedan sobresaliendo un poco y suelen golpearse, provocando a veces que se "tilden" y corriendo el riesgo de eventualmente romperse.

Re: Robot Rover + Cargador USB + Camara + Sensores + Encoder

Posted: Thu Dec 31, 2015 1:22 pm
by actkk2000
Actualización con Encoders y Módulo Sensor de Ruedas:

Como mencioné anteriormente, existe una diferencia inherente en los motores DC que provoca que el robot no vaya completamente derecho en todo momento, sino que se va inclinando ligeramente hacia un lado. Eso se ve influido además por varios factores, como la distribución del peso en el chasis, la fricción que ofrece la superficie donde se desplaza, la posición que adopta la rueda direccional al moverse, pelusas y pelos que se pueden ir acumulando en los ejes, etc...

Cabe aclarar que esta diferencia puede llegar a ser minima o incluso imperceptible, quizá no haga falta hacer nada, o puede ser bastante notoria.
En principio la forma mas sencilla de solucionarlo seria haciendo una calibración manual de la velocidad de los motores, utilizando lo que se conoce como PWM (pulse-width modulation, modulación por ancho de pulsos) que en definitiva la placa Raspirobot V2 soporta y que se expresa en la librería que utiliza como una fracción de la velocidad máxima que es 1 (uno).

Así cada motor puede tener una velocidad distinta como podría ser por ejemplo 0.7 y 0.75, indicando que el motor derecho necesita ir un poco mas rápido que el izquierdo para compensar esa falta de impulso que necesita para mantenerse derecho.
Pero esta corrección manual no es muy precisa ya que además de ser resultado del método de "prueba y error", solo funcionaria bajo ciertas circunstancias, las cuales al cambiar un poco requerirían una nueva calibración.

Para hacer una corrección automática es que se utilizan entonces lo que se conoce como "encoders", que son unos sensores que se colocan en las ruedas para medir la velocidad, e incluso se pueden llegar a utilizar para determinar posición y ángulo de giro. Teóricamente monitoreando estos sensores se podría entonces ir determinando la diferencia de velocidad entre cada motor y de ser necesario aplicar una corrección para solucionarlo.

En las fotos de la pagina de Raspirobot había visto estos encoders (son las pequeñas rueditas (2) con muchos agujeritos) en el kit que venden. Sobre esas rueditas se colocarían entonces los sensores ópticos de horquilla, que también son infrarrojos y lo que hacen es leer cuando pasa el haz a través de los agujeritos para ir contándolos, algo así:
http://www.pridopia.co.uk/pi-motor-encoder.html

Image Image

Pero en el kit Dagu Magician Chassis esas rueditas no venían, sino unos engranajes o sprockets (piñones) que cumplirían la misma función de encoders (algo que descubrí aquí: http://forum.dawnrobotics.co.uk/viewtop ... f=5&t=1269) pero utilizando una placa especialmente diseñada para leerlos llamada "Magician Chassis Wheel Encoder" de Sparkfun que aparentemente viene en su producto Redbot:
https://www.sparkfun.com/products/12617
https://learn.sparkfun.com/tutorials/re ... el-encoder
https://forum.sparkfun.com/viewtopic.php?f=14&t=39951

Image Image

Cuando conseguí este modulo (por fortuna en forma local), procedí a conectarla sobre el chasis en el espacio que tenia destinado y siguiendo las indicaciones de la serigrafía, VCC y GROUND a la alimentación y OUTA y OUTB a un par de pines GPIO para usarlos como entrada de datos, de forma tal que quedara montado así en el espacio destinado:

ImageImage

Por desgracia al intentar leer esos datos con el programa "WheelEncoder.py" que baje del enlace de la pagina pridopia.co.uk que puse anteriormente, vi que no eran para nada precisos ni confiables, quizá por la forma en que hace la lectura que es determinando el rebote de la señal infrarroja a través de los dientes de los engranajes, o por el color de los engranajes (azul en vez de rojo) nunca contaba la cantidad de vueltas como se suponía, así que no iba a poder usar los encoders para hacer andar siempre derecho el robot...

Pero por suerte no estaba todo perdido. Aun había algo para lo cual estos "pseudo" encoders si podían funcionar.
Y es que tal vez no servían para leer datos con precision, pero al menos si leían datos todo el tiempo, aunque fueran dispares o erróneos, al menos si marcaban cantidades de vueltas, sean verdaderas o no.
Eso quería decir que al menos podia determinar si el vehículo estaba en movimiento o no, y en caso que no lo estuviera hacer algo al respecto.

Tomando entonces como base "WheelEncoder.py" y haciéndole modificaciones logré agregarlo a "rover_avoiding.py" para que si el robot se veía detenido por el motivo que fuera, como por ejemplo un choque frontal con un objeto no detectado , es decir, cualquiera de las ruedas deje de girar, entonces retroceda y gire hacia algún costado para continuar y de esa manera dejar de preocuparme que se quede trabado en ningún lado:

Code: Select all

Encode1 = 19
Encode2 = 26
GPIO.setup(Encode1, GPIO.IN, GPIO.PUD_UP)
GPIO.setup(Encode2, GPIO.IN, GPIO.PUD_UP)

motor_speed5 = 0.5
CurRot = 0
Rev = 0
Last1 = 0
Last2 = 0
In = 0
Running = 1
timer1 = 0
timer2 = 0

def Wheel1():
        global Rotate
        global CurRot
        global timer1
        CurRot = CurRot + 1
        if CurRot == 1:
                CurRot = 0
                timer1 = 0

def Wheel2():
        global Rotate
        global CurRot
        global timer2
        CurRot = CurRot + 1
        if CurRot == 1:
                CurRot = 0
                timer2 = 0

    timer1 = timer1 + 1
    if timer1 > 20:
        rr.set_led1(True)
        rr.set_led2(True)
        rr.reverse(reverse_time, motor_speed5)
        turn_randomly()
        timer1 = 0
    In1 = GPIO.input(Encode1)
    timer2 = timer2 + 1
    if timer2 > 20:
        rr.set_led1(True)
        rr.set_led2(True)
        rr.reverse(reverse_time, motor_speed5)
        turn_randomly()
        timer2 = 0
    In2 = GPIO.input(Encode2)
    if GPIO.input(Encode1) == 0 and Last1 == 1:
        Wheel1()
    Last1 = In1
    if GPIO.input(Encode2) == 0 and Last2 == 1:
        Wheel2()
    Last2 = In2
Quizá no fue la utilización mas inteligente de los encoders, ni la mas elegante, pero si considero que fue la mas ingeniosa, ya que he visto en YouTube numerosos casos de robots que al chocar y no poder seguir avanzando, el usuario tiene que acercarse hasta donde está y reubicarlo manualmente para que siga su camino. Si el rover estuviera muy alejado habría que ir hasta el sitio y arreglarlo, y que pasaría entonces si ese sitio fuera otro planeta, como Marte por ejemplo ;) (soñar no cuesta nada).

Lista de materiales para esta parte:

1- Placa módulo "Magician Chassis Wheel Encoder" (También conocida como "SparkFun RedBot Sensor - Wheel Encoder")
2- Tornillos (2) y tuercas (2) (Ya vienen incluidos con el módulo)

Nota: Se podrían utilizar sensores ópticos de horquilla, para el caso de chasis con encoders incorporados.

Cantidad de errores cometidos: 2

1- Comprar un modulo que ya había leído en el link de dawnrobotics.co.uk que puse mas arriba no era muy bueno, pero hice caso omiso:
in reality they don't work very well. Firstly, the hardware seems to be a bit tempermental and misses ticks
2- Pedir ayuda en la pagina de Monkmakes (http://www.monkmakes.com/rrbv2), ya que la respuesta no solo no me aporto nada nuevo, sino que además Simon tardó casi 2 meses en responder:
As far as the code goes, to move forward straight, you will need to count pulses from both sides and if on count starts to get bigger than the other, reduce the speed of the motor that’s going too fast.

Re: Robot Rover + Cargador USB + Camara + Sensores + Encoder

Posted: Sun Jan 03, 2016 9:51 pm
by actkk2000
Actualización con Sensores de Línea y de Intensidad de luz:

Ya inmerso en el mundo de los sensores, había una funcionalidad que quería implementar que había visto también numerosas veces que es la de "line follower" o seguidor de línea, es decir que utilizando justamente unos sensores infrarrojos el robot sea capaz de seguir un camino predeterminado en forma de pista o guía hecha en el suelo.

Los que mas me inspiraron fueron los videos de unos chicos de colegio y Pibot-A:
http://euxton.mediacore.tv/media/line-f ... -using-inf
http://euxton.mediacore.tv/media/raspbe ... using-ultr
http://euxton.mediacore.tv/media/round- ... the-garden
http://www.retas.de/thomas/raspberrypi/pibot-a/

Estos "line follower sensor" (tcrt5000) funcionan de la misma manera que los de obstáculos, incluso tienen potenciómetro para regular la sensibilidad, pero están diseñados para ser colocados apuntando hacia el piso y poder diferenciar entre color blanco y negro y enviar así una señal distinta para cada uno. Utilizando estas señales (1 ó 0), se podría ir guiando al robot para que se mantenga siempre en curso siguiendo una línea o camino dibujado.

Image Image

Tenia varios links al respecto e intentaría adaptar algo a mi diseño y usando la librería de Raspirobot:
viewtopic.php?p=490834#
viewtopic.php?f=37&t=63760
https://github.com/ZacharyIgielman/pi2g ... ollower.py
viewtopic.php?f=37&t=66782&p=488948#p488948

Empecé el montaje en la parte que encontré destinada para estos sensores, pero allí me di cuenta que a diferencia de los robots que había visto, en el mío los sensores quedaban en la parte trasera...
Y es que la placa Raspirobot estaba diseñada para llevar la rueda direccional adelante, pero el resto de los robots rover no...

ImageImage
ImageImage

Se necesitan solo dos sensores, uno izquierdo y otro derecho, para mantener el vehículo siguiendo la línea, pero además un tercer sensor en el medio para regresarlo a la pista si se salía de curso.
El problema es que los sensores iban a tener que guiar desde atrás, lo cual no sabia si iba a servir...

Otro inconveniente era el tipo de superficie usar para dibujar las líneas. Había visto que se podían imprimir en papel con impresora laser: http://robotsquare.com/2012/11/28/line-following/, pero lo mejor seria usar cinta aisladora negra, que por su composición serviría perfectamente para rebotar la señal infrarroja y además podría adaptarla para darle la forma que quisiera al circuito.
Para que tuviera suficiente superficie de detección lo mejor seria usar una cinta ancha del doble de lo normal, de 3.5 cm y por suerte el piso donde lo usaría es blanco.

Por supuesto esta primera aproximación con los sensores atrás no funcionó. Los sensores debían ir por delante para guiar el movimiento de los motores, de forma tal que cuando uno de los sensores detecta la línea, el motor del lado opuesto debe impulsar mas para corregir el rumbo y el del lado que detecta aminorar la marcha, así que no quedó otra que invertir los sensores apuntando hacia afuera y hacer que el robot vaya hacia el otro lado, pero de todas maneras no importaba tanto el sentido en que fuera, lo incomodo era tener que cambiar el orden de los cables cada vez que usara los sensores de línea...

Aunque en realidad quedaba mucho mejor así , aquí se ve la diferencia entre una orientación y la otra:

Image Image

De nuevo había que usar PWM pero empece usando como guía el código de Zachary Igielman que puse anteriormente, identificando los pines correspondientes a los controles de los motores que figuran en la pagina de Raspirobot:

Code: Select all

GPIO.setup(17, GPIO.OUT)
p=GPIO.PWM(17, 100)
p.start(0)
GPIO.setup(10, GPIO.OUT)
q=GPIO.PWM(10, 100)
q.start(0)
GPIO.setup(4, GPIO.OUT)
a=GPIO.PWM(4, 100)
a.start(0)
GPIO.setup(25, GPIO.OUT)
b=GPIO.PWM(25, 100)
b.start(0)

fast = 60
low = 50

while True:
                  if globalstop==1:
                          a.ChangeDutyCycle(0)
                          b.ChangeDutyCycle(0)
                          p.ChangeDutyCycle(0)
                          q.ChangeDutyCycle(0)
                          rr.set_led1(False)
                          rr.set_led2(False)
 #                          print('stop')
                  elif GPIO.input(16)==0 and GPIO.input(21)==0 and GPIO.input(20)==1:
                          p.ChangeDutyCycle(fast)
                          q.ChangeDutyCycle(fast)
                          b.ChangeDutyCycle(0)
                          a.ChangeDutyCycle(0)
                          rr.set_led1(True)
                          rr.set_led2(True)
#                          print('straight')
                  elif GPIO.input(16)==1:
                          q.ChangeDutyCycle(low) 
                          p.ChangeDutyCycle(0) 
                          a.ChangeDutyCycle(low) 
                          b.ChangeDutyCycle(0) 
                          rr.set_led1(False)
                          rr.set_led2(True)
#                          print('right')
                  elif GPIO.input(21)==1:
                          q.ChangeDutyCycle(0) 
                          p.ChangeDutyCycle(low) 
                          a.ChangeDutyCycle(0) 
                          b.ChangeDutyCycle(low) 
                          rr.set_led1(True)
                          rr.set_led2(False)
#                          print('left')

time.sleep(0.2)
De esta forma si funcionó, aquí pongo las de algunos circuitos que probé y un video del funcionamiento al final (hacer click para ver) donde activo el programa conectado por celular con SSH instalado en Android, pero también se ve un balanceo del vehículo que no me gustaba mucho porque podia alterar el rumbo:

ImageImage
Image Image

De todas maneras no estaba conforme porque no estaba utilizando la librería de Raspirobot para esta parte, al menos no para el manejo de los motores, los cables de alimentación de los motores quedaban al revés también, y no usaba el sensor de línea central, lo que me forzó a hacer varios cambios.

Por un lado invertir los cables permanentemente, forzándome también a cambiar los programas "rover.py" y "rover_avoiding.py" para interpretar lo que era reversa ahora como ir hacia adelante, lo mismo que lo que era izquierda y derecha.
Por otro lado logre hacer uso de la librería para controlar la velocidad de los motores y que sirvieran para seguir la línea, incluso con un movimiento mas suave sin balanceo, y finalmente pude hacer lectura del sensor central usando como guía el programa de Pibot-A, para que si no detectaba la línea intentara moverse para volver a ella.

Todo esto en un programa llamado "line_follower.py" con el único inconveniente que al activarlo, el robot va en sentido contrario hacia atrás, pero eso es insalvable en este punto...
También tiene la función "try" que sirve para salir con "Ctrl + C" sin forzar la interrupción del programa, sino que lo detiene:

Code: Select all

import RPi.GPIO as GPIO, sys, random, time
from rrb2 import *

#use physical pin numbering
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

#set up digital line detectors as inputs
GPIO.setup(16, GPIO.IN)
GPIO.setup(21, GPIO.IN)
GPIO.setup(20, GPIO.IN)

#define variables
rr = RRB2()
rr.set_oc1(0)
rr.set_oc2(0)
max_ingap = 100
ingap = 0
speed = 0.65
turn_time = 0.2
motor_speed1 = 0.5
motor_speed2 = 0.1
motor_speed3 = 0.6
motor_speed4 = 0.55

def turn_around():
    if random.randint(1, 2) == 1:
        rr.set_led1(True)
        rr.set_led2(False)
        rr.left(turn_time, speed)
    else:
        rr.set_led1(False)
        rr.set_led2(True)
        rr.right(turn_time, speed)

print " "
print "Press 'Ctrl + C' to exit"
print " "

rr.forward(turn_time, speed)

try:
       while True:
                  distance = rr.get_distance()
                  globalstop=0

                  if GPIO.input(16)==0 and GPIO.input(20)==0 and GPIO.input(21)==0:
                          ingap = ingap + 1
                          if ingap > max_ingap:
                            turn_around()
                  elif GPIO.input(20)==1:
                            ingap = 0
                  if globalstop==1:
                          rr.set_led1(False)
                          rr.set_led2(False)
                          rr.stop()
 #                          print('stop')
                  elif GPIO.input(16)==0 and GPIO.input(21)==0 and GPIO.input(20)==1:
                          rr.set_led1(True)
                          rr.set_led2(True)
                          rr.set_motors(motor_speed3, 0, motor_speed4, 0)
#                          print('straight')
                  elif GPIO.input(16)==1:
                          rr.set_led1(False)
                          rr.set_led2(True)
                          rr.set_motors(motor_speed2, 0, motor_speed1, 0)
#                          print('right')
                  elif GPIO.input(21)==1:
                          rr.set_led1(True)
                          rr.set_led2(False)
                          rr.set_motors(motor_speed1, 0, motor_speed2, 0)
#                          print('left')

       time.sleep(0.1)
except KeyboardInterrupt:

 print '\n'
Como aditamento, le agregue un sensor de intensidad de luz (o si se quiere lumínico) que serviría para que las luces frontales enciendan cuando no haya suficiente luz ambiente, también regulable con el potenciómetro del sensor, aunque solo lo puse en "rover_avoiding.py" para el modo automático.

Image Image

Code: Select all

GPIO.setup(13, GPIO.IN) # Setup pin 23 as an input

    if (GPIO.input(13)): # Setup an if loop to turn on / turn off lights
        rr.set_oc1(1)
        rr.set_oc2(1)
    else:
        rr.set_oc1(0)
        rr.set_oc2(0)
Materiales agregados al robot en esta parte:

1- Sensores Infrarrojos de Linea (3) (Utilizar la salida digital marcada como "DO", no la que dice "AO" que es analógica)
2- Separadores largos de metal (3) y tornillos (6) para ajustar los sensores al chasis (en mi caso tuve que agregar además unos mas cortos como prolongación y una arandela)
3- Cinta aisladora negra ancha para electricidad (se puede usar una de ancho común pegando una parte al lado de la otra, se utilizaría el doble de cantidad)
4- Sensor de Intensidad de Iluminación (tratar de apuntar el fotorresistor que tiene en un extremo hacia arriba)
5- Separador de plástico y tornillos (2) para ajustar el sensor lumínico al chasis (también puede ser separador de metal)

Nota: Quizá se podría utilizar algún modulo que ya tenga incorporado mas sensores de líneas, como estos:
https://ryanteck.uk/sensors/13-3-way-li ... 07221.html
http://www.robotpark.com/TCRT5000-3-Cha ... sor-Module
http://www.robotpark.com/5-Channel-Line ... e-BFD-1000

Cantidad de errores cometidos: 4

1- El ensamblaje del chasis va en contra del diseño para adosar sensores, que en este caso quedan en la parte trasera y no la frontal como fue pensado.
2- No aplicar desde el comienzo la variación de velocidad de los motores utilizando la librería Raspirobot y así ahorrar tiempo.
3- No acomodar en un principio el fotoresistor hacia arriba, ya que quedaba apuntando hacia abajo no detectando bien la luz.
4- El sensor lumínico queda un poco debajo del cargador USB que suele hacerle sombra alterando la detección de luz, pero no quedaba otro espacio libre donde colocarlo.

Re: Robot + Cargador USB + Camara + Sensores + Encoders + Se

Posted: Tue Jan 05, 2016 5:00 am
by actkk2000
Actualización con ServoMotor y Soporte para Cámara:

Después del fiasco con los encoders y sensores de línea, decidí seguir con otra cosa, y de todas maneras ya no había mucho lugar para mas sensores, tenia usados casi todos los pines...
El próximo paso era agregar un servomotor, aunque sea uno pequeño o "micro servo", que a diferencia de los motores eléctricos comunes, incluye un tercer pin controlador que sirve para manejarlo a voluntad indicándole con pulsos la posición a moverse asi también como la dirección. pero tiene un ángulo limitado de movimiento, que puede ir como máximo entre 0⁰ y 180⁰, aunque es mejor no forzarlo hasta ese punto.

Conseguí el micro servo mas común que hay, Tower Pro SG90 que pesa solo 9g y dependiendo del voltaje de funcionamiento puede ejercer bastante fuerza: http://www.servodatabase.com/servo/towerpro/sg90
Y para probarlo un tester de servomotores que tiene 3 modos de funcionamiento, uno automatico, uno manual con potenciómetro y uno para centrarlo.

Image Image

También conseguí un soporte para el sensor ultrasónico HC-SR04 que utilizaría para montar no solo ese sensor, sino también la Raspicam utilizando otro soporte especifico que conseguí en la pagina de Pimoroni (aunque por ahora solo usaría la parte delantera) así como también un cable de 30 cm para extender la conexión de la cámara:
https://shop.pimoroni.com/products/rasp ... mera-mount
https://shop.pimoroni.com/products/rasp ... mera-cable

Image Image
Image Image
Image Image

Todo esto iría montado de alguna manera al frente usando uno de los soportes del chasis, utilizando la otra parte del kit Dagu Pan Tilt que ya tenia, lo que debería darle una buena base metálica de montaje, aunque quedara un poco al costado:

Image Image
Image Image

Ya que estaba con tantas modificaciones, primero le puse un par nuevo de ruedas negras que le darían mejor aspecto al vehículo:

Image Image
Image Image

Luego poner todo el soporte frontal extendiendo la conexión del HC-SR04 y quitar la Raspicam para cambiar el cable, que se notaba mucho mas largo que el original, y mover la luz derecha hacia el otro lado junto a la izquierda:

Image Image

Para finalmente acomodar el servomotor que no sobresalga, y montar el soporte de Raspicam al frente con el nuevo cable mas largo que ahora si permitía colocarla mas alejada de RPI2 y, también tuve que modificar el parámetro "-rot 270" de mjpg-streamer:

Image Image

También agregue al frente otro sensor infrarrojo de obstáculos por lo que el servomotor iría conectado al único pin GPIO libre que quedaba, pero en la placa Raspirobot que era el 18 (GPIO24), que encontré por descarte analizando la tabla de asignación de pines publicada en la pagina:
The RRB2 uses the following pins:
LEFT_GO_PIN = 17
LEFT_DIR_PIN = 4
RIGHT_GO_PIN = 10
RIGHT_DIR_PIN = 25
SW1_PIN = 11
SW2_PIN = 9
LED1_PIN = 7
LED2_PIN = 8
OC1_PIN = 22
OC2_PIN = 27
OC2_PIN_R1 = 21 (rev 1) or 27 rev 2
TRIGGER_PIN = 18
ECHO_PIN = 23
El próximo paso entonces era ver como controlar el servo. Y lo que primero probe fue el control por software, que también utilizando PWM es capaz de manejar los servomotores.
Utilizando estas guías pude hacer un par de pruebas que mostraban que era posible manejar el servo con Python, con distintas velocidades e incrementos en el movimiento (hacer click en los videos para verlos):
http://fpaez.com/controlar-un-servomoto ... pberry-pi/
http://www.toptechboy.com/raspberry-pi/ ... th-python/
http://raspi.tv/2013/rpi-gpio-0-5-2a-no ... -to-use-it
https://www.youtube.com/watch?v=ddlDgUymbxc

Image Image Image

Y al igual que con las demás funcionalidades agregadas anteriormente, tenia que adaptar el código tanto para el funcionamiento automático como el control por web.
Pero serian diferentes las aplicaciones, ya que para el primer caso lo que haría seria un barrido para detectar obstáculos con el sensor ultrasónico y en el segundo tener la posibilidad de girar la cámara hacia los costados para ver sin tener que girar todo el vehículo.

Tomé la primer idea de este proyecto (que es interesante y quizá tome mas ideas sobre montaje :)): http://www.instructables.com/id/Raspber ... /?ALLSTEPS donde dice que una vez detectado un obstáculo busca, moviendo el servo, el camino mas despejado o libre posible a los costados para ir por allí. También de este otro: http://www.uugear.com/portfolio/use-ras ... t-chassis/
Entonces se agregó bastante código a "rover_avoiding.py":

Code: Select all

GPIO.setup(05, GPIO.IN) # Setup pin 29 as an input

i = 7.5         # Define variable at 7.5 to center servo again
x = 0.1         # Define variable for increment
y = 0.02        # Define variable for pause
l = 0           # Define variables for direction
c = 0
r = 0

def frange(start, stop, step):  # Setup function for floating point counter
    j = start
    while j < stop:
      yield j
      j += step

try:
 while True:
    distance = rr.get_distance()
    if random.randint(1, 2) == 1:      # Define start direction for servo in random way
        h = 0
    else:
        h = 1
    if distance < 20 and running: # Setup an if loop to detect objets with sonar
#        turn_randomly()
     rr.stop()
     rr.set_led1(False)
     rr.set_led2(False)
     # Setup servo for sweeping movement
     if h == 0:
       for j in frange(1, 4, x):
         p.ChangeDutyCycle(i)
         i = i + x
         time.sleep(y)
       if i == 10.5 and distance < 10:
            l = 1
       else:
            l = 0
       for j in frange(4, 10, x):
         p.ChangeDutyCycle(i)
         i = i - x
         time.sleep(y)
       if i == 4.4 and distance < 10:
            r = 1
       else:
            r = 0
       for j in frange(10, 13, x):
         p.ChangeDutyCycle(i)
         i = i + x
         time.sleep(y)
       if i == 7.5 and distance < 20:
            c = 1
       else:
            c = 0
       if c == 1:
        if l == 0 and r == 0:
            rr.set_led1(True)
            rr.set_led2(True)
            rr.forward(reverse_time2, motor_speed5)
            turn_randomly()
        if l == 0 and r == 1:
            rr.set_led1(False)
            rr.set_led2(True)
            rr.left(reverse_time, motor_speed4)
        if l == 1 and r == 0:
            rr.set_led1(True)
            rr.set_led2(False)
            rr.right(reverse_time, motor_speed4)
        if l == 1 and r == 1:
            rr.set_led1(True)
            rr.set_led2(True)
            rr.forward(reverse_time2, motor_speed5)
     else:
       for j in frange(1, 4, x):
         p.ChangeDutyCycle(i)
         i = i - x
         time.sleep(y)
       if i == 4.5 and distance < 10:
            r = 1
       else:
            r = 0
       for j in frange(4, 10, x):
         p.ChangeDutyCycle(i)
         i = i + x
         time.sleep(y)
       if i == 10.6 and distance < 10:
            l = 1
       else:
            l = 0
       for j in frange(10, 13, x):
         p.ChangeDutyCycle(i)
         i = i - x
         time.sleep(y)
       if i == 7.5 and distance < 20:
            c = 1
       else:
            c = 0
       if c == 1:
        if l == 0 and r == 0:
            rr.set_led1(True)
            rr.set_led2(True)
            rr.forward(reverse_time2, motor_speed5)
            turn_randomly()
        if l == 0 and r == 1:
            rr.set_led1(False)
            rr.set_led2(True)
            rr.left(reverse_time, motor_speed4)
        if l == 1 and r == 0:
            rr.set_led1(True)
            rr.set_led2(False)
            rr.right(reverse_time, motor_speed4)
        if l == 1 and r == 1:
            rr.set_led1(True)
            rr.set_led2(True)
            rr.forward(reverse_time2, motor_speed5)

    if not (GPIO.input(05)): # Setup an if loop to avoid obstacles on front
        rr.set_led1(True)
        rr.set_led2(True)
        rr.forward(reverse_time, motor_speed5)
        turn_randomly()
En principio funcionaba bastante bien, se puede ver eso en estos videos (el cable de cargador se debe a que justo se agotó el cargador adicional, y no quería esperar para grabar):

Image Image

Lo propio hice para "cambot.py" e "index.html", cambiando algunos botones para dejar lugar para el manejo del servo. Apretando "L" va directamente hacia la izquierda, "R" para la derecha y "C" para centrar:

Code: Select all

def servo_right():
        p.ChangeDutyCycle(4.5)
        webiopi.sleep(0.25)
def servo_left():
        p.ChangeDutyCycle(10.5)
        webiopi.sleep(0.25)
def servo_center():
        p.ChangeDutyCycle(7.5)
        webiopi.sleep(0.25)
def servo_stop():
        p.stop()

server.addMacro(servo_right)
server.addMacro(servo_left)
server.addMacro(servo_center)
server.addMacro(servo_stop)
Image

Desgraciadamente había algo que no me agradaba, y es un efecto secundario sobre el comportamiento de los servomotores debido al control por software, que se puede apreciar casi al final en el video de YouTube que puse anteriormente (https://youtu.be/ddlDgUymbxc) que es como un temblequeo o "stuttering" (tartamudeo) debido a que el procesador debe atender el movimiento del servo junto con el procesamiento del resto del sistema...

Eso se solucionaría implementando un control por hardware a través de DMA, de los cuales hay varios métodos (sin agregar una placa controladora adicional):
https://learn.adafruit.com/adafruits-ra ... ervo-motor
https://pypi.python.org/pypi/RPIO
https://github.com/richardghirst/PiBits ... rvoBlaster
http://electronut.in/controlling-two-se ... i-model-a/
http://abyz.co.uk/rpi/pigpio/
https://github.com/sarfata/pi-blaster

Desgraciadamente algunos ya no funcionan porque son métodos viejos o no sirven para RPI2 y otros me parecieron un poco complicados... así que me incline por el que mejor reputación tenia que es ServoBlaster, que a pesar de utilizarse por línea de comandos se podia invocar desde Python:
http://cihatkeser.com/servo-control-wit ... s-or-less/
http://slicepi.com/setting-up-servoblas ... pberry-pi/

De nuevo "rover_avoiding.py" debía ser modificado, pero a la vez le agregué algunas mejoras y quedo funcionando mas óptimo:

Code: Select all

#Variables for Servo
min = 85
center = 155
max = 225
t = 0.01 	#Define variable for pause
i = center
dl = 100
dr = 100

try:

 while True:
    distance = rr.get_distance()
    if random.randint(1, 2) == 1:      # Define start direction for servo in random way
        h = 0
    else:
        h = 1
    if not (GPIO.input(05)): # Setup an if loop to avoid obstacles on front
        rr.set_led1(True)
        rr.set_led2(True)
        rr.forward(reverse_time, motor_speed5)
        turn_randomly()
    if distance < 30 and running: # Setup an if loop to detect objets with sonar
     rr.stop()
     rr.set_led1(False)
     rr.set_led2(False)
     # Setup servo for sweeping movement
     if h == 0:
       while i < max:
            os.system('echo 0=+1 > /dev/servoblaster')
            i = i + 1
            time.sleep(t)
       distance = rr.get_distance()
       dl = distance
       if i == max and distance < 30:
            l = 1
       else:
            l = 0
       while i > min:
            os.system('echo 0=-1 > /dev/servoblaster')
            i = i - 1
            time.sleep(t)
       distance = rr.get_distance()
       dr = distance
       if i == min and distance < 30:
            r = 1
       else:
            r = 0
       while i < center:
            os.system('echo 0=+1 > /dev/servoblaster')
            i = i + 1
            time.sleep(t)
       distance = rr.get_distance()
       if i == center and distance < 30:
            c = 1
       else:
            c = 0
       if c == 1:
         if l == 0 and r == 0:
            rr.set_led1(True)
            rr.set_led2(True)
            if dl > dr:
                  rr.set_led1(False)
                  rr.set_led2(True)
                  rr.left(reverse_time, motor_speed4)
            else:
                  rr.set_led1(True)
                  rr.set_led2(False)
                  rr.right(reverse_time, motor_speed4)
         if l == 0 and r == 1:
            rr.set_led1(False)
            rr.set_led2(True)
            rr.left(reverse_time, motor_speed4)
         if l == 1 and r == 0:
            rr.set_led1(True)
            rr.set_led2(False)
            rr.right(reverse_time, motor_speed4)
         if l == 1 and r == 1:
            rr.set_led1(True)
            rr.set_led2(True)
            rr.forward(reverse_time2, motor_speed5)
            turn_randomly()
     else:
       while i > min:
            os.system('echo 0=-1 > /dev/servoblaster')
            i = i - 1
            time.sleep(t)
       distance = rr.get_distance()
       dr = distance
       if i == min and distance < 30:
            r = 1
       else:
            r = 0
       while i < max:
            os.system('echo 0=+1 > /dev/servoblaster')
            i = i + 1
            time.sleep(t)
       distance = rr.get_distance()
       dl = distance
       if i == max and distance < 30:
            l = 1
       else:
            l = 0
       while i > center:
            os.system('echo 0=-1 > /dev/servoblaster')
            i = i - 1
            time.sleep(t)
       distance = rr.get_distance()
       if i == center and distance < 30:
            c = 1
       else:
            c = 0
       if c == 1:
         if l == 0 and r == 0:
            rr.set_led1(True)
            rr.set_led2(True)
            if dl > dr:
                  rr.set_led1(False)
                  rr.set_led2(True)
                  rr.left(reverse_time, motor_speed4)
            else:
                  rr.set_led1(True)
                  rr.set_led2(False)
                  rr.right(reverse_time, motor_speed4)
         if l == 0 and r == 1:
            rr.set_led1(False)
            rr.set_led2(True)
            rr.left(reverse_time, motor_speed4)
         if l == 1 and r == 0:
            rr.set_led1(True)
            rr.set_led2(False)
            rr.right(reverse_time, motor_speed4)
         if l == 1 and r == 1:
            rr.set_led1(True)
            rr.set_led2(True)
            rr.forward(reverse_time2, motor_speed5)
            turn_randomly()
También había que modificar "cambot.py" para utilizar ServoBlaster, salvo que tuve que crear una librería "servo_lib.pyc" que incorporara esas funciones y que se pudiera invocar desde "cambot.py":

Code: Select all

from servo_lib import *

i = 155
min = 85
max = 225
center = 155

def servo_right():
        servo_r()
def servo_left():
        servo_l()
def servo_center():
        servo_c()
def servo_stop():
        servo_s()
Cree la librería fácilmente desde un programa llamado "servo_lib.py " que escribi, con esto: http://effbot.org/pyfaq/how-do-i-create-a-pyc-file.htm
La necesidad de una librería se debía a que ahora podría manejar con precision el movimiento hacia la izquierda o derecha con pequeñas variaciones, pero había que detener el servo para que no pasara de los limites de ángulos hacia los costados, aun cuando siguiera pulsando los botones indefinidamente:

Code: Select all

#!/usr/bin/python

import os

i = 155
min = 85
center = 155
max = 225

def servo_r():
        global i
        global min
        if i > min:
           i = i - 5
           os.system('echo 0=-5 > /dev/servoblaster')
        else:
           os.system('echo 0=-0 > /dev/servoblaster')

def servo_l():
        global i
        global min
        if i < max:
           i = i + 5
           os.system('echo 0=+5 > /dev/servoblaster')
        else:
           os.system('echo 0=+0 > /dev/servoblaster')

def servo_c():
        global i
        global center
        i = center
        os.system('echo 0=155 > /dev/servoblaster')

def servo_s():
        os.system('echo 0=+0 > /dev/servoblaster')
Este es el listado de materiales que se agregaron para construir esta parte:

1- Micro Servomotor Tower Pro SG90 o compatible (incluye tornillos y aletas plásticas para usarlas como base para ajustar a los objetos)
2- Servo Controller & Tester (opcional, pero es muy útil para probar servomotores sin necesidad de conectarlos a la RPI2)
3- Soporte para sensor ultrasónico HC-SR04 (también se puede construir con alguna base de plástico)
4- Tornillos para ajustar el HC-SR04 al soporte (4) (tienen que ser muy pequeños y se necesitan por lo menos 2)
5- Soporte para Raspicam (Incluye tornillos y tuercas, utilizar los separadores de plástico anteriores para adosar al conjunto)
6- Cable de 30 cm para Raspicam (puede ser mas largo, hay de 50 cm pero sobraría bastante, dependiendo del chasis)
7- Separador de plástico y tornillos (2) para ajustar el servo a la base metálica (se pueden usar bridas o precintos plásticos)
8- Ruedas plásticas negras con cubiertas de goma (2) (opcional, se pueden seguir utilizando las que vienen por defecto)

Nota: También se podría utilizar un servomotor standard pero al ser mas grande ocuparía mas lugar y quedaría mas alto.

Cantidad de errores cometidos: 3

- Intentar hacer funcionar el servo con control por software, aunque tiene la ventaja que se puede portar a otros SBC que no sean RPI2, en este caso hubiera ahorrado tiempo implementar directamente ServoBlaster.
- La version de control por software en el modo automático en realidad solo leía la distancia una vez por ciclo al empezar, por lo que tomaba siempre el mismo valor para todas las lecturas siguientes hasta el próximo ciclo y no servía para determinar el mejor rumbo (se puede ver en el código que puse).
- Tanto el sensor HC-SR04 como Raspicam quedan sobresaliendo al frente y suelen chocar si no se detecta un obstáculo a tiempo.

Re: Robot + Cargador USB + Camara + Sensores + Encoders + Se

Posted: Sun Jan 10, 2016 3:39 pm
by R0dw3
Hola actkk2000,

Gracias por el post, está genial. ;)

Me surge una duda en cuanto a la alimentación, ¿Se podría alimentar mediante el microusb de la Raspberry y quitar las dos baterías conectadas a la Raspiboard?

Saludos

Re: Robot + Cargador USB + Camara + Sensores + Encoders + Se

Posted: Sun Jan 10, 2016 4:31 pm
by actkk2000
Gracias, aunque aun faltan un par de actualizaciones con las que estoy trabajando, es un "'work in progress"... :)
Respecto de la alimentación suceden dos cosas:

1- La placa "Raspirobot" esta diseñada para alimentar todo el robot, ya que cuenta con su propia fuente integrada que convierte el voltaje a 5V, y de allí distribuye la alimentación, entrando por el pin 2 (y quizá el 4 también) los 5V para Raspberry y por otro lado entrega también la alimentación a los motores, los colectores, al sensor ultrasónico y lo que se conecte al puerto i2c.

Pero para que eso funcione bien tiene que tener un voltaje un poco mayor, para poder mantener constante los 5V, de allí que se recomienda como minimo 7.2V de una batería Lipo.
Cuando se agota uno de los cargadores USB en serie antes que el otro, ahi empieza al comportamiento errático, no funciona bien, se reinicia y puede que la Raspberry continue encendida pero empieza a fallar todo, se nota que le falta "power" en la entrada de Raspirobot.

2- No obstante, para todas las pruebas sobre el escritorio desconecto los cargadores USB y si le conecto un cargador de pared de 5V en la entrada microUSB, para no consumir de los cargadores USB. En ese caso si funciona y la diferencia es que el cargador de pared es de 5V 2A, por lo que esta pensado para soportar la carga extra de alimentación que requieren los motores, sensor, luces, etc. Como no se tiene que mover mucho no importa tanto que quede sujetado al cable USB.

Yo use momentáneamente un cargador de pared pero para no usar el cargador USB azul que puse para los sensores y el servo, aunque por momentos se enredaba un poco... Y precisamente ese cargador esta porque los otros se ven sobrepasados de corriente. La verdad que seria bueno poder quitar todo eso y dejar solo una fuente de alimentación.

La solución seria entonces, si tienes una fuente de alimentación portátil de 5V que sea capaz de mantener constante el voltaje a pesar de la demanda de corriente de todo lo que conectes (quizá un cargador portátil de los buenos y caros), entonces si seria posible alimentar la Raspberry por el microUSB, o si tienes un cable de alimentación USB lo suficientemente largo, se podría alimentar desde un cargador de pared, pero no permitiría mucha movilidad...

Slds!

Re: Robot + Cargador USB + Camara + Sensores + Encoders + Se

Posted: Sun Jan 10, 2016 7:04 pm
by R0dw3
Muchas gracias por la respuesta, he estado mirando baterías Lipo con output de 7,4v y creo que me voy a decantar por comprar una finalmente, el problema que le veo es que cuando se agote la batería tendré que desconectar el cableado y montar un circuito para cargarla de nuevo. Los power bank que he encontrado con output a 7,4v son muy caros como bien dices :S

Re: Robot + Cargador USB + Camara + Sensores + Encoders + Se

Posted: Mon Jan 11, 2016 10:12 am
by vidpi
Suerte que no era un tutorial.... Completísimo. Voy a tener que mirármelo y ver si me animo también.

Re: Robot + Cargador USB + Camara + Sensores + Encoders + Se

Posted: Mon Jan 11, 2016 10:03 pm
by R0dw3
Anímate!! yo ya estoy comprando los materiales xD

Re: Robot + Camara + Sensores + Encoders + Servo + Matriz de

Posted: Fri Jan 15, 2016 5:00 am
by actkk2000
Actualización con Matriz de Leds y Backpack:

Hasta ahora todos mis proyectos con Raspberry u otros han tenido un display de leds de algún tipo (en algún caso incluso virtual), y este no seria la excepción.
Por si no recuerdan, se los muestro:
viewtopic.php?p=793720#p793720

Pero aquí quería hacer algo distinto, que tuviera otro propósito aunque además pudiera mostrar la hora también...
Ya había visto en Pibot-B (http://www.retas.de/thomas/raspberrypi/pibot-b/) y en la pagina de Adafruit (https://www.adafruit.com/products/1050?q=mini%208x8&) los display de matriz de led de 8x8, con los que se podría dibujar una carita :):
Image Image

Lo bueno es que la placa Raspirobot era compatible con las versiones de estas matrices de leds y otros display que funcionan con una placa driver con el chip HT16K33 sobre la que se los monta en su parte posterior (de allí la denominación "backpack" o mochila) y que permiten controlarlos a través de la interfaz I2C, lo que facilita no solo la cantidad de conexiones necesarias y ahorrar pines GPIO sino también que sea muy sencillo manejarlos con librerías ya creadas a tal efecto: https://github.com/simonmonk/raspirobot ... c-displays

En mi caso me decidi por la version de leds "Ultra Bright White", pero la compre junto con el cable de 30 cm de la Raspicam en Pimoroni (el envio sale mas barato): https://shop.pimoroni.com/products/adaf ... c-backpack
De esta manera iba a poder tener además una funcionalidad extra que seria el de iluminación frontal adicional :D

Por las dudas no quise soldar la matriz a la placa driver, sino que le soldé primero unos zócalos de 8 pines que tenia y sobre eso encastré la matriz, lo que permitiría incluso cambiarla por otro modelo de otro color que pudiera conseguir quizá localmente si se me planteara. Aunque los zócalos son un poco largos, dejan un espacio entre medio pero no importa, porque aproveché eso para sujetarla con una brida plástica.

Image Image
Image Image

Luego de ensamblada, procedí a montarla en el lugar que había quedado desocupado cuando moví de locación la cámara y la coloqué al frente, solo era cuestión de usar 4 cables y puentear con el conector marcado como I2C en la placa Raspirobot usando las indicaciones de la serigrafía, sujetándola a la base del soporte para Raspicam que me había sobrado y que encastraba bien sobre el soporte metálico ya montado anteriormente donde estuvo la camara:

Image Image
Image Image

En la pagina de Adafruit están los tutoriales tanto de como soldar la matriz al backpack, instalar las librerías, usar programas de ejemplo con distintas visualizaciones e incluso hacerla funcionar junto con Raspirobot:
https://learn.adafruit.com/adafruit-led ... 8x8-matrix
https://learn.adafruit.com/led-backpack ... lack/usage
https://learn.adafruit.com/matrix-7-seg ... it-library
https://learn.adafruit.com/raspirobot-b ... i-displays
https://learn.adafruit.com/adafruits-ra ... guring-i2c
https://github.com/adafruit/Adafruit_Py ... D_Backpack

También tuve algunos inconvenientes para que funcionara como un error de instalación con Python que por suerte pude solucionar con lo que dice aquí: https://gist.github.com/bot11/66d50ef5272d7dcadc2c (quizá a otros no les pase).
Usando los programas de ejemplo como guía quería implementar librerías que sirvieran para dibujar caritas con distintos gestos según el movimiento del vehículo.

Al fin "Norman" tendría rostro, aunque ya las formas tanto del sensor ultrasónico como la cámara montados juntos semejaban una cara con dos ojos y una boca, seguro iba a quedar mejor si fuera algo que mostrara distintas expresiones.
Otra cosa que se me ocurrió fue conectar la alimentación de la matriz a uno de los colectores de Raspirobot en lugar de una de las luces, y colocar los dos leds juntos en el otro colector, asi tendría justamente mas iluminación y la posibilidad de encender y apagar la matriz como si fuera una de las luces.

Primero hice un programa de prueba que dibujara el mismo rostro circular que había visto, y que, curiosamente, no fui capaz de encontrarlo ni en los ejemplos ni tutoriales, pero me las ingenie para reproducirlo y que al funcionar dibujara diferentes expresiones:

Code: Select all

import time
import datetime
from Adafruit_8x8 import EightByEight
from rrb2 import *

grid = EightByEight(address=0x70)
rr = RRB2()
rr.set_oc2(1) 

print "Press CTRL+C to exit"

#define functions

def face():
        grid.setPixel(0, 2)
        grid.setPixel(0, 3)
        grid.setPixel(0, 4)
        grid.setPixel(0, 5)
        grid.setPixel(1, 1)
        grid.setPixel(1, 6)
        grid.setPixel(2, 0)
        grid.setPixel(2, 7)
        grid.setPixel(3, 0)
        grid.setPixel(3, 7)
        grid.setPixel(4, 0)
        grid.setPixel(4, 7)
        grid.setPixel(5, 0)
        grid.setPixel(5, 7)
        grid.setPixel(6, 1)
        grid.setPixel(6, 6)
        grid.setPixel(7, 2)
        grid.setPixel(7, 3)
        grid.setPixel(7, 4)
        grid.setPixel(7, 5)

def left_e():
        grid.setPixel(2, 2)

def right_e():
        grid.setPixel(5, 2)

def happy():
        grid.setPixel(2, 4)
        grid.setPixel(3, 5)
        grid.setPixel(4, 5)
        grid.setPixel(5, 4)

def sad():
        grid.setPixel(2, 5)
        grid.setPixel(3, 4)
        grid.setPixel(4, 4)
        grid.setPixel(5, 5)

def neither():
        grid.setPixel(2, 5)
        grid.setPixel(3, 5)
        grid.setPixel(4, 5)
        grid.setPixel(5, 5)

try:
 while True:
        #happy face both eyes
        face()
        happy()
        left_e()
        right_e()
 	time.sleep(1)
 	grid.clear()
        #happy face right eye
        face()
        happy()
        right_e()
        time.sleep(1)
        grid.clear()
        #happy face left eye
        face()
        happy()
        left_e()
        time.sleep(1)
        grid.clear()
        #neutral face both eyes
        face()
        neither()
        left_e()
        right_e()
        time.sleep(1)
        grid.clear()
        #neutral face right eye
        face()
        neither()
        right_e()
        time.sleep(1)
        grid.clear()
        #neutral face left eye
        face()
        neither()
        left_e()
        time.sleep(1)
        grid.clear()
        #sad face both eyes
        face()
        sad()
        left_e()
        right_e()
 	time.sleep(1)
 	grid.clear()
        #sad face right eye
        face()
        sad()
        right_e()
        time.sleep(1)
        grid.clear()
        #sad face left eye
        face()
        sad()
        left_e()
        time.sleep(1)
        grid.clear()

except KeyboardInterrupt:
 grid.clear()
Pero no me conformó, me parecía que el display daba para mas así que en vez de seguir con lo que ya había visto antes, decidí mejor hacer el rostro de Norman a mi imagen y semejanza :lol::

Code: Select all

import time
import datetime
from Adafruit_8x8 import EightByEight
from rrb2 import *

grid = EightByEight(address=0x70)
rr = RRB2()
rr.set_oc2(1) 

print "Press CTRL+C to exit"

#define functions

def brow_l_l():
        grid.setPixel(0, 1)
        grid.setPixel(1, 1)
        grid.setPixel(2, 1)

def brow_l_r():
        grid.setPixel(5, 1)
        grid.setPixel(6, 1)
        grid.setPixel(7, 1)

def brow_n_l():
        grid.setPixel(0, 0)
        grid.setPixel(1, 0)
        grid.setPixel(2, 0)

def brow_n_r():
        grid.setPixel(5, 0)
        grid.setPixel(6, 0)
        grid.setPixel(7, 0)

def left_e():
        grid.setPixel(1, 2)
        grid.setPixel(1, 3)
        grid.setPixel(2, 2)
        grid.setPixel(2, 3)

def right_e():
        grid.setPixel(5, 2)
        grid.setPixel(5, 3)
        grid.setPixel(6, 2)
        grid.setPixel(6, 3)

def left_w():
        grid.setPixel(1, 3)
        grid.setPixel(2, 3)

def right_w():
        grid.setPixel(5, 3)
        grid.setPixel(6, 3)

def mouth():
         grid.setPixel(1, 6)
         grid.setPixel(2, 5)
         grid.setPixel(3, 5)
         grid.setPixel(4, 5)
         grid.setPixel(5, 5)
         grid.setPixel(6, 6)

def happy():
        grid.setPixel(0, 5)
        grid.setPixel(1, 5)
        grid.setPixel(2, 7)
        grid.setPixel(3, 7)
        grid.setPixel(4, 7)
        grid.setPixel(5, 7)
        grid.setPixel(6, 5)
        grid.setPixel(7, 5)

def neither():
        grid.setPixel(0, 5)
        grid.setPixel(1, 5)
        grid.setPixel(2, 6)
        grid.setPixel(3, 6)
        grid.setPixel(4, 6)
        grid.setPixel(5, 6)
        grid.setPixel(6, 5)
        grid.setPixel(7, 5)

def sad():
        grid.setPixel(0, 7)
        grid.setPixel(1, 7)
        grid.setPixel(2, 7)
        grid.setPixel(3, 7)
        grid.setPixel(4, 7)
        grid.setPixel(5, 7)
        grid.setPixel(6, 7)
        grid.setPixel(7, 7)

try:
 while True:
        #happy face both eyes
        brow_n_l()
        brow_n_r()
        left_e()
        right_e()
        mouth()
        happy()
 	time.sleep(1)
 	grid.clear()
        #happy face right eye
        brow_l_l()
        brow_n_r()
        left_w()
        right_e()
        mouth()
        happy()
        time.sleep(1)
        grid.clear()
        #happy face left eye
        brow_n_l()
        brow_l_r()
        left_e()
        right_w()
        mouth()
        happy()
        time.sleep(1)
        grid.clear()
        #neutral face both eyes
        brow_n_l()
        brow_n_r()
        left_e()
        right_e()
        mouth()
        neither()
        time.sleep(1)
        grid.clear()
        #neutral face right eye
        brow_l_l()
        brow_n_r()
        left_w()
        right_e()
        mouth()
        neither()
        time.sleep(1)
        grid.clear()
        #neutral face left eye
        brow_n_l()
        brow_l_r()
        left_e()
        right_w()
        mouth()
        neither()
        time.sleep(1)
        grid.clear()
        #sad face both eyes
        brow_n_l()
        brow_n_r()
        left_e()
        right_e()
        mouth()
        sad()
 	time.sleep(1)
 	grid.clear()
        #sad face right eye
        brow_l_l()
        brow_n_r()
        left_w()
        right_e()
        mouth()
        sad()
        time.sleep(1)
        grid.clear()
        #sad face left eye
        brow_n_l()
        brow_l_r()
        left_e()
        right_w()
        mouth()
        sad()
        time.sleep(1)
        grid.clear()

except KeyboardInterrupt:
 grid.clear()
Solo faltaba mi firma personal, es decir el reloj :P, aunque lo que deseaba es que se activara en ciertos momentos, como ser cada 5 minutos y por un tiempo limitado, por ejemplo no mas de 30 segundos:

Code: Select all

import time
from datetime import datetime
from PIL import ImageFont
from PIL import Image
from PIL import ImageDraw
from Adafruit_LED_Backpack import Matrix8x8
from rrb2 import *
import datetime
from Adafruit_8x8 import EightByEight

rr = RRB2()
rr.set_oc2(1)

display = Matrix8x8.Matrix8x8()
display.begin()
brightness = 0
 
font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeSansBold.ttf", 9)
 
im = Image.new("1", (8, 8), "black")
draw = ImageDraw.Draw(im)
width, ignore = font.getsize("88 : 88 : 88")

grid = EightByEight(address=0x70)
 
def format_time():
    d = datetime.datetime.now()
    return "{:%H : %M : %S}".format(d)
 
#Exit gracefully
def exit():
   rr.set_oc2(0)
   print 'bye!'

def clock():
   global now
   global hour
   global minute
   global second
   global min1
   global min2
   global sec1
   global sec2
   now = datetime.datetime.now()
   hour = now.hour
   minute = now.minute
   second = now.second
   min1 = int(minute / 10)
   min2 = minute % 10
   sec1 = int(second / 10)
   sec2 = second % 10

message = format_time()
x = 8

try:

 while True:
   clock()
   if min2 == 0 and sec1 < 3 or min2 == 5 and sec1 < 3:
      message = format_time()
	    x = x - 1
	    if x < -(width + 20):
        	x = 8
	        message = format_time()
	    draw.rectangle((0, 0, 7, 7), outline=0, fill=0)
	    draw.text((x, -1), message, 1, font=font)
	    display.set_image(im)
	    display.write_display()
	    time.sleep(0.1)
	    clock()
   else:
            grid.clear()
   
except KeyboardInterrupt:

 print '\n'
 exit()

# end
Aquí es donde voy a resumir (al fin!) que pude crear las librerías para la matriz de leds con las expresiones que se activarían según el movimiento y usarlas en todos los programas, asi como también que se vea el reloj cada 5 minutos (aunque solo lo puse en "rover_avoiding.py" para el modo automático) y modificar el programa de conexión web para que pudiera encender y apagar la carita, con un agregado de diferentes colores según las funciones para que sean mas fácil de visualizar (solo que voy a adjuntar todos los programas en el update final para no hacerlo aun mas largo)(hacer click para ampliar):

Image

Aquí una serie de videos donde hago diferentes pruebas con el display tanto con los programas que vienen en las carpetas "examples" o los que hice yo, y se puede ver al final alternando la carita con el reloj cuando corresponde. Quizá el único inconveniente es que la velocidad del reloj al hacer scrolling quedo un poco lenta debido a que funciona en forma secuencial con el resto del programa y seria mejor implementar concurrencia (hacer click en cada uno para reproducir, F5 si no carga la vista previa. Los leds son blancos aunque la cámara los capte azules):

Image Image Image Image
Image Image Image Image

Lista de materiales que se agregaron para armar esta parte:

1- Matriz mini 8x8 de leds blancos con backpack I2C de Adafruit (puede ser otro color según el gusto)
2- Zócalos lineales de 8 pines (2) (Opcional, son los que se usan para extender pines en placas Arduino)

Nota: Se pueden utilizar otros tipos de display de leds de Adafruit, pero requeriría modificar los programas según el caso.
También se puede soldar la matriz directamente al backpack si no se estima cambiarla nunca.

Cantidad de errores cometidos: 3

- Tuve que girar la matriz y cambiarla de posición respecto del montaje inicial que hice para que quede con los conectores en la parte superior y así coincidir con la orientación que se usa en las librerías de Adafruit (el nuevo montaje se ve en los videos).
- La primera vez no ajusté bien la matriz de leds al encastrarla en los zócalos por lo que algunos leds no encendían y parecía que no funcionaban.
- Pisé sin querer con versiones viejas algunos programas durante las pruebas y perdí algunas modificaciones que tuve que volver a hacer, con la consiguiente perdida de tiempo.

Re: Robot + Camara + Sensores + Encoders + Servo + Matriz de

Posted: Sat Jan 16, 2016 11:17 pm
by actkk2000
Actualización con Joystick Xbox 360 y Programas Python:

Siempre quise tener un joystick de Microsoft y cuando se me presentó la oportunidad de comprar uno en oferta lo hice ;)
Es uno como este: https://www.microsoft.com/hardware/es-e ... or-windows
Ahora tendría la oportunidad de mejorar el control del robot por joystick con uno que fuera inalámbrico, lo que permitiría manejarlo a mas distancia (tiene un alcance de hasta 9 metros) y con libertad de movimiento.

El joystick viene acompañado de un receptor inalámbrico USB que se conecta a la PC, pero en este caso lo iba a conectar a la RPI2. Claro que ya no le quedaba mucho espacio así que lo mas lógico era que lo colocara apoyado sobre el cargador USB azul y sosteniéndolo con una banda elástica del propio cable que viene enrollado, ya que no quería dejarlo fijo y así seria mas fácil de desconectar y remover:

Image Image
Image Image

Como guías de instalación utilicé estos enlaces, que me sirvieron para adaptar "rover.py" para que funcione específicamente con el joystick de Microsoft:
http://www.stuffaboutcode.com/2014/10/r ... ython.html
http://www.dexterindustries.com/topic/c ... ontroller/

Con el primero vi que lo que se necesitaba era instalar el driver "xboxdrv" pero además instalar un modulo hecho en Python llamado "XboxController.py".
El segundo me sirvió mas para entender como funciona el modulo y poder utilizarlo para controlar las funciones de Raspirobot.
Para que enlace con el joystick hay que apretar el botón central que parece una X y enciende una luz verde parpadeante hasta que sincroniza con el receptor y deja encendida solo una luz que indica el numero de joystick. El receptor también tiene luz, algo asi:

Image Image

Una cosa interesante que encontré es que tanto los botones como el pad de dirección (D Pad, el que parece un signo +) funcionan en forma digital, dando solo dos valores como salida 0 y 1 (aunque con también con signo), a diferencia de las palancas que son analógicas y funcionan igual que en el otro joystick es decir con valores intermedios y con decimales entre -1 y 1.

De esta forma podría detectar mejor los cambios de estado y asignar las funciones, aunque también podría utilizar los valores absolutos de salida de la palanca izquierda para pasárselos a las variables que manejan la velocidad de los motores. Así al menos, en teoría, de acuerdo a la fuerza aplicada al accionar la palanca se regularía la velocidad.

En la practica resultó que si, pero con dos inconvenientes: al no funcionar iguales los motores se acentuaba el movimiento hacia uno de los costados, y al llegar al tope los motores funcionaban a maxima velocidad, forzando el paso de mas corriente de la que soportaban las baterías, por lo que RPI2 se reiniciaba.
La solución seria implementar un limite igual a las variables de velocidad que ya había establecido previamente.

Pero la verdad que encontré que usando el "D Pad" era mas fácil de controlar y también funcionaba mas estable.
También asigné botones para encender y apagar las luces y la carita, y manejar el servo en una dirección u otra y centrarlo.
Y adapté esos controles también a "rover.py" para poder usar el teclado inalámbrico o el otro joystick (aunque ahora quedaba obsoleto) y todo lo que se refiriera al joystick inalámbrico de Microsoft lo cargué en un programa llamado "xbox.py".

Otro inconveniente que se sumó es que debido al peso extra que proveían el receptor USB y su cable, afectaba a otros programas como el de seguidor de línea ("line_follower.py") y "rover_avoiding.py", ya sea evitando movimientos o causando desvíos, por lo que era mejor quitar el receptor para usarlos, lo que además evitaba el consumo innecesario de energía extra para alimentarlo.

Ya para ir finalizando posteo varios videos que grabé donde se puede apreciar en la primer tanda y mediante captura de pantalla la vista en "primera persona" de Norman controlándolo desde otra habitación y en la segunda tanda se ve el funcionamiento de Norman en "tercera persona" y su comportamiento.

En el primer video es controlado con el joystick Xbox, en el segundo con la interfaz web (se puede ver el puntero del mouse moviéndose), el tercer y cuarto video son en funcionamiento automático, por lo que puede ver el movimiento del servo para detección de obstáculos aunque también puede parecer confuso y hasta marear ya que lo hice mas rápido para que no tome tanto tiempo como lo hacia antes para girar el cabezal con el servo. También los objetos parecen mas cerca de lo que realmente están (hacer click en cada uno para reproducir):

Image ImageImage Image

En esta tanda, el primer video muestra a Norman controlado por joystick, donde se puede apreciar que si intento hacerlo chocar lo evita haciendo un pequeño retroceso, y en los otros dos videos se lo ve en funcionamiento automático evitando chocar (o tratando :oops:) contra los objetos en el camino. La ambientación con luz tenue es para evitar confundir a los sensores infrarrojos:

Image Image Image

Y como lo prometido es deuda, dejo adjunto al final un zip con los programas que utilizo, junto con las librerías necesarias, incluso las de Adafruit ya que a pesar de instalarlas no siempre funcionan las llamadas, por lo que es mejor colocarlas a todas juntas en la misma carpeta junto con los programas y ejecutarlos con Python desde allí.

Los programas que se pueden ejecutar serian:
  • rover.py (funciona con teclado wireless ó joystick genérico USB)
    rover_avoiding.py (funcionamiento automático evitando obstáculos)
    line_follower.py (funciona siguiendo una línea negra en el suelo)
    xbox.py (funciona con joystick Microsoft inalámbrico)
    cambot.py (funciona con interfaz Web y cámara, require tener instalado Webiopi y MJPG-streamer)
Estos programas son independientes entre si, pero si dependen de las librerías según el caso y no se debería ejecutar mas de uno al mismo tiempo.

También se les puede dar permiso de ejecución con "chmod +x" y ejecutarlos anteponiendo "./" al nombre, como por ejemplo

Code: Select all

sudo ./rover.py
En cambio las librerías de Raspirobot si funcionaron llamándolas desde cualquier lado por lo que no las incluyo.
He tratado de etiquetar con notas las distintas partes de los programas como guía de su función, pero quizá no sean suficientes.
También hay que tener en cuenta que el montaje del chasis esta invertido por lo que si se ensambla en forma correcta habría que invertir todas las llamadas a "forward" por "reverse" y "right" por "left", y viceversa...

Lista de materiales agregados para completar esta sección:

1- Joystick Microsoft Xbox 360 con receptor inalámbrico USB para Windows (Debería venir incluido todo en el mismo paquete)
2- Banda elástica o "gomita" para sujetar el receptor USB (Puede ser otro medio como brida plástica o cinta adhesiva).

Nota:: Quizá se puedan utilizar imitaciones o versiones compatibles con este joystick, pero no lo he probado.

Cantidad de errores cometidos: 3

- Alguna vez para ahorrar tiempo solo apoyaba el receptor USB sin ajustarlo y por eso se cayó un par de veces con el movimiento del vehículo, bloqueando el receptor Wifi e interrumpiendo la conexión.
- Para utilizar el programa "xbox.py" se debe iniciar el robot conectado a una pantalla, de lo contrario funciona pero el servo motor se vuelve loco y gira para cualquier lado.
- Al tratar de simplificar el funcionamiento usando una librería para el servo modifiqué mal un programa confundiendo derecha por izquierda lo que hacia que el servo no solo se moviera en sentido contrario sino que se veía forzado, sobrepasaba el limite de giro y se trababa.

Conclusión:

En general estoy muy satisfecho con el proyecto, aunque tomó mucho mas tiempo del que esperaba pero eso me dio mas posibilidades de ir mejorándolo a medida que avanzaba.
Lamentablemente alcanzó un limite, tanto en tamaño como posibilidades de expansión así que lo mas lógico es parar aquí e ir planeando las próximas versiones de "Norman"...

En principio se me ocurre dividir las funciones y armar dos robots separados, uno que sea como este pero totalmente reconstruido utilizando la orientación correcta del chasis 2WD y otro con un chasis 4WD (4 ruedas y motores) para darle mas potencia, estabilidad y tamaño para montaje de componentes y accesorios:

- En la version con el chasis 2WD se mantendría todo el conjunto de sensores para realizar los movimientos, mientras que en la 4WD se podría aprovechar la cámara mejor, implementando OpenCV para controlar los movimientos y detectar obstáculos o incluso seguir objetos de colores como por ejemplo pelotas rodando.
- También se podría agregar un sistema "Pan/Tilt" con dos servos que permita girar la cámara no solo en sentido horizontal sino también vertical, dándole una mayor amplitud de movimiento.
- Se podría colocar un mejor receptor Wifi con antena para tener un mayor alcance de conexión y así poder llevar el vehículo mas lejos, como por ejemplo salir a explorar al exterior: viewtopic.php?p=788237&sid=dd176ee02850 ... 23#p788237
- Además se podría agregar un segundo conjunto de servos para controlar una garra o "claw" que permita sujetar pequeños objetos, como por ejemplo tomar muestras de vegetación durante las exploraciones: http://letsmakerobots.com/node/38253
- En ambos modelos se debería mejorar el sistema de alimentación utilizando baterías de polímero de litio (LiPo) que tienen una mayor autonomía y evitaría perder tiempo teniendo que cargarlas muy seguido, aunque quizá sean un poco mas complicadas de manipular: http://www.cochesrc.com/conceptos-basic ... a2627.html
- En ambas versiones se debería mejorar el ensamblaje haciendo el cableado en forma mas prolija y firme para evitar desconexiones involuntarias y facilitando la comprensión de las conexiones para realizar modificaciones o reparaciones, si es posible no utilizar un breadboard sino uno placa para soldar los cables.
- Se podría utilizar en uno de los Rover, en lugar de una placa Raspirobot, un driver de motores DC con el chip L298N que soporta mas corriente y es mas barato, pero requeriría programar nuevas librerías y controlar todo directamente con los pines GPIO: http://www.instructables.com/file/FRJ7W76HZDYE1YD/
- Quizá se podría al menos en uno de los robots que tenga RPI2 instalar Windows 10 IOT para probarlo:
https://www.hackster.io/peejster/rover-c42139
https://www.hackster.io/stepanb/8x8-led ... ore-787467

Una vez mas agradezco si leyeron todo el articulo y espero que al menos algunas ideas les sean útiles y sirvan de guía para sus implementaciones.
Recuerden que esto no es un tutorial, sino tendría que ser mucho mas extenso y detallado, así como tampoco debería contener errores...
Tampoco es un diario sino mas bien un reporte de lo que hice, que espero despierte su interés e imaginación y los anime a armar sus propios robots.
Hasta la próxima!
:D

Slds!