Contenido de la categoría 'NXT'

Instalación de parches de software LEGO Education

En este post vamos a explicar cómo podemos solucionar varios de los problemas de instalación del software de LEGO Educación en diferentes versiones de sistemas operativos de Mac y Windows. Para poder solucionar cada uno de los diferentes fallos debemos descargar un pequeño archivo (llamado parche o “patch”) que nos permitirá solucionar el error. Estos archivos se descargan desde la página de LEGO Education: www.LEGOeducation.com. Una vez hemos accedido a la página hemos de dirigirnos al enlace “MINDSTORMS downloads” y accederemos a la siguiente página, que contiene un listado muy grande de descargas.

A la izquierda debemos seleccionar sólamente las opciones de “Lower Primary”, “Upper Primary”, “Secondary” y “Software Update/Patch” para filtrar y que nos aparezcan sólo los parches para solucionar los fallos de software. La página cambiará y mostrará el suiguiente listado:

A continuación explicaremos brevemente para qué sirve cada una de esos parches:

Para WeDo:

- WeDo MAC: soluciona el problema de instalación en Mac OS 10.7 Lion y las incompatibilidades con Flash 11. Tras la descarga hay que descomprimir el contenido en el escritorio. Este programa debe ser ejecutado cuando el CD de WeDo espera para comenzar la instalación. Se puede encontrar más información en el archivo “Read me” que incluye la descarga. Es necesario que sea la versión 1.2 del software de WeDo.

- WeDo Windows: actualiza la versión 1.2 del software, lo que soluciona la incompatibilidad con Flash 11. Tras la descarga se debe ejecutar el archivo “setup.exe” y seguir las instrucciones que muestre la ventana.

- MAC OS 10.5 (Leopard) Sound Fix for WeDo: soluciona un problema que no permite que el micrófono del ordenador grabe sonido en el software de WeDo en un Mac OS 10.5. Tras la descarga se debe descomprimir el archivo y ejecutar el archivo “WeDo update” que aparece e la nueva carpeta creada tras la descompresión.

- MAC OS 10.7 (Lion) Installer Fix for WeDo: permite la instalación en un MAC OS 10.7 Lion. Tras la descarga hay que descomprimir el contenido en el escritorio. A continuación se deben seguir las instrucciones del archivo “Read me” y se debe ejecutar cuando el CD de WeDo espera para comenzar la instalación. Después de ejecutar este parche se debe ejecutar otro, llamado “WeDo Mac”.

Para LEGO Mindstorms NXT Education Software:

- MAC OS 10.7 (Lion) Installer Fix for NXT: existe un problema que impide instalar el software Mindstomrs NXT en un MAC OS 10.7 Lion a 64 bits. Para poder instalarlo hay que introducir el CD y abrir el contenido del disco. Después se debe ejecutar el archivo terminado en “.mpkg” cuyo nombre se corresponda con el idioma seleccionado.
Si esto no funciona se debería probar lo anterior abriendo el sistema operativo a 32 bits. Para ello se deben presionar simultáneamente las teclas 3 y 2 al encender el ordenador.

- NXT EDU 2.0f3 for Windows: esta aplicación soluciona problemas de compilador y la incompatibilidad con Flash 10.1 que tiene la versión 2.0. Para ello se debe descargar la aplicación y ejecutar el archivo “setup.exe”, y a continuación hay que seguir las instrucciones en pantalla.

- NXT EDU 2.1f3 for Windows: similar a la anterior, esta aplicación soluciona problemas de compilador y la incompatibilidad con Flash 10.1 que tiene la versión 2.1. Este parche sólo es válido si se ha utilizado el parche “NXT Patch”. Para ello se debe descargar la aplicación y ejecutar el archivo “setup.exe”, y a continuación hay que seguir las instrucciones en pantalla.

- NXT EDU 2.f5 for MAC: esta aplicación sirve para corregir todos los fallos de software y los fallos de Flash de la versión 2.0, aunque no añade ningún contenido nuevo. Para ejecutarla se debe tener instalada la version 2.0 y tras la descarga se deben seguir las instrucciones que aparecen en pantalla.

- NXT EDU 2.1f5 for MAC: similar a la anterior, esta aplicación soluciona los problemas de la versión 2.1. Tampoco añade contenido nuevo. Para ejecutarla se debe tener instalada la version 2.1 y tras la descarga se deben seguir las instrucciones que aparecen en pantalla.

- NXT Patch: este parche actualiza la versión 2.0 a la versión 2.1 del software de LEGO Education, incluyendo el Data Logging y otras herramientas del programa.

- Windows 7 Script: este parche permite instalar el software de Mindstorms en un Windows 7 Starter Edition. En primer lugar, si el software se encuentra en un medio físico o en una localización de red, se debe copiar el directorio que contiene el instalador al escritorio. Después se debe descargar el parche y copiar el archivo “AllowWindowsStarter.exe” en el directorio copiado anteriormente, que debería ser el mismo directorio que contiene los archivos “autorun.exe” y “setup.exe.”. Después, al abrir el archivo “AllowWindowsStarter.exe”, el equipo debería permitir la instalación al hacer ejecutar el “autorun.exe”.

Comparativa de conjuntos Minstorms NXT 9797 y 8547

    En este artículo vamos a hacer una comparación entre los diferentes sets de Mindstorms NXT que están ahora en el mercado. Uno es el que distribuye LEGO por su rama comercial habitual, llamado LEGO Mindstorms NXT 2.0, cuya referencia es 8547. El otro modelo es el 9797, que distribuye LEGO Education, y que podemos encontrar en un pack junto al software de programación y el cargador de la batería. A continuación iremos desglosando el contenido de las cajas y viendo las diferencias que existen entre ellas.

    La primera diferencia la encontramos en el packaging. Mientras que el NXT 2.0 viene presentado en caja de cartón sin divisiones, la versión educativa contiene una caja de plástico de almacenaje con dos bandejas repartidoras muy útiles para organizar, contar y no perder piezas.

    Set 8547.

    Set 9797.

    El modelo 8547 no incluye la batería de litio recargable que nos trae el 9797, por lo que para usar el ladrillo NXT (controlador) se requieren 6 pilas alcalinas AA. Aún así, esta batería puede ser adquirida por separado. El modelo educativo, a su vez, también permite la utilización de pilas.

    El número de engranajes de los sets también varía, pudiendo encontrar 31 engranajes en la versión educativa frente a los 11 de la versión comercial.

    Puesto que ambos modelos han sido concebidos para distintos fines, vemos que el contenido en piezas varía de uno a otro. El 8547 incluye 620 piezas, seleccionadas para construir los cuatro modelos mostrados en la caja del producto a modo de ejemplos (Alpha-Rex, ShooterBot, Robo Gator y Colour Sorter). El NXT 9797 enfocado al aprendizaje y la investigación, contiene 431 piezas más variadas, con instrucciones para montar un robot modular que juega al golf. De todas formas, los diferentes surtidos de piezas se pueden combinar de múltiples formas para realizar los montajes que imagines. Además, las piezas que se usan no dejan de ser piezas Technic convencionales, y se pueden combinar con todos los otros modelos LEGO.

    Aún así, existe un caja de ampliación, que contiene un gran surtido de piezas muy útiles a la hora de construir robots. Se trata de la 9695 Conjunto de recursos Mindstorms, en la que se incluyen instrucciones para montar nueve robots diferentes combinándola con la 9797.

    Ahora hablaremos de los elementos electrónicos:

    Sensores:

    8547: 2 sensores de contacto, 1 sensor de ultrasonidos y 1 sensor de color (con tres funciones: detección de 6 colores básicos, intensidad luminosa y actuar como una lámpara con luz roja, azul o verde).

    9797: 2 sensores de contacto, 1 sensor de sonido, 1 sensor de ultrasónidos, 1 sensor de luz.

    Procesador:

    Ambas versiones usan como procesador el 9841 Ladrillo Inteligente NXT.

    Motores:

    Ambas versiones incluyen 3 servomotores 9842 Servo Motor Interactivo NXT.

    Cableado:

    8547: 7 cables de conexión y 1 cable USB.
    9797: 7 cables de conexión, 1 cable USB y 3 cables convertidores (para utilizar los sensores y motores de la versión RCX con NXT).

    Software:

    Es un lenguaje gráfico muy intuitivo que se basa en la generación de secuencias de iconos gráficos extraídos a partir de unas paletas de iconos. Este lenguaje, basado en LabVIEW, surge de la colaboración entre LEGO y National Instruments. A pesar de su simplicidad y facilidad de aprendizaje, dispone de características avanzadas que permitirán la creación de programas muy complejos. Se permite la generación de secuencias lineales, condicionales, bucles, trabajar con variables y constantes, creación de macros, acceso a ficheros, uso de mensajes, comunicación vía bluetooth, etc.

    Esta programación es especialmente atractiva, siendo la funcionalidad de ambas versiones prácticamente la misma. A continuación, se señalarán las diferencias para las dos versiones.

    El software educativo del 9797 incluye bloques de programación de los sensores y motores propios del RCX, así como el uso de lámparas, mientras que el de la versión 8547 no los incluye, aunque pueden ser descargados desde la página de actualizaciones de LEGO Mindstorms. Los idiomas disponibles son inglés, francés, alemán y holandés, mientras que la versión educativa nos permite su instalación en muchos más idiomas, incluyendo el castellano, que tiene una traducción de bastante calidad.

    La principal diferencia entre las dos versiones de software radica en un apartado de apoyo del programa. En el software del 9797, este apartado se llama Robo Center y contiene instrucciones para construir los 4 montajes mencionados anteriormente, y algunos programas de ejemplo para hacer funcionar esos montajes. En la versión educativa nos encontraremos el Robot Educator, que contiene una guía con 50 tutoriales, que proponer 50 retos diferentes con guías de montaje y programación para cada uno. A través de la realización de estas actividades aprendemos a manejar y a comprender mejor todas las posibilidades que nos ofrece el software.

    Software 8547.

    Software 9797.

    Por otro lado, en ambas versiones podemos encontrar herramientas que complementan las funciones del software. En el software del 8547 podemos encontrar un editor de imágenes (para añadir nuestras imágenes a la colección del motor), un editor de sonidos (para añadir sonidos externos a la biblioteca del robot), y un control remoto para manejar en tiempo real los motores de un vehículo construido con el Mindstorms. Estas herramientas no están presentes en el 9797, pero podemos encontrar una herramienta llamada Data Logging. Con ella podemos tomar los datos que reciben los sensores del robot y generar gráficas a tiempo real, muy útil para realizar experimentos científicos.

    Data Logging.

    Por último, ambas versiones se pueden ampliar con nuevos comandos de programación, descargados desde la web de LEGO o de otros fabricantes que ofrecen otros sensores, como HiTechnic, o incluso se pueden crear nuevos bloques con LabVIEW.

    Intelligence Unleashed: Creating LEGO NXT Robots with Java

    Artículo nº 14 de la serie de 14 artículos sobre Libros

    Brian Bagnall acaba de publicar un nuevo libro sobre la programación de robots creados con LEGO MINDSTORMS. Se trata del libro “Intelligence Unleashed: Creating Lego Nxt Robots with Java”. Recordemos que estamos ante uno de los grandes colaboradores en la comunidad leJOS NXJ, la plataforma Java Open Source para el NXT. Este autor ya publicó en el pasado otros títulos similares.

    Con las sencillas instrucciones que posibilita el software leJOS NXJ, este nuevo manual facilita el aprendizaje perfecto del LEGO Mindstorms NXT, un kit increíble para construir y programar robots. Haciendo uso del Java, uno de los lenguajes de programación más popular y fácil de usar, y con la ayuda de este libro, permitirá convertir en realidad los robots de nuestros sueños que tanto ingenieros como aficionados han deseado diseñar y construir. Se trata de todo un diverso conjunto de proyectos que se acompaña de trucos de construcción, código de programación, instrucciones de montaje renderizadas en 3D, y cientos de ilustraciones. Este nuevo manual sirve como complemento perfecto para el juego de LEGO NXT: en él se trabajan tanto las posibilidades de comunicación bluetooth del ladrillo inteligente NXT, como los nuevos sensores disponibles, desde el GPS hasta el sensor RFID.

    En breve dispondremos de unidades de este nuevo ejemplar, por lo que podremos dar más detalles.

    El Desafío Ecológico

      El Cambio climático es la mayor amenaza medioambiental a la que nos enfrentamos toda la humanidad. Si bien los cambios climáticos han existido desde los orígenes de la Tierra, una de las causas que diferencia al actual es la velocidad a la que está sucediendo. Este proceso acelerado de cambio no viene provocado por la Naturaleza, sino por la acción del hombre mediante la tala indiscriminada de arboles, el mal uso del agua potable, etc. Esto ha dado lugar al término Calentamiento Global.

      Podemos definir el Calentamiento Global como el cambio originado en el clima producido directa o indirectamente por la acción del hombre y que se suma a la variabilidad natural del clima. Tal y como se recoge en esta definición, el clima sufre una variabilidad natural, pero es mucho más lenta y progresiva que la que está ocurriendo hoy en día.

      El último informe de la Intergovernmental Panel no Climate Change (IPCC) nos indica que la temperatura de la tierra ha aumentado 0.6º C en los últimos 100 años, y que se prevee aumentará, en los próximos 100, entre 1.0ºC y 3.5ºC.

      Científicos de todo el mundo coinciden en que una de las maneras de paliar ó aminorar está destrucción progresiva del Medio Ambiente, será utilizando las energías alternativas. Una fuente de energía alternativa es aquella que puede suplir a las energías o fuentes energéticas actuales, ya sea por su menor efecto contaminante, o fundamentalmente por su posibilidad de renovación.

      Denominamos Energía Renovable a la energía que se obtiene de fuentes naturales virtualmente inagotables, unas por la inmensa cantidad de energía que contienen, y otras porque son capaces de regenerarse por medios naturales. Pueden dividirse en dos categorías: no contaminantes ó limpias y contaminantes. Entre las primeras, nos encontramos con:

      Energía Azul, que es la energía que se obtiene por la diferencia en la concentración de la sal entre el agua de mar y el agua de río mediante el uso de la electrodiálisis inversa -o de la ósmosis- con membranas de iones específicos. El residuo en este proceso es agua salobre.

      Energía Eólica. ó lo que es lo mismo, la energía cinética generada por efecto de las corrientes de aire, y que es transformada en otras formas útiles para las actividades humanas.

      El viento hace girar las aspas del aerogenerador (normalmente suelen ser tres), y ésta energía de rotación se transforma a través de un conjunto de engranajes en energía eléctrica mediante un dinamo gigante. Para conseguir una mayor eficiencia suele emplazarse la turbina en una zona elevada con respecto al suelo. Por este motivo solemos ver los aerogeneradores en lo alto de grandes torres o mástiles.

      El tamaño de las aspas delimita el radio del área que éstas barren al girar, por lo que para incrementar la potencia generada de un aerogenerador debemos alargar la longitud de las aspas. Los aerogeneradores domésticos, con unos diámetros entre 1 y 5 metros, pueden generar desde 400 W a 3 kW. Estos generadores son capaces de funcionar con vientos más suaves que los de mayor tamaño, que requieren de una velocidad mínima superior para empezar a funcionar. Como podemos imaginarnos el estudio de la ubicación del aerogenerador será determinante en el rendimiento obtenido del mismo.

      Energía Geotérmica es aquella energía que puede obtenerse mediante el aprovechamiento del calor del interior de la Tierra.

      Energía Hidráulica: aquella que se obtiene del aprovechamiento de las energías cinética y potencial de la corriente del agua, saltos de agua o mareas, generalmente de los ríos y corrientes de agua dulce.

      Energía Mareomotriz, que es la que se obtiene aprovechando las mareas. El efecto gravitatorio de la Luna sobre la Tierra produce desplazamientos de las masas de agua terrestres, dependiendo de la posición relativa de la Tierra y la Luna. Las mareas modifican la altura media de los mares y ésta diferencia de alturas puede aprovecharse interponiendo partes móviles al movimiento natural de ascenso o descenso de las aguas, junto con mecanismos de canalización y depósito. Si bien se trata de una energía de bajo costo no contaminante, silenciosa y disponible en cualquier clima y época del año, su gran inconveniente es el impacto visual y estructural sobre el paisaje costero.

      Energía Solar es la energía obtenida mediante la captación de la luz y el calor emitidos por el Sol. Cada año el sol arroja cuatro mil veces más energía que la que consumimos, siendo su potencial prácticamente ilimitado. Se trata de una energía gratuita cuya utilización no produce emisiones de gases de efecto invernadero.

      • La Energía Solar Fotovoltaica se emplea para generar electricidad. Esta energía se consigue a través de una instalación fotovoltaica, que consta de un conjunto de células solares, que son las que conforman los paneles solares, un regulador de carga, un acumulador y un inversor. Los paneles solares son los encargados de transformar, gracias al efecto fotoeléctrico, la energía fotovoltaica procedente del sol en energía eléctrica, en concreto en forma de corriente contínua. Acto seguido ésta se almacena en los acumuladores. Este almacenaje permite disponer de energía no sólo en lo momentos en los que ésta se produce, sino en instantes de baja o nula productividad (consecuencia de la presencia de nubes, una mala orientación, horas nocturnas, etc.). Por último, la finalidad del inversor es la de transformar la corriente contínua, almacenada en los acumuladores, en la corriente alterna para su uso doméstico.
      • La Energía Solar Térmica, que genera calor, es la que se utiliza para producción de agua caliente, en calefacción, climatización de piscinas, etc. Se trata de la más económica y rentable de las energías renovables.

      Otros usos menos conocidos de esta Fuente de Energía es la Potabilización del Agua, las Estufas Solares, la Refrigeración ó la Destilación.

      La preocupación de LEGO por el tema se muestra a través de modelos comerciales como los mostrados anteriormente o, por ejemplo, en la presencia de los paneles solares del actual LEGO Creator 5771, la casa de la colina.

      Pero más patente es la fuerte apuesta de LEGO Education por inculcar en los futuros científicos, que ahora son nuestros pequeños, la preocupación por el medio ambiente, la influencia de nuestra presencia en el mismo y la forma de encontrar solución a nuestras necesidades energéticas cada vez mayores. En esa línea podemos trabajar el tema de las energías renovables a través del conjunto 9688, que puede combinarse bien con el LEGO MINDSTORMS NXT 9797 o bien con el conjunto de mecanismos simples y motorizados 9686.

      El 9594 es un conjunto temático que guía a los estudiantes a través de la construcción con LEGO MINDSTORMS y mediante la programación de una manera estructurada. Contiene tres tapetes de entrenamiento, una alfombra de desafío y un montón de elementos para la construcción de los modelos del desafío, tales como una turbina para la planta de energía eólica o la presa. Disponemos además de las actividades adicionales proporcionadas por el 2009594.

      El nuevo desafío ecológico es pues, crear un nuevo paradigma que defina las condiciones del equilibrio entre el Hombre y la Naturaleza. Para ello, hemos empezado a concebir un nuevo estilo de ciudad, la ecológica, donde la gente es consciente de sus responsabilidades para el medio ambiente, de tal manera que los problemas medioambientales son comentados de una manera continua y proactiva.

      Agricultura a pequeña escala, incluso la agricultura tradicional con sus sistemas de regadío sostenida por los propios habitantes, el uso de aerogeneradores, celulas solares, la edificación a menor altura que permita la circulación de las corrientes de aire, reduciendo el uso de aparatos de aire acondicionado, reservar un 20% de la superficie de la ciudad a zonas verdes ó el transporte mejorado, con grandes zonas peatonales, hacen que, en su conjunto, el Impacto Medioambiental se reduzca en un alto porcentaje. De esta manera, podemos concluir con que una ciudad ecológia se caracteriza por: el uso de eficiente de la energía, el reciclaje, la agricultura comunitaria y la peatonalización.

      Robótica para niños: Scratch day

        El pasado Sábado 21 de mayo se celebró el Scratch day 2011 en Madrid. ElectricBricks impartió dos talleres, uno dirigido a niños a partir de 8 años y otro dirigido a adultos.

        Durante 3 horas, los niños conocieron a fondo el manejo de la herramienta, desarrollando y programando proyectos con Scratch y WeDo. El conjunto de construcción WeDo, perteneciente a la serie LEGO Education, permite construir y programar sencillos modelos LEGO conectados a un equipo informático.

        En esta ocasión construimos una peonza que era impulsada a través de un brazo, dotado de un motor y un sensor de distancia. Los niños manejaron las piezas, aprendieron el funcionamiento de los engranajes, el concepto de fricción, todo ello en un entorno de diversión y colorido propio de LEGO. Con este tipo de actividades Electricbricks pone al alcance del niño el mundo de la ciencia y la tecnología desde un punto de vista educativo, propiciando su desarrollo cognitivo, es decir, el producto del esfuerzo del niño por comprender y actuar en el mundo.

        Esta metodología no está vinculada ni a la inteligencia, ni al coeficiente intelectual, sino a la propia personalidad. Paralelamente, al observar, tocar, manipular y analizar cada uno de los proyectos propuestos, el niño va descubriendo objetos, creados directamente por él mismo, y sus características, mejorando así su desarrollo sensorial. Una de las actitudes que el niño aprende es a ayudar a los compañeros y a participar en retos colectivos de juego de carácter lúdico, sin que sea relevante el grado de resolución del proyecto propuesto, evitando así el carácter competitivo y fomentando a su vez el respeto hacia los demás y el conocimiento de las propias posibilidades.

        Próximas actividades con Plazo de Matriculación abierto:

        Cursos Intensivos:
        JUNIO:
        Primer Taller: 21, 22, 23 y 24.
        Segundo Taller: 27, 28, 29 y 30.
        JULIO
        Tercer Taller: 4, 5, 6 y 7.
        Horario: 17:00 a 20:00 (12h en total)
        Edades: 8+

        Os mostramos a continuación como se desarrolló la actividad.

        El monitor explica el proyecto que se va a realizar.

        Programando la peonza realizada con WeDo

        Asteroids 2.0 – NXT + PF

          asteroidsHoy os queremos presentar una versión de nuestro juego Asteroids para NXT, modificada por nuestro forero JIP. En este caso, se le ha añadido la posibilidad de controlar nuestra nave haciendo uso del Control Remoto IR de Power Functions (ref. 8885-1).

          Los cambios se han realizado en el bloque Ship, que es el que controla la nave, el resto del programa se mantiene igual. En esta versión, el nuevo bloque recibirá la denominación Ship2.0, y en él se sustituye el control de la nave mediante los botones del NXT, por un sistema de control dependiente de las palancas del mando PF.

          Primero hay una inicialización de variables


          var-ini

          El canal del mando no esta prefijado en el programa si no que se lee del fichero canaliir cargándolo en la variable canal a través del bloque Cargar-canal.

          Cargar-canal

          Si no existe el fichero la variable canal toma el valor 1.

          NOTA: Si no queréis hacer uso de un fichero, podéis pedir el valor del canal al usuario, almacenándolo en una variable, tal y como os explicamos en el apartado 2 del artículo sobre el sensor IR-receiver.

          La lectura de los valores del control remoto se realiza en el bloque leermando

          Leermando

          El resultado es un valor en la variable estado y a partir de su valor se mueve la nave por la pantalla.

          Los valores posibles que retorna el control son

          Pos. Palanca
          Dirección
          Potencia
          Arriba
          Verdadero
          100
          Centro
          Verdadero
          0
          Abajo
          Falso
          100

          Estos valores se almacenan en las variables rojo y azul (para las direcciones) y projo y pazul (para las potencias).

          Leermando_1

          El valor que se guardará en la variable estado depende de la posición de las palancas

          ROJA
          AZUL
          VALOR ESTADO
          Centro
          Centro
          00
          Centro
          Arriba
          01
          Centro
          Abajo
          02
          Arriba
          Centro
          10
          Arriba
          Arriba
          11
          Arriba
          Abajo
          12
          Abajo
          Centro
          20
          Abajo
          Arriba
          21
          Abajo
          Abajo
          22

          Leermando_2

          Según el valor de la variable estado la nave se mueve por la pantalla de la siguiente manera

          ESTADO
          MOVIMIENTO
          00
          No se mueve
          01
          Arriba-Izquierda
          02
          Abajo-Derecha
          10
          Arriba-Derecha
          11
          Arriba
          12
          Derecha
          20
          Abajo-Izquierda
          21
          Izquierda
          22
          Abajo


          Leermando_3

          Como estaba en el original, a través de las variables ship_x, ship_y controlamos la posición para dibujar la nave. Por otro lado, las variables counter_x y counter_y controlan los limites de la pantalla (ancho y alto).

          Os dejamos el programa para que podáis descargarlo y probarlo, es un fichero .zip que contiene, por un lado, el archivo Asterois20.rbtx (recordemos que es un encapsulado con todos los bloques, sonidos, imágenes, etc…), y por otro el archivo canaliir.txt.

          Programa Asteroids 2.0

          NOTA: Para que el programa funcione correctamente, será necesario enviar al NXT el fichero “canaliir.txt”.

          Esperamos que os haya gustado y aprovechamos para agradecer de nuevo a JIP su aportación.

          Primer Torneo de Fútbol Róbotico

            El Domingo 17 de Abril tuvo lugar el Primer Torneo de Fútbol Robótico en electricBricks. A pesar de que la asistencia fue menor que en otras competiciones debido a la fecha, el torneo fue muy intenso y divertido.
            Tuvieron lugar tres partidos, en los que se enfrentaron cada vez dos equipos con cuatro robots cada uno, todos controlados remotamente, por infrarrojos (en el caso de los participantes que usaban Power Functions) o por Bluetooth (en el caso de los Mindstorms).

            Los modelos Mindstorms de electricBricks estaban controlados desde PC, mientras que los que trajeron los asistentes eran PF puro o híbrido de NXT y PF, haciendo uso del sensor receptor de infrarrojos.

            Las reglas eran las siguientes:

          • Duración
            Los partidos son dos tiempos de tres minutos de duración cada uno. En caso de que el resultado sea empate, se realizará una

          • Prorroga
            Se jugarán 2 tiempos de 1 minuto.

          • Gol
            Se considerará que un jugador ha metido gol cuando la pelota rebase la línea, imaginaria, entre los 2 palos.

          • Saques
            Para el primer saque se tira una moneda. El capitán del equipo vencedor saca desde el centro hacia su campo, mientras que el equipo contrario permanece al fondo de su campo. Ningún contrincante podrá tocar el balón antes de que el equipo haga un segundo toque del balón.

          • Faltas
            Se considerarán falta: volcar o enganchar a un rival, que la pelota quede dentro de nuestra estructura (mano) y estar dentro de la portería.
            Dichas acciones se castigarán con un saque desde el centro del campo.

          • Pérdida de piezas

            La pérdida de piezas no será sancionada, pero el jugador deberá llevar su vehículo hasta su portería para poder recogerlo y repararlo.

            A pesar de que la normativa de formas y tamaños era clara, algunos de los jugadores tuvieron que recurrir a soluciones de emergencia para poder participar, como estructuras o piezas prestadas. Por lo que finalmente encontramos modelos dispares.

            Os dejamos con algunos vídeos de los partidos.

            Se trata de una selección de las mejores jugadas, debido a la duración resultaba imposible subirlos completos.

            Agradecer desde aquí a todos los jugadores y asistentes su participación en el torneo y ¡confiamos en que lo pasaran tan bien como nosotros! ¡Nos vemos en la próxima!

          • Exoplanetas NXT

            Se denomina exoplaneta o planeta extrasolar a aquel planeta que se encuentra fuera de nuestro Sistema Solar. Si bien durante mucho tiempo se supuso su existencia, no ha sido hasta muy recientemente cuando se ha confirmado su existencia. El descubrimiento en 1992 del primero de ellos alentó el estudio sobre la posibilidad de vida extraterrestre. El objetivo del presente artículo es intentar emular, con carácter educativo, uno de los sistemas de detección de exoplanetas.

            1.- Introducción.
            Ante la pregunta de si estamos solos en el Universo los científicos se plantean la posibilidad de vida extraterrestre. Una forma de medir dicha probabilidad es mediante la ecuación de Drake, que es un modelo simple de estimar el número posible de civilizaciones extraterrestres en la Vía Láctea. Dicho cálculo requiere el conocer la posibilidad de que una estrella contenga planetas, lo que explica que la identificación de los primeros exoplanetas renovara el interés por la existencia de vida alienígena.

            2.- Detección de exoplanetas.

            La detección de planetas extrasolares mediante la observación visual directa sería el método que más información proporcionaría, pero este sistema es en la actualidad impracticable salvo en casos excepcionales. La falta de éxito de este sistema obliga al uso de métodos indirectos. El motivo por el que la visualización directa es tan difícil radica en que la luz que vemos reflejada por un planeta es comparativamente mucho menor que la de la estrella, del orden de una millonésima parte inferior. Es decir, la luz directa de la estrella suele deslumbrar respecto de la reflejada por el planeta. Los pocos casos en los que la visualización directa tiene éxito son aquellos en los que los exoplanetas son excepcionalmente grandes o muy alejados de la estrella

            3.- Tránsito astronómico.

            Se denomina tránsito al fenómeno en el que un astro pasa entre otro astro y el punto de observación. Dada esta situación, el astro que pasa delante del otro astro limita su visibilidad. Desde la Tierra podemos ver tránsitos planetarios, este fenómeno ocurre generalmente con Venus y Mercurio, distinguiéndose su silueta al pasar por delante del Sol. Otro fenómeno más espectacular, el eclipse solar, se considera también como tránsito astronómico. También son visibles los tránsitos de satélites sobre su planeta, como el de Titán sobre Saturno, que además deja ver su sombra sobre la superficie del planeta.


            Imagen de Venus en tránsito delante del Sol.

            El estudio de los tránsitos astronómicos es el método más extendido para el descubrimiento de exoplanetas. La observación sistemática de ciertas estrellas “sospechosas” de tener planetas orbitando a su alrededor permite ver variaciones en la intensidad de la luz que nos llega de ellas. Si se da una disminución en la intensidad de su luz de forma periódica, es muy probable que esté producida por el paso de un planeta por delante de dicha estrella. Este método se complementa con otros como la astrometría o el estudio de las velocidades radiales.

            Simulacion del tránsito

            El gráfico anterior es una simulación en el que se representa la variación de la luminosidad motivada por el tránsito. La curva está normalizada verticalmente, y en el eje horizontal tenemos el transcurso temporal en número de horas. El valor importante a destacar de la gráfica es el intervalo existente entre los picos, que viene determinado por el tiempo de tránsito.

            4.- Experimento con LEGO MINDSTORMS NXT.

            Hemos planteado un experimento que utiliza el estudio del tránsito para descubrir si existen planetas orbitando alrededor de una estrella. Queremos comprobar si con el hardware disponible a nuestro alcance somos capaces de realizar una emulación de un pequeño sistema solar artificial, con una estrella y dos planetas orbitando a su alrededor, y de descubrir si, a través de la medida de infrarrojos realizada por nuestro satélite, somos capaces de discriminar los tránsitos de los planetas. Como “estrella” hemos usado la IR Ball, que ya usamos en artículos anteriores. A falta de la misma también se puede emplear una lámpara con bombilla incandescente o halógeno, puesto que necesitamos que se caliente y emita infrarrojos. Como planetas valdría cualquier objeto opaco.

            sistemasolar

            Alrededor de esta estrella orbitan dos planetas con trayectorias diferentes y a diferente velocidad, controlados con un Mindstorms con dos motores. Las velocidades de los motores son diferentes, para que los ciclos de los planetas no coincidan. A continuación podemos ver un detalle de la mecánica que produce los movimientos de los tres astros.

            detalle

            Observando este sistema solar se encuentra un satélite. Como la estrella emite infrarrojos, el satélite está equipado con un Sensor de Búsqueda IR NXT para medir su emisión.

            satelite

            Con este experimento queremos comprobar si es posible descubrir el tránsito de algún planeta por delante de la estrella. En teoría, la estrella emite constantemente al mismo nivel, aunque presuponemos que habrá una ligera oscilación en la intensidad y que la recepción de los infrarrojos no será totalmente constante, debido a las limitaciones del sensor. Si un planeta pasa entre la estrella y el sensor, el nivel de infrarrojos recibido será menor que el de la estrella en cierto momento, que se repetirá a intervalos regulares.

            satellite_eB

            Representación artística del experimento.

            Aquí mostramos el programa del satélite, que es bastante sencillo. Básicamente es un bucle de 30 segundos de duración durante el cual se generan dos archivos: uno recoge los datos que recibe el sensor de infrarrojos y el otro recoge los datos del milisegundo en el que se toma la medida.

            Programa para el cálculo de los tránsitos en NXT-G, por electricBricks

            A continuación podemos ver un vídeo del experimento:

            Y estos son los resultados que hemos obtenido. Hemos generado una gráfica que representa en el eje vertical el nivel de intensidad de los infrarrojos recibidos por el sensor, y en el eje horizontal el tiempo, en un intervalo de 30 segundos.

            grafica

            Se puede observar que si la estrella no tuviese ningún planeta la oscilación sería mínima. En el caso de que la estrella tenga un planeta, se pueden observar los picos en los que el planeta eclipsa la vista de la estrella, que se repiten a intervalos regulares. Si hay dos planetas se pueden observar dos intervalos diferentes, uno más corto y uno más largo. El más corto coincide con el intervalo del caso en el que había un planeta. El más largo es prácticamente 1,5 veces el corto, con lo cual coincide una de cada dos vueltas. Podemos ver un poco mejor los ciclos en la siguiente imagen.

            Relación entre períodos de tránsito de los exoplanetas, por electricBricks

            Ahora ya sabéis cómo detectar planetas en el espacio exterior con nuestros robots. Aún así no aconsejamos lanzar un NXT al espacio… de momento.

            5.- Conclusión.
            Con el presente artículo mostramos la posibilidad de realizar tanto un experimento atípico como el darle un uso también atípico, tanto al sensor de infrarrojos como a la bola emisora de infrarrojos. En el futuro tenemos previsto realizar un segundo artículo ampliando las posibilidades del experimento, puesto que la elevada sensibilidad del sensor lo permite. Como opciones futuras para mejorar el experimento puede analizarse cuál es la influencia de los siguientes factores bien en la intensidad relativa de la señal recibida o en el período del tránsito medido modificando:

            • Las distancias entre planetas y estrella.
            • Las distancias entre estrella y satélite.
            • Tamaño de los planetas.
            • Tamaño relativo entre los planetas.
            • Planeta orbitando en un plano distinto al del satélite, etc.

            6.- Información adicional:

            Lógica difusa & NXT

              logica1La lógica difusa o lógica heuristica se basa en lo relativo de lo observado, utiliza expresiones que no son ni totalmente ciertas ni completamente falsas (lógica binaria o booleana), es decir, es la lógica aplicada a conceptos que pueden tomar un valor cualquiera de veracidad dentro de un conjunto de valores que oscilan entre dos extremos, la verdad absoluta y la falsedad total.

              Según esta teoría, la función que la represente será una función de transferencia (que tomará cualquiera de los valores reales comprendidos en el intervalo [0,1]) la que determine el grado de pertenencia de un elemento a un conjunto.

              Vamos a utilizar la siguiente imagen como explicación
              logica1

            • El rectángulo de la izquierda representaría una lógica binaria (blanco o negro),
            • El de la derecha representa lógica difusa:
              Tenemos todo un conjunto de valores posibles, en este caso una escala de grises. Nuestro intervalo se desplaza del blanco al negro, con todos los valores intermedios.

              La lógica difusa se basa en reglas heurísticas de la forma

              SI (antecedente) ENTONCES (consecuente)

              donde el antecedente y el consecuente son también conjuntos difusos, ya sea puros o resultado de operar con ellos. Los métodos de inferencia para esta base de reglas deben ser simples, veloces y eficaces. Los resultados de dichos métodos son un área final, fruto de un conjunto de áreas solapadas entre sí (cada área es resultado de una regla de inferencia).

              Fuzzy

              Aplicando esto a la robótica, en concreto a la inteligencia artificial, la lógica difusa se utiliza para la resolución de una variedad de problemas, principalmente los relacionados con control de procesos industriales complejos y sistemas de decisión en general, la resolución la compresión de datos. Los sistemas basados en lógica difusa imitan la forma en que toman decisiones los humanos, con la ventaja de ser mucho más rápidos. Estos sistemas son generalmente robustos y tolerantes a imprecisiones y ruidos en los datos de entrada.

              En nuestro caso, vamos a aplicar la lógica difusa a un caso clásico de toma de decisiones: ¡robot sigue líneas!

              La idea es no trabajar solamente con blanco y negro, si no también con los valores intermedios.

              Si el sensor está exactamente en el borde, el valor no será ni blanco ni negro, será un valor entre ambos, de tal manera que, cuanto más entra en sensor en la línea, más próximo al negro es el color que se percibe y viceversa.

              De esta manera ya no estamos limitados a girar sólo a la derecha o a la izquierda, si no que podremos girar más o menos hacia la derecha o a la izquierda, en función de lo próximos que estemos al negro o al blanco respectivamente. ¿Y si estamos justo en el borde? -> Seguimos rectos.

              Para conseguir esto, debemos poder controlar los motores en función de esas variaciones de luz, para lo cual utilizaremos la siguiente fórmula:

              Potencia Motor B=(Valor Sensor-Valor Negro)*Factor de corrección
              Potencia Motor C=(Valor Blanco-Valor Sensor)*Factor de corrección

              Para unos valores de 30 para negro y 70 para blanco, aplicamos un factor de corrección de 2.5 y obtenemos los siguientes resultados.

              Valor del sensor
              Potencia motor B
              Potencia motor C
              30
              0
              100
              40
              25
              75
              50
              50
              50
              60
              75
              25
              70
              100
              0

              gráfica

              NOTA: ¿De donde sale el factor de conversión?
              La medidas del sensor están en el intervalo [0,100], igual que la potencia. Debido a las condiciones específicas de los colores de nuestro experimento, no abarcaremos todo el intervalo posible, sólo los valores comprendidos en el intervalo [30,70] por lo que nos veremos obligados a aplicar una corrección. Es recomendable empezar con valores pequeños e ir aumentando progresivamente la corrección para ganar velocidad.

              Veamos la implementación en NXT-G

              programa_NXT-G

            • 1 variable donde se guarda la información leída por el sensor : Luminosidad
            • 3 constantes para los valores conocidos, a saber: Blanco, Negro y Corrección

              Implementación en RobotC


              #pragma config(Sensor, S3, SensorLuz, sensorLightActive)
              #pragma config(Motor, motorB, , tmotorNormal, PIDControl, )
              #pragma config(Motor, motorC, , tmotorNormal, PIDControl, )
              //*!!Code automatically generated by 'ROBOTC' configuration wizard!!*//

              task main(){

              const short Negro= 25;
              const short Blanco= 65;
              const float correccion =2.5;

              int Luminosidad=0;

              while(true){
              Luminosidad=SensorValue[SensorLuz];
              motor[motorB]= (Luminosidad -Negro)* correccion;
              motor[motorC]= (Blanco- Luminosidad)* correccion;
              }
              }

              Vídeo de los resultados

              Como se puede apreciar, la navegación es mucho más fluida, no existe cabeceo. El robot trata, en todo momento, de moverse justo por el borde la línea. Y lo consigue con bastante exactitud.

              La implementación para NXT está basada en el trabajo de Stefan’s Robots.

              Para saber más:

            • Lógica difusa y NXT en LeJOS: Proyecto NxtFuzzyLogic
            • Wikipedia: Lógica difusa
            • Conceptos Generales sobre lógica difusa (PDF): Extracto de una tesis de la UPC
            • ROS (Robot Operating System) & NXT (2/2)

              rvizVamos allá con la segunda y última entrega de nuestra mini serie sobre ROS. Tras el tutorial de instalación, hoy vamos a centrarnos en dotar a nuestro robot de una de las herramientas que a buen seguro más os llamaron la atención del vídeo presentación: la posibilidad de mostrar nuestro robot en una representación 3D en el ordenador, respondiendo en tiempo real a lo que ocurre en el mundo físico.

              La herramienta que se encargará de esto se llama rviz, podéis ver la presentación de este proyecto en el siguiente vídeo:

              ¿Sorprendidos con sus posibilidades? ¡Vamos a instalarlo!

              1. Instalación de rviz

              Para instalarlo, tendremos que asegurarnos de satisfacer todas las dependencias necesarias, para ello usaremos el siguiente comando:


              rosdep install rviz

              Ahora generamos el visualizador:


              rosmake rviz

              Finalmente, podremos ejecutarlo con el siguiente comando:


              rosrun rviz rviz

              NOTA: Para poder ejecutar rviz, tendremos que tener en otra terminal un servidor funcionando para poder comunicarnos con el NXT.
              En estas pruebas, estamos usando el mismo que utilizamos al final del artículo anterior (ROS- Robot controlado desde el PC).


              roslaunch nxt_robot_sensor_car robot.launch

              Si no tenéis un servidor funcionando al ejecutar rviz, obtendréis el siguiente mensaje

              rVIZ- error_

              2. Configuración de rviz

              Cuando lo ejecutemos, tendremos una serie de terminales funcionando, algo como esto

              Pantallazo-RVIZ_

              Nótese que en cada terminal se está ejecutando una herramienta distinta:

            • Terminal Superior Izquierda: Servidor

              roslaunch nxt_robot_sensor_car robot.launch

            • Terminal Superior Derecha: Programa de control por teclado

              roslaunch nxt_teleop teleop_keyboard.launch

            • Terminal Inferior Izquierda: Rviz

              rosrun rviz rviz

              Por otro lado, si es la primera ejecución, tendremos un entorno gráfico de representación como el siguiente

              Rviz_Ini_

              Lo que os hará preguntaros dónde está ese robot en 3D que se parece tanto a nuestro tribot.

              Vamos por partes. La herramienta rviz nos ofrece un entorno 3D que puede trabajar con prácticamente cualquier plataforma de robótica, por lo que sus opciones de configuración son muchas, y muy amplias.

              Lo primero que haremos es poner ese cono verde tan chulo que nos muestra las mediciones del sensor ultrasónico

              Rviz-Add Ultrasonic_

              que tendremos que configurar así

              Rviz-AddTopic_

              Después pondremos una rejilla (grid) en pantalla para poder posicionar mejor nuestro robot en el espacio:

              Rviz-Add Grid_

              Y ahora, lo que todos estábamos esperando… ¡el robot!

              rviz trabaja con archivos “.urdf” para la representación de los modelos, estos archivos contienen información sobre los elementos del robot, así como su orientación y su nomenclatura.

              Para este tutorial hemos usado el modelo de ejemplo que se incluye en el paquete ROS Base que instalamos en el artículo anterior, por lo que su representación 3D difiere del tribot con el que hacemos las pruebas.

              Si queremos, podemos diseñar nuestro modelo con LDraw o LEGO Digital Designer y exportarlo a rviz con una herramienta llamada nxt_lxf2urdf, de la que podéis encontrar más información en el enlace correspondiente, al final de este artículo.

              Vamos a cargar el modelo en el visualizador:

              Rviz-Add Robot_

              ¡Listo!… Ya tenemos nuestro robot mostrando información en pantalla

              Pantallazo-grid_

              Si ponéis algún objeto delante y lo movéis comprobaréis cómo cambian las dimensiones del cono que representa el sensor ultrasónico. Podéis cambiar la vista con los botones del ratón, hasta encontrar una que os guste.

              ¿Habéis notado ya que falta algo?
              Lo del sensor está muy bien pero… ¿Cómo consigo que el robot se mueva en la pantalla tal y como lo hace en el mundo real?

              Esa es una muy buena pregunta, que tiene una respuesta muy sencilla:

              Rviz-Odometría_

              Como podéis ver, dentro de la configuración general, tenemos la posibilidad de decirle al visualizador qué define la vista de nuestro robot mediante las opciones Fixed Frame (marco fijo) y Target Frame (marco objetivo). Conseguir que nuestro robot responda tal y como lo hace en el mundo real es tan simple como seleccionar en ambos marcos la opción “odom_combined”.

              Esta opción permite al visualizador tomar la información referente a los movimientos y posicionamiento del robot (odometría) desde el programa servidor. Recordemos que nuestros motores llevan integrado un sensor de rotación que nos permite saber cuánto y hacia dónde se han movido, y por tanto, tendremos información de los cambios de posición que ha sufrido el robot.

              Ahora sí está listo, os ponemos un vídeo para que veáis los resultados de este tutorial.

              NOTA: rviz guarda la configuración al cerrar, por lo que no tendréis que volver a añadir todos los elementos cada que lo abráis.

              Información adicional:

            • Programación Orientada a Objetos

                En muchas ocasiones la programación orientada a objetos (OOP) puede convertirse en una forma excesivamente compleja de resolver un problema, en especial si se trata de un problema simple. Pero el nivel de abstracción que ofrece esta metodología permite acometer problemas complejos de forma mucho más simple que la programación clásica. A pesar de que ya hemos hecho uso de este tipo de técnicas en los artículos anteriores, en el presente trataremos de centrarnos en las características que diferencian a estas técnicas.

                En la actualidad los grandes problemas del mundo real se están descomponiendo, desde el punto de vista de programación, en objetos que encierran ciertas características y engloban no solamente a los datos sino también a las funciones relacionadas y que operan con ellos. Esto permite la modularidad, el encapsulado, polimorfismo y la herencia, que son las características principales de la programación orientada a objetos.

                Diferencias y evolución de la programación estructurada a la OOP.

              • La programación estructurada se caracteriza por un enfoque top-down con estructuras orientadas a bloques en las que el código fuente se divide muchas veces en bloques dependientes de las sentencias condicionales, bucles, etc. Este tipo de programación es apropiada para el diseño de pequeños programas, en los que no se requiere de los recursos adicionales que puede necesitar una aplicación orientada a objetos. En el caso de tener que abordar grandes problemas, la metodología top-down opta por descomponer el problema en otros de menor tamaño de forma sucesiva hasta que la complejidad de cada uno de estos subproblemas es tratable directamente. Una de las principales desventajas de la programación estructurada es que resulta muy difícil reutilizar el trabajo hecho para poder aplicarlo en otros proyectos.

                El Pascal es el lenguaje de programación estructurada clásico por excelencia. El auge de este tipo tipo de programación se produjo en la década de los años 70 a los 80, y sus carencias irían superámdose con el diseño denominado bottom-up, en el que se trabaja de modo inverso al modo anterior: se parte de pequeños problemas que ya han sido resueltos previamente, de forma que se puede reutilizar código, y se trata de avanzar hacia la solución del problema global mediante estos componente de software ya existentes. El éxito de esta metodología pasa por un diseño modular de componentes, en el que deberán especificarse y documentarse correctamente estos módulos para facilitar su reutilización posterior.

                Desde el momento en el que disponemos de varios de los módulos anteriores y nos disponemos a crear una nueva aplicación haciendo uso de dichos módulos, lo único que nos interesa conocer es cómo debemos hacer uso de los mismos, pero sin importarnos su implementación interna, los datos o los procedimientos que contiene. Es decir, estaremos interesados exclusivamente en lo que se denomina como el interfaz del módulo, y el hecho de poder despreocuparnos del interior de esos módulos se denomina information hiding, que es la base de la ingeniería del software.

              • La evolución final de la idea anterior acabó por plasmarse en lo que hoy se conoce como la programación orientada a objetos. La OOP es más apropiada en proyectos de cierta envergadura, en donde la modularidad de los objetos facilita la labor del programador y favorece la reutilización del código. En este caso pueden emplearse diferentes clases para representar áreas de funcionalidad diferente. El Java o el C++ son dos de los más populares lenguajes de programación orientada a objetos.

                En términos generales podemos decir que los programas estructurados son más fáciles de entender -sin que ello implique que sean más fáciles de mantener- y de ejecución más rápida. En la actualidad se ha extendido ampliamente la programación orientada a objetos y el pilar sobre el que se sustenta es la reutilización de código. La idea es ir construyendo una librería de código a medida que vamos programando, con objeto de que cuando tengamos que crear una nueva aplicación podamos comprobar previamente si algunos de los módulos ya creados pueden ser reutilizados. Esto conlleva una cultura entre los programadores involucrados, que les obligue a tener presente al programar que no tratamos sólo de resolver un problema puntual actual, sino que además pretendemos que el tiempo invertido en ello pueda ser amortizado reutilizando dichos módulos en el futuro.

                La reutilización de código tiene varias ventajas, como:

              • Fiabilidad, porque se trabaja con módulos ya probados. La reutilización de estos módulos en aplicaciones diferentes por varios diseñadores permitirá la detección de problemas no conocidos, lo que puede retroalimentarse para optimizar el código.
              • Eficiencia.
              • Reducción de costes.
              • Consistencia, la creación de las librerías de código obliga a homologar o estandarizar la forma de programar entre todos los programadores.
              • Llegados a este punto conviene definir más concretamente qué es un objeto y una clase, así como sus propiedades.

                Una clase es una definición abstracta de un objeto. En esta definición se engloban tanto las características del objeto, sus propiedades y también las funciones para operar con ellos. Pero una clase no representa a un objeto concreto sino que no es nada más que una caracterización o definición: la implementación se materializa cuando creamos al objeto como perteneciente a una clase determinada. Este proceso de creación del objeto suele denominarse instanciación.

                El enfoque anterior, basado en clases, es el más extendido en el mundo de la programación OO, y con ella trabajan lenguajes como Java o C++. Pero existe una alternativa a la propuesta basada en clases: es la basada en prototipos. Esta segunda propuesta crea los objetos mediante un proceso denominado de clonación sobre el prototipo, que no es otra cosa que un estereotipo que proporciona la base o estructura para la operación de copiado. Esta alternativa está menos extendida que la primera.

                Describamos las propiedades generales de los objetos:

                • Encapsulación. Esta es la propiedad que permite ocultar la información. En un mundo dominado por la información como el nuestro, podríamos preguntarnos para qué puede servir la posibilidad de ofuscar cierta información. El objetivo de esto no otro que el de presentar a un objeto a través de un interfaz que oculte los detalles que no son necesarios para operar con un objeto. En muchas ocasiones simplemente queremos realizar ciertas operaciones con objetos, y nos tiene sin cuidado cómo han sido implementados éstos internamente: el encapsulado es la propiedad que lo permite.
                • Polimorfismo. Es la propiedad que permite asignar un diferente significado o aplicar un uso determinado dependiendo del contexto en el que se emplea:
                  1) Aplicado a una variable, por ejemplo USUARIO, si permitimos que la variable usuario pueda tener varias formas, como que pueda ser de tipo entero, o de tipo string, entonces el programa debería elegir cuál de esas formas debería aplicar al ejecutar el código. De este modo, si empleamos un formulario que acepte como datos de entrada los provenientes de la variable USUARIO, estaríamos permitiendo la posibilidad de que el registro de entrada se realice bien con un nº de usuario, o bien con una secuencia de caracteres. El programa debería tener previsto qué hacer en cada caso.
                  2) Si aplicamos el polimorfismo a un método nos encontrarnos con el concepto denominado overload (sobrecarga): Los métodos sobrecargados son aquellos qie mantienen el nombre dentro de la misma clase. Al sobrecargar un método modificamos su funcionalidad dependiendo del tipo de parámetros de entrada que éste recibe (lo cual viene especificado por su declaración): De hecho el criterio que determina si los métodos sobrecargados en Java pueden compilar es que las listas de parámetros de los métodos sobrecargados puedan determinar el método al que llamar. Así, podemos crear dos métodos con el mismo nombre: es importante por ello reseñar la diferencia en la declaración de los dos métodos empleados para ello, puesto que dependiendo de los parámetros recibidos y de dichas definiciones el compilador deberá seleccionar qué método emplear:

                public int sum(int num1, int num2);
                }
                ...
                }

                public string sum(string cad1, string cad2);
                }
                ...
                }

                • La herencia es una característica que permite la reutilización del código y facilita la extensibilidad del software. Gracias a ella podemos crear nuevas clases partiendo de una jerarquía de clases ya existente, lo que evita tanto el rediseño, como tener que volver a verificar el código ya probado. Del mismo modo se facilita la creación de objetos a partir de otros ya existentes, obteniendo características (métodos y atributos) similares a los ya existentes. La herencia es la relación entre una clase general y otra clase más especifica: permite crear una clase derivada a partir de una primera clase, que se denomina superclase.

                Relacionando el presente artículo con el LEGO MINDSTORMS, nos encontramos que si requerimos de un lenguaje orientado a objetos para programar el NXT podemos hacerlo básicamente con LeJOS, la versión Java para NXT. Por otro lado disponemos del Lua: aunque Lua no es un lenguaje orientado a objetos en sentido estricto, dispone de meta-mecanismos que permiten la implementación de clases y la herencia.

                ROS (Robot Operating System) & NXT (1/2)

                ros_orgEl proyecto ROS (Robot Operating System) nace con la intención que dotar a los desarrolladores de software de librerías y herramientas específicas para crear aplicaciones de robótica. Una de sus principales ventajas es que nos permite abstraernos del hardware específico, dándonos además acceso a drivers para los diferentes dispositivos, librerías, visualizadores, gestión de mensajes y paquetes de datos y mucho más.

                Esta potente herramienta de desarrollo de código libre (o abierto) ha llegado a nuestro NXT. La integración de ROS con el NXT se ha llevado a cabo en colaboración con el proyecto NXT-Python, y los resultados son impresionantes, como se puede apreciar en el siguiente vídeo.

                Para todos aquellos que queráis dar un paso más con vuestro NXT, a continuación podréis encontrar una guía paso a paso sobre cómo instalarlo y hacerlo funcionar.

                Aviso a navegantes, toda la instalación, configuración y pruebas de esta guía se han realizado en Ubuntu, en concreto en su versión 10.4 (Lucid Lynx).

                1. Configuración del ordenador.

                Añadimos el grupo

                sudo groupadd lego

                Añadimos el usuario al grupo creado

                sudo usermod -a -G lego {username}

                Creamos una reglas para poder usar el USB sin problemas


                echo "BUS==\"usb\", ATTRS{idVendor}==\"0694\", GROUP=\"lego\", MODE=\"0660\"" > /tmp/70-lego.rules && sudo mv /tmp/70-lego.rules /etc/udev/rules.d/70-lego.rules

                Reiniciamos udev


                sudo restart udev

                Finalmente cerramos la sesión de usuario y la volvemos a abrir.

                Nota: Los que hayáis seguido previamente nuestro tutorial de instalación LeJOS en Linux, en principio, podéis omitir este paso porque ya está hecho.

                2. Instalación de ROS.

                2.1 Añadir repositorios

                Tendremos que asegurarnos primero de que nuestro Ubuntu acepta repositorios “restricted”, “Universe” y “Multiverse”.

                SoftwareSources


                SoftwareSources (1)

                Una vez hecho esto, desde la terminal, añadimos los repositorios de ROS, dependiendo de vuestra versión de Ubuntu:

                Ubuntu 9.04 (Jaunty)

                sudo sh -c 'echo "deb http://code.ros.org/packages/ros/ubuntu jaunty main" > /etc/apt/sources.list.d/ros-latest.list'

                Ubuntu 9.10 (Karmic)

                sudo sh -c 'echo "deb http://code.ros.org/packages/ros/ubuntu karmic main" > /etc/apt/sources.list.d/ros-latest.list'

                Ubuntu 10.04 (Lucid)

                sudo sh -c 'echo "deb http://code.ros.org/packages/ros/ubuntu lucid main" > /etc/apt/sources.list.d/ros-latest.list'

                Ubuntu 10.10 (Maverick)

                sudo sh -c 'echo "deb http://code.ros.org/packages/ros/ubuntu maverick main" > /etc/apt/sources.list.d/ros-latest.list'

                2.2 Configuración de las claves (Keys)


                wget http://code.ros.org/packages/ros.key -O - | sudo apt-key add -

                2.3 Instalación

                Actualizamos el gestor de paquetes


                sudo apt-get update

                Instalamos el paquete ROS Base, que nos dará el conjunto completo de herramientas para robótica, que incluye software de navegación, visualización, …

                NOTA: Tendremos que ser pacientes, pues el paquete que descargaremos es bastante grande (unos 2Gb)


                sudo apt-get install ros-cturtle-base

                2.4 Configuración de variables de entorno


                echo "source /opt/ros/cturtle/setup.bash" >> ~/.bashrc
                . ~/.bashrc

                3. Instalación de NXT-ROS

                Para instalar NXT-ROS necesitaremos descargar primero las siguientes herramientas:


                sudo apt-get install python-setuptools
                sudo easy_install -U rosinstall
                sudo apt-get install mercurial

                Ahora vamos a descargar y generar los enlaces de NXT-ROS


                rosinstall ~/nxtros /opt/ros/cturtle "http://www.ros.org/wiki/nxt/Installation?action=AttachFile&do=get&target=nxt-0.1.0.rosinstall"
                . ~/nxtros/setup.sh
                rosmake nxt nxt_apps nxt_robots --rosdep-install

                Cada vez que abramos una nueva terminal y tengamos que hacer uso del NXT-ROS, tendremos que introducir en la consola lo siguiente:


                . ~/nxtros/setup.sh

                4. Probando la instalación.

                IMPORTANTE: Para que NXT-ROS funcione con nuestro Mindstorms, necesitaremos tener el firmware 1.28 o superior. Si no sabéis cómo actualizar, podéis seguir el siguiente tutorial: Actualizando el Firmware del NXT

                Vamos a comprobar antes que nada si nuestro NXT se conecta correctamente, para eso, vamos a realizar una prueba muy sencilla:

                Conectamos un sensor de contacto al puerto 1 de nuestro NXT

                En una terminal, tecleamos


                . ~/nxtros/setup.sh
                rosmake nxt_python
                roscore

                En otra terminal distinta


                . ~/nxtros/setup.sh
                rosrun nxt_python touch_sensor_test.py

                Deberíamos estar viendo el estado actual de nuestro sensor, como se puede apreciar en la siguiente captura

                Pantallazo-OK

                Si obtenéis el siguiente mensaje al ejecutar el test


                Traceback (most recent call last):
                File "/u/mwise/external_repos/foote-ros-pkg/nxt/trunk/nxt_python/sensor_tests/touch_sensor_test.py", line 21, in {module}
                sock = nxt.locator.find_one_brick()
                File "/u/mwise/external_repos/foote-ros-pkg/nxt/trunk/nxt_python/src/nxt/locator.py", line 57, in find_one_brick
                raise BrickNotFoundError
                nxt.locator.BrickNotFoundError

                Pantallazo-error

                Significa que hay algún problema de conexión o configuración, por lo que deberéis comprobar que habéis seguido todos los pasos correctamente.

                5. ¡Robot controlado desde el PC!.

                Todo ha ido bien, ya tenemos ROS instalado en nuestro ordenador, vamos a realizar un pequeño experimento.

                Para hacer una primera prueba, vamos a modificar uno de los archivos que nos hemos descargado, en concreto se trata del archivo “robot.yaml” que se encuentra en:

                /home/NOMBRE_USUARIO/nxtros/nxt/nxt_robots/nxt_robot_sensor_car

                Este archivo es el que nos indica qué tiene conectado el robot y a qué puerto/s. Para asegurarnos de no cargarnos nada, hacemos una copia de seguridad del archivo, y luego lo abrimos (por ejemplo con gedit).

                Borramos su contenido y lo sustituimos por:


                nxt_robot:
                - type: motor
                name: r_wheel_joint
                port: PORT_A
                desired_frequency: 16.0

                - type: motor
                name: l_wheel_joint
                port: PORT_B
                desired_frequency: 16.0

                - type: ultrasonic
                frame_id: ultrasonic_link
                name: ultrasonic_sensor
                port: PORT_4
                spread_angle: 0.2
                min_range: 0.01
                max_range: 2.5
                desired_frequency: 5.0

                Y guardamos el archivo. Lo que hemos hecho es simplificarlo para que tenga sólo 2 motores, en los puertos A y B, y 1 sensor ultrasónico en el puerto 4.

                Ahora tenemos que volver a generar el ejecutable desde la terminal, para que tenga el cuenta las modificaciones.


                rosmake nxt_robot_sensor_car

                Una vez hecho esto, podemos ejecutarlo con el siguiente comando

                roslaunch nxt_robot_sensor_car robot.launch

                Dejamos esta terminal funcionando y abrimos una nueva terminal, es aquí donde haremos funcionar el programa controlador.

                Para utilizar el teclado utilizaremos:

                roslaunch nxt_teleop teleop_keyboard.launch

                Si por el contrario tenemos un joystick en el ordenador (aunque no es muy común hoy en día) y queremos usarlo para controlar el NXT, usaremos


                roslaunch nxt_teleop teleop_joy.launch

                Y tendremos esto en las terminales

                Pantallazo-control

                Os dejamos con un vídeo para que veáis cómo se comporta el NXT.

                Segunda parte ya publicada. Exploramos la posibilidad de representación gráfica del NXT en la pantalla de nuestro ordenador, como se puede ver en el vídeo que abre el artículo.

                ROS (Robot Operating System) & NXT (2/2)

                Esperamos que os haya gustado, si tenéis alguna duda, podéis postearla en el foro.

                Controla tu NXT con PF: Sensor Receptor de IR

                  Tribot-IRYa hablamos de las posibilidades de control de elementos Power Functions con NXT (Sensor IR Link). En esta ocasión queremos centrarnos en explorar la opción inversa, es decir, controlar el NXT desde un mando IR de PF. Esta opción es la más utilizada en los modelos híbridos que hemos visto en las competiciones. Por eso, hoy vamos a trabajar con el Sensor Receptor de IR (ref. MS1032)

                  Su principal ventaja es la facilidad de manejo de los mandos PF frente a otras soluciones, como pueden ser los móviles, ordenadores u otro NXT (joystick NXT). Por contra nos encontraremos con las limitaciones propias de cualquier modelo PF: alcance limitado, limitación de canales disponibles (para poder participar en una competición, por ejemplo), o dependencia de factores externos que pueden deteriorar la señal (luz solar).

                  Para poder hacer uso de este sensor en vuestro NXT-G necesitaréis descargaros el bloque correspondiente:

                  Sensor Receptor IR

                  Una vez lo hemos descargado vamos a explorar las posibilidades de este nuevo bloque:

                  IRReceiver2El siguiente esquema muestra la relación de entradas/salidas.
                  Vamos a explicar para qué sirve cada una:

                  1. El puerto al que está conectado el Receptor IR.

                  2. Canal de IR en el que está el mando de PF.

                  3. Dirección de giro del motor rojo (salida de color rojo del mando).

                  4. Potencia del motor rojo (de 0 a 100).

                  5. Dirección de giro del motor azul (salida de color azul del mando).

                  6. Potencia del motor azul (de 0 a 100).

                  7. Freno de motor rojo.

                  8. Freno de motor azul.

                  9. Configuración del motor rojo (Información de velocidad que envía el mando, de -7 a +7).

                  10. Configuración del motor azul (Información de velocidad que envía el mando, de -7 a +7).

                  Ahora que ya sabemos qué es cada conector, veamos con qué información trabajan:

                  Conector Tipo de Datos Rango ¿Qué significa el valor?
                  Puerto Número 1 – 4 El número indica el puerto al que está conectado el sensor
                  Canal Número 1 – 4 El número indica el canal en el que emite el mando
                  Dirección Lógica Verdadero/Falso Verdadero = Hacia delante

                  Falso = Hacia atrás
                  Potencia Número 0 – 100
                  Freno Lógica Verdadero/Falso Verdadero = Freno

                  Falso = Flotación
                  Configuración Número -7 – +7 Configuración de velocidad que proporciona el mando con control de velocidad (ref. 8879)

                  Vamos a hacer un par de experimentos para ver cómo funciona:

                  Tribot-IR

                  Antes de nada, tendréis que importar el nuevo bloque al software NXT-G, si tenéis alguna duda de cómo hacerlo, lo explicamos en el artículo sobre el Sensor de Color (ref. 9694)

                  1. Tribot básico

                  Este experimento, aunque muy simple, nos permitirá hacernos una idea rápida de las opciones de control más usadas. En este caso, vamos a asignar a cada motor una de las salidas del mando (roja o azul), de tal forma que podamos controlar los 2 motores de forma independiente.

                  El programa es muy sencillo


                  Programa 1

                  Llevamos un cable de datos de las salidas correspondientes a sentido de giro (dirección) y potencia hasta las entradas de los bloques de motor.

                  Vamos a complicarlo un poco más, añadiendo una funcionalidad de lo más útil: poder cambiar el canal IR al arrancar el programa, sin necesidad de reprogramar desde el ordenador.

                  2. Tribot con Selección de Canal IR

                  Programa 2

                  Como se puede ver, el programa es muy similar al anterior, sólo que ahora tenemos una variable llamada canal, que se encarga de almacenar el valor que introduce el usuario.

                  Para obtener este valor del usuario, usamos el bloque Elige canal, que podemos ver en detalle a continuación:

                  Detalle Bloque_

                  Lo primero es mostrar en pantalla el mensaje para el usuario e inicializar la variable canal a 1 (ya que nuestros canales van de 1 a 4).
                  Mientras que el usuario no pulse el botón “intro” (condición de salida del bucle) sumamos 1 cada vez que nos pulsen el “botón derecho”.
                  Antes de salir del bloque, mostramos en pantalla el canal elegido, este texto se mantendrá durante toda la ejecución del programa principal.

                  Os dejamos el vídeo

                  Esperamos que os guste.

                  Mindstorms NXT controlado con Kinect

                    Kinect-tribotLas pasadas navidades llegó a las tiendas el nuevo sistema de juego “Kinect” para Xbox 360 de Microsoft, revolucionando la forma de jugar/ interactuar con la consola. ¿En qué consiste? básicamente permite a la consola recibir información sobre los movimientos del usuario, de tal forma que dichos movimientos pueden pasar a formar parte del juego. Los desarrolladores, conscientes del enorme potencial del nuevo dispositivo, no tardaron mucho en crear herramientas que les permitieran exprimir las innumerables aplicaciones y posibilidades de una tecnología semejante.

                    Kinect

                    La compañía PrimeSense, responsable del desarrollo del proyecto, fue fundada por antiguos ingenieros del ejército Israelí. De hecho, parte de la tecnología utilizada en Kinect se basa en prototipos militares de detección de enemigos en campo de batalla. Por lo tanto parece lógico que, aunque se nos presente como un accesorio más de la videoconsola, se puede usar para eso y para mucho, mucho más.

                    ¿Cómo funciona?

                    El sistema Kinect combina un emisor de IR con una cámara VGA (640 x 480), proporcionando una imagen en la que cada píxel es una posición 3D independiente. De esta manera el sistema proporciona información sobre 307.200 puntos independientes en el espacio, consiguiendo un seguimiento prácticamente perfecto de los movimientos del usuario situado frente al dispositivo.


                    Content_63.6

                    Cómo no podía ser de otra manera, los experimentos no han tardado en llegar al campo de la robótica, dando lugar a creaciones tan impresionantes como este cuadricóptero autónomo que utiliza el Kinect para detectar obstáculos.

                    Kinect-Flying-Machine

                    Está desarrollado por la Universidad de Berkeley. Os dejamos con un vídeo para que veáis cómo funciona.

                    Vamos ahora al experimento que nos interesa, un NXT controlado con Kinect.

                    El modelo que utiliza de base es un tribot, por lo que la tracción es diferencial. El funcionamiento es sencillo, cada brazo controla un motor, moviendo los brazos hacia delante o hacia atrás desde una posición inicial o neutral, hacemos girar los motores en una dirección u otra, a diferente potencia.

                    Resulta bastante impresionante la exactitud de los datos capturados por los sensores, así como la rapidez con la que la señal se procesa y se envía al NXT, consiguiendo una respuesta prácticamente inmediata.

                    El creador de este original proyecto, rasomuro, nos da en su página la siguiente información.

                    Detalles técnicos:

                  • Hardware:

                    - Sensor Kinect
                    - Ordenador con puerto USB (para el sensor) y bluetooth (para conectarse al NXT)
                    - Un set NXT

                  • Software:

                    - OpenNI drivers, permiten capturar, procesar y utilizar la información que se recibe del sensor Kinect.

                    - Fantom drivers, permiten al ordenador comunicarse con el NXT, se instalan por defecto en nuestro ordenador al realizar la instalación del software oficial de LEGO, aunque también pueden descargarse de la web de LEGO Mindstorms. Más información al final del artículo.

                    Para conseguir su objetivo, rasomuro, modificó 2 programas de ejemplo en C++. El primero de ellos le permite capturar la información del sensor y enviarla a un puerto UDP (User Datagram Protocol). El segundo le permite convertir la información en ordenes de movimiento que serán enviadas al tribot.

                    ¿Quieres saber más?

                  • Página del proyecto NXT-Kinect.
                  • Página oficial de PrimeSense.
                  • Fantom Drivers.
                  • OpenNI.
                  • Puerto UDP (Wikipedia)

                  • Competición de Vehículos de Combate

                      DaniRacer_2Como bien sabéis, el pasado Domingo 27 de Marzo se celebró en electricBricks la primera competición de vehículos de combate. Para ir abriendo boca, ya publicamos en el blog algunos artículos al respecto, como el dedicado a nuestro tanque NXT o el de la torreta Lanzabolas. Además de las extensísimas charlas que tuvimos a propósito de tan original concurso en su correspondiente hilo del foro (Vehículos Combate – Foro). Sólo hay un adjetivo para describir el ambiente: divertido, ante todo muy divertido.

                      Veamos algunos números para hacernos una idea de la magnitud del evento:

                    • 9 vehículos traídos por los asistentes.
                    • 1 vehículo de la organización.
                    • 2 torretas lanzabolas controladas por los voluntarios.
                    • 16 feroces combates.
                    • ¡Más de 150 proyectiles en el campo de batalla!…

                      ¡Sin palabras!

                      Vamos, como es tradición, a presentar a los participantes:

                    • Cicuta

                      Cicuta_1

                      Cicuta_2

                    • Canon’s JIP

                      CanonsJIP_1

                      CanonsJIP_2

                    • ALV 2

                      AVL2_1

                      AVL2_2

                    • HMT

                      HTM_1

                      HTM_2

                    • Biónico

                      Bionico_1

                      Bionico_2

                    • Rápido

                      Rápido_1

                      Rápido_2

                    • Minero

                      Minero_1

                      Minero_2

                    • DaniRacer

                      DaniRacer_1

                      DaniRacer_2

                    • Machacanator 3

                      Machacanator3_1

                      Machacanator3_2

                      Tabla de combates y resultado final

                      Cicuta Canon ALV2 HMT Biónico Rápido DaniRacer Machaca Victorias
                      Cicuta
                      -
                      Cicuta HMT Cicuta
                      -
                      DaniRacer
                      -
                      2
                      Canon
                      -
                      Canon Biónico Canon DaniRacer Canon
                      3
                      ALV2
                      -
                      -
                      Rápido
                      -
                      -
                      -
                      HTM HTM HTM DaniRacer Machaca
                      3
                      Bionico
                      -
                      DaniRacer
                      -
                      1
                      Rápido
                      -
                      -
                      1
                      DaniRacer DaniRacer
                      5
                      Machaca
                      1

                      Finalmente, fue DaniRacer quién se alzó con la victoria, ¡enhorabuena!.

                      Primer vídeo resumen de la competición

                      Segundo vídeo resumen de la competición

                      ¡Seguid atentos porque iremos terminando de subir los vídeos en los próximos días!

                    • Torreta lanza-bolas NXT

                        IMG_3591En este post mostramos otro avance de la competición de tanques de LEGO radiocontrolados que tendrá lugar el Domingo 27 de Marzo en electricBricks. Para complicar un poco las cosas, los competidores no sólo tendrán que dispararse unos a otros, sino que tendrán que evitar los disparos del tanque electricBricks, mostrado en un post anterior, y además tendrán que esquivar los proyectiles de dos torretas de base fija situadas en el campo de batalla. Aunque puedan parecer inofensivas dada su falta de movilidad, el fuego rápido de estas armas puede ser decisivo en la batalla.

                        La torreta consta de tres movimientos, cada uno efectuado por un servomotor Mindstorms NXT. El sistema de disparo se efectúa gracias al lanzador de esferas zamor. Este lanzador tiene unas pestañas en las que cabe una bola (mejor dicho “esfera zamor”). Si introducimos un eje en el lanzador, éste empuja la bola contra las pestañas y las fuerza, hasta que supera cierto punto, en el que por la presión de las pestañas la bola sale disparada.

                        Podemos ver el sistema que empuja el eje en la siguiente fotografía. El motor hace girar un engranaje de 12 dientes conectado a uno de 36, con relación de 1:1/3, para aumentar la potencia. El segundo engranaje mueve una palanca, que con una biela empuja y tira del eje. Al empujar, la bola sale disparada, y al tirar, cae una bola nueva del cargador, que tiene capacidad para 7 esferas.

                        El movimiento vertical se realiza mediante un tornillo sin fin que hace girar un engranaje de 24 dientes, reducción suficiente para poder mover el peso del sistema disparador. La rotación del tornillo lo produce un motor situado debajo de la base.

                        El movimiento lateral se produce de forma muy similar al vertical: un tornillo sin fin hace girar la base giratoria. Podemos ver en la imagen también el motor que produce el giro vertical, cuyo eje atraviesa la base por el hueco del centro de la misma.

                        La torreta se controla desde una “posición segura”, donde se encuentra el procesador NXT. El movimiento vertical se realiza pulsando los sensores de contacto situados a la izquierda del procesador, mientras que el movimiento lateral se controla desde los botones izquierdo y derecho del procesador. El disparo se ejecuta al presionar el botón central del procesador.

                        El programa que controla el proceso es bastante sencillo, basado en una serie de bifurcaciones dentro de un bucle. Cada bifurcación comprueba si se pulsa un botón, si es así, realiza un movimiento de 20 grados, si no, pasa a la comprobación de otro botón, y así sucesivamente. De esta forma, si tenemos pulsado un botón, se acumulan movimientos cortos en la misma dirección. Si pulsamos dos botones a la vez, los movimientos se intercalan. A continuación podéis ver el programa.

                        Por último os mostramos un vídeo del funcionamiento de la torreta.

                        ¡Venid preparados, porque no habrá misericordia para nadie!

                        Tanque LEGO NXT

                          Detalle LateralCon la mirada puesta en la competición de tanques que se celebrará el Domingo 27 de Marzo en electricBricks, hemos querido mostrar el modelo NXT que participará como mediador en la competición. ¿Mediador? Bueno, digamos que será otro rival que participará de parte de electricBricks, que se encargará de hacerles las cosas difíciles al resto de participantes. Para ello está equipado con un lanzador de vigas de 1 x 3, con capacidad para 12 vigas por cargador.

                          Este modelo está completamente montado haciendo uso de la electrónica NXT y se maneja remotamente mediante bluetooth, por lo que no supondrá una limitación de canales IR para los modelos que usen Power Functions.

                          La base móvil monta 2 motores de Mindstorms, con una relación de engranajes que permite transmitir el movimiento desde la parte delantera del vehículo (donde están situados los motores) hacia las ruedas de la parte trasera. El sistema de giro es tipo tanque, sin eje direccional.

                          Os dejamos algunas fotos en detalle para que podáis estudiarlo… ¡o replicarlo!

                          Primero un par de fotos generales, para ir abriendo boca

                          Lateral

                          Frontal

                          Como se puede apreciar, la base es bastante simple, en la parte superior, tenemos el sistema de disparo y a modo de cresta o aleta dorsal tenemos el cargador.

                          Ahora algunas en detalle

                        • Detalle del lateral

                          Detalle resorte

                          En esta imagen se puede apreciar el motor que mueve el mecanismo de disparo.

                        • Detalle del mecanismo de disparo

                          Detalle cañón

                          El mecanismo de disparo se compone de dos partes:

                          - Un sistema para situar el proyectil
                          - El percutor, para disparar el proyectil

                        • Detalle de la parte delantera

                          Detalle frontal

                          Se puede apreciar la protección frontal (¿contramedidas?).

                          NOTA: Como se puede apreciar en las fotos, nuestro modelo no lleva cestillo/bolsa para los proyectiles (conocido cariñosamente como Doña Rogelia) ya que participa fuera de concurso y sólo estará en las batallas para ponerles las cosas más difíciles a los combatientes.

                          Ahora un vídeo sobre su control y disparo.

                          Como podéis comprobar, la velocidad a la que se mueve el vehículo es bastante elevada (llegando incluso a poder hacer caballitos, aunque no se aprecie en el vídeo). Sobre el tapete resulta bastante difícil girar, debido a la gran fricción de las ruedas sobre la superficie, sin embargo, confiamos plenamente en que sus capacidades motoras mejoren sobre tierra, que es donde se disputará la prueba.

                          En cuanto al disparo, el hecho de que la inclinación del cañón no sea variable, limita mucho las posibilidades de hacer diana al disparar (en el vídeo no acertamos ni siquiera 1 proyectil en el objetivo). Además de encontrarnos con algunos problemas mecánicos, en el sistema de disparo, durante la grabación de este primer test que impedían que las vigas fueran lanzadas con la precisión y fuerza necesarias.

                          ¿A alguno le quedaba alguna duda de que la competición va a ser enormemente divertida?

                          Ahora nos dejamos de bromas y ponemos un vídeo en detalle del sistema de disparo

                          El funcionamiento es relativamente simple, el motor gira un número concreto de grados, haciendo que una rueda de oruga gire. Al girar la rueda de oruga, desplaza el percutor hacia atrás, el percutor está conectado a una viga que coloca la pieza (viga de 1 x 3) en la posición adecuada para poder dispararlo.

                          ¡Podéis ir poniéndoos las pilas, porque va a ser una batalla muy reñida!

                        • LEGO Mindstorms NXT volador

                            Lego_VoladorRecientemente, en la BrickWorld de Copenhague (Dinamarca), se ha presentado el primer modelo de NXT Volador. Es un impresionante proyecto que ha sido desarrollado por el equipo de BrickIt.dk entre Octubre de 2010 y el pasado mes de Febrero de 2011.

                            Tiene unas 500 piezas, y ha requerido de unas 30 horas de montaje, 10 de programación más 10 horas adicionales (presumiblemente para pruebas y correcciones). Más que complejo, estamos ante un proyecto principalmente original.

                            ¿Cómo funciona? Se trata de un modelo que utiliza 2 globos de hidrógeno que permiten que sea capaz de elevarse. También podría haberse realizado con helio, pero el hidrógeno tiene menor densidad. Para desplazarse dispone de 3 juegos de hélices del conjunto 9688. Teniendo en cuenta el tamaño de estas hélices, podemos hacernos una idea de las grandes dimensiones de este artilugio.

                            Volador-detalle

                            En la foto superior puede apreciarse el tamaño del ladrillo NXT en relación con las dimensiones totales del modelo. Se trata de un ladrillo de color negro y se encuentra en el centro de la parte inferior de la imagen, ¡por si alguno no lo había visto!

                            Utiliza además:

                          • 2 motores RC
                          • 5 motores NXT: 3 en el mando (muy parecido a nuestro joystick) y 2 en el zeppelin o dirigible.
                          • 1 Sensor de Contacto
                          • 2 ladrillos NXT
                          • 1 unidad controladora RC (parecer ser la ref. 6272c01, aunque no se aprecia en el vídeo)

                            Os dejamos con un vídeo con las imágenes de la BrickWorld, algunas de las cuales están grabadas desde el aire con la cámara que equipa el propio modelo.

                            Sí sí, pero… ¿cómo han conseguido que se levante del suelo?

                            Principio físico

                            De acuerdo al principio de Arquímedes, todo cuerpo sumergido en un fluido recibe una fuerza de abajo hacia arriba equivalente al peso del fluido desplazado. Resulta obvio que el dirigible, un cuerpo inmerso en la mezcla de gases llamada aire, recibirá una fuerza ascensional resultante (P) equivalente al peso de aire ocupado por su volumen (V), menos el peso de su estructura y su carga (Q);

                            Siendo γ el peso específico del gas utilizado para llenar los depósitos del dirigible, y Γ el del aire, entonces:
                            formula


                            Arquímedes

                            Comportamiento de un sólido de densidad ds en un líquido de densidad dL

                            Ahora … ¿Cómo podemos saber cuál será el tamaño de los globos necesario para que nuestro globo sea capaz de elevarse?

                            Tenemos para nuestro ejemplo un peso del montaje de 1 Kg (es una estimación, teniendo en cuenta que sólo la electrónica pesa aproximadamente 700 g).

                            Recordemos que tenemos: Q (Masa del objeto) y P (empuje hacia arriba o fuerza ascensional). Además de saber que:
                            Densidad del aire: 1,18g/l
                            Densidad del hidrógeno : 0,071g/l

                            Para que el cuerpo suba se requiere que:

                            P > Q

                            Calculemos el peso y luego el empuje (llamamos V al volumen del cuerpo que, vacío, tiene masa despreciable, pero que llenamos de hidrógeno, además de adosarle una masa de 1 kg) :

                            Q = 0,071 V 9,8 + 1 • 9,8

                            P = peso del aire desalojado (principio de Arquímedes) = 1,18 V 9,8

                            Como tendrá que ser que E > P,

                            1,18 V 9,8 > 0,071 V 9,8 + 1 • 9,8

                            Dividiendo todo por 9,8 :

                            1,18 V > 0,071 V + 1 – > 1,18 V – 0,071 V > 1 -> 1,12 V > 1 -> V > 0.90 m³

                            Se necesitan como mínimo 900 litros de hidrógeno para elevar 1 Kg.

                            Ahora hay que calcular el diámetro del globo, para ello usaremos la fórmula del volumen:

                            volumen esfera

                            De donde sacamos que r= 0,59m o 59 cm

                            Vamos a ver si esto se cumple en este caso concreto:

                            Tenemos para nuestro ejemplo:

                          • un peso del montaje de 1 Kg
                          • Densidad del aire: 1,18g/l
                          • Densidad del hidrógeno : 0,071g/l

                            Diferencia entre ambos: 1,109g/l

                            Es decir, cada litro de hidrógeno levanta 1,109g.

                            Un diámetro estimado de 100 cm para cada globo
                            volumen esfera
                            Lo que, según la formula anterior, nos da un volumen de 0,523 m3 o 523 L (1m3=1000L)

                            Si cada globo tiene 523L, tenemos un total de 1046 L de hidrógeno en nuestros globos, lo que nos permitiría levantar unos 1160 g, por lo tanto… ¡Lo hemos conseguido!

                            ¡Esperamos que os haya gustado!

                          • Competición Trial Febrero 2011

                              GrupoLa pasada competición de vehículos LEGO de Trial (Domingo día 27 de Febrero) fue un auténtico éxito, tanto por la enorme participación como por el nivel de los asistentes. En esta ocasión realizamos la prueba en exteriores, en un emplazamiento lleno de baches, grietas y cuestas que hicieron las delicias de los más valientes, al tiempo que algunos veían como sus vehículos sufrían irremediablemente con la dureza de las pruebas. Gracias a todos los participantes, tanto los que compitieron como los que fueron a compartir la experiencia. Hizo un frío de justicia, pero ¡qué bien lo pasamos!

                              Esta competición estuvo compuesta de 3 tramos, o etapas, principales más un cuarto tramo de desempate.

                              Grupo

                              En esta ocasión la galería de fotos inicial recoge aproximadamente al 75% de los participantes, ya que algunos no pudieron llegar a tiempo y se incorporaron con la carrera ya empezada. Aún así, vamos con ellos.

                              CicutaTrial

                              CicutaTrial_1

                              CicutaTrial_2

                              El único vehículo que arriesgó con el Mindstorms NXT, eso sí, con un híbrido equipado con sensor receptor de infrarrojos y mando de PF.

                              Quad’s JIP

                              Quad's JIP_1

                              Quad's JIP_2

                              Un modelo pequeño, con tracción trasera, que se vio en dificultades con obstáculos de gran tamaño, pero que consiguió resolver otros con mucha soltura.

                              El Minero

                              El Minero_1

                              El Minero_2

                              Dos grandes diferencias con el resto de montajes, una torsión en la zona central que le hacía superar obstáculos con facilidad pasmosa, y ¡falta de eje direccional! Compensado, por supuesto, con un sistema de giro tipo tanque.

                              Julio

                              Julio_1

                              Julio_2

                              Con un diseño muy similar al 8297, este modelo sorprendió por su sistema de dirección en los 2 ejes. Tuvo algún que otro problema mecánico, pero supo resolver correctamente todos los tramos.

                              Lagarto

                              Lagarto_1

                              Lagarto_2

                              Sin lugar a dudas un vehículo singular, eje delantero para tracción, tracción integral (en los 4 ejes), 2 baterías de litio, 4 motores XL, uno por cada eje. Y un diseño muy llamativo, para que negarlo.

                              Machacanator

                              Machacanator_1

                              Machacanator_2

                              Un modelo en el que primaba con mucho la potencia frente a la velocidad, no tuvo pilas suficientes para superar la primera prueba.

                              La Bestia

                              La Bestia_1

                              La Bestia_2

                              Con un enorme parecido a un camión de trial real, montaba 2 motores XL para la tracción y 1 mediano para la dirección. Una rotura del eje trasero lo dejaba fuera de juego en la primera prueba.

                              DaniRacer

                              DaniRacer_1

                              DaniRacer_2

                              Un vehículo compacto y peculiar, al que su escasa altura le dio algunos quebraderos de cabeza. Sin embargo, apenas tuvo penalizaciones por toque, lo que demuestra su eficacia.

                              Lagartija

                              Lagartija_1

                              Lagartija_2

                              El segundo vehículo de Sheepo, un modelo muy similar al ganador dela edición anterior. Tracción integral, unas ruedas con buen agarre y una buena altura con respecto al suelo han demostrado ser las bazas para ganar.

                              También compitieron, aunque no llegaron a la sesión de fotos: Biónico, Miguel, Óscar y El Innombrable.

                              Tabla de clasificación

                              En esta competición no se establece límite de tiempo, por lo que el piloto recibirá la puntuación máxima (10 puntos) por cada tramo completado sin tocar el vehículo y será penalizado (- 2 puntos) cada vez que lo toque. La mínima puntuación que se puede obtener en cada prueba es 1.

                              Importante: La competición se hizo a 3 tramos, y se produjo un triple empate. Los tres vehículos empatados (Lagarto, Lagartija y Miguel) se vieron las caras en un cuarto tramo de desempate.

                              Piloto
                              Tramo 1
                              Tramo 2
                              Tramo 3
                              Tramo 4
                              CicutaTrial
                              6
                              2
                              8
                              -
                              Quad’s JIP
                              2
                              4
                              1
                              -
                              El Minero
                              10
                              8
                              10
                              -
                              Julio
                              8
                              6
                              10
                              -
                              Lagarto
                              10
                              10
                              10
                              10
                              Machacanator
                              1
                              -
                              -
                              -
                              La Bestia
                              1
                              -
                              -
                              -
                              DaniRacer
                              8
                              8
                              6
                              -
                              Lagartija
                              10
                              10
                              10
                              10
                              Biónico
                              4
                              8
                              2
                              -
                              Miguel
                              10
                              10
                              10
                              7
                              Óscar
                              6
                              6
                              1
                              -
                              El Innombrable
                              1
                              1
                              4
                              -

                              Ahora los vídeos, os recordamos que para facilitar su visualización se ha multiplicado su velocidad por 3.

                            • Vídeo de la primera prueba

                            • Vídeo de la segunda prueba

                            • Vídeo de la tercera prueba

                            • Vídeo de la cuarta prueba

                            • Vídeo Extra

                              Esperamos que lo pasarais tan bien como nosotros y ¡nos vemos en la próxima!

                            • RS485 en NXT-G

                                BloqueAndy Milluzzi, junto con John Hansen, ha creado el primer bloque de NXT-G que permite utilizar la comunicación RS485 de nuestro NXT. Esto nos permite comunicar 2 ladrillos NXT directamente a través del puerto 4 haciendo uso de un cable entándar, con velocidades de hasta 921600 bps. Esta es una velocidad muy superior a la proporcionada por el bluetooth, además de evitar los problemas derivados del uso de los buzones.

                                A pesar de que se trata de una primera versión, ya es posible descargar el bloque desde el siguiente enlace:

                                Bloque RS485 NXT-G


                                Bloque

                                Este bloque nos permite enviar y recibir cadenas de caracteres (strings). Por el momento no admite la transferencia de números ni de señales lógicas, pero haciendo uso del bloque Número a Texto, podemos convertir los datos numéricos en strings, con lo cual tendríamos parte del problema resuelto.

                                Para probar estos nuevos bloques hemos conectado dos ladrillos LEGO MINDSTORMS NXT entre sí mediante un cable NXT convencional conectado entre los puertos 4 de cada uno de ellos. Tras las pruebas que hemos realizado, con un sencillo bucle que muestra en pantalla en número de mensajes enviados y recibidos, con unas cifras en torno a los 85 mensajes por segundo. Puede parecer un número relativamente bajo, pero debemos de tener en cuenta que en esta rutina también se están presentando datos en pantalla.

                                En el caso del programa emisor, enviamos como información el número de iteraciones del bucle, mientras que el programa receptor muestra en pantalla la información recibida.

                                Programas_

                                El autor promete ir actualizando con nuevas funcionalidades, por lo que la idea promete y abre posibilidades para otro tipo de aplicaciones.

                                RS-485
                                El RS485 es un protocolo de comunicaciones serie de gran aceptación en la industria. Se emplea habitualmente en aplicaciones de control y adquisición de datos. Las características principales de este estándar son las siguientes:

                              • El medio físico de transmisión es un par entrelazado.
                              • Posibilita la comunicación entre varios nodos entre sí, es decir, no está limitado a la comunicación punto a punto.
                              • La comunicación es semidúplex: se puede transmitir en los dos sentidos, pero no de forma simultánea. El dispositivo físico que proporciona este interfaz en el hardware de LEGO es el ST485.
                              • Se trata de un sistema de comunicación diferencial. Esto reduce la existencia de bucles de tierra, una fuente habitual de problemas de comunicaciones. Los receptores del dispositivo comparan la diferencia de tensión entre las dos líneas de señal en lugar de medir el nivel absoluto de tensión, por lo que es posible la transmisión in cluso en canales ruidosos, siendo posible trabajar a velocidades de 35 Mbps (hasta 10 metros) y de 100 Kbps (hasta 1.200 metros de alcance).
                              • Juego Asteroids con NXT

                                  Atari2600aAsteroids es un clásico de los arcades para recreativas de los años 80. Su planteamiento es muy sencillo, el jugador controla una nave que puede moverse libremente en un campo de asteroides, disparando hasta destruirlos para conseguir puntos. Además debe evitar chocar contra ellos. Hoy hemos querido traeros nuestra particular versión de este juego programada por completo en NXT-G.

                                  El juego fue concebido por Lyle Rains, programado y diseñado por Ed Logg. Para su implementación se utilizó el hardware desarrollado por Howard Delman (la pantalla vectorial o generadora de vectores). Para jugar, se hacía uso de un joystick y un botón de disparo.
                                  Su lanzamiento supuso un tremendo éxito en los Estados Unidos, tanto que se convirtió en el juego más vendido en toda la historia de Atari.

                                  Atari 5200 Asteroids

                                  Como curiosidad sobre este juego, el record establecido en 1982 por Scott Safran de 15 años, tardaría 27 años en superarse. En concreto, hubo que esperar hasta el 3 de abril de 2010. El jugador en cuestión fue John McAllister, que inició una partida de 3 días de duración tras la cual lograría batir dicho record (41,336,440 puntos) con una puntuación de 41.338.740, es decir, un 0′006% por encima de la anterior puntuación.
                                  El sistema que utilizó consistía en conseguir suficientes vidas extras y, en ese momento, aprovechar para comer un bocadillo o algo rápido mientras vigilaba no perder ninguna de esas vidas. De hecho, una anécdota al respecto fue que una vez le falló el cálculo y cuando volvió del cuarto de baño sólo le quedaban 2 vidas, pudiendo continuar con la partida por poco.

                                  Este es el aspecto del juego en la versión de 1982

                                  asteroids_large

                                  Nuestra versión está algo simplificada, aunque no descartamos ampliarla en un futuro. En este caso nuestro objetivo será esquivar los asteroides, obteniendo 10 puntos por cada uno que evitemos. Si el asteroide impacta contra nosotros perderemos 1 vida, hasta quedarnos sin las 3 disponibles, lo que supondrá el final de la partida.

                                  Este es el programa completo

                                  Programa_

                                  Como se puede comprobar, y es habitual en los programas largos, lo hemos dividido en diferentes bloques personalizados.

                                • Bloque Presentación:

                                  Bloque_Presentation_

                                  Muestra en pantalla el nombre del programa, lo que serían los créditos iniciales.

                                • Bloque Var_Ini


                                  Bloque_Var_Ini_

                                  Inicializa las variables principales del juego, que se usan en el resto de los bloques.

                                • Bloque Ship

                                  Bloque_Ship_

                                  Este bloque controla los movimientos de la nave por la pantalla, en esta versión sólo puede desplazarse hacia los lados, no en vertical. Para ello, utilizamos la variable Counter inicializada a 50, a la cual restamos o sumamos en función de si el usuario nos presiona la flecha izquierda o derecha respectivamente.

                                  A continuación comprobamos si el valor de Counter está dentro de los límites de la pantalla, en caso contrario, los corregimos.

                                  Este bloque controla también si la nave ha chocado con el asteroide, por medio del bloque IsOver, del que hablaremos un poco más abajo.

                                • Bloque Ast

                                  Bloque_Ast_

                                  Controla los movimientos del asteroide por la pantalla. Los asteroides bajan (disminuye el valor de Y) por la pantalla en línea recta, Y-1 cada vez hasta llegar al límite inferior que viene marcado por la recta del marcador (Y=10).
                                  La posición X inicial viene dada por un bloque aleatoria dentro del intervalo 0 – 99, mientras que la Y inicial es 64 (para que aparezca desde arriba).

                                  Este bloque controla también la puntuación, ya que cada vez que llegamos el final de la pantalla, si no hemos chocado con la nave, sumamos 10 a la puntuación actual.

                                • Bloque IsOver

                                  Bloque_IsOver_

                                  Este bloque comprueba si el asteroide y la nave ocupan el mismo espacio en pantalla, es decir, si nos hemos chocado. Si es así restamos 1 a las vidas disponibles, mostrando el dato en pantalla.

                                  Si nos quedan 0 vidas, indicamos el final del juego GameOver=true, lo que interrumpe la ejecución de los bloques Ast y Ship, devolviendo el control al programa principal.

                                  Hay que destacar 2 variables creadas adrede para interactuar con otros bloques del programa, se trata de :

                                • Variable Reset (tipo: lógica):

                                  Indica al bloque Ship que debe situar la nave en posición inicial, ya que se ha perdido una vida.

                                • Variable Pause (tipo: lógica):

                                  Al cambiar su estado a True, indica al bloque Ast que debe interrumpir los movimientos del asterisco por la pantalla, hasta que vuelve a cambiar su estado a False. Además, reinicia la posición del asterisco en pantalla.

                                • Bloque End


                                  Bloque_End_

                                  Muestra el final del juego y espera que presionemos el botón Intro.

                                  Podéis descargaros el código desde este enlace:

                                  Juego Asteroids NXT-G

                                  Se trata de un fichero zip que contiene un archivo con extensión .rbtx, creado con la herramienta “Pack and Go” incluida en el propio software NXT-G. Esta herramienta nos permite crear un paquete que contiene toda la información necesaria para abrir ese programa desde cualquier otro ordenador: los bloques personalizados, las imágenes y los sonidos.

                                  Para los que no sepáis cómo hacerlo, os dejo esta captura de pantalla

                                  Pack&Go_

                                  En breve colgaremos un vídeo para que lo veáis funcionando.

                                  Esperamos que os haya gustado, si alguien se anima a continuarlo y mejorarlo, estaremos encantados de comentarlo en el foro.

                                  Información adicional:

                                  vectorDisplay

                                • Pantalla vectorial: Un monitor o pantalla vectorial es un tipo de CRT (Tubo de Rayos Catódicos, del inglés Cathode Ray Tube), similar al osciloscopio, pero que presente información en pantalla usando deflexión magnética (en lugar de electrostática). Aquí el haz trazaba líneas entre puntos arbitrarios, repitiendo el movimiento lo más rápidamente posible.
                                  Las pantallas vectoriales se emplearon en la mayoría de los monitores de ordenador desde finales de los años 70 hasta mediados de los 80.
                                  La visualización vectorial para ordenador no sufre de aliasing ni pixelización, pero está limitada ya que sólo puede definir los contornos de las formas, y una escasa cantidad de texto, preferiblemente de un tamaño grande. Esto es así porque la velocidad de visualización es inversamente proporcional al número de vectores que deben dibujarse y “rellenar” una zona utilizando muchos vectores es imposible, así como escribir una gran cantidad de texto.
                                  Algunos monitores vectoriales eran capaces de mostrar varios colores, a menudo utilizando dos o tres capas de fósforo. En estos monitores, controlando la fuerza del haz de electrones, se controla la capa alcanzada y en consecuencia el color mostrado, que generalmente era verde, naranja o rojo.

                                  Atari_1040STf

                                • Atari Inc.: Fundada en los Estados Unidos en 1972 por Nolan Bushnell y Ted Dabney, Atari puede ser considerada la fundadora de la industria del videojuego, gracias al PONG. La versión casera de PONG, que se conectaba a una televisión, fue una de las primeras consolas de videojuegos. También ha estado presente desde los primeros días de las máquinas de arcade, Atari fue creador de las consolas caseras, como la Atari 2600 (llamada originalmente VCS “Video Computer System” — Sistema Informático de Vídeo); produjo una serie de computadores de 8 bits (Atari 400/800 y la serie XL/XE); tomó parte en el mercado de los 16 bits con el Atari ST; creó la consola Atari Jaguar de 64 bits, revolucionaria para su época; y lanzó una cónsola portátil, la Atari Lynx.

                                • Telesketch NXT

                                    telesketchTelesketch o Sketch es un juguete inventado en durante los años 50 por el francés Arthur Granjean, fue presentado en la Exhibición Internacional de Juguetes de Nuremburg, Alemania en 1959. Tardaría solamente un año en ser comercializado en Estados Unidos con el nombre de Etch-A-Sketch por Ohio Art Company y en España por Borrás. Granjean lo llamó originalmente “la Pantalla Mágica”.

                                    etchASketch

                                    Se trata de un juguete relativamente plano y rectangular, con la apariencia de una pequeña pantalla de televisión. Tuvo una gran acogida en los años 80, por lo revolucionario de su idea, y aún hoy sigue haciendo disfrutar a niños de todas las edades, a pesar de haber sido objeto de numerosas modificaciones y evoluciones.

                                    El Telesketch es una versión muy simplificada de un plotter. Veamos cómo está montado y cómo funciona.

                                    El Teleskecth desmontado sin la pantalla


                                    etch7

                                    Se pueden apreciar los mandos giratorios que servirán para mover la punta metálica para arriba, para abajo y hacia los lados.

                                    Detalle del interior


                                    etch10

                                    El interior está relleno de polvo de aluminio y partículas de estireno, que al adherirse a la parte posterior de la pantalla, formarán el lienzo sobre el que dibujar. Para borrar el dibujo sólo hay que ponerlo boca abajo y agitarlo para que el aluminio y el estireno vuelvan a recubrir la superficie.

                                    Detalle de la punta


                                    etch1

                                    La punta metálica está montada sobre un par de raíles ortogonales. Dicha punta araña la superficie cubierta de polvo de aluminio generando el dibujo.

                                    Crear una línea recta en diagonal o con una curva suave con un Telesketch es notablemente difícil y una verdadera prueba de coordinación. Una solución es alternar cuidadosamente líneas verticales y horizontales con incrementos muy pequeños, una técnica que guarda semejanza con las líneas pixeladas generadas en las pantallas de los ordenadores.

                                    En esta era de grandes tecnologías, juguetes como este siguen siendo un clásico y su impacto es tan grande que hoy en día hay artistas que lo utilizan como base de su trabajo, como es el caso del retratista George Vlosich.

                                    ToyStory

                                    O que quieren homenajear su diseño en los gadgets más punteros, como esta funda para el iPad:

                                    etch-a-sketch-frontal

                                    Como curiosidad, ahora os dejamos uno de los primeros encuentros entre un Mindstorms NXT y un Telesketch.

                                    En este caso, se trata de un NXT controlando un TeleSketch:

                                    Como podéis ver en la imagen siguiente, el montaje de nuestro Telesketch es bastante sencillo: dos motores a los lados de la pantalla, dos ruedas dentadas, algunas piezas para conectarlo todo y nada más.

                                    telesketch

                                    Código de NXT-G

                                    En la imagen siguiente mostramos un ejemplo de programa explicado:

                                    telesketchd

                                    Código en RobotC


                                    #pragma config(Motor, motorA, MotorX, tmotorNormal, PIDControl, reversed)
                                    #pragma config(Motor, motorC, MotorY, tmotorNormal, PIDControl, )
                                    //*!!Code automatically generated by 'ROBOTC' configuration wizard !!*//

                                    task main(){
                                    int X;
                                    int Y;

                                    while (true){
                                    eraseDisplay();

                                    while(nNxtButtonPressed!=2){
                                    X=nMotorEncoder[MotorX]/10;
                                    Y=nMotorEncoder[MotorY]/10;
                                    if(X<1){
                                    X=1;
                                    }
                                    else if(X>99){
                                    X = 99;
                                    }
                                    if(Y<1){
                                    Y = 1;
                                    }
                                    else if(Y>63){
                                    Y = 63;
                                    }
                                    nxtSetPixel(X,Y);
                                    }
                                    }
                                    }

                                  • Líneas 6 y 7: definimos 2 variables X,Y que representan nuestra posición en pantalla.
                                  • Línea 9: bucle infinito.
                                  • Línea 12: bucle dependiente del botón intro, que interrumpirá el programa y borrará la pantalla (Línea 10)
                                  • Líneas 13 y 14: Tomamos el valor del tacómetro de los motores dividido entre 10, para hacer el desplazamiento más lento.
                                  • Líneas 15 a 25: filtrado de los valores para evitar salirnos de la pantalla, hay que recordar que tenemos 100 píxels de largo (X), por 64 de alto (Y).
                                  • Línea 27: Función que pinta en pantalla un único píxel, en la posición X, Y.

                                    Os ponemos un pequeño vídeo para que veáis cómo funciona este experimento

                                    ¡Esperamos que os haya gustado!

                                    Fuentes:

                                    - HowStuffWorks
                                    - Wikipedia
                                    - George Vlosich

                                  • Competición de Sumo electricBricks Enero 2011

                                      foto grupoEl pasado Sábado tuvo lugar la primera competición de sumo de este año 2011, tras el tremendo éxito de ediciones anteriores (Sumo Julio 2009, Sumo Enero 2010), en esta ocasión esperábamos una participación de un grandísimo nivel, con un buen número de luchadores, y no nos han defraudado. Algunos luchadores habituales y muchos nuevos, nos han dado una batalla campal de 2 horas increíblemente divertidas, que queremos compartir con todos vosotros.

                                      foto grupo

                                      Vamos a presentar a los luchadores según el orden de presentación en la sesión de fotos. Aunque hay un par de modelos que llegaron un poco más tarde y no pudieron hacerse las fotos oficiales, sí que los podremos ver en los vídeos de los diferentes combates.

                                      Machacanator
                                      Machacanator_1
                                      Machacanator_2

                                      Un modelo potente y ágil, que tuvo que retirarse por falta de alimentación eléctrica, es decir, que se quedó sin pilas. Un diseño completo con PF, 4 motores XL, 2 cajas de baterías y 2 receptores.

                                      Castor
                                      Castor_1
                                      Castor_2

                                      Modelo con Power Functions, 4 baterías, 4 receptores, una buena dotación de motores y muy bien protegido. El ganador indiscutible, muy potente y pesado era, sin embargo, muy maniobrable. El rival que más le aguantó en el ring no llegó a los 15 segundos.

                                      NeXT
                                      NeXT_1
                                      NeXT_2

                                      Este modelo, que confiaba toda la resistencia frente a los ataques a sus potentes defensas, no contaba sin embargo con ningún tipo de armamento. Controlado por bluetooth desde un portátil, era un luchador con uso exclusivo de Mindstorms NXT.

                                      Wall-E
                                      Wall-E_1
                                      Wall-E_2

                                      Modelo híbrido, cuyo parecido con el entrañable robot de Pixar resultaba engañoso, pues demostró ser un rival bastante duro. Era capaz de perder completamente la tracción de ruedas sin por ello tener que abandonar el combate. Base de NXT controlado por PF con el sensor de IR correspondiente.

                                      Cicuta
                                      Cicuta_1
                                      cicuta_2

                                      Otro modelo híbrido, con el mismo sistema de su compañero Wall-E. Increíbles defensas laterales, aunque un armamento escaso, plantó cara a los rivales más duros con honor. Se echó en falta un poco más de tracción.

                                      Julio

                                      Julio_1
                                      Julio_2

                                      Este prometedor luchador de PF no pudo llegar a competir por problemas de tracción.

                                      TumaCo
                                      Tuma-Co_1
                                      Tuma-Co_2

                                      Amenazantes fauces dentadas, modelo pequeño y extremadamente ligero. Completo con PF.

                                      Doid Bot
                                      DroidBot_1
                                      DroidBot_2

                                      ALV1
                                      ALV_1
                                      ALV_2

                                      Makitos
                                      Makitos_1
                                      Makitos_2

                                      DaniRacer
                                      DaniRacer_1
                                      DaniRacer_2

                                      Yago (sin foto)
                                      ScareCrow (sin foto)

                                      Tabla Clasificatoria Final

                                      Puesto Luchador Ganados Empates
                                      1 Castor 8 -
                                      2 DaniRacer 7 -
                                      3 NeXT 5 1
                                      4 Wall-E 4 1
                                      5 ScareCrow 4 1
                                      6 Cicuta 3 2
                                      7 Makitos 2 1
                                      8 ALV1 - 5
                                      9 TumaCo - 3
                                      10 Yago - 1
                                      11 Machacanator - -
                                      12 Julio - -
                                      13 DroidBot - -

                                      Debido a la gran cantidad de vídeos, no podemos colgarlos todos, pero hemos montado un pequeño resumen (¡preparad las palomitas, que son 5 minutos!) de los mejores momentos.

                                      Esperamos que lo pasarais tan bien como nosotros, agradecer desde aquí a todos los participantes y a todos los asistentes, su participación y el buen rato que pasamos.

                                      NXT AirScooter v 2.0

                                        Vehículo_frontalYa hicimos un primer proyecto de vehículo propulsado por hélices, que bautizamos como NXT AirScooter. Hoy queremos enseñaros una nueva versión, reconstruida y mejorada, que cuenta con control remoto desde otro NXT por bluetooth.

                                        Vehículo_trasera

                                        En este caso hemos utilizado las nuevas palas que vienen en el conjunto Energías Renovables (review 9688), que también utilizamos en el experimento sobre energía eólica.

                                        Desde el punto de vista mecánico, este nuevo sistema es más fiable y seguro, ya que las nuevas palas pueden anclarse directamente con pines a una estructura de viga o ladrillo técnico, mientras que, en la versión anterior se trataba de una gran plancha sujeta por sus studs. Esto simplifica el anclaje y reduce su tamaño.

                                        NXT AirScooter. Vista delantera de la hélice

                                        Es importante también, por otra parte, tratar de evitar cualquier tipo de pérdidas energéticas, que al final se pueden traducir en una reducción de la velocidad del vehículo. En este sentido ha sido particularmente importante evitar cualquier tipo de vibración en los brazos que soportan las hélices: estas vibraciones son también pérdidas energéticas y su existencia propició que versiones anteriores del vehículo no fueran operativas. El AirScooter se limitaba a no avanzar, eso sí, vibrando como un poseso.

                                        NXT AirScooter. Detalle del anclaje de la hélice

                                        También se han reducido las vibraciones en el eje de la hélice aprovechando el doble ataque con los dos engranajes, desde cada uno de los extremos del servomotor del Mindstorms NXT.

                                        El sistema de dirección empleado también es determinante para el avance del vehículo. En nuestro caso hemos probado varios sistemas, como una rueda loca y dos fijas o tres ruedas locas, que es el que permite la máxima maniobrabilidad, aunque impide un control preciso de la dirección. Podeis ver en detalle la forma en la que la hemos implementado.

                                        NXT AirScooter. Rueda loca, por electricBricks

                                        Mando_lateral

                                        Veamos el programa del mando.


                                        Programa mando_

                                        Este programa recibe información de los 4 sensores de contacto, enviando por bluetooth un número diferente cuando están en reposo, no pulsados, y cuando están activos, pulsados.
                                        Los sensores 1 y 3 controlan los movimientos del motor derecho .
                                        Los sensores 2 y 4 controlarán el motor izquierdo.
                                        Cuando un determinado sensor está pulsado, el motor correspondiente gira en un sentido, si se pulsa su complementario se invertirá el sentido de giro. Si no se pulsa ninguno se envía “0″ para indicar el reposo.

                                        Vehículo_frontal

                                        Y ahora el programa del vehículo


                                        Programa vehículo_

                                        En este caso, debemos gestionar la señal numérica recibida, de tal manera que:

                                      • “0″ es reposo
                                      • “1″ giro en un sentido
                                      • “2″ giro en sentido contrario

                                        Si bien la forma de las palas hace que sean idóneas para un desplazamiento hacia delante, activando ambos motores “marcha atrás” no conseguiremos ningún resultado. Sin embargo, la posibilidad de que cada motor gire en un sentido permite giros mucho más rápidos y ágiles.

                                        Aquí dejamos un pequeño vídeo para que lo veáis en funcionamiento.

                                        Id calentando motores, porque dentro de poco publicaremos las fechas de las nuevas competiciones, y este tipo de vehículos será e objetivo de una de ellas.

                                      • Probamos Enchanting: NXT sigue líneas

                                        Artículo nº 4 de la serie de 4 artículos sobre Scratch

                                          ProgramTras la entrada anterior, en la que os comentábamos la existencia de este nuevo software de programación que une Scratch y LeJOS, hemos querido poner a prueba sus posibilidades como herramienta de desarrollo gráfica.

                                          Lo primero que debemos tener en cuenta es que trabaja sobre la base de LeJOS, por lo que tendremos que tener instalado y correctamente configurado LeJOS en nuestro ordenador, si aún no lo habéis hecho o tenéis alguna duda, podéis consultar nuestros tutoriales:

                                          - LejOS en Windows
                                          - LeJOS en Linux
                                          - LeJOS en MAC OS X

                                          Además, no nos podemos olvidar de ¡cambiar el firmware de nuestro NXT!

                                          Una vez realizados estos pasos previos ya estamos preparados para empezar.
                                          La carpeta que nos hemos descargado (Enchanting) contiene los ejecutables correspondientes a las tres plataformas que hemos mencionado antes. No es necesario instalar, bastará con abrir el ejecutable que nos interese.
                                          En nuestro caso, hemos realizado esta prueba desde Windows, y el ejecutable se llama “Enchanting.exe”.

                                          Para aquellos que quieran familiarizarse primero con Scratch, podéis leer nuestra serie de artículos de Scratch.

                                          Ahora, vamos a ponernos manos a la obra con nuestro proyecto, vamos a programar un sigue líneas con Enchanting.

                                          Lo primero que debemos hacer es configurar los motores

                                          Motors1

                                          Tenemos la opción de controlar motores de corriente directa, como los Power Functions, o motores con un encoder, como los de NXT. Sólo tendremos que arrastrar el tipo de motor que nos interese a la pestaña que representa el puerto al que está conectado. Una vez hecho esto, tendremos que posibilidad de elegir las acciones que ejecutarán esos motores.

                                          Ahora vamos con los sensores

                                          Sensors1

                                          Igual que con los motores, bastará con arrastrar el sensor que nos interese al puerto.

                                          Para hacer nuestro sigue líneas vamos a hacer uso del algoritmo más simple, que se basa en 2 sencillos pasos:

                                          1) Si estamos dentro de la línea, nos moveremos hasta encontrar el final.
                                          2) Si estamos fuera de la línea, nos movemos hasta encontrarla.
                                          Finalmente, tendremos que repetir estos dos pasos en un bucle infinito.

                                          Lo primero que hacemos, desde la pestaña de “Control” es añadir un arranque al programa, y un bucle infinito. Dentro de ese bucle debemos incluir una comprobación del estado del robot con respecto a la línea. Para ello usamos la estructura “Si … si no…” que dependerá del resultado de evaluar el valor de luz reflejada con respecto al valor umbral.
                                          Realizar esta evaluación es sencillo, basta con utilizar un operador de “>” o “<” que tenga como entrada, por un lado la recibida por el sensor de luz, y por otro el valor que tomamos como umbral.

                                          Program_01

                                          Ahora tenemos que añadir los motores. En esta primera versión haremos que los motores alternen su funcionamiento, de tal forma que en cada condición de nuestra bifurcación sólo funcione uno de ellos, por lo que tendremos que asegurarnos de parar el otro.

                                          Program_011

                                          Al completar el proceso, tendremos algo como esto:

                                          Program_21

                                          En la parte superior derecha tenemos los botones de control que ya conocemos de Scratch:

                                          - El círculo rojo, que nos permite interrumpir la ejecución, no está implementado aún, por lo que tendremos que parar el programa de forma manual.
                                          - La bandera verde (marcada con un círculo Rojo en la imagen superior), que comienza la ejecución. En este caso llamará a LeJOS para que se encargue del proceso, mostrándonos lo siguiente:

                                          Compiling1

                                          Y un nuevo icono que se ha añadido a Enchanting que tiene como función indicarnos que la parte gráfica: Scratch y la parte código: LeJOS se están comunicando correctamente. Este icono es un pequeño ladrillo NXT (marcado con un círculo Azul en la imagen del programa).

                                          Como se puede ver, este nuevo software, aún en una fase muy temprana de su desarrollo, nos permite utilizar la simplicidad de la interfaz de Scratch, combinada con la potencia del lenguaje LeJOS de una forma sencilla y bastante completa. Esperamos que os haya gustado el experimento.

                                          Enchanting – Programando NXT con Scratch

                                            enchantingYa hemos hablado en varias ocasiones de Scratch y sus posibilidades como lenguaje de programación polivalente para los más pequeños, en concreto con el sistema de construcción WeDo. En esta ocasión queremos hablaros de un software basado en Scratch, y denominado Enchanting, con una peculiaridad que lo hace de lo más atractivo: ¡la posibilidad de controlar nuestro NXT!

                                            Ya sabemos que existe un software gráfico para NXT, el NXT-G, pero también conocemos sus limitaciones. En este caso, Enchanting nos permitirá hacer uso de una interfaz gráfica sobre el lenguaje de programación LeJOS.

                                            enchanting

                                            En este imagen podemos ver las nuevas opciones de motores para el NXT.

                                            Os dejamos con un vídeo de presentación de este nuevo software de programación (en inglés).

                                            En estos momentos se encuentra en la versión 0.0.4, y es compatible con Windows, Linux y Mac OS X. Otra buena noticia para los usuarios de Linux, que no podía utilizar NXT-G en sus equipos de forma nativa y tenían que recurrir a máquinas virtuales o programas como Wine para poder hacer funcionar programas de Windows.

                                            Podéis descargarlo desde aquí:

                                            - Descarga Enchanting 0.0.4 (todos los SO)

                                            Para poder utilizarlo, necesitaremos tener previamente instalado y configurado LeJOS en nuestro ordenador, por lo que, si aún no lo tenéis, podéis echar un vistazo a estos tutoriales:

                                            - LejOS en Windows
                                            - LeJOS en Linux
                                            - LeJOS en MAC OS X

                                            En la página del programa se agradece al equipo del MIT que desarrolló el software Scratch y distribuye su código para que los usuarios puedan hacer sus modificaciones. Hemos querido incluir aquí ese agradecimiento, ya que gracias a este tipo de iniciativas podemos hacer llegar la tecnología a todas las edades.

                                            Fuente:
                                            - Página web oficial del Proyecto Enchanting

                                            Amplía las posibilidades de tu NXT: Cable convertidor NXT

                                              ExperimentoHoy vamos a centrarnos en otra posible expansión de las posibilidades de nuestro NXT. Uno de los objetivos principales de LEGO es que podamos combinar diferentes colecciones o técnica, aparentemente dispares, entre si. Como puede ser la unión de ladrillo clásico y NXT (exposición de MOCs con NXT). O poder trabajar con la antigua electrónica de 9V o con Power Functions desde nuestro Mindstorms. Es en este último caso en el que nos queremos centrar, y por eso vamos a realizar un experimento con el cable convertidor NXT (x1676 o 8528). Este cable nos permitirá controlar electrónica de 9V antigua o electrónica PF.

                                              La utilidad principal de este cable es que podemos mover varios motores con un sólo puerto, aunque tendremos que tener en cuenta que estás apilados (conectados todos a una misma señal de salida) por lo que se moverán todos en las mismas condiciones.

                                              Lo primero que deberemos tener en cuenta es que el cable convertidor da salida directa a electrónica 9V antigua, utiliza una clavija negra de 2 x 2 con las pletinas de conexión en los studs/ laterales de la pieza.

                                              NXT converter Cable

                                              Este tipo de clavija no se utiliza sólo para los motores, si no también para las luces, como nuestro famoso semáforo NXT.

                                              detalle semáforo

                                              Para poder controlar la electrónica de PF, más moderna, necesitaremos un cable de extensión PF (8886 – 20cm- o 8871 -50cm-)

                                              Cable extensión

                                              Vamos a explorar un poco más esta posibilidad de control de motores PF. Conociendo la limitación de puertos de motor de un NXT (tiene 3), supongamos un ejemplo de robot tipo rover que requiere de tracción integral, dirección y al menos 1 manipulador. Si utilizamos un motor XL para cada eje de tracción (se trata de un vehículo de dimensiones considerables, por lo que necesitaremos más de un motor), otro motor para la dirección y un cuarto para el manipulador, ya hemos superado la limitación de 3 puertos.

                                              Con ayuda del cable convertidor y el cable de extensión PF podemos hacer que 2 de los motores reciban la salida de un mismo puerto (y por lo tanto funcionarán a la vez), con esto tendríamos tracción total con 2 motores pero consumiendo un solo puerto.

                                              Lo que nos deja 2 puertos más disponibles para funciones adicionales, como la dirección o nuestro manipulador.

                                              Para poder programar estos motores PF necesitaremos hacer uso de la librería Legacy Block, se trata de una librería oficial que nos permitirá controlar electrónica de 9V, ya sean motores o lámparas.

                                              Descarga Legacy block

                                              Una vez descomprimido el archivo (es un .zip) necesitaremos importar a NXT-G un bloque, en concreto se trata del bloque Old Motor

                                              Old Motor Block

                                              Una vez importado, vamos a ver un breve ejemplo de funcionamiento.

                                              Para ello hemos hecho un montaje con dos servos, conectados a A y C y un cable convertidor (en B) que da salida a 3 motores PF.

                                              Experimento

                                              Este es el programa que lo controla

                                              Programa Prueba_2

                                              Se trata simplemente se una sucesión de órdenes de movimiento a los distintos motores.

                                              Aquí tenéis un vídeo de muestra

                                              Un último apunte a tener en cuenta es que, si bien a priori podemos apilar tantos motores como nos parezca, debemos tener en cuenta el mayor consumo de batería que esto supone. En el vídeo podemos ver 3 motores, un PF mediano (ref. ) y dos motores e-motor de la caja de energías renovables (review 9688).
                                              Como se puede observar, el sistema funciona perfectamente, sin que aparentemente haya problemas de capacidad, sin embargo esto se debe a que los motores giran sin carga. Si los motores se conectan a algún mecanismo, nos encontraríamos seguro con problemas de alimentación.

                                              Esperamos que os haya gustado y os anime a seguir creando.

                                              Amplía las posibilidades de tu NXT: Sensor IR Link

                                                modeloHoy queremos mostraros un sensor que puede ampliar hasta límites insospechados las posibilidades de nuestro LEGO Mindstorms. Se trata del sensor de enlace IR (MS 1046). Este sensor nos permitirá controlar cualquier elemento Power Functions de forma remota (mediante infrarrojos).

                                                La combinación NXT+ Power Functions multiplica las posibilidades de ambos mundos, tradicionalmente independientes. La comunicación por infrarrojos permite dos nuevas interesantes combinaciones:

                                                • Podemos ampliar las opciones del NXT porque gracias a este nuevo enlace podemos añadir nuevas funciones que el NXT podrá controlar, no sólo motores, sino también luces. Baste imaginar un vehículo con orugas como un tanque: Con los 3 posibles servomotores del NXT no tendremos la posibilidad de controlar todas las funciones que podría requerir: 2 motores para la tracción de las orugas, el giro de la torreta, la elevación y posible disparo del cañón o luces. Existen por otra parte una serie de funciones que no requieren de las posibilidades que nos brindan los tacómetros de los servomotores, por lo que su aportación se infrautiliza. Sería éste el caso de los motores encargados de dar tracción a las orugas del tanque. El control de la navegación de un vehículo que se desplaza basado en la adherencia de las orugas, deslizante sobre muchas superficies, hace que el uso de los tacómetros sea inútil o muy difícil en este tipo de aplicaciones. En esta aplicación en concreto sería más útil el uso del servomotor para mantener la posición de la torreta en un ángulo determinado, basado en la lectura de un sensor magnético, mientras que la tracción de las orugas podría provenir de la potencia de los motores Power Functions, controlados desde el NXT mediante un sensor de enlace por infrarrojos.

                                                Leopard 2A4, por Sariel

                                                El tanque anterior es, a fecha de hoy, la última e impresionante construcción de Sariel. En él se emplean 4 motores XL PF para proporcionarle tracción, así como 3 x PF XL, 2 x PF Medium, 1 x Micromotor para funciones adicionales. Esta fantástica creación es puramente Power Functions, por lo que carece de sistemas automáticos que podría proporcionar un NXT, como la comentada anteriormente de mantener la torreta en un ángulo determinado, independiente del ángulo de la base. Esta posibilidad la podría proporcionar la combinación Power Functions + NXT.

                                                • Una segunda e interesante opción es la de controlar el NXT mediante un Control Remoto IR Power Functions. Esta segunda posibilidad se analizará en próximos artículos.

                                                Si bien en este artículo vamos a mostrar cómo hacer uso del sensor IR Link mediante el lenguaje gráfico NXT-G, la programación de este dispositivo no está limitada a este entorno, sino que también puede emplearse con otros lenguajes.

                                                Programación del sensor de enlace IR mediante NXT-G

                                                Para poder utilizar este nuevo sensor en NXT-G tendremos que descargarnos en primer lugar los bloques necesarios, de forma que podamos añadirlos a nuestra paleta de herramientas de NXT-G. Para añadir los bloques podéis seguir los pasos del tutorial de importación de bloques para el nuevo sensor de color 9694. Si aún tenéis alguna duda, podéis consultar este hilo del foro.

                                              • Bloque PF IR Link (Descarga)


                                                PF IRLink Block

                                                Este icono nos permitirá controlar los motores PF de la misma manera que el mando tradicional (8885): podemos girar en un sentido, en el contrario, o detener el motor. Tendremos, como si del mando se tratara, 4 canales. Además podremos controlar de forma independiente las 2 salidas de nuestro receptor IR.

                                              • Bloque PFE IR Link (Descarga)


                                                PFE IRLink Block

                                                Este bloque hace que nuestro Mindstorms se asemeje más al nuevo mando (8879) pudiendo no sólo elegir el canal y la salida en el receptor, sino que también permite controlar completamente nuestros motores: dirección, velocidad y la posibilidad de frenar los motores o realizar flotación (encadenando movimientos, tal y como pasa con los servos de NXT).

                                                Como comentábamos más arriba, este sensor se comporta como un control remoto con cualquier dispositivo que integre recepción por infrarrojos, como un vehículo, o un tren, por ejemplo.

                                                Vamos a ver un ejemplo de su uso para controlar un motor PF.

                                                Este sería el programa:

                                                Programa 1_2

                                                En este caso usamos los botones de Derecha e Izquierda del ladrillo para hacer girar el motor en un sentido o en otro. Si no pulsamos ningún botón, paramos el motor.

                                                Esta última condición debería poder obviarse, pero si no la incluimos, el sensor conserva la última información que hemos mandado, por lo que al volver a apuntar al receptor (o al iniciar el programa) el motor no se mantiene en espera, si no que empieza a girar, sin que tengamos control sobre él.

                                                Os dejamos un vídeo del funcionamiento de este experimento.

                                                Un segundo experimento, más complejo, nos permitirá controlar un tren de forma progresiva haciendo uso del bloque PFE. De esta manera, podemos imitar el comportamiento del mando 8879. Con la salvedad de que el movimiento no se mantiene si interrumpimos la señal.

                                                El programa es el siguiente

                                                Programa 2

                                                Utilizamos los botones Derecho e Izquierdo no para indicar la dirección si no para fijar la velocidad a la que giraremos (de -7 a 7). Además usamos el botón central o Intro para realizar una parada inmediata.
                                                Nótese que hemos querido preservar el carácter cíclico de la velocidad, de tal manera que si estamos en 7 y tratamos de subir una velocidad más, nos llevará a 0. Igual pasa si estamos en -7.

                                                Vídeo del funcionamiento

                                                Comentar que hay un tercer bloque para NXT-G, diseñado expresamente para los trenes y denominado

                                              • Train IR Link (Descarga)

                                                Que nos permitirá controlar hasta 3 trenes de manera simultánea, manteniendo además los estados de movimiento.

                                                Material necesario
                                                Lo que necesitamos para expandir nuestro NXT mediante el método explicado anteriormente dependerá en gran medida de las funciones adicionales que queramos añadir. Si nos atenemos al clásico diagrama del sistema Power Funtions, veremos que la parte que estamos modificando es la de control:

                                                Esquema de combinaciones Power Functions

                                                Según el artículo actual, estamos reemplazando lo que serían los controles remotos de la izquierda (bien el 8885 o bien el 8879) por la combinación del NXT + sensor de enlace IR (MS-1046), puesto que va a ser ahora el NXT el que envíe las órdenes al receptor de IR a través del sensor. El resto es el del sistema Power Functions habitual que queramos controlar, salvo que ahora el control puede provenir de un programa en el NXT. Por tanto, el sistema mínimo para ampliar nuestro NXT será el de:

                                                • Sensor de enlace IR (MS 1046).
                                                • Receptor IR 8884. Uno o varios.
                                                • Sistema de alimentación del sistema controlado por IR, que puede ser proporcionado por una caja de baterías 8881 o una batería recargable Power Functions 8878. Las necesidades de alimentación dependerán obviamente de la carga que tengamos.
                                                • Lo que queramos controlar a través del o de los receptores: motores M, XL, o luces.

                                                Esperamos que os haya gustado el artículo. Para cualquier duda podéis recurrir al foro.

                                              • MINDdroid: Mindstorms aterriza en dispositivos Android

                                                  MindDroidNuevamente LEGO sorprende a todos los fans de Mindstorms NXT con una actualización de su aplicación de control remoto para dispositivos móviles. En este caso se trata de una aplicación para dispositivos con Android como SO, nos permitirá hacer uso del acelerómetro del dispositivo, así como utilizar un botón específico para el motor de acción.

                                                  En la página oficial además nos regalan una divertida frase dedicada a todos los aficionados a la ciencia ficción: “Do Androids Dream of LEGO MINDSTORMS?”, que es un evidente y muy acertado guiño a Do Androids Dream of Electric Sheep?, una novela corta escrita por Phillip K. Dick en 1968 y que sirvió de base para la genial película Blade Runner, dirigida por Ridley Scott en el 1982.

                                                  Además de hablarnos un poco sobre las posibilidades de este nuevo control remoto, podremos también descargarnos al código fuente de la aplicación, para estudiarlo o modificarlo a nuestro antojo.

                                                  Queremos resaltar un par de detalles:

                                                • Sólo está disponible para dispositivos con Android 2.1 o superior
                                                • Se conecta con el NXT por bluetooth (no necesitamos librerías no oficiales o un ordenador como intermediario (ver artículo sobre iPhone-NXT)
                                                • Está disponible para su descarga desde el App Market.

                                                  Queremos aprovechar además para agradecer a los AFOLs que lo han hecho posible, en colabaración con el LEGO Mindstorms Team, se trata de Günther Hölzl y Shawn Brown, que han dedicado parte de su tiempo libre a este proyecto.

                                                • Paso a nivel NXT + NXT-G

                                                  Artículo nº 16 de la serie de 15 artículos sobre Trenes

                                                    800px-Bue10_Hilfposten_ET423Seguimos automatizando nuestros dioramas de trenes, en este caso, vamos a realizar una segunda versión de paso a nivel autónomo con Mindstorms NXT programado en NXT-G. Para más referencias os recomendamos al artículo sobre automatización con Scratch+WeDo.

                                                    En esta ocasión hemos querido reproducir de la forma más fiel posible un paso a nivel inglés, con su característico semáforo de 3 luces.
                                                    El funcionamiento de estos semáforos es muy simple, las luces están apagadas mientras la barrera está levantada, cuando se acerca un tren se enciende una luz ámbar fija (vértice inferior) que da paso, en unos segundos a 2 luces rojas intermitentes (vértices superiores), en ese momento se baja también la barrera. Dicho funcionamiento se puede apreciar muy claramente en el siguiente vídeo.

                                                    Encontraremos 2 tipos:

                                                  • Luces en forma de triángulo invertido sobre un soporte con la misma forma

                                                    Finnish_level_crossing_activated

                                                  • Luces en forma de triángulo invertido sobre un soporte retangular

                                                    Acton_Central_level_crossing

                                                    Es en este último en el que basaremos nuestro diseño.

                                                    Se trata de un modelo minimalista, que requiere de muy pocas piezas, un motor, un ladrillo inteligente NXT y 2 lámparas (es necesario un cable adaptador para conectarlas al NXT).

                                                    rail crossing NXT

                                                    Este es el programa que lo controla

                                                    Programa PasoNivel

                                                    Detalle del Mi Bloque “Abrir y luces”

                                                    Bloque abrir

                                                    Vídeo del funcionamiento

                                                    Esperamos que os haya gustado.

                                                  • Código Morse en NXT-G

                                                      samuel_morse_telegraphHoy hemos querido utilizar uno de los sensores que menos aparece normalmente en los artículos, pero no por ello es menos importante: el sensor de sonido. Este sensor es capaz de medir intensidad sonora, ya sea valor directo (0-1024) o valor escalado (0-100). Esto nos puede permitir, por ejemplo, programar nuestros robots para avanzar cuando demos una palmada (valor de sonido más alto que el umbral) o tener un robot bailarín que se queje cuando bajamos la música (valor de sonido por debajo del valor umbral). En este caso utilizaremos el sensor para que nuestro NXT muestre en pantalla código morse en función del mensaje que estemos recibiendo.

                                                      Este experimento utiliza como base de comunicación el código morse. Este código permite representar todos los números y letras del alfabeto como secuencias de puntos y rayas.

                                                      Esta imagen es una representación del primer alfabeto morse internacional, en este primer alfabeto no existían signos de puntuación ni letras que no aparecieran en el alfabeto inglés (como la CH o la Ñ), que sí aparecerían más adelante, junto con signos de puntuación como: ?, . , ”


                                                      Intcode

                                                      Como se explica en la parte superior:

                                                      1. Una raya equivale a 3 puntos.
                                                      2. El espacio entre las partes de una misma letra es de 1 punto.
                                                      3. El espacio entre dos letras es de 3 puntos.
                                                      4. El espacio entre palabras es de 5 puntos.

                                                      Es decir, para hacer posible la comunicación se establece como unidad de medida la duración (en tiempo) de un punto, por lo que una raya equivale en duración a haber mandado 3 puntos, etc…

                                                      Aquí tenéis la tabla actual, extraída de Wikipedia, en este caso, se puede observar la inclusión de símbolos nuevos, tal y como comentábamos antes:

                                                      Signo Código   Signo Código   Signo Código
                                                      A · —   N — ·   0 — — — — —
                                                      B — · · ·   Ñ — — · — —   1 · — — — —
                                                      C — · — ·   O — — —   2 · · — — —
                                                      CH — — — —   P · — — ·   3 · · · — —
                                                      D — · ·   Q — — · —   4 · · · · —
                                                      E ·   R · — ·   5 · · · · ·
                                                      F · · — ·   S · · ·   6 — · · · ·
                                                      G — — ·   T   7 — — · · ·
                                                      H · · · ·   U · · —   8 — — — · ·
                                                      I · ·   V · · · —   9 — — — — ·
                                                      J · — — —   W · — —   . · — · — · —
                                                      K — · —   X — · · —   , — — · · — —
                                                      L · — · ·   Y — · — —    ? · · — — · ·
                                                      M — —   Z — — · ·   · — · · — ·

                                                      Convenciones: — : raya (señal larga) · : punto (señal corta)
                                                      Sí se comete un error al transmitir el mensaje en morse, la señal «error» son ocho señales cortas (. . . . . . . .)

                                                      ¿Para qué se utiliza?

                                                      samuel-morseSamuel Morse tardó 7 días en enterarse de la muerte de su esposa en 1832, debido a lo penoso de las comunicaciones a larga distancia en aquella época, tras este desgraciado hecho se decidió a inventar un dispositivo que agilizara ese tipo comunicaciones. El 6 de enero de 1833, Morse realizaría la primera demostración pública de su telégrafo.

                                                      Una vez presentado el invento, se concentró en la tarea de construir un telégrafo práctico y despertar el interés del público y del gobierno en el aparato para luego ponerlo en marcha. En 1835 apareció el primer modelo telegráfico que desarrolló Morse.

                                                      El funcionamiento del dispositivo es el siguiente:


                                                      Telegrafo

                                                      Cuando en la estación emisora se cierra el interruptor (manipulador) circula una corriente por el siguiente circuito: polo positivo, línea, electroimán, tierra, polo negativo, lo que tiene como consecuencia que, activado el electroiman atraída una pieza metálica terminada en un punzón que presiona una tira de papel, que se desplaza mediante unos rodillos de arrastre, movidos por un mecanismo de relojería, sobre un cilindro impregnado de tinta, de tal forma que, según la duración de la pulsación del interruptor, se traducirá en la impresión de un punto o una raya en la tira de papel. (Wikipedia)

                                                      Durante este tiempo, Morse desarrolla junto con el telégrafo un código de señales para facilitar la comunicación, dicho código se perfecciona durante 1838, y toma el nombre de Código Morse.

                                                      Intentó implantar líneas telegráficas primero en Estados Unidos y luego en Europa pero ambos intentos fracasaron. Por fin, Morse consiguió que el Congreso de su país aprobara un proyecto de ley para proporcionar 30.000 dólares designados a construir una línea telegráfica de 60 km. Varios meses después el proyecto fue aprobado, y la línea se extendería a lo largo de 37 millas entre Baltimore y Washington.

                                                      Ya hemos visto un poquito de historia, ahora… el experimento.

                                                      A forma de rudimentario telégrafo inalámbrico, donde la duración de los pulsos la marca el operador según las normas que veíamos más arriba, hemos construido dos robots, emisor y receptor, que se comunican mediante código morse.

                                                      El robot emisor cuenta con un sensor de contacto

                                                      Robot emisor

                                                      y un sencillo programa en el cual emitimos un sonido (una nota musical) mientras nos mantengan presionado el sensor.


                                                      Emisor

                                                      El receptor está equipado con un sensor de sonido (9845),

                                                      Robot receptor

                                                      con un programa algo más largo que le permitirá mostrar en pantalla puntos o rayas en función de la intensidad del sonido.

                                                      Receptor2

                                                      Nótese que miramos la intensidad del sonido y no su duración, de tal manera que a cada vuelta de bucle comprobaremos si hay o no sonido, pintando puntos cuando sí que haya.
                                                      De esta manera si recibimos sonido durante varias iteraciones de bucle pintaremos varios puntos juntos o, lo que es lo mismo, una raya.

                                                      Además hay una línea de control continua que nos permite comprobar que el programa está en marcha, aunque no esté recibiendo señal. Esta línea se pinta en X=Numérica, Y=0, donde Numérica es la varible que representa la posición de X en función del tiempo, se obtiene de dividir el valor del temporizador entre 100 .
                                                      Mientras que el mensaje se muestra en X=Numérica, Y=30.

                                                      Por último, indicar que, para evitar que la información se solape en pantalla, debemos borrar la pantalla cuando Numérica alcance el máximo que admite la pantalla de ancho, es decir 99.

                                                      Aquí tenéis un vídeo del funcionamiento

                                                      Esperamos que os haya gustado el artículo y os anime a experimentar con nuevos proyectos, además de buscar nuevas funcionalidades para nuestro set de robótica.

                                                      Silla de ruedas de bajo coste

                                                        SmartWheelchairLOGOgrayAlgunas veces, dentro de la comunidad LEGO, nos encontramos con noticias impactantes y algunas que nos llegan al corazón. Esta es una de ellas. El nuevo proyecto del genial Daniele Benedettelli es la creación de una silla de ruedas autónoma de bajo coste, para ayudar a niños con discapacidad y a sus familias, que en muchos casos son incapaces de afrontar el enorme gasto que una silla de ruedas motorizada tradicional puede alcanzar.

                                                        Este proyecto comenzó en Agosto de 2008, tras la petición Gina Wilson Burns, madre de un niño con discapacidad, a Danielle para crear una silla de ruedas autónoma controlada por un NXT, consiguiendo así reducir enormemente los costes del equipo tradicional.

                                                        Dicho y hecho, Daniele se puso manos a la obra con el proyecto, su primer paso fue comprobar las posibilidades de navegación autónoma de un dispositivo equipado con NXT. En este caso se trata de un sistema de navegación con balizas láser, hace uso de un sensor fabricado por el propio Benedettelli que puede emitir un haz láser y evaluar su posición debido a la reflexión con respecto a las balizas.

                                                        Recientemente, durante el mes de Abril de este mismo año, Benedettelli presentó su prototipo durante la celebración de la Campus Party Europe celebrada en Madrid, resultando galardonado con una mención de honor.

                                                        A continuación podéis encontrar el vídeo de la presentación del prototipo.

                                                        Daniele Benedettelli es bien conocido en la comunidad Mindstorms NXT como el creador de algunos proyectos que ya hemos comentado en el blog, como :

                                                        - NXT Retratista
                                                        - Resolvedor del Cubo de Rubik

                                                        Así como su libro: LEGO MINDSTRORMS NXT Thinking Robots

                                                        Le deseamos toda la suerte del mundo en este proyecto.

                                                        Enlaces de interés:

                                                        - Blog de Gina Wilson Burns y su hijo Mac
                                                        - Blog de Daniele Benedettelli sobre el proyecto, Podéis encontrar también una dirección de contacto para colaborar con él en el proyecto.

                                                        NI LabVIEW 2010

                                                          lv2010_hero_shotEn agosto National Instruments anunciaba la próxima salida al mercado del software LabVIEW 2010, y hoy podemos ya confirmaros que es una realidad. Vamos a repasar las características y mejoras más importantes de este nuevo software frente a sus predecesores.

                                                          El software NI LabVIEW 2010 tiene un compilador mejorado que genera código máquina optimizado, disminuyendo el tiempo de ejecución de la aplicación hasta en un 20 por ciento. Además, LabVIEW 2010 aborda temas más importantes como apoyo con la instalación de software o configuración de hardware basado en Web.

                                                          Con la última versión de LabVIEW, ahora es posible integrar los temporizadores y sincronización con el control, en el lenguaje G. El motor de nanosegundos que utiliza LabVIEW en sus mecanismos temporizadores ahora se puede sincronizar con estándares como IEEE 1588.

                                                          Mejoras destacadas en LabVIEW 2010

                                                        • Compilador optimizado
                                                        • Comparte información en tiempo real por red
                                                        • Salvar VIs sin código compilado
                                                        • Ejecución de SubVIs lineal
                                                        • Salvar datos de los gráficos directamente a Excel
                                                        • Configuración de hardware vía web

                                                          Podéis ver el Webcast de presentación en el siguiente enlace:

                                                          NI LabVIEW 2010 Webcast

                                                        • Parque de atracciones LEGO

                                                            IMG_2885Nuestro forero DaniRacer, que ya nos sorprendió con una exposición de MOCs con NXT, nos ha mostrado hoy un impresionante parque de atracciones utilizando PF y mindstorms, y hemos querido compartirlo con todos vosotros. El parque consta de 5 atracciones, a cual más original. Además queremos señalar el impresionante trabajo mecánico y de recreación que se ha hecho de algunos de los modelos, que imitan a la perfección a los originales.

                                                            Vamos a verlas en detalle.

                                                            Las famosas Sillas Voladoras:

                                                            Sillas

                                                            Sobre esta atracción, podemos leer en la página del Parque de Atracciones:

                                                            “También popularmente conocidas como Las Cadenas, Las Sillas Voladoras es un carrusel del que penden cadenas con sillas que se elevan y giran. Te harán sentir la sensación de flotación, prácticamente igual que si estuvieras volando.”

                                                            Las sillas voladoras del siguiente vídeo pertenecen al Luna Park de New York. La velocidad de giro es muy inferior a la que ha creado DaniRacer. La perspectiva con la que ha sido tomado el vídeo nos da la sensación de que las sillas se abren más hacia el exterior de lo que realmente lo hacen: el ángulo con el que se abren depende básicamente de la velocidad de giro y del radio. Podéis estudiar la dinámica del movimiento siguiendo el enlace inferior.

                                                            Por lo que podéis ver en el vídeo que hay al final del artículo, DaniRacer ha conseguido imitar esa sensación de vuelo a la perfección, aunque a mayor velocidad.

                                                            La mítica lanzadera

                                                            lanzadera2

                                                            “Espectacular caida libre desde 63 metros de altura a 80 km/h.

                                                            La mejor forma de experimentar el “puenting” seguro sin salir de Madrid. Esta instalación, traída de EE.UU., otorga el privilegio de sentir una caída libre a 80 km/h desde 63 metros de altura. La intensa sensación dura apenas 2 segundos: suficiente para poner en alerta todo el sistema nervioso.”

                                                            Un curiosa atracción pendular

                                                            Gira

                                                            Que me recuerda poderosamente al Aladino/TIR:

                                                            “T.I.R. [1989 - 2009]: Inaugurada el 28 de julio de 1989 con el nombre “Aladino” y desmantelada en primavera de 2009. Consistía en una viga vertical de 15m con el eje en el centro y en cuyo extremo se localizan los asientos. El eje realiza giros de 360º haciendo que la viga gire de manera similar a las manecillas de un reloj. El extremo donde se sitúan los asientos también tiene un eje que gira en sentido contrario al eje principal, haciendo que los asientos siempre mantengan la misma posición horizontal. Se situaba en la zona Oeste del parque, al lado de las Sillas Voladoras y el Tifón. Ninguna atracción ha ocupado su lugar.”


                                                            TIR

                                                            Para los amantes de atracciones más relajadas, tenemos la barcaza

                                                            Barca

                                                            Que se desplaza con un suave movimiento pendular mientras gira sobre si misma. Lo que recuerda al nuevo Tifón, aunque este último es mucho más rápido y para nada relajante…

                                                            tifon

                                                            “En Abril de 2008, el Parque de Atracciones de Madrid incorpora TIFÓN en la Zona del Maquinismo. En esta atracción puedes subir y balancearte a la vez que giran y se trasladan de la forma más divertida. Un divertido viaje en el que se alcanza una altura de más de 15 metros y sus asientos son similares a los de una moto pero con un arnés automático trasero que maximiza las sensaciones.”

                                                            Por último, para los nostálgicos de las atracciones más clásicas… los coches de choque

                                                            IMG_2885

                                                            Estos coches de choque son completamente automáticos y están dotados de un ingenioso sistema que imita un movimiento pseudo aleatorio.

                                                            Detalle del sistema de engranajes

                                                            IMG_2889

                                                            Os dejamos con el vídeo de las atracciones en funcionamiento, esperamos que os guste.

                                                            Es curioso, pero a pesar del innegable atractivo que han tenido las ferias en todos nosotros, son muy pocos los conjuntos relacionados con esta temática que LEGO ha ofrecido. Uno de ellos es el caso de la noria 4957, que pasó sin pena ni gloria por nuestro país, no porque no gustara, sino porque estuvo en catálogo durante 3 meses únicamente!

                                                            4957-1 Noria

                                                            El segundo modelo es el tiovivo: sí es cierto que hubo un pequeño carrusel en el 1989 de 23 piezas, nada que ver con el reto actual de 3263 piezas, que no sólo tiene una estética impecable sino que además tiene un motor que proporciona movimiento circular al conjunto y a los caballitos que suben y bajan. Y, ¡cómo no… el tiovivo tiene música también!

                                                            Información adicional:

                                                            LEGO Educativo 9694 – Nuevo Sensor de Color

                                                            9694-1Ya está disponible el nuevo sensor de LEGO Educativo, que hasta ahora sólo podían disfrutar los usuarios de la versión comercial 2.0 del NXT. Se trata de un sensor con tres funciones: color, luminosidad y lámpara, todo en un mismo puerto. Es el 9694, Sensor de Color NXT.

                                                            El nuevo sensor puede ser utilizado como un sensor de luminosidad clásico, para detectar colores (hasta 6 distintos en NXT-G) o como lámpara, emitiendo luz roja, verde o azul. Los usuarios del set educativo 9797 ya tenían una lámpara, pero debían usarla con un cable especial y consumían un puerto de motor, ahora esto no es necesario.

                                                            Para poder hacer uso de las funcionalidades del nuevo sensor, necesitaremos descargarnos 2 bloques nuevos:

                                                          • Bloque Sensor de Color
                                                          • Bloque Lámpara (para Sensor de Color)

                                                            NOTA: Después de descargar los archivos zip, los descomprimimos y entramos en la carpeta. Debemos buscar un archivo zip que corresponda con el idioma de nuestro software.
                                                            En nuestro caso, los archivos se llaman Spanish Color Sensor 2_x.zip y Spanish Color Lamp 2_x.zip
                                                            Debemos descomprimir este segundo archivo, ya que es el que lleva la información necesaria para importar el bloque.

                                                            Ahora debemos añadirlos a nuestra paleta de bloques, para ello podemos seguir estos sencillos pasos:


                                                            Importar Bloques1

                                                            Cuando se nos abra la nueva ventana, pulsamos “Examinar”


                                                            Importar Bloques2

                                                            Recordemos que la ruta del bloque será:

                                                            [Ruta del archivo descomprimido]\ColorLamp\Color Lamp\Spanish Color Lamp 2_x

                                                            o

                                                            [Ruta del archivo descomprimido]\ColorSensor\Color Sensor\Spanish Color Sensor 2_x

                                                            Seleccionamos el bloque y la paleta en la que queremos incluirlo y hacemos clic en “Importar”.


                                                            Importar Bloques3

                                                            Cuando se termine la importación, recibiremos el mensaje “Importación correcta”.


                                                            Importar Bloques4

                                                            Y este es el aspecto que tendrán nuestros nuevos bloques una vez importados:

                                                            Bloque Color


                                                            Sensor Color

                                                            Bloque Lámpara


                                                            Lámpara Color

                                                            Importante:

                                                            Para poder usar el nuevo sensor como un sensor de luminosidad, no hay ningún bloque, hay que utilizar el bloque Color (que acabamos de importar) y seleccionar:

                                                            “Acción – Sensor Luz”


                                                            Sensor Luz

                                                            Y nuestro menú cambiará a este:


                                                            Menu Sensor Luz

                                                            Donde tenemos las opciones habituales del sensor de luminosidad clásico, más la posibilidad de, si tenemos marcada la opción de emitir luz, emitir en rojo, verde o azul.

                                                            Cualquier duda o problema, podéis visitar el foro.

                                                          • IMPORTANTE: Actualización Software Educativo NXT 2.1

                                                            Artículo nº 8 de la serie de 10 artículos sobre NXT-G

                                                            NXTLogFile_medium¡Noticia importante! Ya está disponible la actualización -gratuita- para los usuarios del Software Educativo NXT que tengan la versión 2.0. Esta actualización es compatible con Windows y MAC.

                                                            Este parche, que actualiza a la versión 2.1, corrige los siguientes problemas:

                                                          • El desplazamiento de un programa consume un volumen de memoria que nunca se libera, lo cual conduce a una reducción de rendimiento o a un bloqueo de software.
                                                          • Colocar tres bloques Bifurcación seguidos y activar la opción “Vista plana” provoca que el segundo bloque Bifurcación cubra parte del tercero.
                                                          • Eliminar un elemento y deshacer dicha acción después de crear un archivo Pack and Go provoca un bloqueo de software.
                                                          • Eliminar un bloque de Constante contenido en un bloque Mi bloque y deshacer dicha acción provoca un bloqueo de software.
                                                          • Los números de puerto no son correctos al abrir un bloque Sensor ultrasónico de la versión 1.X en la versión 2.0.
                                                          • Problemas de representación al acercar/alejar el contenido de NXT Datalogging Academy.
                                                          • Ocasionalmente, los números de diapositiva de Academy no son válidos al cambiar entre instrucciones de construcción.
                                                          • La velocidad mínima del bloque Registro de datos se limita a aproximadamente 65 segundos entre puntos, incluso aunque la interfaz de usuario indique otro valor.

                                                            Descarga la actualización 2.1 (Windows)

                                                            Instalación en Windows:

                                                            Para instalar el software, ejecuta el archivo “setup.exe” que está en el directorio del instalador y sigue las instrucciones que aparecerán en la pantalla.
                                                            Una vez instalado el software, haz doble clic en el icono del software MINDSTORMS NXT situado en el escritorio o incluido en el menú Inicio. Al hacerlo, se iniciará el software LEGO MINDSTORMS NXT.

                                                            Descarga la actualización 2.1 (MAC)

                                                            Instalación en MAC:

                                                            Para instalar el software, haz doble clic en el paquete de instalación, correspondiente al idioma previamente instalado, desde el directorio del instalador y sigue las instrucciones que aparecerán en la pantalla.
                                                            Una vez instalado el software, selecciona Aplicaciones -> LEGO Mindstorms NXT y haz clic en Mindstorms NXT. Al hacerlo, se iniciará el software LEGO MINDSTORMS NXT.

                                                          • NXT Holonómico

                                                            Lateral LejosYa hemos hablado de los sistemas holonómicos, además, hemos visto varios ejemplos realizados con NXT. Todos ellos basan su capacidad de desplazamiento en ruedas omnidireccionales – o en esferas – , sin embargo, hoy queremos mostraros un tipo de robot holonómico que utiliza ruedas estándar combinadas con un tipo de dirección sincronizada integral (a las 4 ruedas). Esto le permite moverse en cualquier dirección de forma prácticamente inmediata.

                                                            Utiliza únicamente 2 motores, por lo que aún nos deja un puerto de motor libre para cualquier cosa que se nos pueda ocurrir, desde una pala a un brazo/manipulador.

                                                            Lateral

                                                            Vista frontal

                                                            Frontal

                                                            Se trata de un modelo con tracción a las 4 ruedas, que también tiene dirección sincronizada en las 4 ruedas, por lo que puede orientar las ruedas en cualquier dirección sin desplazarse, es decir, no necesita hacer maniobras para cambiar la dirección.

                                                            Detalle del sistema de tracción, un lateral

                                                            Detalle Lateral 1

                                                            El otro lateral

                                                            Detalle Lateral 2

                                                            Imaginaros el potencial de un vehículo como este para acciones cotidianas como aparcar, avanzamos hasta encontrar el sitio, ponemos las ruedas perpendiculares a la acera… y aparcamos.

                                                            Vamos a ver en detalle la dirección sincronizada.

                                                            Detalle Parte Baja

                                                            Aquí tenéis el vídeo del vehículo aparcando, además de poder ver en detalle su funcionamiento.

                                                            Esta versión no está equipada con ningún sensor, el aparcamiento se hace con distancia conocida, pero si montamos un sensor ultrasónico (o varios), por ejemplo, podríamos hacer que aparcara en cualquier lugar y circunstancia. O podríamos añadirle un sensor brújula que nos permitiera saber hacia dónde nos estamos moviendo, algo que es difícil de controlar, ya que puede desplazarse en todas direcciones.

                                                            Esperamos que os haya gustado, para cualquier cosa, podéis utilizar el foro.

                                                            LEGO Education 9695 – Conjunto de recursos para Mindstorms

                                                              XL_9695NewTopCardYa se encuentra disponible la actualización del conjunto de recursos para robótica de LEGO Educativo. Nuevas piezas y nuevas posibilidades. Incluye una gran cantidad de elementos especiales, como orugas, conectores exclusivos, tornillos sin fin, y elementos estructurales LEGO, como vigas, ejes y conectores. Esta nueva caja de LEGO Educativo tiene 817 piezas y viene con la presentación tradicional en caja de plástico con clasificadores para las piezas.

                                                              Se trata de una gran herramienta de expansión para nuestra caja de robótica, ya sea la versión comercial (ref. 8547) o la educativa (ref. 9797), ofreciéndonos posibilidades que hasta entonces no teníamos, ya sea por la inclusión de nuevas piezas o por tener más cantidad de algunas que puedan hacernos falta.

                                                              Recordemos que muchos de los diseños que podemos encontrar hacían uso de la referencia 9648, la antigua caja de recursos de LEGO Educativo, pues bien, este nuevo conjunto de piezas es la actualización de dicho set.


                                                              XL_979695EdResourceSet

                                                              Existen infinidad de modelos disponibles que podéis construir con estas nuevas piezas, pero si queréis una pequeña guía para empezar, a continuación encontraréis las instrucciones para 9 robots.

                                                              NOTA: estos robots están pensados para usar los sets 9695 + 9797
                                                              Para descargar las instrucciones, haced click derecho en la imagen correspondiente y seleccionad “Guardar enlace como…”.
                                                              También podéis visualizarlo en el navegador simplemente haciendo click en la imagen del modelo que os interese.

                                                              Algunos robots clásicos:

                                                              Tribot
                                                              Tribot

                                                              RoboArm


                                                              RoboArm

                                                              Humanoide


                                                              Humanoide

                                                              Reloj Clásico

                                                              Reloj

                                                              Escorpión


                                                              Escorpión

                                                              SoundBot

                                                              SoundBot

                                                              Y algunos nuevos

                                                              Vehículo Inteligente

                                                              Coche Inteligente

                                                              Clasificador de colores


                                                              Clasificador colores

                                                              Rover con 4 orugas


                                                              Rover

                                                              Funciones lógicas en LabVIEW

                                                              Artículo nº 2 de la serie de 14 artículos sobre LabVIEW

                                                                El bit es la mínima cantidad de información posible, puesto que éste almacena únicamente un posible valor, que podemos representar de varias formas: como verdadero o falso, activo-desactivado, 0 ó 1, etc. El operar con este tipo de información constituye la plataforma sobre la que se fundamenta la lógica computacional moderna, en definitiva de los microprocesadores actuales. Recibe el nombre de Lógica Booleana en honor quien la inventó en el siglo XIX, George Boole.

                                                                Consideremos que los dos posibles valores que puede tomar un bit de información son 0 ó 1. Con los bits podemos realizar varias operaciones. Una forma de representar estas operaciones es mediante unos símbolos que se denominan puertas u operadores lógicos. Dichos operadores requieren de uno o más bits como entrada y producen una salida que depende de ellos y del propio operador. La relación entre los bits de entrada y el resultado producido se pueden representar en una tabla, que se denomina tabla de verdad. La tabla de verdad no nos indica más que el valor que obtendremos a la salida de la función lógica dependiendo de los valores que ésta tenga a su entrada.

                                                                Los sistemas digitales se construyen mediante combinación de puertas lógicas. De ellas, las más comunes son NOT, AND, OR, NAND, NOR y XOR.

                                                                La puerta lógica más simple es el inversor, también denominado NOT. El resultado de una inversión es cambiar el valor de entrada a su opuesto: si a la entrada tenemos un 0, el resultado será un 1, y viceversa. La tabla de verdad del inversor es la siguiente:

                                                                NOT (~)

                                                                I O
                                                                0 1
                                                                1 0
                                                                Desde el punto de vista lógico NOT es la única puerta lógica que trabaja sobre una única entrada. En electrónica existen también otras funciones de una sola entrada, los denominados buffers, pero aquí los obviaremos dado que los buffers son transparentes en el sentido lógico.
                                                                AND (&)

                                                                A B A&B
                                                                0 0 0
                                                                0 1 0
                                                                1 0 0
                                                                1 1 1
                                                                La salida de la puerta AND es únicamente uno cuando TODAS las entradas lo son. Esto puede generalizarse para el caso en el que el AND es de más de 2 entradas.
                                                                OR (|)

                                                                A B A|B
                                                                0 0 0
                                                                0 1 1
                                                                1 0 1
                                                                1 1 1
                                                                La salida de la puerta OR es activa siempre que exista algún una entrada activa. lo mismo puede decirse si la puerta OR es de más de 2 entradas.
                                                                NAND

                                                                A B NAND
                                                                0 0 1
                                                                0 1 1
                                                                1 0 1
                                                                1 1 0
                                                                La puerta NAND realiza la función inversa de la AND, por lo que podría construirse enlazando un AND y un NOT. Cuando todas las entradas son activas, el resultado es un cero, en caso contrario la salida es uno.
                                                                NOR

                                                                A B NOR
                                                                0 0 1
                                                                0 1 0
                                                                1 0 0
                                                                1 1 0
                                                                La puerta NOR realiza la función inversa de la OR, por lo que sólo produce un uno cuando las dos entradas son cero.
                                                                XOR (^)

                                                                A B A^B
                                                                0 0 0
                                                                0 1 1
                                                                1 0 1
                                                                1 1 0
                                                                La función XOR, también llamada OR-Exclusivo, produce un uno siempre que las entradas difieren.

                                                                La paleta de funciones booleanas en LabVIEW tiene el siguiente aspecto:

                                                                Paleta de funciones Booleanas en LabVIEW

                                                                Hagamos un ejemplo minimalista para mostrar las funciones booleanas anteriores. En el Panel Frontal hemos dibujado unas flechas, no son más que meros elementos decorativos. Partiendo de las dos señales booleanas A y B realizamos las operaciones lógicas nombradas anteriormente.

                                                                Panel Frontal de las funciones Booleanas en LabVIEW

                                                                En el diagrama de bloques queremos hacer patente que las funciones NAND y NOR son las versiones negadas de las AND y OR respectivamente. Es decir,

                                                                A NAND B = NOT (A AND B)
                                                                A NOR B = NOT (A OR B)

                                                                Diagrama de bloques de las funciones lógicas empleadas

                                                                Si ejecutamos el programa vemos que el color de los indicadores de salida va cambiando en función de los valores de las señales de entrada X e Y según la función aplicada. Hemos elegido el color verde claro para el valor TRUE y oscuro para FALSE.

                                                                En el ejemplo anterior hemos realizado estas operaciones trabajando con unas entradas de tipo boolean. Este es el funcionamiento más habitual para este tipo de operadores. Pero estas funciones lógicas también pueden aplicarse sobre otros tipos de datos. Dentro del ámbito de los lenguajes de programación se denomina polimorfismo al hecho de posibilitar que un mismo interfaz pueda ser empleado sobre diferentes tipos de datos. En nuestro caso podemos decir que las funciones lógicas que nos ofrece LabVIEW son polimórficas, queriendo decir con ello que el mismo símbolo nos va a permitir operar no sólo con variables de entrada de tipo boolean, sino que también permite trabajar con otro tipo de datos. Si aplicamos el NOT a una matriz obtendremos una matriz cuyos elementos serán los negados de los originales, elemento a elemento. En general, podemos aplicar estos operadores sobre arrays de booleans, sobre clusters de booleans, números, arrays numéricos o matrices.

                                                                Veamos con otro ejemplo una de las aplicaciones típicas de los operadores numéricos: las máscaras. Las máscaras nos permiten extraer cierto tipo de información del dato sobre el que se aplica. Supongamos que tenemos una secuencia de 8 bit como 01101011 y queremos saber cuáles de los 4 de mayor peso están a uno. Del resto de la secuencia no nos interesa conocer nada, por lo que podemos anularla. Para ello podemos aplicar la función lógica AND con un patrón de 8 bits en el que ponemos a 1 los 4 bits de más peso, tal que 11110000. El resultado 01100000 es un AND que se ejecuta bit a bit.

                                                                01101011 AND 11110000 = 01100000

                                                                En el siguiente ejemplo de LabVIEW realizamos la máscara del dato de entrada, lo que es equivalente a aplicar la función AND. Al ejecutarlo podemos modificar tanto el valor de entrada como el de la máscara. A pesar de que nosotros introducimos el valor de los datos como enteros, se muestra su valor en binario para que veamos cómo se están realizando las operaciones bit a bit.

                                                                Máscara sobre datos de entrada, panel frontal y diagrama de bloques

                                                                Los ejemplos anteriores de LabVIEW se han ejecutado en el ordenador. Ahora vamos a implementar un pequeño ejemplo práctico pero trabajando sobre un LEGO MINDSTORMS NXT. Hemos conectado un servomotor en el puerto A del robot NXT. Nuestro objetivo es hacer un sencillo programa que nos permita controlar dicho servomotores con dos pulsadores.

                                                                Vamos a crear un pequeño interfaz que deliberadamente nos obligue a hacer uso de funciones lógicas. Para ello activaremos el motor según dos posibles pulsadores:

                                                                • Un pulsador lo hará avanzar en un sentido, y que denominaremos como pulsador P1 (FWD).
                                                                • El segundo pulsador lo hará avanzar en sentido inverso, pulsador P2 (BACK).

                                                                Por la forma en la que lo planteamos se van a producir -deliberadamente- 4 casos posibles, según estén pulsados o no cada uno de los dos pulsadores. Podemos representar en una tabla como las anteriores lo que puede suceder:

                                                                Control motor

                                                                P1 P2 Resultado
                                                                0 0 Motor detenido
                                                                0 1 Motor retrocede
                                                                1 0 Motor avanza
                                                                1 1 …¿?
                                                                Lo que debe hacer el motor NXT en los tres primeros casos es en cierta medida “lógico”, pero …¿que debe hacer el motor en el caso de que los dos pulsadores estén presionados simultáneamente, indicando cada uno de ellos que haga lo contrario de lo que dice el otro? Como el problema lo definimos nosotros, vamos a decidir que en este caso el motor se detenga, igual que en el primer caso.
                                                                Por tanto vemos que vamos a crear tres posibles comportamientos:
                                                                • El motor se detiene.
                                                                • El motor avanza.
                                                                • El motor retrocede.

                                                                Ahora reescribamos, para cada uno de los resultados, la misma tabla anterior. Empecemos por la tabla en la que el motor avanza. Así, la siguiente tabla representará cuándo el motor va a avanzar. Si avanza, lo representaremos con un 1 lógico, en caso contrario, con un cero.

                                                                Motor avanza

                                                                P1 P2 Resultado
                                                                0 0 0
                                                                0 1 0
                                                                1 0 1 (motor avanza!!!)
                                                                1 1 0
                                                                Según la tabla anterior, veíamos que el motor sólo avanzaba en uno de los 4 posibles casos, que es lo que representa esta tabla. Por lo tanto, veamos en qué única circunstancia se va a producir el hecho de avanza: sólo cuando el pulsador P1 está pulsado y, además, P2 no lo está. Esto podemos escribirlo en la siguiente ecuación:
                                                                Avanzar = P1 AND (NOT P2)

                                                                Podemos razonar del mismo modo para el caso en el que el motor debe retroceder, y obtenemos que sólo se produce el retroceso cuando P1 no está pulsado, pero sí lo está P2, es decir:

                                                                Retroceder = (NOT P1) AND P2

                                                                El caso en el que se detiene el motor es un poco más complicado, porque puede darse en dos situaciones posibles, en la primera o en la última, es decir:

                                                                Detener = ((NOT P1) AND (NOT P2)) OR (P1 AND P2)

                                                                Dado que ya tenemos las ecuaciones lógicas que van a controlar el control de lo motores, podemos implementar el programa en LabVIEW. Como cualquier problema, éste puede resolverse de muchas formas posibles. Nosotros hemos elegido con finalidad didáctica el controlar el motor mediante una estructura case.

                                                                Programa de control del motor NXT

                                                                Y lo que se ejecuta en los otros dos casos del case es:

                                                                Casos alternativos para el control del motor

                                                                Y el panel frontal que hemos preparado es el que sigue:

                                                                Panel Frontal del control del motor

                                                                El programa consta de varias partes:

                                                                • Una primera en la que construyo las funciones lógicas de Avanzar, Retroceder y Detener partiendo de las señales de los pulsadores P1 y P2, tal como he explicado antes. Para comprobar que su funcionamiento es correcto he añadido unos indicadores que muestran su valor en función del estado de los pulsadores. He añadido también dos comentarios para indicar Not P1 y Not P2 con objeto de facilitar la lectura.
                                                                • Con lo anterior ya disponemos de las tres funciones, pero como vamos a trabajar con un case para controlar el estado del motor del NXT… ¿Cómo relaciono Función Build Arraydichas señales con la entrada de control del case? La solución por la que he optado es por la de agrupar las tres señales en un array de tres elementos y convertir a continuación dicho array en un valor numérico, que es el que controlará el caso a ejecutar. Para construir dicho array de booleansFunción Boolean Array To Number hago uso del icono Build Array, que podemos encontrar en la paleta de funciones de Array. El siguiente paso en transformar ese valor numérico, porque un array no puede emplearse para controlar los casos de un case. Empleo la función Boolean Array To Number. Para comprobar qué valor se está asociando muestro el valor numérico con el indicador que llamo Número.
                                                                • A la estructura Case debemos añadirle un caso adicional, porque tal como lo introducimos en el diagrama de bloques sólo tiene dos casos posibles. Además debemos nombrar cada uno de los casos según nos aparezcan los valores en Número, y debemos hacerlos coincidir con según la ejecución adecuada. Para poder ejecutar el programa y observar qué valores ha asignado LabVIEW a esos casos, podemos realizar una primera ejecución asignando a los casos los valores 1, 2 y 3. Debemos además asignar el caso Detener como Default.
                                                                • Todo lo anterior se ejecuta en el interior de un while con su correspondiente botón para detener el programa.

                                                                Si todo es correcto:

                                                                • Cuando presionemos P1 debe iluminarse el LED de Avanzar y el motor girar en un sentido.
                                                                • Cuando presionemos P2 debe iluminarse el LED de Retroceder y el motor girar en el sentido contrario al anterior.
                                                                • Cuando presionamos los dos pulsadores o cuando ninguno está presionado, debe iluminarse el LED Detener y el motor debe detenerse.

                                                                * Ejecución en modo Remoto: Este programa debe ejecutarse en el PC sin descargar el programa en el NXT, sino mediante control directo desde el LabVIEW en tiempo real, porque la estructura case con casos enumerados no puede compilarse para el NXT.

                                                                Para cualquier duda, como siempre, podéis acudir al foro.

                                                                NXT Futbolista. Bola Emisora de IR + Sensor de Búsqueda de Infrarrojos

                                                                  lateralEn este artículo hemos querido programar nuestro propio robot futbolista. Es un clásico de la robótica, que además nos servirá de punto de partida para una serie de experimentos con infrarrojos. Con un tribot modificado, la bola de IR y un sensor de búsqueda de infrarrojos, nos hemos puesto manos a la obra.

                                                                  Vamos a revisar primero el material:

                                                                  Bola de IR


                                                                  IRBall-Image-1

                                                                  La bola emisora de infrarrojos consta de un array de leds distribuidos de forma uniforme por toda la bola. Además, tendremos 4 modos de emisión:

                                                                • A: RCJ – Pulso modulado
                                                                • B: RCJ – Emisión Continua DC
                                                                  Estos dos modos de emisión están adaptados a las especificaciones de la RoboCup Jr.

                                                                • C: 600Hz – Onda modulada de 600Hz
                                                                • D: 1200Hz – Onda Modulada de 1200Hz

                                                                  Como alimentación, utiliza 4 pilas AAA, y la duración oscila entre las 7 horas y media del modo A, a poco más de una hora en el modo B; los modos C y D tienen una autonomía de 3 horas y media.

                                                                  Sensor de búsqueda de infrarrojos

                                                                  El sensor de búsqueda de infrarrojos nos devuelve la dirección desde la que recibe la señal, según el siguiente esquema:


                                                                  iRSeeker_Diag2

                                                                  Lo cual nos permitirá saber de dónde nos viene la señal, además de la intensidad de señal recibida en cada una de las 9 direcciones posibles. Esto nos puede permitir, entre otras cosas, calcular mucho mejor la posición del objeto que queremos localizar o seguir.

                                                                  Para nuestra primera prueba, vamos a hacer un programa en NXT-G muy sencillo:

                                                                  ¡OJO! En nuestro robot el sensor está dado la vuelta, con respecto al dibujo de las direcciones que hay más arriba. Hay que tenerlo en cuanta al hacer los giros.

                                                                  frontal

                                                                • Si la bola está en los valores 1-4: Giramos a la derecha
                                                                • Si la bola está en los valores 6-9: Giramos a la Izquierda
                                                                • Si la bola está en el 5: Avanzamos hacia ella
                                                                • Si no hay señal de IR (la salida de dirección nos devuelve 0): Permanecemos quietos

                                                                  Hemos utilizado bifurcaciones anidadas para distinguir todos los casos, y este es el resultado:

                                                                  Programa

                                                                  NOTA: Cuando la bola está en la dirección 5 (frente al robot), avanzamos hacia ella hasta que cambia de dirección, para asegurarnos de detectar los cambios lo más rápido posible.

                                                                  Detalle avance

                                                                  Aquí tenéis el vídeo del funcionamiento.

                                                                • Controlar un NXT con comandos de Voz. MAC + LeJOS

                                                                  experiment logo¿Quién no ha querido alguna vez decirle a su NXT qué debía hacer y conseguir que lo hiciera? Pues aquí tenéis un primer paso para conseguirlo. Hasta que lenguajes como ROILA sean plenamente funcionales no podremos conseguir que el robot nos entienda directamente, pero sí tenemos herramientas de reconocimiento de voz para el ordenador, y sabemos como comunicarnos remotamente con el NXT desde el ordenador, así que… con mucha paciencia y algunos trucos podremos conseguir que nuestro robot reconozca nuestras instrucciones y las obedezca.

                                                                  Ya hay algunos programas que basan el reconocimiento de voz en la diferente intensidad con la que se dicen las sílabas de la palabra clave, y utilizan el sensor de sonido (NXTPrograms).
                                                                  Incluso, hay varios programas que permiten control con voz, dos de los más relevantes son:

                                                                • LegoNXTRemote para Mac
                                                                • NXTVoiceCommander para Windows, que utiliza el software de reconocimiento de voz tazti para Windows XP/Vista

                                                                  Nosotros queremos enseñaros a programar vuestro propio control de voz en MAC, utilizando LeJOS.

                                                                  Para conseguirlo hemos utilizado el sistema de reconocimiento de voz que viene de serie en MAC OS X. Este programa nos permite, no sólo utilizar comandos predefinidos del sistema, si no crear nuestros propios comandos de voz. Estos comandos, programados en AppleScript, son capaces de ejecutar código en nuestro ordenador, vamos a utilizar esta capacidad para darle las instrucciones en el ordenador.

                                                                  Aquí tenéis un vídeo para que veáis que funciona.

                                                                  Subiremos uno con mejor calidad tan pronto como sea posible…

                                                                  Ahora vamos a lo importante… ¿Cómo funciona?

                                                                  Reconocimiento de Comandos de Voz + AppleScript + LeJOS = movimiento del robot

                                                                  Utilizamos comandos de voz personalizados que nos permiten ejecutar código, en este caso vamos a ejecutar un programa de LeJOS capaz de conectarse vía bluetooth al NXT, enviando instrucciones a los motores.

                                                                  Nuestro robot admite 5 comandos de voz:

                                                                • Adelante: Up
                                                                • Atrás: Down
                                                                • Derecha: Right
                                                                • Izquierda: Left
                                                                • Parar: Stop

                                                                  Para que el sistema de reconocimiento de voz pueda entendernos, los comandos deben estar preferiblemente en inglés, además nuestra pronunciación deberá ser razonablemente buena, o no funcionará.

                                                                  Una vez el ordenador ha reconocido el comando de voz, lanza el script con el programa de LeJOS, al cual le debemos pasar los argumentos de entrada adecuados para que realice la acción correspondiente.

                                                                  El programa de LeJOS está compilado para trabajar residente en el ordenador, el decir, no se descarga nada al NXT, sólo se le envía, por bluetooth, información sobre qué motores debe mover y cómo hacerlo, esto acelera la comunicación.

                                                                  Pasos a seguir para implementar el sistema

                                                                  1. Crear un comando de voz personalizado

                                                                  Abrimos el editor de AppleScript (o nuestro editor de código favorito) y guardamos el script en: “/Users/MyUser/Library/Speech/Speakable Items” con formato Script el nombre será el comando de voz que utilizaremos para llamar al script, así que interesa que sea breve y sencillo. Por ejemplo: “Up”
                                                                  Este script debe, al ser ejecutado, llamar al programa de LeJOS con el parametro adecuado, es algo así:


                                                                  on run
                                                                  do shell script "cd ~/lejos_nxj/bin; ./nxjpc Connecting Up"
                                                                  end run

                                                                  En la linea 2 le pedimos a la shell (no se ejecuta en terminal, no hay feedback visible de la operación) que vaya a la carpeta lejos_nxt/bin -cd ~/lejos_nxj/bin- y luego -;- que ejecute el programa Connecting con el parametro adecuado – ./nxjpc Connecting Up-.

                                                                  Haríamos lo mismo para todos los comandos que quisiéramos añadir.

                                                                  NOTA: Si quisiéramos ejecutar el comando en el Terminal, tendríamos que escribir lo siguiente:


                                                                  on run
                                                                  tell application "Terminal"
                                                                  activate
                                                                  do script "cd ~/lejos_nxj/bin; ./nxjpc Connecting Up"
                                                                  end tell
                                                                  end run

                                                                  Esto ejecutaría el comando en el clásico Terminal, por lo que podríamos ver qué pasa.
                                                                  Nótese la desaparición de shell en la línea 4.

                                                                  2. Crear un programa para enviar la información al NXT

                                                                  Nosotros hemos optado por hacerlo en LeJOS, ya que hemos estado trabajando con LeJOS en Mac durante varios artículos (Instalación LeJOS en MAC, Conexión Bluetooth en MAC, Instalación de Eclipse en MAC).

                                                                  Os recomiendo echar un vistazo al artículo sobre Manejo Remoto del NXT desde PC, ya que vamos a trabajar sobre esa misma base.

                                                                  Este es el código del programa:


                                                                  import lejos.nxt.*;
                                                                  import lejos.nxt.remote.NXTCommand;
                                                                  import lejos.pc.comm.*;

                                                                  public class Connecting {

                                                                  public static void main(String [] args) throws Exception {
                                                                  NXTConnector conn = new NXTConnector();

                                                                  if (!conn.connectTo("Mac", NXTComm.LCP)) {
                                                                  System.err.println("Conexión Fallida");
                                                                  System.exit(1);
                                                                  }
                                                                  NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());

                                                                  if (args[0].equalsIgnoreCase("Up"))
                                                                  {
                                                                  Motor.A.rotate(720,true);
                                                                  Motor.B.rotate(720);
                                                                  }
                                                                  else if (args[0].equalsIgnoreCase("Down"))
                                                                  {
                                                                  Motor.A.rotate(-360,true);
                                                                  Motor.B.rotate(-360);
                                                                  }
                                                                  else if (args[0].equalsIgnoreCase("Right"))
                                                                  {
                                                                  Motor.A.rotate(-360,true);
                                                                  Motor.B.rotate(360);
                                                                  }
                                                                  else if (args[0].equalsIgnoreCase("Left"))
                                                                  {
                                                                  Motor.A.rotate(360,true);
                                                                  Motor.B.rotate(-360);
                                                                  }
                                                                  else if (args[0].equalsIgnoreCase("Stop"))
                                                                  {
                                                                  Motor.A.setSpeed(0);
                                                                  Motor.B.setSpeed(0);
                                                                  conn.close();
                                                                  }
                                                                  }
                                                                  }

                                                                  Líneas 8-14: Nos conectamos con el dispositivo
                                                                  Líneas 6-40: En función del argumento de entrada, realizamos un movimiento u otro.
                                                                  Hemos utilizado una función de comparación de cadenas estándar de Java para comparar el argumento de entrada con nuestro valor de control (“Up”, “Down”, …)

                                                                  args[0].equalsIgnoreCase("Up")

                                                                  En este caso hemos optado por comprobar la igualdad de las cadenas ignorando la distinción mayúsculas/minúsculas.
                                                                  NOTA: Cuando utilizamos el bluetooth para comunicarnos con el NXT desde el PC/MAC, por norma general, debemos utilizar

                                                                  conn.close();

                                                                  Para asegurarnos que la conexión se cierra correctamente entre ambos dispositivos.
                                                                  Pero esto tiene un pequeño problema en un programa como este, y es que: si cerramos la conexión después de cada movimiento, perderemos tiempo de reacción al tener que iniciarla de nuevo.

                                                                  Por eso nuestro programa sólo incluye cierre de conexión con la palabra clave Stop.

                                                                  Para compilar correctamente este programa (recordemos que se ejecuta en el ordenador, no en el NXT), tendremos que hacer uso del comando:


                                                                  nxjpcc Connecting.java

                                                                  Para ejecutarlo


                                                                  nxjpc Connecting ArgumentoDeEntrada

                                                                  Esperamos que os haya gustado, y para cualquier duda, podéis utilizar el foro.

                                                                • ¿NXT como catálogo de productos?

                                                                  Artículo nº 1 de la serie de 10 artículos sobre NXT-G

                                                                    Pantalla NXTEso es exactamente lo que hace este original programa de nuestro forero CSM. Basado en el programa de la calculadora NXT-G y con mucha paciencia, ha introducido 33 referencias distintas del stock de electricBricks en su catálogo virtual, ¡y con precios actualizados!. Al introducir el código del artículo, tendremos, no sólo el precio, si no también una imagen del producto.

                                                                    Toda la programación se ha realizado en NXT-G, utilizando la versión comercial 2.0, más concretamente la herramienta de edición de imágenes que esta nueva versión lleva integrada.

                                                                    El esquema del programa es el siguiente:

                                                                    1. Presentación del catálogo
                                                                    2. Nos pide los 4 dígitos del artículo (uno a uno) y opera los cuatro dígitos independientes para obtener uno de la siguiente manera Número final= ((Cifra 1*10 + Cifra 2)*10 + Cifra 3) *10 +Cifra 4, de esta manera, el sistema nos va mostrando los números que hemos ido introduciendo en la parte superior, según los elegimos, ordenados de izquierda (el primero) a derecha (el último).
                                                                    3. Comprobación de si el artículo está (o no) en el catálogo, para esto hace uso de la estructura case que se trabajó en la calculadora.
                                                                    4. Por ultimo, tras mostrarnos el artículo, o el mensaje de error correspondiente en caso de que el artículo no exista en la Base de Datos (estructura case). Nos preguntará si queremos mirar otro artículo.

                                                                    Como podemos apreciar, el funcionamiento es muy sencillo, y la programación encapsulada en Mis Bloques hace que todo sea mucho más claro y también más fácil de modificar.

                                                                    programa

                                                                    Vamos a ver en detalle los bloques

                                                                    Bloque Presentación

                                                                    presentación

                                                                    Bloque Variables

                                                                    Variables

                                                                    Bloque IntroNum

                                                                    IntroNum

                                                                    Bloque list

                                                                    list

                                                                    Bloque otro

                                                                    otro

                                                                    Por último un vídeo del funcionamiento

                                                                    Instalación de Eclipse para LeJOS en Mac

                                                                    Artículo nº 5 de la serie de 21 artículos sobre LeJOS

                                                                      logo-Eclipse+MACDespués del tutorial de instalación de LeJOS en MAC vamos a ver cómo instalar y configurar el entorno de programación Eclipse para trabajar con LeJOS. Primero lo instalaremos y posteriormente haremos nuestro programa Hola Mundo desde Eclipse.

                                                                      1. Instalar y configurar Eclipse

                                                                      Lo primero que tenemos que hacer es ir a la página de descarga de Eclipse y bajarnos Eclipse IDE for Java EE Developers.

                                                                      Eclipse

                                                                      Como Eclipse no necesita instalación, basta con descomprimir el archivo y colocar la carpeta resultante donde queramos, por ejemplo, en la carpeta Aplicaciones de nuestro MAC.

                                                                      Para abrir la aplicación simplemente hacemos click sobre el icono de Eclipse.

                                                                      Eclipse Folder

                                                                      Lo siguiente que nos vamos a encontrar es una pantalla que nos pide el nombre de nuestro espacio de trabajo (Workspace)

                                                                      Eclipse Workspace

                                                                      Tras esto estaremos en la pantalla de bienvenida de Eclipse

                                                                      Eclipse interface

                                                                      Ahora tenemos que crear un nuevo proyecto File – New… – Project

                                                                      Create New Project

                                                                      Y seleccionamos como tipo de proyecto Java Project

                                                                      Java Project

                                                                      Ahora damos un nombre a nuestro proyecto

                                                                      New Project

                                                                      Abrimos Propiedades


                                                                      Properties

                                                                      Y tendremos que editar:

                                                                    • Java Build Path

                                                                      Build Path

                                                                      En la pestaña Libraries, seleccionamos la opción “Add External JARs”

                                                                      Debemos añadir el archivo classes.jar que se encuentra en el directorio lejos_nxj/lib
                                                                      Classes_jar

                                                                      NOTA: Con LeJOS 0.85 y Java versión 1.6 (la más actual en el momento de escribir este artículo) NO hace falta modificar nada en el apartado Java Compiler del menú Propiedades

                                                                      Si tenéis una versión anterior, se recomienda esta configuración:

                                                                      compiler

                                                                      Ahora ya tenemos los archivos necesarios configurados, así como el compilador, vamos a hacer los ajustes finales para utilizar LeJOS en Eclipse.

                                                                      Vamos a Run – External Tools – External tools Configurations…

                                                                      External tools

                                                                      Y creamos una nueva configuración con el botón “New Launch Configuration”

                                                                      Aquí vamos a crear dos configuraciones distintas, es importante fijarse bien, y poner las confiruraciones tal y como están en las imágenes, o no funcionará correctamente:

                                                                    • lejos_Compile

                                                                      LeJOS_Compile

                                                                    • lejos_Download

                                                                      LeJOS_Download

                                                                      Una vez creadas, aplicamos los cambios y cerramos la ventana.

                                                                      Por último, vamos a crear unos accesos directos para tener estas herramientas a mano cuando nos hagan falta, para ello vamos a Run – External Tools – Organize Favorites

                                                                      External Tools Favorites

                                                                      2. Nuestro primer programa: “Hola Mundo”

                                                                      Para poder hacer el programa, primero tendremos que añadir la clase correspondiente, para eso vamos a File – New – Class

                                                                      New Java Class

                                                                      Es importante que en el campo Source Folder indiquemos NombreProyecto/src, además damos a nuestra clase el nombre correspondiente HolaMundo. Debemos marcar también la opción public static void main(String[] args).

                                                                      Una vez creada la clase, tendremos un archivo HolaMundo.java dentro del desplegable NombreProyecto/src, lo abrimos y escribimos este código


                                                                      import lejos.nxt.*;
                                                                      public class HolaMundo {

                                                                      /**
                                                                      * @param args
                                                                      */
                                                                      public static void main(String[] args) {
                                                                      // TODO Auto-generated method stub
                                                                      LCD.drawString("Hola Mundo!", 2, 2);
                                                                      LCD.refresh();
                                                                      while(true);
                                                                      }
                                                                      }

                                                                      Líneas 9 y 10: aquí hemos usado en método propio de LeJOS para mostrar datos en la pantalla.

                                                                      Guardamos el archivo y le damos a nuestra recién creada configuración lejos_Compile para asegurarnos de que todo es correcto, y luego a lejos_Download para comprobar el resultado en nuestro NXT.

                                                                      Para que lejos_Download funcione, podéis enchufar el cable USB o seguir el tutorial de conexión bluetooth para MAC y dejar que se descargue directamente por bluetooth. Ambos métodos funcionan.

                                                                      Os dejo una captura de lo que muestra la consola de Eclipse al descargar por Bluetooth

                                                                      Bluetooth Download

                                                                      Si tenéis cualquier duda o problema, podéis preguntar en el foro.

                                                                    • Conectar NXT por Bluetooth a MAC

                                                                      Artículo nº 4 de la serie de 21 artículos sobre LeJOS

                                                                      Bluetooth MACMuchos usuarios han tenido problemas de conexión bluetooth entre sus ladrillos NTX y su MAC. Vamos a dar un pequeño repaso a la configuración que necesitamos para que todo funcione correctamente. Además, veremos cómo realizar la conexión desde NXT-G y desde LeJOS (instalación de LeJOS en MAC).

                                                                      Emparejamiento

                                                                      Lo primero que debemos hacer es emparejar el NXT con el MAC. Para que todo funcione correctamente, os recomendamos que sea el ordenador el que realice la búsqueda y posterior emparejamiento.

                                                                      Primero vamos al menú “Preferencias de Bluetooth”


                                                                      Bluetooth Preferences

                                                                      ¡IMPORTANTE! Antes de continuar, os recomendamos que borréis todos los NXT que hayan sido emparejados previamente con el ordenador.

                                                                      Nos aseguramos de que nuestro NXT tiene el bluetooth encendido y en modo visible.
                                                                      Seleccionamos “Añadir nuevo dispositivo”, el símbolo + abajo a la izquierda.

                                                                      New device

                                                                      Cuando el MAC detecte nuestro NXT, lo seleccionamos y le damos a continuar

                                                                      Bluetooth Setup Assistant

                                                                      Nos aparecerá la pantalla de emparejamiento, por defecto, la clave de emparejamiento bluetooth de nuestro MAC es 0000

                                                                      passkey

                                                                      Debemos estar atentos a nuestro NXT, pues nos pedirá que confirmemos la clave.
                                                                      Aquí tenemos 2 opciones:

                                                                      1. Cambiar la clave por defecto del NXT: 1234, por la de nuestro MAC: 0000.
                                                                      2. Confirmar la clave 1234, y cuando el ordenador nos devuelva error, cambiamos la clave del MAC (eligiendo, lógicamente 1234)

                                                                      Si todo ha ido correctamente, en unos segundos deberíamos tener una pantalla como esta

                                                                      Pairing succesful

                                                                      Ahora, para comprobar que el proceso ha finalizado de forma correcta, volvemos a “Preferencias de Bluetooth” y comprobamos la configuración de puertos del nuevo dispositivo

                                                                      Edit Serial Ports

                                                                      Tenemos que tener exactamente esta configuración

                                                                      Port Config

                                                                      Si no es así, la cambiamos.

                                                                      Con esto finaliza el proceso de emparejamiento, que no es más que hacer que los dispositivos se reconozcan entre si, ahora vamos a conectarnos al NXT.

                                                                      Conectando al NXT por bluetooth con NXT-G

                                                                      La conexión desde NXT-G es muy intuitiva, sólo debemos tener el cuenta que el NXT no debe estar conectado previamente con el MAC (sí emparejado, no confundir los dos términos).

                                                                      Seleccionamos la opción Visualizar NXT en el menú de la parte inferior derecha


                                                                      NXT

                                                                      Cuando se nos abra la ventana, le damos a Buscar


                                                                      Screen data

                                                                      Entonces nos saltará una ventana de sistema que nos preguntará qué dispositivo queremos añadir a la lista de conexiones de NXT-G

                                                                      select device

                                                                      Buscamos el nuestro en la lista y le damos a Seleccionar. Aquí es donde la búsqueda difiere de Windows, si recordáis, Windows nos muestra una lista de todos los que encuentra disponibles, sin embargo MAC sólo nos muestra los que le indiquemos.

                                                                      Una vez seleccionado, pasará a nuestra lista de NXT-G, donde le damos a Conectar (si está Disponible, claro).

                                                                      connect

                                                                      En unos segundos, si todo ha ido bien, tendremos esta pantalla

                                                                      connected

                                                                      Conectando al NXT por bluetooth con LeJOS

                                                                      Si no tenéis LeJOS y queréis usarlo en vuestro MAC, podéis seguir el tutorial de instalación de LeJOS en MAC.

                                                                      Por defecto, la versión 0.85 de LeJOS para MAC lleva incluida la librería BlueCove 2.1.0, que nos permitirá conectarnos por bluetooth a nuestro dispositivo sin problemas.

                                                                      Abrimos la terminal y tecleamos

                                                                      cd lejos_nxj/bin
                                                                      (nosotros lo tenemos instalado en Home)

                                                                      Con esto estamos dentro de la carpeta de ejecutables de LeJOS.
                                                                      Ahora, el comando

                                                                      ./nxjconsoleviewer

                                                                      Commands

                                                                      Esta utilidad te permite monitorizar las conexiones y la salida de consola del NXT.

                                                                      Seleccionamos como tipo de conexión Bluetooth, introducimos el nombre del NXT (el nuestro se llama Mac) y le damos a Connect. El campo Address se puede dejar en blanco, realizará la búsqueda por nombre.

                                                                      nxjviewer

                                                                      Puede que te pida la clave de emparejamiento (el NXT tiene por defecto 1234), introdúcela y dale a Emparejar (Pair).

                                                                      Passcode

                                                                      Tras unos segundos, nos aparecerá esta pantalla indicando que todo ha ido bien.

                                                                      Successful

                                                                      Si esta utilidad no os convence, o no funciona correctamente, podéis probar con el comando

                                                                      ./nxjbrowse -b

                                                                      Terminal

                                                                      -b fuerza a la aplicación a realizar una búsqueda de dispositivos bluetooth (-u es lo mismo pero para USB).
                                                                      Debería localizarlo correctamente, después, simplemente le damos a Connect


                                                                      nxj browse

                                                                      Esta otra aplicación nos permite, no sólo conectarnos al NXT, si no también administrar sus archivos, cargar y descargar programas o incluso ejecutarlos, cambiarle el nombre al ladrillo, etc…

                                                                      nxjBrowse success

                                                                      Con esto terminamos el tutorial de conexión Bluetooth, si tenéis cualquier duda o problema, podéis acudir al foro.

                                                                      Sistemas holonómicos

                                                                        Una de las posibles formas de clasificar los robots es distinguiendo si son holonómicos o no. Es una distinción que está relacionada con su movilidad. Simplificando, podemos decir que un robot es holonómico si es capaz de modificar su dirección instantáneamente (en esta consideración se considera masa nula), y sin necesidad de rotar previmente. Un vehículo con un sistema de dirección como el de un coche, por ejemplo, no lo es, porque para poder desplazarse en el sentido lateral tiene que realizar varias maniobras previas. Del mismo modo, un robot con 2 ruedas es no-holonómico ya que no puede moverse hacia la izquierda o la derecha. Siempre lo hace hacia delante en la dirección definida por la velocidad de sus ruedas.

                                                                        El Dr. Masaaki Kumagai, director del Robot Development Engineering Laboratory de la Universidad de Tohoku Gakuin en Japón ideó un robot que se desplazaba sobre una esfera mediante el uso de ruedas omnidireccionales. El siguiente vídeo abre las puertas a un sinfín de posibles aplicaciones como la ayuda para el transporte de objetos pesados mediante uno o varios de estos robots en sistemas colaborativos.

                                                                        Grados de libertad
                                                                        Se dice que el número de grados de libertad (GDL) de un robot es el número de magnitudes que pueden variarse independientemente. En un brazo robotizado cada articulación proporciona habitualmente un grado de libertad. Si queremos posicionar un manipulador en cualquier punto del espacio en cualquier orientación necesitamos 6 grados de libertad, 3 para la posición y 3 para la orientación del manipulador. En el caso de un vehículo que se desplaza en un plano, estamos hablando de 3 grados de libertad: 2 para la posición y 1 para la orientación.

                                                                        El control está relacionado con los de grados de libertad. En general cada grado de libertad requiere de un actuador. Si disponemos de un actuador por cada GDL, diremos que todos los GDLs son controlables. Habitualmente existe un actuador por grado de libertad, pero no siempre es así.

                                                                        Diremos que un robot es holonómico si tiene los mismos grados de libertad efectivos que controlables. En general, un robot no holonómico tiene menos grados de libertad controlables que número total de grados de libertad, mientras que si sucede lo contrario, el robot es redundante. En general, a mayor diferencia entre grados de libertad controlables y grados totales, más difícil será controlar el robot.

                                                                        En el ejemplo del coche, decimos que es no holonómico porque tiene 3 GDLs y sólo se controlan dos. Normalmente cuando montamos un coche de LEGO trabajamos con 2 motores: uno para tracción y otro para dirección. De ahí que sea tan difícil controlarlo (no podemos aparcar lateralmente).

                                                                        Vehículos omnidireccionales
                                                                        ¿Cómo se construye un sistema capaz de desplazarse omnidireccionalmente? Aunque comercialmente no existen vehículos de estas características, empresas como Mitsubishi están trabajando en prototipos como el MMR25, un concepto muy adelantado a fecha de hoy que emplea ruedas omnidireccionales, del que podeis ver las siguientes fotos a continuación. Para un conductor como nosotros, habituados a un sistema de dirección tradicional, el controlar un vehículo de estas características encierra nuevos problemas que no existen en los vehículos actuales, como la necesidad de disponer de un sistema de visión también omnidireccional (1).

                                                                        Mitsubishi MMR25

                                                                        Mitsubishi MMR25

                                                                        Con una autonomía de 1 hora y una velocidad máxima de 6km/h, el Honda U3-X es un medio de transporte para distancias cortas. El vídeo comercial de Honda explica cómo el U3-X es capaz de desplazarse en cualquier dirección mediante el uso de una gran rueda omnidireccional.

                                                                        Si bien la idea como vehículo personal de transporte queda lejana a título particular, el sistema de tracción omnidireccional ya existe y son muchos los robots que lo integran. La principal ventaja de un sistema omnidireccional es su maniobrabilidad, pero esto complica tanto el diseño mecánico como el control. Una forma de conseguir este tipo de tracción/dirección es mediante el uso de ruedas omnidireccionales.

                                                                        La forma más simple de conseguir una rueda omnidireccional es haciendo uso de una esfera. Pero esta idea simple encierra problemas tanto desde el punto de vista mecánico como algorítmico. En el siguiente vídeo de Tomás Arribas podemos ver cómo se resuelve el problema del péndulo invertido sobre una esfera haciendo uso de esta idea. Se emplean dos giróscopos para tener noción de la velocidad de rotación en cada uno de los ejes y de dos motores para controlar el giro del robot sobre la esfera.

                                                                        Las ruedas omnidireccionales no son una sola rueda en realidad, sino que constan de varias. Sobre la periferia de una gran rueda central se montan una serie de pequeñas ruedas: Rueda omnidireccional de Tetrixmientras que la rueda central se comporta como una rueda tradicional, las periféricas rotan perpendicularmente. Las ruedas holonómicas tienen dos grados de libertad. Existen variedad de ruedas omnidireccionales posibles en el mercado. LEGO no fabrica una rueda de este tipo, por lo que si necesitamos una tendremos que construirla o hacer uso de la versión metálica de Tetrix.

                                                                        Los sistemas dotados de ruedas omnidireccionales habitualmente montan 3 ó 4 de estas ruedas. La principal ventaja de los sistemas de 3 ruedas es que reducen el coste, no sólo de la propia rueda -estas ruedas son relativamente caras- sino por la reducción de la mecánica asociada. Se trata de un sistema con tres puntos de apoyo, por lo que sus tres puntos siempre están en contacto con el suelo, cosa que no tiene porqué ocurrir en un sistema de 4 ruedas. Sin embargo, el control de velocidad de cada una de las tres ruedas es totalmente independiente, lo que obliga a una mayor capacidad de cálculo frente a un sistema de 4 ruedas, en el que las velocidades de las ruedas pueden relacionarse por pares de manera muy simple. La capacidad de cálculo puede ser un factor limitador en sistemas basados en micros pequeños. Uno de los montajes típicos en tres ruedas es la denominada plataforma de Killough, que será motivo de otro artículo.

                                                                        El siguiente modelo fue construido por Xander para el Lego World 2009.

                                                                        Rueda omnidireccional, por Xander

                                                                        Las ruedas omnidireccionales también han sido construidas mediante la unión de una gran cantidad de pequeñas ruedas de LEGO.

                                                                        Robot NXT omnidireccional, por Xander

                                                                        El robot se controlaba mediante un mando Power Functions y un sensor de enlace IR que recibía dichas órdenes y se las transmitía al NXT:

                                                                        Una de la ventajas de un sistema de dirección holonómico frente al sistema de dirección tradicional es que su control es mucho más intuitivo. Dado que el vehículo holonómico puede desplazarse en cualquier dirección, si éste no es autónomo y debe ser controlado con un joystick, por ejemplo, el control del mismo puede ser inmediato: la dirección de avance será la del joystick, y la velocidad podría ser proporcional a la inclinación del mismo. En algunos casos puede ser incluso necesario controlar el ángulo de giro del robot, es decir, su rotación. Esto puede ser inmediato en el caso de que el joystick permita el giro, en caso negativo el control de rotación tendrá que ser implementado mediante un sistema auxiliar o un segundo joystick. Si bien los conceptos anteriores son muy sencillos, su implementación no lo es tanto, y la dificultad se acrecienta cuando necesitamos controlar no sólo la dirección de avance sino también la longitud exacta de traslación. La dificultad del proceso radica en que la traslación mediante ruedas omnidireccionales se basa en el deslizamiento de las mismas, y la medida de este deslizamiento depende de un gran número de factores, entre los que se encuentran los materiales de las propias ruedas, el coeficiente de rozamiento con el material sobre el que se desplazan, el ángulo de desplazamiento, velocidad, peso del robot y su distribución sobre las ruedas… estos motivos obligan muchas veces a cálculos empíricos.

                                                                        Información adicional:

                                                                        LEGO NXT AirScooter

                                                                          hoverHay muchos vehículos que hacen uso de las hélices para desplazarse, como aviones o helicópteros, pero pocos resultan tan llamativos y polivalentes como un hovercraft (aero deslizador en español). Estos vehículos suelen ser capaces de desplazarse a velocidades relativamente altas tanto en tierra como por el agua.

                                                                          Generalmente lo que todos asociamos con un aero deslizador es algo parecido a esto


                                                                          184hovercraft

                                                                          O a esto


                                                                          hovercraft rc 400

                                                                          Que nos dejan vídeos tan impresionantes como este:

                                                                          LEGO también sacó una versión de hovercraft de bomberos, el set 7944, que vio la luz en 2007.

                                                                          7944

                                                                          Partiendo de esta genial idea de mover un vehículo con la fuerza de unas hélices, nos hemos lanzado a construir nuestro propio aero deslizador. Este modelo, construido por nuestro forero CSM, está basado en el modelo de Air Scooter de leggor, que podemos ver a continuación

                                                                          airscooter1

                                                                          En el modelo de CSM, el air scooter está totalmente realizado con technic y NXT, tiene sólo dos palas y el giro se realiza desde la parte trasera.

                                                                          Frontal

                                                                          _7120138

                                                                          Trasera

                                                                          _7120135

                                                                          Lateral

                                                                          _7120137

                                                                          Aquí un vídeo para que lo veáis en marcha

                                                                          Semáforo NXT

                                                                          detalle luces El pasado sábado 26 de Junio tuvo lugar la primera competición de karts de electricBricks, en una reproducción a escala del circuito de Mónaco. Como no podía ser de otra manera, este circuito requería de un semáforo a su altura, y lo hicimos.

                                                                          Frontal

                                                                          Se trata de un semáforo controlado por un NXT con un sencillo programa en NXT-G. Intentamos hacerlo lo más fiel posible a los reales (las dos luces rojas se mantienen encendidas hasta que se enciende la verde), pero las limitaciones de consumo (hay 12 bombillas) nos obligaron a controlar el tiempo de encendido de las luces.

                                                                          Programa

                                                                          La forma de controlar la duración de encendido de las luces depende del tiempo durante el cual se emite el sonido (tenemos marcado “Esperar hasta finalización”).
                                                                          El proceso es el siguiente:

                                                                        • Encendemos la bombilla
                                                                        • Emitimos un sonido con una duración de un segundo (esperando hasta finalización)
                                                                        • Apagamos la bombilla
                                                                        • Esperamos un segundo
                                                                        • Se repite el proceso …

                                                                          La estructura une ladrillo técnico con vigas, y os puedo asegurar que hicieron falta muchos pines para montar este semáforo colgante y que fuera lo suficientemente estable y sólido, no sólo para soportar el peso del NXT y mantenerse en pie, si no también para aguantar posibles embestidas de vehículos descontrolados, no podemos olvidar que se diseñó para una carrera.

                                                                          Aquí podemos ver con detalle la estructura

                                                                          Lateral

                                                                          detalle lateral

                                                                          Detalle de la zona central y luces

                                                                          detalle luces

                                                                          Un pequeño vídeo del funcionamiento (hay muchas más muestras en los vídeos de la carrera)

                                                                        • Kart NXT controlado remotamente con LeJOS (II)

                                                                          Con mandoEn este artículo voy presentar una mejora del programa que ya puse en el artículo Kart NXT controlado remotamente con LeJOS. Lo que se plantea en este artículo es conseguir un Kart NXT radio-controlado mediante otro NXT (un mando) que funcione eficientemente (tenga retardos factibles, y sea manejable). El de este artículo fue el modelo final que se usó en la competición de Karts del domingo, y el encargado de llevar la cámara (un Iphone 3G).

                                                                          El problema principal radica en conseguir un modelo radiocontrol en NXT que pueda competir contra Karts creados con motores y componentes Power Function. En primer lugar estos Karts pesarán menos de base, debido al menor peso de sus motores, y a que no necesitan un ladrillo NXT. También tienen un manejo mucho más sencillo, debido a su sistema de control mediante mando de infrarrojos. La única ventaja con la que partimos es que la comunicación Bluetooth tiene un mayor alcance y además no se ve influida por la luz solar, la cuál interfiere mucho en las comunicaciones mediante infrarrojos.

                                                                          Con mando

                                                                          El problema de la latencia:

                                                                          Uno de los problemas que tenía el programa anterior era el de las latencias. Debido a la necesidad de enviar suficientemente lento para que el receptor sea capaz de procesar los datos, necesitamos añadir un retardo en el emisor. Este retardo produce, entre otras cosas, que haya una latencia notable entre que nosotros movemos el mando y el kart actúa en consecuencia. Hay varias formas de mitigar esto:

                                                                          - Reducir el tiempo que se tardan en procesar los datos.

                                                                          - Ajustar al máximo el retardo a ese tiempo de procesamiento.

                                                                          - Enviar datos cuando sea estrictamente necesario.

                                                                          En primer lugar es muy importante que reduzcamos el tiempo que se tardan en procesar los datos. Por ejemplo en el programa del artículo anterior tardábamos mucho en procesar el giro ya que el método rotateTo de la clase Navigator no devuelve el control hasta que el motor haya girado al ángulo determinado. Para eliminar todo este tiempo lo que hacemos es usar la función rotateTo que devuelve el control (ver código del Kart, línea 55). De este modo podremos seguir recibiendo datos tranquilamente mientras rota, y si recibe otra orden de girar, lo que hacemos es parar los motores, y volver a ejecutar el rotateTo con el nuevo grado objetivo y en modo devolver control.

                                                                          Otra cosa que ayuda a reducir el tiempo de procesamiento en el Kart es procesar la mayor parte posible de los datos en el mando antes de enviarlos, de manera que solo hagamos en el Kart lo que no podríamos hacer en el mando.

                                                                          Ajustar el retardo en función de el tiempo de procesamiento es una tarea complicada, ya que físicamente es imposible medirlo, por lo que se consigue mediante prueba de ensayo y error.

                                                                          El último punto es muy importante. Si enviamos datos irrelevantes, y el Kart gasta tiempo procesandolos, perdemos ese tiempo que podríamos estar usandolo para algo realmente necesario o importante. Por tanto el programa del mando se tiene que encargar de enviar información cuando sea estrictamente necesario. Por ejemplo ya habíamos visto que el hecho de enviar constantemente información de que uno de los tacómetros del mando marca 0 grados no tiene sentido. Se ha de mandar información cuando haya cambios relevantes en el mando, como que hallamos movido un motor más de X grados. Por supuesto esto puede crear imprecisión en el mando, por lo que debemos tener en cuenta en que casos nos interesa mayor fineza a la hora de manejar el robot, y que otros casos podemos prescindir de contar con tanta precisión.

                                                                          En el programa del mando podéis ver como se envía información del giro con mayor o menor frecuencia dependiendo del grado al que queramos rotar (líneas 61 a 81).

                                                                          Ajustando valores:

                                                                          Un factor a tener en cuenta es la relación entre cuantos grados giremos los motores de nuestro mando, y cuanta aceleración o giro vamos a dar al Kart. El caso de girar la dirección es especialmente delicado, ya que necesitaremos máxima precisión para poder conducir sin chocarnos o salir constantemente de la pista. En este caso además tenemos que contar con que la dirección no puede girar más de un número determinado de grados o podría romperse. Por eso buscamos un límite de grados, y lo hacemos corresponder con el máximo de grados que queremos girar el volante de nuestro mandos.

                                                                          Debido a las marchas reductoras la conversión entre giro de mando y de dirección no es exacta, pero lo importante es que el mando sea cómodo. Por eso un limite de 90 grados al girar el mando ya debería dar como resultado el giro máximo de la suspensión, y en un giro de entre 1 y 45 grados deberíamos poder manejar al kart para que realice la mayoría de curvas del circuito.

                                                                          En el programa se puede ver como tenemos una mayor precisión para los giros más cortos (de la linea 61 a la 65), y está precisión va disminuyendo progresivamente cuanto más giremos el mando (líneas 66 a 81).

                                                                          Por supuesto este escalado de precisión se debe aplicar también a la velocidad, como se ve en la línea 90, donde aplicamos un cubo para el cálculo de velocidad, lo que hará que los primeros grados tengan un control preciso de velocidades bajas, mientras que cuando giremos mucho el control el coche saldrá disparado.

                                                                          Un mando manejable:

                                                                          La forma física del mando es importante también. El kart se hacía muy difícil de manejar con el joystick, así que creamos un nuevo mando con el siguiente aspecto:

                                                                          IMG_2658

                                                                          Como véis cuenta con un gatillo para acelerar, un pequeño volante para girar, y un botón de freno que usaremos en casos de emergencia. Es un modelo mejorable, pero es mucho más manejable. Uno de los problemas principales es su peso, pero éste es inherente a su naturaleza NXT.

                                                                          Programa final:

                                                                          El código a sido bastante modificado con respecto a del artículo anterior. Se han tratado de incluir las mejoras comentadas en los apartados anteriores, para mejorar sobretodo el control. Respecto a la parte física se ha cambiado la transmisión.

                                                                          En conjunto se ha conseguido un retardo muy bajo de respuesta (entre 150 y 200 ms aprox), muy similar al que tienen los mandos de infrarrojos, así que en este apartado se ha conseguido alcanzar a Power Functions. El manejo es mucho más complejo, ya que nos permite girar los grados que queramos la dirección, y no un giro fijo como el que se consigue con el sistema de gomas usado por los otros karts en la competición. Respecto al peso sigue estando en desventaja, y es imposible mejorar este aspecto. Además al ser el coche cámara de la competición lo que aumenta su peso en más de 150 gramos, haciendo que fuera el único kart de la competición en superar el kilogramo de peso.

                                                                          Aquí os dejo el código del mando:

                                                                          import lejos.nxt.Button;
                                                                          import lejos.nxt.LCD;
                                                                          import lejos.nxt.Motor;
                                                                          import lejos.nxt.SensorPort;
                                                                          import lejos.nxt.TouchSensor;
                                                                          import lejos.nxt.comm.BTConnection;
                                                                          import lejos.nxt.comm.Bluetooth;
                                                                          import java.io.*;
                                                                          import javax.bluetooth.*;
                                                                          import java.lang.Math;

                                                                          public class Mando {

                                                                          /**
                                                                          * @param args
                                                                          */
                                                                          public static void main(String[] args) throws Exception {
                                                                          // TODO Auto-generated method stub
                                                                          String nombre = "NXT1";
                                                                          LCD.drawString("Conectando...", 2, 1);
                                                                          TouchSensor contacto = new TouchSensor(SensorPort.S1);
                                                                          LCD.refresh();
                                                                          int ant_gir = 0;
                                                                          int ant = 0;

                                                                          RemoteDevice bt = Bluetooth.getKnownDevice(nombre);

                                                                          if (bt == null){
                                                                          LCD.clear();
                                                                          LCD.drawString("No existe ese dispositivo", 0, 1);
                                                                          Thread.sleep(2000);
                                                                          System.exit(1);
                                                                          }

                                                                          BTConnection btc = Bluetooth.connect(bt);

                                                                          if (btc == null){
                                                                          LCD.clear();
                                                                          LCD.drawString("Conexión fallida", 1, 1);
                                                                          Thread.sleep(2000);
                                                                          System.exit(1);
                                                                          }

                                                                          LCD.clear();
                                                                          LCD.drawString("Conectado", 2, 1);
                                                                          Thread.sleep(2000);

                                                                          DataOutputStream dos = btc.openDataOutputStream();
                                                                          LCD.clear();
                                                                          LCD.drawString("Mando en funcionamiento", 2, 1);
                                                                          while(!Button.ESCAPE.isPressed()){
                                                                          Thread.sleep(50);
                                                                          //Giro
                                                                          int i = Motor.B.getTachoCount();
                                                                          if (contacto.isPressed()){
                                                                          dos.writeInt(0);
                                                                          dos.flush();
                                                                          Thread.sleep(500);
                                                                          }
                                                                          if (Math.abs(i) < 30){
                                                                          if ((i > (ant_gir)) || (i < (ant_gir))){
                                                                          dos.writeInt(4*i);
                                                                          dos.flush();
                                                                          }
                                                                          }else if (Math.abs(i) < 60){
                                                                          if ((i > (ant_gir + 1)) || (i < (ant_gir - 1))){
                                                                          dos.writeInt(8*i);
                                                                          dos.flush();
                                                                          }
                                                                          }else if (Math.abs(i) < 90){
                                                                          if ((i > (ant_gir + 2)) || (i < (ant_gir - 2))){
                                                                          dos.writeInt(16*i);
                                                                          dos.flush();
                                                                          }
                                                                          }else{
                                                                          if ((i > (ant_gir + 3)) || (i < (ant_gir - 3))){
                                                                          dos.writeInt(32*i);
                                                                          dos.flush();
                                                                          }
                                                                          }

                                                                          ant_gir = i;

                                                                          //Aceleración
                                                                          int j = Motor.C.getTachoCount();

                                                                          if ((j > (ant)) || (j < (ant))){
                                                                          float aux = j/10;
                                                                          j = (int) (aux * Math.abs(aux)*Math.abs(aux));
                                                                          dos.writeInt(j + 1000);
                                                                          dos.flush();
                                                                          }
                                                                          ant = j;
                                                                          }

                                                                          LCD.clear();
                                                                          LCD.drawString("Cerrando conexion", 0, 1);
                                                                          dos.close();
                                                                          btc.close();

                                                                          LCD.clear();
                                                                          LCD.drawString("Finalizado", 0, 1);
                                                                          Thread.sleep(2000);
                                                                          }
                                                                          }

                                                                          Y el del kart:

                                                                          import lejos.nxt.Motor;
                                                                          import lejos.robotics.navigation.Pilot;
                                                                          import lejos.robotics.navigation.TachoPilot;
                                                                          import lejos.nxt.Button;
                                                                          import lejos.nxt.LCD;
                                                                          import lejos.nxt.comm.BTConnection;
                                                                          import lejos.nxt.comm.Bluetooth;
                                                                          import java.io.*;
                                                                          import java.lang.Math;

                                                                          public class Kart {

                                                                          /**
                                                                          * @param args
                                                                          */
                                                                          public static void main(String[] args) throws Exception{
                                                                          // TODO Auto-generated method stub
                                                                          Pilot navigator = new TachoPilot(6.8f, 17.8f, Motor.A, Motor.C, true);
                                                                          int limite = 120;
                                                                          LCD.clear();
                                                                          LCD.drawString("Esperando...", 2, 1);
                                                                          LCD.refresh();

                                                                          BTConnection btc = Bluetooth.waitForConnection();

                                                                          LCD.clear();
                                                                          LCD.drawString("Conectado", 2, 1);
                                                                          Motor.B.setPower(100);
                                                                          Motor.A.setPower(100);
                                                                          Motor.C.setPower(100);
                                                                          DataInputStream dis = btc.openDataInputStream();

                                                                          while(!Button.ESCAPE.isPressed()){
                                                                          int i = dis.readInt();
                                                                          if (i == 0){
                                                                          navigator.stop();
                                                                          }else{
                                                                          if (i > 1000){
                                                                          //Acelerar
                                                                          navigator.forward();
                                                                          navigator.setMoveSpeed((i - 1000));

                                                                          }else if (i > 800){
                                                                          navigator.backward();
                                                                          navigator.setMoveSpeed(Math.abs(i - 1000));
                                                                          }else{
                                                                          //Girar
                                                                          Motor.B.stop();
                                                                          boolean signo;
                                                                          if (i >= 0)
                                                                          signo = true;
                                                                          else
                                                                          signo = false;
                                                                          if (Math.abs(i) < limite){
                                                                          Motor.B.rotateTo(-i, true);
                                                                          }else{
                                                                          if (signo)
                                                                          Motor.B.rotateTo(-limite, true);
                                                                          else
                                                                          Motor.B.rotateTo(limite, true);
                                                                          }
                                                                          }
                                                                          }
                                                                          }
                                                                          Thread.sleep(2000);
                                                                          LCD.clear();
                                                                          LCD.drawString("Cerrando conexion", 0, 1);
                                                                          dis.close();
                                                                          btc.close();
                                                                          Thread.sleep(2000);
                                                                          }
                                                                          }

                                                                          Como podéis comprobar están los cambios comentados en el artículo, y el resultado ha sido bastante bueno. El único problema es que el manejo es algo complicado y se necesita práctica para conducirlo. Os recomiendo que miréis el artículo sobre la carrera. Finalmente deciros como siempre que si tenéis alguna duda podéis escribir vuestras dudas en el foro.

                                                                          Manejo de tareas en LeJOS: clase Arbitrator

                                                                          Artículo nº 18 de la serie de 21 artículos sobre LeJOS

                                                                            arbitroEn el los artículos Control de tareas en RobotC y Control de prioridades de tareas en RobotC vimos como manejar tareas en RobotC. En este artículo vamos a ver el manejo de tareas en LeJOS. La mejor forma de manejar las tareas y sus prioridades en LeJOS es mediante la clase Arbitrator. Este método consiste en crear unas tareas denominadas comportamientos, no muy complejas, que en su conjunto crearán el programa que deseemos implementar.

                                                                            Modelo de programacion basado en comportamientos:

                                                                            Los programas que hemos escrito en nuestro robot presentan la apariencia habitual de un modelo de programacion estructurado. El flujo de ejecucion comienza en un punto y se van sucediendo instrucciones que modi can los actuadores de nuestro robot. En artículos anteriores hemos aprendido a crear nuevos threads y permitir que haya varios flujos de ejecucion en paralelo. A medida que la dificultad de la tarea a realizar aumenta, el codigo se vuelve cada vez mas complejo y difícil de escalar.

                                                                            Un modelo de programacion basado en comportamientos permite simpli car el diseño de nuestros programas. Ahora nuestro codigo estara compuesto de varios comportamientos. Los comportamientos son tasks (tareas) relativamente simples e independientes, es decir, no necesitan de otros comportamientos para su ejecucion: Avanzar, esquivar obstaculo, coger pelota, rotar 360 grados, encender leds, etc. Una vez de finidos los comportamientos tendremos que establecer una política de activación de los mismos. Por ejemplo podríamos tener un automata de estados finitos que activara uno o varios comportamientos según las necesidades del robot.

                                                                            Otra alternativa es utilizar un arbitro que se encargue de elegir el comportamiento mas adecuado para cada momento en funcion de algun criterio. LeJOS dispone de un paquete llamado lejos.subsumption que permite programar nuestra aplicacion bajo este paradigma. Cada uno de nuestros comportamientos debera escribirse en una clase Java e implementar el interfaz Behavior. Este interfaz obliga a implementar tres métodos:

                                                                            public boolean takeControl(): Metodo que indicará si este comportamiento deberá activarse en este momento o no. Por ejemplo, si nuestro comportamiento se encarga de esquivar un obstaculo, su funcion takeControl() deberá devolver true cuando haya un obstaculo cerca del robot.

                                                                            public void action(): Método que se ejecutará cuando se active el comportamiento.Por tanto deberá contener todas las instrucciones que desempeñaran la labor para la que ha sido creado el comportamiento.

                                                                            public void suppress(): Metodo que se ejecutara cuando se desactive el comportamiento. Si es necesario devolver el robot a un determinado estado, aquí sera el sitio para hacerlo. Un comportamiento será desactivado cuando otro comportamiento tome el control en su lugar.

                                                                            Clase Arbitrator y prioridades de los comportamientos:

                                                                            Una vez creados nuestros comportamientos deberemos crear un objeto de tipo Arbitrator, que se encargará de activar y desactivar los comportamientos. El constructor de Arbitrator acepta como parametro un array de comportamientos (en el programa de ejemplo podéis como hacerlo). Todos estos comportamientos serán los candidatos a activarse durante la ejecución del programa.

                                                                            Cuando se ejecute el método start() de nuestro objeto de tipo Arbitrator se pondrá en marcha el arbitro que activa la ejecucion de los comportamientos. La poltica que se utiliza es una poltica de prioridades (tasks con prioridades). Tienen mayor prioridad aquellos comportamientos cuyo índice del array sea mayor. El arbitro recorrera el array de comportamientos desde el fi nal hasta el comportamiento que ocupe la posicion 0 y ejecutará el metodo takeControl() de cada comportamiento. Si alguno de esos metodos devuelve true, se ejecutará una iteracion de ese comportamiento llamando a su metodo action().

                                                                            Este arbitro se encargará contnuamente de evaluar que comportamiento es el que hay que ejecutar. En caso de que otro comportamiento distinto al actual tenga que ser ejecutado, se llamará el método supress() del comportamiento actual y despues el método action() del nuevo comportamiento.

                                                                            Programa de ejemplo:

                                                                            Como programa de ejemplo hemos puesto un clásico, el Bump&Go. Es un ejemplo sencillo, así que podréis comprender mejor como funciona la clase Arbitrator. En este caso tenemos dos comportamientos: Avanza y Esquiva. Uno se encargará de ir en línea recta, y el otro de esquivar cuando detecte un obstáculo. Por último el Arbitro se encargará de gestionarlo todo.

                                                                            Bump&Go2

                                                                            Cada comportamiento va en una clase distinta, y el Arbitro también, por lo que tendremos tres archivos (con una clase cada uno). Uno será el de la clase Avanza:

                                                                            import lejos.robotics.subsumption.*;
                                                                            import lejos.nxt.*;

                                                                            public class Avanza implements Behavior {

                                                                            public boolean takeControl() {
                                                                            return true;
                                                                            }

                                                                            public void suppress() {
                                                                            Motor.A.stop();
                                                                            Motor.C.stop();
                                                                            }

                                                                            public void action() {
                                                                            Motor.A.forward();
                                                                            Motor.C.forward();
                                                                            }
                                                                            }

                                                                            Otro el de la clase Esquiva:

                                                                            import lejos.robotics.subsumption.*;
                                                                            import lejos.nxt.*;

                                                                            public class Esquiva implements Behavior {

                                                                            public TouchSensor touch = new TouchSensor(SensorPort.S1);

                                                                            public boolean takeControl() {
                                                                            return touch.isPressed();
                                                                            }

                                                                            public void suppress() {
                                                                            Motor.A.stop();
                                                                            Motor.C.stop();
                                                                            }

                                                                            public void action() {
                                                                            Motor.A.backward();
                                                                            Motor.C.backward();
                                                                            try{
                                                                            Thread.sleep(500);
                                                                            }catch(Exception e) {}

                                                                            Motor.A.stop();
                                                                            try{
                                                                            Thread.sleep(500);
                                                                            }catch(Exception e) {}
                                                                            Motor.C.stop();
                                                                            }
                                                                            }

                                                                            Y finalmente el de la clase Bump&Go, que es el arbitro:

                                                                            import lejos.robotics.subsumption.*;

                                                                            public class BumpAndGo {

                                                                            public static void main(String [] args) {
                                                                            Behavior b1 = new Avanza();
                                                                            Behavior b2 = new Esquiva();
                                                                            Behavior [] bArray = {b1, b2};
                                                                            Arbitrator arby = new Arbitrator(bArray);
                                                                            arby.start();
                                                                            }
                                                                            }

                                                                            Esta es una manera muy cómoda de gestionar tareas, amén de que facilita mucho la programación de programas complejos dividiéndolos en tareas. Si tenéis alguna duda, os recomiendo que os paséis por el foro y preguntéis.

                                                                            Calculadora completa en NXT-G

                                                                            Artículo nº 7 de la serie de 10 artículos sobre NXT-G

                                                                              CalcYa vimos cómo hacer una calculadora en RobotC, hoy queremos enseñaros a programar vuestra propia calculadora en NXT-G. Vamos a hacerlo de forma modular, es decir, haremos usos de “Mi bloque” para cada una de las funciones de la calculadora. Lo primero que debemos hacer es comprobar en cuántas funciones queremos dividir el trabajo, nosotros hemos utilizado 6.

                                                                              Este es el programa completo

                                                                              Programa

                                                                              Como podéis ver leyendo los nombres de los bloques y con algún comentario adicional, podemos hacernos una idea muy clara de lo que hará nuestra calculadora.
                                                                              Hay que tener en cuenta que, para que los bloques manejen la información correctamente, y sobre todo, sean capaces de intercambiar información unos con otros, debemos hacer que todas las variables utilizadas estén declaradas, no sólo dentro del bloque correspondiente, si no también en el programa principal, os recomiendo la lectura del tutorial de Variables y constantes.

                                                                              Para poder hacer el programa lo más reutilizable posible, hemos separado 6 funciones:

                                                                            • Inicializar variables: CalcInicia
                                                                            • CalcInicia

                                                                            • Pedir dígito 1: CalcD1
                                                                            • CalcD1

                                                                            • Menú de operaciones disponibles: CalcMenu
                                                                            • CalcMenu

                                                                            • Pedir dígito 2: CalcD2
                                                                            • CalcD2

                                                                              NOTA: Dígito 2 sólo se pide si la operación lo requiere, por ejemplo x^2 no necesita dos dígitos.

                                                                            • Calcular el resultado en función de los dígitos y la operación elegida: CalcOpera

                                                                              Hay 8 operaciones disponibles
                                                                              - Suma

                                                                              CalcOpera-Suma

                                                                              - Resta

                                                                              CalcOpera-Resta

                                                                              - Multiplicación

                                                                              CalcOpera-Mult

                                                                              - División

                                                                              CalcOpera-Div

                                                                              - √x

                                                                              CalcOpera-Raiz

                                                                              Para realizar esta operación hemos utilizado el bloque de matemáticas de la versión 2.0, si no tenéis esta versión, podéis descargar el bloque Raiz Cuadrada, de Claude Bauman.

                                                                              - x^2

                                                                              CalcOpera-x2

                                                                              - x^y

                                                                              CalcOpera-xy

                                                                              Si la operación x^2 es tan simple como hacer x * x, para poder hacer esta operación necesitamos saber cuántas veces debemos hacer la multiplicación de X por si mismo, para esto usamos el contador del bucle, de tal forma que cada nueva iteración será igual al resultado previo por X.

                                                                              - 1/x

                                                                              CalcOpera-1x

                                                                            • Preguntar al usuario si quiere realizar otra operación: CalcOtra
                                                                            • CalcOtra

                                                                              Si la respuesta es afirmativa, volvemos al principio del bucle, reseteamos las variables y todo vuelve a empezar.

                                                                              FACTORIAL

                                                                              Para terminar, a las 8 operaciones que hemos puesto más arriba, hemos añadido una novena, la operación factorial, el factorial de un número entero positivo se define como el producto de todos los números naturales anteriores o iguales a él. Se escribe n! (NOTA: Por definición el factorial de 0 es 1: 0!=1)

                                                                              Esta operación requiere, por tanto, un control previo del valor de Digito 1 que nos da el usuario, tendremos que comprobar:
                                                                              - Si es 0, en cuyo caso devolvemos 1
                                                                              - Si es <0, devolvemos 0 para indicar error (el factorial sólo está definido para valores positivos)
                                                                              - Si es >0, calculamos el valor.

                                                                              Para calcular el valor vamos a analizar la definición: 4!= 4*3*2*1 que es lo mismo que decir 4!= 1*2*3*4, ya que el orden de los factores no altera el producto.
                                                                              Esto nos permite usar el contador interno del bucle para realizar las operaciones, multiplicando el resultado de la iteración previa por el contador +1 – el contador del bucle empieza en 0, al sumarle uno forzamos que la primera multiplicación sea Ant*1-

                                                                              Este es el programa del factorial (está dentro de CalcOpera, es la operación 9)

                                                                              CalcOpera-Factorial

                                                                              Como podéis ver, hemos dado una vuelta de tuerca a las tradicionales calculadoras con sólo 4 operaciones, añadiendo otras muy comunes, que en algún caso son algo más difíciles de implementar, pero que esperamos hayan quedado claras.

                                                                              Cualquier duda, como siempre, en el foro.

                                                                              Sigue líneas con visión, usando cámara iPhone

                                                                              Artículo nº 8 de la serie de 8 artículos sobre Visión Artificial

                                                                              iphone3gTras una buena cantidad de artículos sobre visión artificial llega por fin el esperado sigue líneas con visión. Las dificultades que implica el manejo de imágenes en tiempo real son muchas, sobre todo el retardo entre que la cámara capta la imagen y nosotros la procesamos y enviamos los datos por bluetooth. Estamos utilizando la cámara de un iPhone 3G conectada mediante wifi al ordenador, enviado imágenes gracias al programa Pocket Cam. Capturamos imágenes periódicamente gracias al programa Yawcam, y las procesamos con LabVIEW, que finalmente enviará las ordenes necesarias al NXT mediante Bluetooth.

                                                                              Funcionalidades que hemos usado:

                                                                              Si no estáis familiarizados con el LabVIEW os recomiendo que os leáis los artículos de la serie de LabVIEW, sobretodo LabVIEW para usuarios del NXT (I), (II) y (III), así como Manejo de imagenes y shift register en LabVIEW.

                                                                              El uso de los shift register es fundamental para este programa, pues lo necesitamos para ser capaces de detectar lineas. Así mismo lo son el uso de bucles, sentencias case, manejo de imágenes, etc… Necesitaremos hacer uso de múltiples variables de distintos tipos para ir guardando valores que necesitaremos posteriormente.

                                                                              Proceso de captura de imágenes:

                                                                              El proceso de captura de las imágenes tiene varios pasos. En primer lugar necesitamos capturar imágenes con el Iphone. Mediante el programa Pocket Cam podemos capturar imágenes cada segundo por la cámara del Iphone, y enviarlas por wifi al ordenador. A su vez tenemos otro programa actuando en el PC denominado YawCam, que se encarga de recoger estas imágenes enviadas por el Iphone por wifi, y guardarlas en el disco duro (es capaz de hacerlo a varias imágenes por segundo, aunque debido a la limitación del Pocket Cam del Iphone de una imagen por segundo estamos limitados a esa velocidad máxima).

                                                                              Una vez en disco ya podemos procesarla con LabVIEW, para esto tenemos toda la funcionalidad que explique en Manejo de imagenes y shift register en LabVIEW.

                                                                              Procesado de la imagen:

                                                                              Una vez tenemos la imagen ya podemos procesarla. Lo primero es abrirla y usar la función unbound de clusters para poder acceder al array donde se encuentran los datos de la imagen. Recorreremos una de las líneas de la imagen, preferiblemente una intermedia, en busca de la línea negra. Tendremos que escoger un valor RGB umbral para decidir que es negro (línea) y que es blanco. Con que todos los valores estén por debajo de 60 es suficiente. Recordar que no hay que iluminar la línea negra desde arriba o creará reflejo. Buscaremos en la linea píxeles negros que estén por debajo del umbral. Para que no detectemos un píxel aislado como parte de la línea es importante que busquemos secuencias de varios píxeles negros. Si por ejemplo detectamos 6 píxeles negros seguidos sabremos que estamos ya sobre la línea.

                                                                              Foto tribot-iphone

                                                                              Una vez que estamos sobre dicha línea seguimos buscando píxeles hasta que detectemos el otro borde. Tenemos que guardar tanto el número del primer píxel como el del último píxel de la línea para calcular el punto medio. Sabiendo ya este punto medio, tenemos que ver su distancia con el centro de la imagen, cuanto más lejos estemos del centro, más nos estamos desviando de la línea. Que la diferencia entre el punto medio calculado y el centro de la imagen sea positivo o negativo indica hacia que lado nos estamos desviando (derecha o izquierda), y tendremos que actuar en consecuencia, poniendo en funcionamiento unos u otros motores.

                                                                              Este proceso se mete dentro de un bucle infinito, al que podremos poner algún botón de control para pararlo. Mientras el bucle siga en marcha seguiremos recorriendo la imagen y actuando en consecuencia.

                                                                              Programa:

                                                                              El programa es bastante largo, en el hemos usado toda la funcionalidad descrita en anteriores artículos, como bucles, sentencias case. shift registers, uso de variables… El programa sigue básicamente el proceso descrito anteriormente. Aquí podéis ver el Block Diagram:

                                                                              Block Diagram

                                                                              Y el Front Panel:

                                                                              Front Panel

                                                                              Debido al gran retardo entre que la imagen se captura en el Iphone, y el robot actúa en consecuencia el robot no es perfecto, y se necesita mover bastante lento para no salirse de la línea. Lo importante es que es capaz de seguirla. Aquí tenéis un vídeo del experimento:

                                                                              Espero que os haya gustado, ha sido difícil de implementar, pero ha merecido la pena. Para cualquier pregunta no dudéis en visitar el foro.

                                                                              Kart NXT controlado remotamente con LeJOS

                                                                              Artículo nº 17 de la serie de 21 artículos sobre LeJOS

                                                                              joystick-KartEl objetivo de este artículo es realizar un vehículo controlado remotamente por bluetooth mediante LeJOS. La novedad de este proyecto es que es el primero que realizamos cuya tracción no es diferencial (como los anteriores).

                                                                              Mientras que los vehículos con tracción diferencial como el tribot son capaces de cambiar su dirección ajustando la velocidad de los motores, este vehículo cuenta con un motor que se encarga exclusivamente de la dirección ya que los otros dos se encargarán exclusivamente de dar potencia (para dar tracción emplearemos dos motores en lugar de uno solo).

                                                                              joystick-Kart

                                                                              Como mando usaremos el joystick que ya hemos empleado en otros artículos, que se comunicará con nuestro kart mediante bluetooth. Cada vez que haya una diferencia significativa en los tacómetros de los motores del joystick (lo que significará que hemos movido el joystick), este enviará una señal al kart para que gire la dirección, o acelere, según el eje del joystick que hayamos movido.

                                                                              Comunicación Bluetooth en LeJOS:

                                                                              En primer lugar os recomiendo que os leáis el artículo Manejo de Bluetooth en LeJOS en el que se explica como comunicar dos NXT mediante bluetooth para enviar cualquier tipo de información. En este caso lo que enviaremos serán datos del valor de los tacómetros de los motores del joystick, los cuales nos indican la cantidad de de grados que queremos girar, o cuanto aumentar la velocidad.

                                                                              El envío de los datos entre los NXT se hace mediante un buffer, una especie de túnel en el que un NXT va metiendo datos por un lado y el otro los recoge por el otro lado. Es posible que la comunicación no desarrolle como queramos debido a diversos factores. Uno de los más comunes es que el NXT que envía información lo haga más rápido que el tiempo que tarda el NXT que la recibe en procesarla. Esto crea un problema serio, ya que el buffer se irá llenando de información, y llegará un momento que no pueda meter más y se empiecen a perder datos. Además como el receptor recibe más datos de los que tiene tiempo de procesar, se generará un retardo que irá en aumento, traduciéndose en un comportamiento no deseado.

                                                                              Este es nuestro vehículo:

                                                                              detalle kart

                                                                              Control del Kart:

                                                                              El control de la velocidad del Kart esta manejado por la clase navigator de LeJOS (que nos asegura que los dos motores A y C, que se encargan de la velocidad del Kart, se muevan a la misma velocidad). El control no es directo, sino que tiene velocidad aumentada gracias a un sistema de engranajes. La velocidad del kart se basa en el giro que experimenta el joystick en uno de sus ejes (el controlado por el motor A).

                                                                              Detalle de los motores de tracción:

                                                                              Detalle tracción

                                                                              El control de la dirección es directo desde el motor B del NXT. Dado que existe una limitación mecánica del angulo máximo de giro, el programa debe tenerla en cuenta para evitar que el motor intente girar más de lo que mecánicamente es posible, bloqueándose. El ángulo girado pretende seguir al giro que experimenta el joystick en uno de sus ejes (el controlado por el motor B).

                                                                              Detalle del sistema de dirección:

                                                                              Detalle dirección

                                                                              Programa:

                                                                              La implementación consiste en dos programas, uno para el kart y otro para el joystick. Ambos programas se comunicarán mediante bluetooth por un buffer. El joystick será el encargado de enviarle cuantos grados ha girado en sus ejes, y el kart el encargado de recibir dicha información, procesarla, y transformarla en movimiento.

                                                                              Los primeros experimentos que hicimos intentando controlar la dirección no daban el resultado que pretendíamos. Esto era debido a que estábamos enviando una gran cantidad de datos por el buffer, y el kart no era capaz de procesarlos a la velocidad suficiente, lo que creaba un retardo cada vez mayor entre el movimiento del joystick y la respuesta del kart. Por eso hemos hecho un método en el que el joystick solo envía información cada cierto tiempo, y si la posición del joystick a cambiado, por lo que no estará enviando datos todo el rato aunque el joystick esté quieto.

                                                                              También se han resuelto otros problemas como el de la transmisión comentado anteriormente, y se han hecho procesamientos con los datos para que el control fuera más sencillo. Aún así los programas son aún mejorables, y los modificaremos para reducir problemas como el del retardo, y para finalmente poder participar en la competición de karts de la semana que viene con el único kart controlado remotamente hecho en exclusiva con NXT.

                                                                              Aquí os dejo el código del mando:

                                                                              import lejos.nxt.Button;
                                                                              import lejos.nxt.LCD;
                                                                              import lejos.nxt.Motor;
                                                                              import lejos.nxt.comm.BTConnection;
                                                                              import lejos.nxt.comm.Bluetooth;
                                                                              import java.io.*;
                                                                              import javax.bluetooth.*;

                                                                              public class Mando {

                                                                              /**
                                                                              * @param args
                                                                              */
                                                                              public static void main(String[] args) throws Exception {
                                                                              // TODO Auto-generated method stub
                                                                              String nombre = "NXT1";
                                                                              LCD.drawString("Conectando...", 2, 1);
                                                                              LCD.refresh();
                                                                              int ant_gir = 0;
                                                                              int ant = 0;

                                                                              RemoteDevice bt = Bluetooth.getKnownDevice(nombre);

                                                                              if (bt == null){
                                                                              LCD.clear();
                                                                              LCD.drawString("No existe ese dispositivo", 0, 1);
                                                                              Thread.sleep(2000);
                                                                              System.exit(1);
                                                                              }

                                                                              BTConnection btc = Bluetooth.connect(bt);

                                                                              if (btc == null){
                                                                              LCD.clear();
                                                                              LCD.drawString("Conexión fallida", 1, 1);
                                                                              Thread.sleep(2000);
                                                                              System.exit(1);
                                                                              }

                                                                              LCD.clear();
                                                                              LCD.drawString("Conectado", 2, 1);
                                                                              Thread.sleep(2000);

                                                                              DataOutputStream dos = btc.openDataOutputStream();
                                                                              LCD.clear();
                                                                              LCD.drawString("Mando en funcionamiento", 2, 1);
                                                                              while(!Button.ESCAPE.isPressed()){
                                                                              Thread.sleep(150);
                                                                              //Giro
                                                                              int i = Motor.B.getTachoCount();

                                                                              if ((i > (ant_gir + 5)) || (i < (ant_gir - 5))){
                                                                              dos.writeInt(i);
                                                                              dos.flush();
                                                                              }
                                                                              ant_gir = i;

                                                                              //Aceleración
                                                                              int j = Motor.C.getTachoCount();

                                                                              if ((j > (ant + 4)) || (j < (ant - 4))){
                                                                              dos.writeInt(j + 1000);
                                                                              dos.flush();
                                                                              }
                                                                              ant = i;
                                                                              }

                                                                              LCD.clear();
                                                                              LCD.drawString("Cerrando conexion", 0, 1);
                                                                              dos.close();
                                                                              btc.close();

                                                                              LCD.clear();
                                                                              LCD.drawString("Finalizado", 0, 1);
                                                                              Thread.sleep(2000);
                                                                              }
                                                                              }

                                                                              Y el del kart:

                                                                              import lejos.nxt.Motor;
                                                                              import lejos.robotics.navigation.Pilot;
                                                                              import lejos.robotics.navigation.TachoPilot;
                                                                              import lejos.nxt.Button;
                                                                              import lejos.nxt.LCD;
                                                                              import lejos.nxt.comm.BTConnection;
                                                                              import lejos.nxt.comm.Bluetooth;
                                                                              import java.io.*;
                                                                              import java.lang.Math;

                                                                              public class Kart {

                                                                              /**
                                                                              * @param args
                                                                              */
                                                                              public static void main(String[] args) throws Exception{
                                                                              // TODO Auto-generated method stub
                                                                              Pilot navigator = new TachoPilot(5.4f, 17.8f, Motor.A, Motor.C, true);

                                                                              LCD.clear();
                                                                              LCD.drawString("Esperando...", 2, 1);
                                                                              LCD.refresh();

                                                                              BTConnection btc = Bluetooth.waitForConnection();

                                                                              LCD.clear();
                                                                              LCD.drawString("Conectado", 2, 1);
                                                                              Motor.B.setPower(100);
                                                                              DataInputStream dis = btc.openDataInputStream();

                                                                              while(!Button.ESCAPE.isPressed()){
                                                                              int i = dis.readInt();
                                                                              if (i > 1000){
                                                                              //Acelerar
                                                                              navigator.backward();
                                                                              navigator.setMoveSpeed((i - 1000)/2);
                                                                              }else if (i > 800){
                                                                              navigator.forward();
                                                                              navigator.setMoveSpeed(Math.abs(i - 1000)/2);
                                                                              }else{
                                                                              //Girar
                                                                              boolean signo;
                                                                              if (i >= 0)
                                                                              signo = true;
                                                                              else
                                                                              signo = false;
                                                                              i = i/10;
                                                                              i = i*i;
                                                                              if (i < 40){
                                                                              if (signo)
                                                                              Motor.B.rotateTo(-i);
                                                                              else
                                                                              Motor.B.rotateTo(i);
                                                                              }else{
                                                                              if (signo)
                                                                              Motor.B.rotateTo(-40);
                                                                              else
                                                                              Motor.B.rotateTo(40);
                                                                              }
                                                                              }
                                                                              }
                                                                              Thread.sleep(2000);
                                                                              LCD.clear();
                                                                              LCD.drawString("Cerrando conexion", 0, 1);
                                                                              dis.close();
                                                                              btc.close();
                                                                              Thread.sleep(2000);
                                                                              }
                                                                              }

                                                                              Aquí tenéis un vídeo del funcionamiento

                                                                              Son códigos de cierta complejidad, así que si tenéis alguna duda podéis preguntarnos en el foro.

                                                                              Control de prioridades de tareas en RobotC

                                                                              Round RobinEn el artículo anterior de RobotC vimos cómo manejar varias tareas, gestionando el uso de recursos compartidos como los motores. En este artículo ampliaré la información sobre el manejo de tareas, viendo cómo poner mayor o menor prioridad a una tarea, y el efecto que tiene sobre la ejecución del programa. También veremos en qué consiste la planificación de tareas Round Robin y al final pondré un par de programas de prueba con vídeos incluidos.

                                                                              ¿Que significa prioridad?:

                                                                              A la hora de iniciar una tarea en RobotC podemos asignarle una prioridad. Esta prioridad tiene un valor numérico que va del 0 al 255. Cuando una tarea se está ejecutando en la CPU el resto de tareas están esperando a que la deje libre. La tarea que tendrá el control de la CPU después de que la tarea que la estaba utilizando la deje libre será la que mayor prioridad tenga. Esto significa que las tareas que tengan mayor prioridad se ejecutarán antes que las demás que estén esperando.

                                                                              RobotC usa planificación Round Robin para asignar tiempo de CPU a cada tarea.

                                                                              Planificación Round-robin:

                                                                              Round robin es un método para seleccionar todos los elementos en un grupo de manera equitativa y en un orden racional, normalmente comenzando por el primer elemento de la lista hasta llegar al último y empezando de nuevo desde el primer elemento. El planeamiento Round Robin es tan simple como fácil de implementar, y está libre de inanición (significa que ningún proceso esperará durante demasiado tiempo a que le den tiempo de CPU).

                                                                              Round Robin asigna a cada proceso una porción de tiempo equitativa y ordenada, por lo que ningún proceso tendrá mayor tiempo de ejecución que los demás. Normalmente Round Robin trata a todos los procesos con la misma prioridad, aunque en caso de RobotC podemos aplicar prioridades, que solo afectarán en el orden en que entren a CPU las tareas que estén esperando, pero nunca en el tiempo de CPU que se les asigne.

                                                                              Tareas con prioridad en Robotc:

                                                                              En RobotC se le puede asignar una prioridad a una tarea al iniciarla. Esta prioridad debe estar entre el 0 y el 255. A mayor prioridad antes se ejecutará dicha tarea. Para asignar prioridad a una tarea al iniciarla se usa la función:

                                                                              StartTaskWithPriority(nombre_tarea, prioridad): Inicia una tarea con la prioridad que hayamos escogido.

                                                                              Hay que tener en cuenta que la prioridad no será algo perceptible, ya que la asignación de CPUs a tareas se hace muy rápido, pero sin embargo tiene utilidad a la hora de programar, ya que nos aseguramos que ciertas tareas se ejecuten antes que otras mientras ambas estén esperando.

                                                                              Programa de prueba:

                                                                              Un programa bastante sencillo para probar que en efecto todas las tareas reciben el mismo tiempo de CPU es el siguiente: se crean cuatro tareas, cada una de ellas se encarga de aumentar un contador y mostrar su número por pantalla. Si todas las tareas tuvieran el mismo tiempo de CPU los números deberían avanzar a la vez.

                                                                              El código es el siguiente:

                                                                              int cont1 = 0, cont2 = 0, cont3 = 0, cont4 = 0;

                                                                              task tarea1()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(300);
                                                                              cont1++;
                                                                              nxtDisplayCenteredTextLine(0, "Tarea 1: %d", cont1);
                                                                              }
                                                                              }

                                                                              task tarea2()
                                                                              {
                                                                              while (true){
                                                                              wait1Msec(300);
                                                                              cont2++;
                                                                              nxtDisplayCenteredTextLine(2, "Tarea 2: %d", cont2);
                                                                              }
                                                                              }

                                                                              task tarea3()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(300);
                                                                              cont3++;
                                                                              nxtDisplayCenteredTextLine(4, "Tarea 3: %d", cont3);
                                                                              }
                                                                              }

                                                                              task tarea4()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(300);
                                                                              cont4++;
                                                                              nxtDisplayCenteredTextLine(6, "Tarea 4: %d", cont4);
                                                                              }
                                                                              }

                                                                              task main()
                                                                              {
                                                                              StartTaskWithPriority(tarea1, 250);
                                                                              StartTaskWithPriority(tarea2, 150);
                                                                              StartTaskWithPriority(tarea3, 100);
                                                                              StartTaskWithPriority(tarea4, 25);
                                                                              while(true)
                                                                              {
                                                                              wait1Msec(300);
                                                                              }
                                                                              return;
                                                                              }

                                                                              Y aquí os pongo un video de su funcionamiento:

                                                                              También se puede hacer una pequeñas variación, cambiando los retardos dentro de cada tarea para que sean proporcionales. Si por ejemplo ponemos a una tarea el doble de retardo que otra, está cambiará el contador a la mitad de velocidad. Aquí está un código de ejemplo:

                                                                              int cont1 = 0, cont2 = 0, cont3 = 0, cont4 = 0;

                                                                              task tarea1()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(1000);
                                                                              cont1++;
                                                                              nxtDisplayCenteredTextLine(0, "Tarea 1: %d", cont1);
                                                                              }
                                                                              }

                                                                              task tarea2()
                                                                              {
                                                                              while (true){
                                                                              wait1Msec(500);
                                                                              cont2++;
                                                                              nxtDisplayCenteredTextLine(2, "Tarea 2: %d", cont2);
                                                                              }
                                                                              }

                                                                              task tarea3()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(250);
                                                                              cont3++;
                                                                              nxtDisplayCenteredTextLine(4, "Tarea 3: %d", cont3);
                                                                              }
                                                                              }

                                                                              task tarea4()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(125);
                                                                              cont4++;
                                                                              nxtDisplayCenteredTextLine(6, "Tarea 4: %d", cont4);
                                                                              }
                                                                              }

                                                                              task main()
                                                                              {
                                                                              StartTaskWithPriority(tarea1, 250);
                                                                              StartTaskWithPriority(tarea2, 150);
                                                                              StartTaskWithPriority(tarea3, 100);
                                                                              StartTaskWithPriority(tarea4, 25);
                                                                              while(true)
                                                                              {
                                                                              wait1Msec(300);
                                                                              }
                                                                              return;
                                                                              }

                                                                              Y el vídeo:

                                                                              En este caso la diferente espera interna de cada tarea produce que los contadores crezcan de forma inversamente proporcional, de ahí que exista una diferencia de velocidades de 1 a 8 entre la tarea 1 y la 4, la misma relación inversa que sus retardos. En este caso la prioridad de cada una de las tareas no afecta para nada al resultado: sabemos que cada vez que se han ejecutado las 4 tareas siempre ha sido la tarea 1 la primera de ellas, a continuación la 2, la 3 y finalmente la 4, debido a la siguiente parte del código:


                                                                              StartTaskWithPriority(tarea1, 250);
                                                                              StartTaskWithPriority(tarea2, 150);
                                                                              StartTaskWithPriority(tarea3, 100);
                                                                              StartTaskWithPriority(tarea4, 25);

                                                                              En esta pequeña aplicación no ha afectado para nada la prioridad de ejecución, pero puede que en otra aplicación sí sea necesario el uso de prioridades.

                                                                              Espero que os haya resultado interesante. Ya sabéis, ante cualquier duda estamos en el foro.

                                                                              Control de tareas en RobotC

                                                                              Artículo nº 14 de la serie de 14 artículos sobre RobotC

                                                                              El objetivo principal del procesador es ejecutar la secuencia de instrucciones de los programas de entre un posible conjunto de programas. Cuando es uno sólo el programa a ejecutar, podemos intuir fácilmente cuál es la secuencia de operaciones que se está ejecutando sin más que seguir la traza del proceso en curso. La ayuda de los entornos de programación puede ser de valor incalculable cuando nuestra aplicación se complica y son varios los procesos que deben ser procesados.

                                                                              En un caso monoprocesador la programación concurrente está relacionada con la forma en la que se decide cuál es el orden de ejecución y de interacción de las tareas a ejecutar, cuánto tiempo de procesador se asigna a cada uno de dichos procesos, o cuál debe ser la forma de acceso a los recursos compartidos.

                                                                              La forma de conseguir que un sistema con un único procesador que sólo puede ejecutar una instrucción simultáneamente parezca que está ejecutando varios programas a la vez es intercalando en el tiempo su ejecución.

                                                                              Sabemos que podemos seguir el comportamiento que tiene el procesador a través de la información que tenemos de la traza: cuando hay varias tareas en ejecución las instrucciones de cada una de ellas se irán intercalando dependiendo de la política que exista de la asignación de recursos. ¿Tenemos algún tipo de control sobre la forma en que se ejecutan dichos procesos? ¿Cómo debemos escribir nuestro código para ejecutar varias tareas en paralelo, para que una de ellas tenga una mayor prioridad de uso de tiempo de procesador, o simplemente capture al completo al procesador en determinados momentos?

                                                                              Control de tareas en RobotC:

                                                                              En RobotC es posible crear tareas para que se ejecuten simultáneamente. Ya he usado tareas en algún otro artículo como el de Manejo de Bluetooth en RobotC, donde una tarea se encargaba de recibir mensajes y otra de mandarlos. En este artículo ahondaremos sobre el tema, explicando diversas funciones.

                                                                              Para definir una tarea en RobotC se usa la palabra reservada task, seguida del nombre que queramos darle a la tarea, y entre corchetes el código que queremos que ejecute dicha tarea. Por ejemplo:

                                                                              task HolaMundo()
                                                                              {
                                                                              nxtDisplayCenteredTextLine(3, "Hola Mundo");
                                                                              }

                                                                              Siempre tiene que haber una tarea principal denominada task main(), desde la que lanzaremos y controlaremos a las demás. Esta tarea principal se iniciará automáticamente cuando empiece el programa, y en ella es donde debemos iniciar el resto de tareas. Para iniciar las tareas necesitamos utilizar la función StartTask:

                                                                              StartTask(nombre_tarea): Inicia la tarea especificada.

                                                                              Podemos detener una tarea en cualquier momento con la función:

                                                                              StopTask(nombre_tarea): Detiene la tarea especificada, matándola.

                                                                              También se pueden detener todas las tareas de golpe con las funcion:

                                                                              StopAllTasks(): Detiene todas las tareas.

                                                                              Existe una función que permite que la tarea que se está ejecutando en este momento hago uso de todo el tiempo de CPU. Esta función es:

                                                                              hogCPU(): Suspende todas las tareas excepto la actual, dandole por tanto todo el tiempo de CPU a esta tarea.

                                                                              Esta función tiene la utilidad de poderle dar a una tarea toda la potencia de la CPU cuando queramos. Por desgracia no funciona del todo bien, y no permite por ejemplo un correcto control de motores si tenemos otra tarea que ya los está manejando. Es importante que luego, una vez terminado lo que deseamos hacer, volvamos a reiniciar los procesos que hemos suspendido. Para ello utilizaremos la función:

                                                                              releaseCPU(): Reinicia las tareas suspendidas (el scheduler) con la función hogCPU.

                                                                              De momento no veremos más funciones para no complicar las cosa.

                                                                              Bump&Go con control de tareas:

                                                                              El Bump&Go es un programa en el que el robot se mueve aleatoriamente por una habitación esquivando los obstáculos que detecta a su paso. Ya vimos cómo programarlo en RobotC en el artículo Bump&Go en RobotC. En este caso lo vamos a implementar con multitasking (multitareas). Puesto que es un programa sencillo no necesitaremos muchas tareas. Necesitamos lo siguiente:

                                                                              • Una tarea principal que se encargue de controlar a las otras,
                                                                              • una tarea que se encargue de mover al robot en línea recta y, por último,
                                                                              • una tarea que se encargue de esquivar los obstáculos cuando el robot los detecte.

                                                                              Bump&Go1

                                                                              Puesto que tanto la tarea de mover el robot como la de esquivar obstáculos hacen uso de un recurso compartido (los motores) no podemos dejar que se ejecuten a la vez, o interferirían entre sí. Por tanto una vez detectemos el obstáculo pararemos un momento la tarea de moverse en línea recta, y una vez hayamos esquivado el obstáculo la volveremos a poner en marcha.

                                                                              El código del programa es el siguiente:

                                                                              #pragma config(Sensor,S4,contacto,sensorTouch)
                                                                              #pragma config(Motor,motorA,motor_der,tmotorNormal,PIDControl,encoder)
                                                                              #pragma config(Motor,motorC,motor_izq,tmotorNormal, PIDControl, encoder)
                                                                              //*!Code automatically generated by 'ROBOTC' configuration wizard!*/

                                                                              void esquivarObstaculo()
                                                                              {
                                                                              int tiempoMA, tiempoGiro;

                                                                              nxtDisplayClearTextLine(3);
                                                                              nxtDisplayCenteredTextLine(6, "ESQUIVANDO");
                                                                              tiempoMA = 200 + random(300);
                                                                              motor[motor_der] = -50;
                                                                              motor[motor_izq] = -50;
                                                                              wait1Msec(tiempoMA);
                                                                              tiempoGiro = 200 + random(400);
                                                                              motor[motor_izq] = 0;
                                                                              wait1Msec(tiempoGiro);
                                                                              }

                                                                              task moverRobot()
                                                                              {
                                                                              while(true){
                                                                              wait1Msec(300);
                                                                              nxtDisplayClearTextLine(6);
                                                                              nxtDisplayCenteredTextLine(3, "MOVIENDOSE");
                                                                              motor[motor_der] = 50;
                                                                              motor[motor_izq] = 50;
                                                                              }
                                                                              }

                                                                              task controlChoques()
                                                                              {
                                                                              while (true){
                                                                              wait1Msec(300);
                                                                              if (SensorValue[contacto]){
                                                                              StopTask(moverRobot);
                                                                              esquivarObstaculo();
                                                                              StartTask(moverRobot);
                                                                              }
                                                                              }
                                                                              return;
                                                                              }

                                                                              task main()
                                                                              {
                                                                              StartTask(moverRobot);
                                                                              StartTask(controlChoques);
                                                                              while(true)
                                                                              {
                                                                              wait1Msec(300);
                                                                              nxtDisplayCenteredTextLine(0, "PRINCIPAL");
                                                                              }
                                                                              return;
                                                                              }

                                                                              Y aquí una breve explicación de algunas líneas:

                                                                              1 a 3 – Definimos los sensores y motores

                                                                              6 a 19 – Esta función se encarga de esquivar obstáculos

                                                                              22 - Aquí empieza la tarea mover robot. Las tareas han de ser declaradas antes de ser utilizadas por otras tareas como la main, por eso task main será la ultima que escribamos.

                                                                              24 - Bucle infinito

                                                                              25 – Un pequeño retardo que nos asegura que la tarea anterior haya terminado. Importante ponerlo antes de hacer nada.

                                                                              26 a 29 - Decimos que se está moviendo, borrando otros mensajes, y ponemos los motores en marcha a la misma potencia.

                                                                              37 - Dentro de la tarea control de choques solo actuaremos si el sensor de contacto esta presionado.

                                                                              38 - Antes de manejar detendremos la otra tarea que controla los motores.

                                                                              39 – Esquivamos el obstáculo.

                                                                              40 – Y volvemos a poner en marcha al robot.

                                                                              49 y 50 – En la tarea principal (task main) iniciamos las otras dos tareas.

                                                                              54 - Dentro de un bucle infinito mostramos por pantalla que la tarea principal esta activa. Si se terminara esta tarea se acabaría el programa, por lo que siempre la necesitamos tener activa (en ejecución o en espera).

                                                                              Espero que os haya quedado claro. En cualquier caso si tenéis alguna duda podéis consultarnos en nuestro foro.

                                                                              Robot Multiproceso en NXT-G

                                                                                IMG_2556Hoy queremos presentar un pequeño experimento realizado por nuestros alumnos en uno de nuestros talleres de robótica. Se trata de un robot que utiliza dos ladrillos NXT que se comunican entre sí mediante bluetooth. Este experimento nos permitirá explorar aún más las posibilidades de interacción bluetooth entre ladrillos, fundamental para controles remotos (como ya hemos visto en varios artículos). Pero también para robots que tengan más de un NXT.

                                                                                Para poder hacer esto tenemos que ser capaces de controlar cuándo y cómo actuamos con cada NXT. Para eso utilizamos un sistema de intercambio de flags entre ladrillos. Para que nos sea más fácil identificarlos, durante el artículos los llamaremos NXT-M (NXT- Master) y NXT-S (NXT-Slave).

                                                                                Modelo

                                                                                Cada uno de los procesadores se encarga de unas tareas a realizar. El Master es el encargado de controlar la tracción del vehículo, mientras que al Slave se le conectan los motores de la pinza, por lo que debe controlar sus movimientos. Cada uno de ellos necesita su programa independiente.

                                                                                El siguiente es el programa de NXT-M (controla la tracción del vehículo).

                                                                                Programa Master

                                                                                Este programa es muy fácil de seguir: nos movemos en línea recta hasta que detectamos un objeto, giramos para encararnos al objeto con las pinzas y enviamos a NXT-S el flag a True, indicándole que ya empieza su parte (1) seguimos avanzando hasta que recibimos el flag False desde NXT-S (2). Ahora nos mantendremos quietos hasta recibir flag True (3) y giraremos de nuevo para soltar el objeto, para lo cual mandaremos a NXT-S flag False (4).

                                                                                Y este el del ladrillo esclavo (controla los movimientos de la pinza)

                                                                                Programa Slave

                                                                                Este es aún más sencillo, ya que sólo controlaremos la pinza. Esperamos la señal de NXT-M – marcada como (1) en la explicación anterior- Enviamos flag False a NXT-M (2). Cerramos las pinzas y levantamos el objeto. Enviamos flag True (3) y esperamos la señal desde NXT-M (4). Finalmente liberamos el objeto.

                                                                                Como se puede observar ambos ladrillos tienen esperas o realizan acciones hasta un cambio en el flag, dicho cambio es enviado por bluetooth desde el otro ladrillo.

                                                                                Es muy importante tener en cuenta que, para que el ladrillo esclavo pueda comunicarse con el maestro, debe enviar los mensajes siempre a la conexión 0. Sin embargo, el ladrillo maestro deberá enviarlos a la conexión que tenga ocupada (1, 2 o 3).

                                                                                Os ponemos un vídeo para que lo veáis en funcionamiento.

                                                                                LabVIEW para usuarios del NXT (III)

                                                                                Artículo nº 10 de la serie de 14 artículos sobre LabVIEW

                                                                                LabVIEW CaseEn esta tercera entrega de los artículos tutoriales sobre LabVIEW vamos a ver nuevos sensores, como el de ultrasonido, nuevas estructuras, y otros métodos para usar los motores. Esto nos servirá para ampliar los conocimientos que ya tenemos y así poder crear programas más complejos.

                                                                                Manejo de Motores:

                                                                                Hemos visto como poner los motores en funcionamiento o pararlos, pero sin embargo a veces queremos que recorran cierta distancia (que giren un número de grados determinados). Para ello existe esta función:

                                                                                Drive distance

                                                                                LabVIEW - Girar grados

                                                                                Gira el motor x grados, donde x es el valor que especifiquemos en distancia. En Output Port debemos indicar a que puerto esta conectado el motor/motores que queremos girar.

                                                                                Esta función la podemos encontrar haciendo click derecho en el Block Diagram y metiéndonos en NXT I/O → Complete → Motor → Drive Distance.

                                                                                Manejo de Sensores:

                                                                                En el artículo anterior vimos como poner un sensor, seleccionar su tipo, y vimos el funcionamiento del sensor de contacto y de luz. En este artículo introduciré el sensor de ultrasonido y el sensor de rotación:

                                                                                Sensor de ultrasonido

                                                                                LabVIEW - Ultrasonido

                                                                                El sensor de ultrasonido devuelve la distancia al obstáculo más próximo que tenga enfrente medida en cm. Hay que indicar a que puerto de sensores está conectado (preferiblemente el 4 ya que es el puerto rápido). La posibilidad de medir distancias abre muchas posibilidades, nos permite que el robot navegue por un entorno sin chocar con obstáculos por ejemplo, o también nos sirve para saber como de cerca estamos de nuestro objetivo.

                                                                                Sensor de rotación

                                                                                LabVIEW - Rotación

                                                                                El sensor de rotación mide la cantidad de grados que hemos recorrido desde que iniciamos el programa. Esto sirve básicamente para saber la distancia recorrida, y compararla por ejemplo con la que nosotros queriamos que recorriera. No hay que especificar puerto de sensores, sino de motores, ya que el sensor de rotación se encuentra dentro de los motores.

                                                                                Estructura Case:

                                                                                La estructura case es una estructura condicional que nos permite ejecutar una parte u otra del código según que valor tome determinada variable o elemento de nuestro programa. La estructura case la podéis encontrar en Structures → Case Structure:

                                                                                LabVIEW - Case 1

                                                                                En este caso vamos a utilizar su variante más simple, que usarla como una estructura if. Lo que le metemos es una valor booleano, en caso de que valga True ejecutará una parte del programa, y en caso de que sea False otra. Para escribir en una parte u otra tenemos que clickear en la pestaña superior para que se nos abra el menú desplegable:

                                                                                LabVIEW - Case 2

                                                                                Hay muchas cosas que devuelven valores booleanos, como el sensor de contacto, o todas las comparaciones que podéis encontrar en Comparison (igual, menor que. mayor que…).

                                                                                Programa de prueba:

                                                                                En el programa de hoy vamos a utilizar todo lo que hemos visto hoy más lo que vimos en el anterior tutorial. El programa va a consistir en un robot móvil que se desplaza gracias a dos motores (A y C). El robot cuenta con un sensor de ultrasonidos, y cuando este detecte un obstáculo a menos de 40 cm. el robot se parará instantáneamente y girará para esquivar el supuesto obstáculo. Una vez termine esta maniobra seguirá adelante hasta que detecte otro obstáculo. El robot se detendrá cuando su motor A haya recorrido más de 6000º.

                                                                                Por tanto para este programa necesitamos un bucle while, un case que ejecute cosas diferentes dependiendo del valor del sensor de ultrasonidos, un sensor de ultrasonidos, un sensor de rotación, y manejo de motores, tanto para moverlos y frenarlos, como para decirles que recorran una distancia determinada. El Block Diagram es el siguiente:

                                                                                En caso de que haya un obstáculo a menos de 40cm.:

                                                                                LabVIEW - Programa 1

                                                                                En caso de que no haya nada a menos de 40cm.:

                                                                                LabVIEW - Programa 2

                                                                                Espero que os animéis a intentar programar cosas con LabVIEW ya que es sencillo de manejar y tiene muchas más posibilidades que NXT-G. Ante cualquier duda podéis preguntar en el foro.

                                                                                Problema de la deriva en NXT y LeJOS

                                                                                Artículo nº 16 de la serie de 21 artículos sobre LeJOS

                                                                                lateralUno de los problemas que tienen cualquier sistema que tenga que desplazarse, y en particular un robot NXT móvil, es la deriva en la navegación. La deriva es el desvío de la trayectoria real respecto de la trayectoria prevista, y se debe a diversos factores. Cuanto menos deriva tenga un robot más preciso será su desplazamiento y menos se desviará de su destino objetivo al finalizar el movimiento. Este problema es muy común con los LEGO Mindstorms NXT debido a que no son robots con una forma única, sino que hay muchos montajes diferentes de robots móviles. La forma en que estén implementados los programas también influye en esta deriva.

                                                                                Cosas que influyen en la deriva:

                                                                                En el aspecto físico del robot hay muchas cosas que influyen en la deriva. Influye por ejemplo la diferencia de tamaño entre las ruedas, aunque está sea mínima porque una está mas hinchada que otra. Influye el peso del robot en gran medida, y por supuesto el cómo está distribuido este peso. Es importante que el mayor peso posible se encuentre sobre las ruedas tractoras, lo que impedirá que el robot se desvíe de su camino porque le pese demasiado la parte de delante o de atrás:

                                                                                frontal

                                                                                Influye la solidez del vehículo, que no modifique su forma mientras se va desplazando, su simetría. Otro aspecto importante es que no tenga rozamiento con nada, ya que el mínimo rozamiento podría crear una modificación de la trayectoria. La velocidad es bastante importante también, un vehículo que se desplace rápido es mucho más difícil de controlar que uno que lo haga lentamente.

                                                                                La parte de implementación es también muy importante, por ejemplo si está usando dos motores para desplazarse, ambos deben ir con la misma potencia y velocidad. También es importante que a la hora de arrancar lo hagan simultáneamente, o el robot comenzará a moverse ya desviado de la trayectoria. Así mismo es importante que se detengan simultáneamente cuando el robot tenga que parar.

                                                                                Diametro Rueda

                                                                                El diámetro teórico de una rueda no suele coincidir con su diametro real, debido entre otras cosas al peso que tenga encima o a lo descinchada que esté. Hay ruedas más duras que otras que tienen menos problemas de este estilo. Las orugas por ejemplo tienen mucha deformación lo que agrava la deriva. El tipo de suelo también influye mucho.

                                                                                A pesar de todas estas precauciones es difícil mantener determinada trayectoria de forma continuada. Por ejemplo es imposible mantener a un robot realizando desplazamientos en forma de cuadrado de manera indefinida, y que siempre vuelva al mismo punto de partida, lo que pasará es que en cada vuelta el robot se desviará más y más hasta que ya nunca pase por su posición inicial.

                                                                                Clase Pilot, la mayor precisión:

                                                                                Una de las maneras de alcanzar una precisión muy alta a la hora de desplazar el robot es mediante la clase Pilot de LeJOS. Esta clase, que ya introduje en el artículo Manejo de motores avanzado: clase Pilot, introduce una nueva forma de manejar el robot. Lo primero que se hace es configurar un objeto de esa clase indicando que motores vamos a utilizar (dos motores, ya que esta clase esta solo diseñada para tribots), que distancia hay entre los puntos medios de las ruedas, y el diámetro de la rueda:

                                                                                Pilot navegador = new TachoPilot(diametro_ruedas, distancia_ruedas, Motor.A, Motor.C);

                                                                                Aunque las ruedas de los NXT tiene un diámetro de 5.6 cm es importante que pongáis un poco menos, sobre todo si vuestro robot pesa mucho, ya que el peso hace que las ruedas se aplanen un poco. La distancia entre las ruedas debe ser bastante precisa, ya que es de vital importancia sobre todo a la hora de las rotaciones.

                                                                                A los métodos ya comentados en el artículo sobre la clase Pilot es importante añadir:

                                                                                void setMoveSpeed(float distancia): Establece la velocidad del robot a la distancia que pongamos por segundo. Por ejemplo si ponemos 5, se moverá a 5 cm por segundo (que seán cm, pulgadas o una medida distinta solo depende de que tipo de medida hayamos usado para configurar el Pilot).

                                                                                void setTurnSpeed(float grados): Establece la velocidad del robot, pero en este caso es en grados por segundo. Si ponemos 360 las ruedas realizarán una rotación completa por segundo.

                                                                                Es importante controlar la velocidad, ya que si vemos que nuestro robot no es capaz de moverse con precisión a cierta velocidad deberíamos reducirsela. Otro método útil es:

                                                                                void steer(float velocidad_giro, float angulo, boolean devolverControl): El robot se mueve en una trayectoria curva a la velocidad de giro indicada hasta que complete el número de ángulos deseado. Si devolver control es True el robot seguirá ejecutando las siguientes líneas de código mientras gira. No hace falta indicarle un angulo, si omitimos este campo el robot girará indefinidamente, o hasta que lo detengamos con otra instrucción.

                                                                                Programa de prueba:

                                                                                Para probar la precisión de la clase Pilot he creado un programa que describe un gran rectángulo de 240 cm. Cuanto más cerca quede el robot de la posición inicial más preciso será el programa. Además contamos con un montaje simétrico, relativamente ligero, y con la mayoría del peso sobre las ruedas tractoras. La rueda trasera no es la mejor posible, pero hace bien su función. El programa es el siguiente:

                                                                                public class figuras {

                                                                                /**
                                                                                * @param args
                                                                                */
                                                                                public static void main(String[] args) throws Exception {
                                                                                // TODO Auto-generated method stub
                                                                                Pilot navigator = new TachoPilot(5.4f, 10.4f, Motor.A, Motor.C);
                                                                                LCD.clear();

                                                                                navigator.reset();

                                                                                //Rectángulo
                                                                                navigator.travel(90);
                                                                                navigator.rotate(90);
                                                                                navigator.travel(30);
                                                                                navigator.rotate(90);
                                                                                navigator.travel(90);
                                                                                navigator.rotate(90);
                                                                                navigator.travel(30);
                                                                                navigator.rotate(90);

                                                                                LCD.drawInt((int)navigator.getTravelDistance(), 6,4);
                                                                                Button.waitForPress();
                                                                                }

                                                                                }

                                                                                Aquí tenéis el vídeo demostrativo del programa, en el podéis ver que la deriva es mínima (apenas un centímetro), habiendo recorrido 240 cm. y realizado tres rotaciones de 90º:

                                                                                Ante cualquier duda podéis preguntar en el foro.

                                                                                LabVIEW para usuarios del NXT (II)

                                                                                Artículo nº 9 de la serie de 14 artículos sobre LabVIEW

                                                                                LabVIEW NXT- PortadaEn el artículo LabVIEW para usuarios del NXT (I) vimos como crear un VI, y empezamos con un programa de prueba (el famoso “Hola Mundo”). En el artículo de hoy vamos a ahondar un poco más en el manejo básico de LabVIEW, y ver manejo de motores y de algún sensor.

                                                                                Manejo de Motores:

                                                                                Para manejar el motor la función que debemos usar es Motor, que la podéis encontrar haciendo click derecho en el Block Diagram y seleccionando NXT I/O → Motor:

                                                                                LabVIEW NXT- 1

                                                                                Una vez coloquemos nuestro motor podemos observar que tiene varios modos de funcionamiento con distintas entradas y salidas. Para cambiar entre estos modos tenemos que clickear en el menú desplegable. Los modos son los siguientes:

                                                                                Motor On: Forward

                                                                                LabVIEW NXT- 2

                                                                                Hace que los motores se muevan marcha adelante con la potencia que escojamos. En Output Port escogemos el Puerto (Port A, Port B, Port C o todos), en Power la potencia deseada, y los demás conectores son para el flujo del programa como ya vimos en el artículo anterior.

                                                                                Motor On: Reverse

                                                                                LabVIEW NXT- 3

                                                                                Funciona de manera parecida al anterior, solo que con los motores marcha atrás.

                                                                                Motor Off: Brake

                                                                                LabVIEW NXT- 4

                                                                                El motor frena, tratando de detenerse lo más rápido posible. En Port elegimos el puerto del motor que deseamos que frene.

                                                                                Motor Off: Coast

                                                                                LabVIEW NXT- 5

                                                                                El motor deja de acelerar, volviendo poco a poco a la inmovilidad. No es un frenazo brusco, por lo que se seguirá desplazando hasta detenerse. Igual que en el anterior elegimos el puerto del motor.

                                                                                A la hora de elegir a que puertos están conectados, o con que potencia van a moverse, tenemos que enganchar los valores deseados a las entradas (ya sea mediante constantes, controles….). La manera más cómoda de hacerlo es creando estas constantes, controladores o indicadores ya enganchados al las salidas y entradas. Para ello haced click derecho en la entrada o salida deseada y seleccionad Create y lo que queráis enganchar:

                                                                                LabVIEW NXT- 6

                                                                                Manejo de Sensores:

                                                                                Para manejar los sensores utilizamos la función Sensor, que la podéis encontrar haciendo click derecho en el Block Diagram y seleccionando NXT I/O → Sensor. En este caso tenemos muchos sensores distintos disponibles, aunque no vamos a ver todos aquí. De momento me centraré en los dos más simples, el de contacto y el de luz.

                                                                                Contacto: Pressed

                                                                                LabVIEW NXTContacto

                                                                                Nos devuelve si el sensor de contacto está presionado o no (es un valor booleano). Tenemos que decirle a que puerto de sensores está conectado (Port 1, Port 2, Port 3 o Port 4).

                                                                                Luz: Luz encendida

                                                                                LabVIEW NXT Luz On

                                                                                El sensor de luz nos devuelve un valor que representa la cantidad de luz ambiente que detecta. En este caso el sensor enciende una pequeña luz que le sirve para ver mejor, reflectando esta luz contra objetos cercanos por ejemplo, lo que sirve para distinguir colores. Hay que decir a que puerto está conectado.

                                                                                Luz: Luz apagada

                                                                                LabVIEW NXT Luz Off

                                                                                De funcionamiento parecido al anterior esta vez actúa con su luz apagada, lo cual nos sirve para detectar luz ambiente, y saber por ejemplo que parte de una habitación está más iluminada (ya que al no tener el sensor su luz encendida, esta no interfiere en los valores que capta). Sin embargo este modo no sirve para distinguir colores.

                                                                                Bucles While:

                                                                                Un bucle es una estructura en la que continuamente se repite lo que haya dentro hasta que se cumpla una condición de salida que elegimos nosotros. Esta condición de salida tiene que devolver un valor booleano (verdadero o falso), como el que devuelve el sensor de contacto. Los búcles While los podéis encontrar en Structures → While Loop:

                                                                                LabVIEW NXT While

                                                                                Veréis un ejemplo sencillo de su uso en el programa de prueba que pongo a continuación.

                                                                                Programa de prueba:

                                                                                Vamos ha hacer un pequeño programa en el que usaremos un par de motores y un sensor de luz. El programa consistirá en un robot que va girando mientras toma valores con el sensor de luz y los muestra por el Display del NXT. El programa terminará cuando presionemos el sensor de contacto Para que un robot gire un motor ha de moverse más rápido que el otro, esto se consigue aumentando la potencia de uno de los dos. En este caso vamos a tener un robot con los motores A y C, y le daremos mayor potencia al motor A.

                                                                                Para que se repita la lectura del sensor de luz, y el display del valor en la pantalla del NXT, es necesario meter todo esto en un bucle (while en este caso). Como condición de salida del bucle pondremos que el sensor de contacto esté presionado, de manera que el programa terminará su ejecución cuando esto ocurra. El Block Diagram quedaría así:

                                                                                LabVIEW NXTPrograma

                                                                                Cuando este terminado lo compiláis y descargáis al NXT, y lo ejecutáis. Si tenéis alguna duda podéis consultar nuestro foro.

                                                                                Manejo de imagenes y shift register en LabVIEW

                                                                                Artículo nº 7 de la serie de 8 artículos sobre Visión Artificial

                                                                                Shift Register 5En el artículo del Lunes vimos como recorrer imágenes con LabVIEW. Para ello necesitábamos un bucle anidado que podíamos recorrer gracias a que teníamos la información de la altura, la anchura y el color de la imagen. A la hora de manejar bucles muchas veces nos interesa saber los resultados de operaciones en iteraciones anteriores para poder manejarlos y operar con ellos, aquí es donde entran los Shift Register, que guardan la información que queramos de una iteración anterior para que podamos usarla cuando queramos. En el artículo de hoy vamos a crear una nueva imagen a partir de la información que saquemos de una que ya tengamos.

                                                                                Uso del Shift Register:

                                                                                En primer lugar necesitamos crear el bucle. Una vez tengamos el bucle creado (ya sea while, for…) clickeamos en el con el botón derecho y le damos a Add Shift Register:

                                                                                Shift Register 1

                                                                                Y quedará de la siguiente manera:

                                                                                Shift Register 2

                                                                                Donde la conexión exterior del lado izquierdo es donde se inicializa (lo que va a valer en la primera iteración), la interior de este mismo lado es el valor de la variable que en la iteracción anterior hayamos conectado a la conexión interior del derecho. Finalmente la conexión exterior del lado derecho es el resultado de la ultima iteración.

                                                                                Es fundamental inicializar el Shift Register usando la conexión exterior del lado izquierdo, o tendrá un valor indeterminado y hará que la operación que hagamos en la primera iteración de un resultado no deseado. Al tener un elemento conectado a la salida (conexión exterior del lado derecho) nos aseguramos de que se ejecute de manera secuencial, ya que tendrá que esperar a que acabe el bucle para ejecutarse.

                                                                                Un ejemplo sencillo de uso de los Shift Register sería una multiplicación. Una multiplicación no es más que sumar un número a si mismo varias veces. Por tanto podríamos hacer un bucle donde en cada iteración sume dicho numero a lo que ya tuviéramos en la iteración anterior, teniendo en cuenta que inicializaríamos a 0 el Shift Register. Ese programa quedaría de la siguiente manera:

                                                                                Shift Register 3

                                                                                Es posible utilizar el valor de varias iteraciones atrás, no solo de la anterior. Para ello podéis arrastrar en Shift Register una vez lo hayáis puesto. Cada Shift Register coincide con una iteración anterior, si tenemos cinco significa que tendremos los valores de las cinco iteraciones anteriores:

                                                                                Shift Register 4

                                                                                Crear una imagen:

                                                                                Para crear una imagen necesitamos crear un cluster que contenga todos los datos necesarios. Eso se hace con la función bundle que podéis encontrar en Programming → Cluster, Class & Variant:

                                                                                Clusters LabVIEW

                                                                                Este cluster lo conectaremos a una función de escritura de imágenes, como por ejemplo Write JPEG File que podéis encontrar en Programming → Graphics & Sound → Graphics:

                                                                                Write jpg

                                                                                Una vez tengamos la imagen podremos manejarla tranquilamente para mostrarla por pantalla por ejemplo.

                                                                                Programa de ejemplo:

                                                                                En este programa vamos a leer una imagen, vamos a recorrerla píxel a píxel, y en una imagen nueva vamos a dibujar un píxel de color negro por cada píxel negro que detectemos en la primera. Usaremos un umbral RGB que iremos variando para ver los resultados, como cuanta línea es capaz de detectar en la imagen de una linea negra sobre fondo blanco.

                                                                                El Block Diagram sería el siguiente:

                                                                                Shift Register 5

                                                                                Tarda un poco en recorrer la imagen y crear la nueva. Hemos hecho varias pruebas, para que veáis un ejemplo aquí tenéis dos imágenes del panel frontal con distintos RGB umbral:

                                                                                RGB por debajo de 60:

                                                                                Linea valor 60

                                                                                RGB por debajo de 100:

                                                                                Linea valor 100

                                                                                Como podéis imaginar esto tiene muchas aplicaciones, y es por ejemplo el primer paso para un sigue líneas con cámara. Teniendo la linea sabemos por ejemplo dónde se encuentra el robot respecto a ella y podemos corregir su posición.

                                                                                Microsoft Robotics Developer Studio, ¡ahora Gratis!

                                                                                Microsoft acaba de anunciar que su Robotics Developer Studio será gratuito a partir de ahora. Esta herramienta permite programar y controlar (incluso remotamente) Mindstorms NXT haciendo uso de Visual Basic y C#. Anteriormente existían 3 versiones de este software, dos de las cuales requerían licencias de pago, ahora las 3 versiones se han unido en una sola, que puede descargarse y usarse sin coste alguno desde la página de Robotics Developer Studio.

                                                                                Microsoft Robotics Developer Studio 2008 R3 (Microsoft RDS) proporciona una amplia gama de soporte para hacer más fácil el desarrollo de aplicaciones de robots. Microsoft RDS incluye un modelo de programación que hace que sea fácil desarrollar aplicaciones asincronas, orientadas a estados. Microsoft RDS proporciona un marco común de programación que se pueden aplicar para apoyar una amplia variedad de robots, lo que facilita la transmisión de código y conocimientos.

                                                                                Microsoft RDS incluye un ligero tiempo de ejecución asíncrona orientada a servicios, un conjunto de autoría visual y herramientas de simulación, así como plantillas, tutoriales y código de ejemplo para facilitar el comienzo.

                                                                                Las siguientes son las características generales:

                                                                                • El CCR (Concurrency and Coordination Runtime) hace más fácil el manejo de las entradas y salidas asíncronas, lo que elimina las complejidades convencionales de los hilos, locks, y semáforos. Un entorno ligero de Servicios de Software Descentralizado orientado a estados permite la creación de módulos de programas que pueden interoperar en un robot y en los ordenadores personales conectados mediante un protocolo sencillo y abierto.
                                                                                • VPL proporciona una sencilla herramienta de programación visual que simplifica la creación de aplicaciones de robótica. VPL también proporciona la capacidad de tomar un conjunto de bloques conectados y reutilizarlos como único bloque en otra parte de su programa. VPL permite también la posibilidad de generar código C# legible.
                                                                                • DSS Manifest Editor permite la creación de escenarios de configuración de la aplicación y distribución.
                                                                                • El DSS Log Analyzer permite ver los flujos de mensajes a través de servicios múltiples DSS. DSS Log Analyzer también posibilita inspeccionar los detalles del mensaje.
                                                                                • VSE proporciona la capacidad de simular y probar aplicaciones robóticas utilizando una herramienta de simulación en 3D basada en la física. Esto permite a los desarrolladores crear aplicaciones de robótica sin el hardware. Ejemplo de modelos de simulación y ambientes le permite probar la aplicación en una variedad de entornos virtuales 3D.

                                                                                Control remoto del NXT desde servidor Web

                                                                                NXToIPSeguimos explorando las posibilidades de control remoto para el Mindstorms NXT, ya hemos visto varios ejemplos de control remoto vía bluetooth, ahora vamos a ver NXToIP, una aplicación que permite controlar un NXT desde un servidor web en tiempo real.

                                                                                Este proyecto tiene 3 partes fundamentales:

                                                                              • Lego Mindstorms NXT, utiliza un tribot como base del experimento.
                                                                              • Cámara inalámbrica (por bluetooth), usa un Nokia 6600 con la aplicación Mobiola Web Cam Software.
                                                                              • Servidor web con bluetooth (para recibir tanto la señal de la cámara como para comunicarse con el mindstorms). se trata de un PC Windows con un servidor ASP.NET, la interfaz de usuario no necesita ningún plugin, se trata de una simple página html. La interacción en tiempo real utiliza el framework AJAX de ASP.NET.
                                                                                AJAX- JavaScript asíncrono y XML, es una técnica de desarrollo web para crear aplicaciones interactivas o RIA (Rich Internet Applications). Estas aplicaciones se ejecutan en el cliente, es decir, en el navegador de los usuarios mientras se mantiene la comunicación asíncrona con el servidor en segundo plano. De esta forma es posible realizar cambios sobre las páginas sin necesidad de recargarlas, lo que significa aumentar la interactividad, velocidad y usabilidad en las aplicaciones -.

                                                                                Aquí tenéis un pequeño vídeo demostrativo

                                                                                Su creador es el mismo que ya nos sorprendió con la aplicación Lego Drive para iPhone.

                                                                                La importancia de este experimento radica en que muestra posibilidades de control para el Mindstorms que aún están por explorar.

                                                                                Otro proyecto que merece la pena ha sido desarrollado por Legoguy (Control Remoto NXT, parece que está offline por un tiempo, veremos si se vuelve a poner en marcha) ¡que nos permitirá controlar remotamente el Mindstorms de Legoguy desde nuestra propia casa!

                                                                                Este es el aspecto que tiene la página en funcionamiento

                                                                                dashboard

                                                                                Y este el robot que controlaremos


                                                                                LegoGuy

                                                                                Además, podemos encontrar algunas aplicaciones que nos permitirán montar nuestro servidor web en casa y controlar nuestro robot, en este caso un RCX, se trata de: WebBrick y WebRCX.

                                                                              • Marble Run en LabVIEW

                                                                                Artículo nº 7 de la serie de 14 artículos sobre LabVIEW

                                                                                Ini_lupaYa hemos visto en un artículo anterior una implementación de máquina de estados que nos permitía hacer un sigue líneas. Ahora vamos a utilizar el Template de máquina de estados que viene con LabVIEW para un poner en marcha nuestra marble run. Este diseño se basa en el trabajo de Erling (Posibilidades del Datalog), aunque en este caso hemos utilizado un sensor de luminosidad para detectar la presencia de la bola y diferenciar el valor.

                                                                                Vamos a estudiar primero los estados que tiene nuestro sistema, luego veremos cómo lo implementamos en LabVIEW.

                                                                                1. Estado Inicial
                                                                                2. Estado Color :

                                                                              • 2.1 – Estado Azul
                                                                              • 2.2 – Estado Rojo
                                                                                3. Estado Motor

                                                                                El siguiente es un diagrama de estados simplificado. Las condiciones para saltar entre estados están especificadas en el VI.

                                                                                diagrama

                                                                                Nótese que hemos utilizado la nomenclatura Estado Color y 2.1, 2.2 en lugar de 2 y 3 para los estados Azul y Rojo. Esto es porque el estado Inicial necesariamente irá a uno y sólo a uno de los estados posibles de color.

                                                                                Lo primero que tendremos que hacer es abrir el Template de Máquinas de Estado, y nos encontraremos algo como esto.

                                                                                Estados

                                                                                Abrimos Type Def y añadimos los estados que nos interesan.

                                                                                Este es el resultado de cada uno de los estados de nuestro sistema.

                                                                                Estado inicial

                                                                                Ini

                                                                                Lo primero que hacemos es medir la luminosidad inicial, usaremos este valor para saber cuando hay una bola en la pala, además de usar las constantes numéricas para ajustar la diferencia entre este luz inicial y los distintos colores de las bolas.

                                                                                La parte más compleja de esta máquina de estados es que el siguiente estado al inicial es variable, en función del color de la bola.

                                                                                Ini_lupa

                                                                                Estado Azul

                                                                                Azul

                                                                                Contador Azul +1

                                                                                Estado Rojo

                                                                                Rojo

                                                                                Contador Rojo +1

                                                                                Estado Motor

                                                                                Motor

                                                                                Hacemos los movimientos de los motores que nos permiten subir/bajar la pala de recepción de bolas y la barrera.

                                                                                Este es nuestro panel frontal, donde presentamos la información sobre el número de bolas que han recorrido el circuito, la luminosidad y el interruptor de salida.

                                                                                Panel

                                                                                Aquí tenéis un vídeo de muestra

                                                                              • Las posibilidades del DataLog, by Erling

                                                                                ExcelAl hablar de robótica inmediatamente nos imaginamos máquinas capaces de andar, de moverse o de actuar de forma inteligente. Muchas de ellas solo están, aún, en nuestra imaginación, otras las vamos realizando a pequeña escala con nuestro NXT.

                                                                                El campo en el que más se usa la robótica en la actualidad es el de la automatización industrial. En él, máquinas automáticas y robots, como por ejemplo los observados en la cadena de montaje de los automóviles, realizan las actividades que antes realizaban los humanos.

                                                                                cadena-montaje

                                                                                Es en dicho sector donde cobra especial importancia la toma de datos. Obtener y almacenar datos de nuestros robots, como valores de variables, valores de los sensores etc, y después saber representarlas e interpretarlas pueden ayudarnos a conocer los fallos del diseño o las posibles mejoras de este. Una forma de almacenar estos datos en el NXT es mediante el uso de datalogs.

                                                                                Para realizar nuestro ejemplo usaremos un proyecto realizado originalmente por NxtPrograms. El proyecto consiste en un carril inclinado por el que circularan bolas de colores. Al llegar a la parte más baja, un sensor de color detectará la bola y la pala se elevará para colocar dicha bola, de nuevo, al principio del circuito, en un bucle casi infinito.

                                                                                Como todo lo que construimos, el paso del diseño al papel implica una serie de problemas que deberemos resolver. Es ocasional que por un mal montaje o una mala programación, las bolas se salgan del circuito, o bien se junten en el carril dando problemas a la pala. Para solucionar esto hemos colocado una barrera en la última sección del carril que haga esperar a una bola mientras la otra está en la pala.

                                                                                marble run

                                                                                Una vez tenemos nuestro proyecto queremos analizar los tiempos que tarda cada bola en completar una vez el circuito, así como su tiempo cuando hay más de una bola en el carril.

                                                                                Para ello realizamos un programa que, además de realizar el comportamiento de la máquina, introduzca los datos que deseamos en un datalog. La elección de qué datos introducir y en qué momento del programa influirá mucho en la buena realización del experimento.

                                                                                En este caso, como queremos contar el tiempo que tarda cada bola en volver a la pala, añadiremos un dato más al datalog cada vez que la bola pase por esta, reiniciando el temporizador tras ello. Como vamos a disponer de dos bolas distintas, una roja y otra azul, usaremos dos temporizadores distintos, pero esto no debe preocuparnos, pues el datalog almacenara, junto al dato del tiempo, una referencia que nos permitirá identificar cada temporizador.

                                                                                Este es el código, implementado por Erling.


                                                                                #pragma config(Sensor, S1, color, sensorI2CHiTechnicColor)
                                                                                //*!!Code automatically generated by 'ROBOTC' configuration wizard !!*//

                                                                                task control();//Control de barrera//
                                                                                task cuenta();//Cuenta de bolas//
                                                                                task lcd();//Muestra de cuenta de bolas en pantalla//
                                                                                task data();//Almacenamiento de datos en datalog//

                                                                                bool pala=0;//Pala con o sin bola//
                                                                                int rojo=0;
                                                                                int azul=0;

                                                                                task main()
                                                                                {
                                                                                StartTask(control);
                                                                                StartTask(cuenta);
                                                                                StartTask(lcd);
                                                                                StartTask(data);

                                                                                while(true)
                                                                                {
                                                                                if(SensorValue(color)!=0)//Si detecta color//
                                                                                {
                                                                                pala=true;
                                                                                wait1Msec(200);
                                                                                nMotorEncoderTarget[motorA]=60;
                                                                                motor[motorA]=-100;
                                                                                wait1Msec(750);
                                                                                nMotorEncoderTarget[motorA]=60;
                                                                                motor[motorA]=100;
                                                                                wait1Msec(10);
                                                                                pala=false;
                                                                                }
                                                                                }
                                                                                }
                                                                                task control()
                                                                                {
                                                                                while(true)
                                                                                {
                                                                                if((pala==true))//Si la pala esta operando//
                                                                                {
                                                                                nMotorEncoderTarget[motorB]=90;
                                                                                motor[motorB]=-100;
                                                                                wait1Msec(1000);
                                                                                while(pala==true)//Espera hasta que la bola sale//
                                                                                {
                                                                                wait1Msec(100);
                                                                                }
                                                                                nMotorEncoderTarget[motorB]=90;
                                                                                motor[motorB]=100;
                                                                                wait1Msec(10);
                                                                                }
                                                                                }
                                                                                }
                                                                                task cuenta()
                                                                                {
                                                                                while(true)
                                                                                {
                                                                                if(SensorValue(color)==9)
                                                                                {
                                                                                wait1Msec(1000);
                                                                                rojo++;
                                                                                }
                                                                                if(SensorValue(color)==2)
                                                                                {
                                                                                wait1Msec(1000);
                                                                                azul++;
                                                                                }
                                                                                }
                                                                                }
                                                                                task lcd()
                                                                                {
                                                                                while(true)
                                                                                {
                                                                                nxtDisplayCenteredBigTextLine(2, "ROJO=%d", rojo);
                                                                                nxtDisplayCenteredBigTextLine(4, "AZUL=%d", azul);
                                                                                wait1Msec(100);//Tiempo de refresco//
                                                                                }
                                                                                }
                                                                                task data()
                                                                                {
                                                                                nDatalogSize=500;
                                                                                while(true)
                                                                                {
                                                                                if(SensorValue(color)==9)
                                                                                {
                                                                                AddToDatalog(time1[T1]);
                                                                                time1[T1]=0;
                                                                                wait1Msec(1500);
                                                                                SaveNxtDatalog();
                                                                                }
                                                                                if(SensorValue(color)==2)
                                                                                {
                                                                                AddToDatalog(time1[T2]);
                                                                                time1[T2]=0;
                                                                                wait1Msec(1500);
                                                                                SaveNxtDatalog();
                                                                                }
                                                                                }
                                                                                }

                                                                                Realizaremos dos experimentos, el primero con una sola bola de color rojo. Y el segundo con una bola roja y otra azul. Es importante que realicemos el mismo número de tomas de datos, es decir, las veces que la bola repite el circuito, para poder compararlos de forma correcta.

                                                                                Una vez que tenemos los datos de nuestros dos experimentos almacenados en nuestro datalog, hemos de ordenarlos con una tabla de excel para poder representarlos. Lo primero es pasar el formato ”.cvs” del datalog a uno más manejable. Para ello seguiremos este tutorial.

                                                                                Tras obtener un nuevo formato para nuestros datos, podemos clasificar estos por color de la bola (el primer número corresponde a la referencia del temporizador, la segunda al tiempo en milisegundos ) y, tras ello, representarlos. Obteniendo las siguientes gráficas:

                                                                                Dos bolas

                                                                                En nuestro eje “x” tenemos el número de toma del dato, y en nuestro eje “y” el tiempo que ha tardado la bola en realizar el circuito en milisegundos.
                                                                                Como comprobamos en esta gráfica, los tiempos para la bola roja y la bola azul son prácticamente iguales. Algo totalmente lógico si tenemos en cuenta que poseen el mismo peso y diámetro, siendo su única diferencia el color, que no afectará a su comportamiento en su movimiento por el carril.
                                                                                Pero: ¿Qué pasaría si tuviéramos bolas de distintos tamaños y pesos? Podríamos diferenciar cuales son las más rápidas y determinar que el radio influye mucho en su velocidad. O bien podríamos detectar bolas que, aunque en un principio deberían ser iguales, presentan alguna anomalía.

                                                                                Si comparamos el comportamiento de la bola roja sola y de esta junto a la bola azul:

                                                                                Comparativa bolas rojas

                                                                                Como se ve los tiempos se reducen considerablemente de un caso a otro, además de haber menor dispersión de datos. Es decir, con una sola bola, los recorridos en tiempo son muchísimo más similares. Algo lógico al no tener que esperar en la barrera debido a que haya otra bola. También es lógico comprobar que el tiempo medio de diferencia entre ambos casos (427 ms, aproximadamente medio segundo) se corresponde al tiempo adicional que tarda la bola por esperar en la barrera reguladora.

                                                                                Ahora bien: ¿Qué pasaría si añadiéramos otra bola más al circuito? La pala estaría más tiempo ocupada, por lo que las bolas tendrían que esperar más veces en la barrera, aumentando los tiempos para cada recorrido.

                                                                                Esto, traducido a un caso real de una fábrica, puede darnos conocimientos sobre con cuantas unidades un proceso se satura, formándose cuellos de botella y tardando con ello mucho más tiempo en realizar cada tarea.

                                                                                En definitiva, la toma de datos y el análisis de estos son una parte fundamental en el diseño de maquinaría en fábricas, y puede resultarnos muy útil para entender de una forma precisa como se relaciona nuestro robot con su medio.

                                                                                Controlando el NXT desde iPod Touch/ iPhone

                                                                                NXT-iPhoneUna de las cosas más reclamadas por los usuarios de NXT es la posibilidad de manejarlo remotamente desde el ordenador o desde un dispositivo móvil. El control remoto desde el ordenador ya es algo usado por todos, de hecho el Mindstorms NXT 2.0 ya la incluye dentro de su interfaz. Pero los usuarios pedían más movilidad, por eso, LEGO sacó hace un tiempo un programa que permitía a algunos teléfonos móviles controlar su robot mediante una sencilla interfaz gráfica (puede descargarse desde la página oficial de LEGO Mindstorms).

                                                                                Sin embargo, los modelos que admiten esta aplicación son bastante limitados, según los datos oficiales de LEGO, sólo funciona para móviles de las marcas Nokia, Sony-Ericsson y BenQ-Siemens, que además deben tener bluetooth (lógico) y soportar Java (JRS-82).

                                                                                Además, este software está bastante desactualizado y nuevos gigantes de la tecnología móvil como HTC o Apple no tienen soporte para esta aplicación.


                                                                                NXT-iPhone

                                                                                Por suerte para todos los usuarios de Apple, en concreto de los dispositivos móviles iPod Touch y iPhone (en cualquiera de sus versiones) recientemente ha aparecido una aplicación en llamada Lego Drive – programada por Saiyin Topkaya- que nos permite controlar de forma directa (bluetooth) un vehículo con configuración diferencial (ver artículo sobre la rueda loca).


                                                                                LegoDrive

                                                                                Para usar esta aplicación tendréis que escribir la Device ID (identificador de dispositivo) de vuestro NXT, esta información se encuentra en “Settings – NXT Info” y pulsar en “Connect”
                                                                                Como la mayoría ya sabéis, Apple tiene restringidos los dispositivos que se pueden conectar con sus productos, por lo que su autor ha tenido que utilizar la librería btstack de Matthias Ringwald, esta librería permite que el iTouch o el iPhone se conecten con dispositivos no “oficiales”.

                                                                                Para poder usar esta aplicación necesitaréis tener el “jailbreak” hecho (posibilidad de usar aplicaciones no aprobadas por Apple). La aplicación es de descarga gratuita desde Cydia, está en el repositorio de BigBoss.

                                                                                Os dejamos un vídeo del funcionamiento de LegoDrive

                                                                                Si no tenéis el “jailbreak” o queréis una aplicación aprobada por Apple tenéis iNXT Remote, con un precio de 3.99€ y se puede descargar desde la App Store. Aunque también podremos controlar el NXT desde el iPhone, esta segunda aplicación necesita de un PC o un MAC con un programa que recibe la señal del iPhone/iTouch por WiFi y se encarga de enviar la señal al NXT por bluetooth. Funciona bastante bien y la interfaz es mucho más completa (el otro carece de interfaz) y enormemente intuitiva. Aunque eso sí, cada vez que queráis usarlo tendréis que llevaros el ordenador con vosotros.


                                                                                iNXT Remote

                                                                                Rueda loca

                                                                                CW_sweivel_LEGOLa rueda loca (caster wheel en inglés) es una rueda sin tracción, simple o doble, que puede girar libremente y que generalmente está situada en la parte inferior de una estructura. Se utiliza en carros de la compra, sillas de oficina o vehículos… como nuestro tribot NXT. Vamos a detenernos un momento a revsar este elemento mecánico tan utilizado.

                                                                                Existen dos tipos de rueda loca:

                                                                              • Rígida

                                                                                Sujeta a la superficie con una estructura rígida que le permite sin embargo girar adelante y atrás.


                                                                                CW_rigidCW_rigid_LEGO

                                                                              • De pivote o Rotatoria

                                                                                Sujeta a la superficie con una estructura que tiene un eje en su centro, anclado a la rueda que puede girar libremente.


                                                                                CW_sweivelCW_sweivel_LEGO

                                                                                Este último tipo de rueda tiene una ventaja fundamental, y es que puede girar en todas direcciones, sin embargo esta facilidad puede convertirse también un problema, conocido como aleteo. El aleteo es ese característico movimiento de vaivén o zigzag que experimentan, por ejemplo los carritos de la compra cuando no tienen peso.

                                                                                En robótica, las ruedas locas se utilizan generalmente en los modelos con una configuración diferencial, que consta de dos ruedas situadas diametralmente opuestas en un eje perpendicular a la dirección del robot. Cada una de ellas irá dotada de un motor, de forma que los giros se realizan dándoles diferentes velocidades, o frenando una rueda mientras gira la otra.

                                                                                Con dos ruedas es imposible mantener la horizontalidad del robot. Se producen cabeceos al cambiar la dirección. Para solventar este problema, se colocan ruedas locas. Estas ruedas, como hemos visto más arriba, no llevan ningún motor, giran libremente según la velocidad del robot. Además, pueden orientarse según la dirección del movimiento.


                                                                                Configuración diferencial

                                                                                Hoy queremos mostraros la nueva rueda loca minimalista.

                                                                                tribot

                                                                                Para construir esta rueda, necesitaréis estas piezas:

                                                                                piezas

                                                                                La rueda que aparece es la ref. 42610

                                                                                Y seguir estas pequeñas instrucciones:

                                                                                inst

                                                                                Aquí tenéis una imagen comparativa entre la rueda loca tradicional y la minimalista


                                                                                comparativa

                                                                                Esta nueva versión de la rueda loca ocupa mucho menos espacio, requiere menos piezas y baja el perfil del modelo, lo cual también puede provocar ciertos problemas de roces cuando el vehículo lleva mucho peso. Sin embargo, para modelos ligeros es sorprendentemente ágil, todos los robots del experimento de Vehículos de Braitenberg en NXT-G montaban esta nueva rueda.

                                                                                Podéis encontrar un estudio en profundidad sobre la física de las ruedas locas en el artículo de James J. Kauzlarich (en inglés).

                                                                              • RobotC para MINDSTORMS 2.16 Beta

                                                                                robotc_nxtAcaba de lanzarse la versión Beta2.16 de RobotC para MINDSTORMS. La principal característica es el soporte para el Sensor de Color y el sensor de Temperatura por I2C, ambos de LEGO Education.

                                                                                La siguiente es la lista de correcciones que han incluido en esta revisión:

                                                                                • Mantiene el tipo de plataforma cuando se elimina la información del Registro.
                                                                                • Mejora la asignación de código de las variables temporales.
                                                                                • Se incorpora “unsigned” para eliminar la advertencia del compilador.
                                                                                • Se añade la clausula “default” para las sentencias switch.
                                                                                • parámetro “SGN” código de operación para ‘flotar’ fue incorrectamente el control de la ‘corto’ resultado para ver si se trataba de un ‘float’ NAN (Not A Number). Esta comprobación sólo debe realizarse cuando el retorno de la función es una variable de tipo float. Este problema se ha solucionado.
                                                                                • Se genera mensaje de error si la declaración pragma “motor and sensors setup” no se encuentra en las primeras líneas del programa. Algunos usuarios tenían estas declaraciones en otros lugares lo que producía ciertos errores.
                                                                                • La herramienta de la barra de estado muestra ahora el número de “puerto” o “nombre de NXT” del robot conectado, que anteriormente era mostrado como “===========”.
                                                                                • Se ha corregido el mal funcionamiento de la barra de desplazamiento vertical cuando la pantalla se ampliaba hasta hacer cabera un fichero completo.

                                                                                Fuente: RobotC

                                                                                Vehículos de Braitenberg en NXT-G

                                                                                todosSiguiendo con la serie de artículos sobre los vehículos de Braitenberg, que ya vimos en los artículos Braitenberg en LeJOS y Braitenberg en RobotC, hemos querido ir un poco más allá haciendo un sistema de robots que, a modo de tren autónomo, buscan la luz de su compañero de delante para seguir el recorrido.

                                                                                El primero de los robots, el que haría de locomotora siguiendo con el símil de los trenes, está controlado remotamente desde el joystick en NXT-G al mismo tiempo que enciende unas bombillas en la parte trasera para dar un punto de referencia al robot que debe seguirlo.

                                                                                El montaje

                                                                                lateral

                                                                                Hay una diferencia importante entre este montaje y el que usamos en los artículos anteriores sobre Braitenberg, y es la posición de los sensores:

                                                                                frontal

                                                                                En este caso hemos optado por ponerlos perpendiculares al robot, en lugar de diagonales como en el caso del montaje anterior. Esto favorece la posibilidad de reconocer la luz que se encuentra frente al robot, ya que tenemos 4 estados (ver artículo sobre máquinas de estados):
                                                                                1. No hay luz o está demasiado lejos (los sensores no reciben luz o es muy escasa) — Parado
                                                                                2. La luz se mueve hacia la derecha (el sensor de la derecha recibe más luz) — Giro a la derecha
                                                                                3. La luz se mueve hacia la izquierda (el sensor de la izquierda recibe más luz) — Giro a la izquierda
                                                                                4. La luz está frente al robot (ambos sensores reciben por igual) — Movimiento hacia delante

                                                                                La implementación de esta máquina de estados está el el programa que viene a continuación.

                                                                                Programa

                                                                                Tomamos inicialmente el valor de la luz ambiente (cuando no se han encendido aún las bombillas) luego comprobamos si la diferencia entre las nuevas medidas de luz son suficientemente grandes con respecto a luz ambiente como para considerar que esa es nuestra bombilla objetivo, y si ese es el caso nos moveremos de acuerdo con sus desplazamientos: girando cuando la bombilla gira o recto si el robot guía sigue recto.

                                                                                El valor con el cual comparamos depende directamente de nuestras condiciones de luz y del montaje concreto de nuestro robot, en nuestros experimentos esa diferencia ha tomado valores entre 7 y 15.

                                                                                Es muy importante reseñar la necesidad de unas condiciones correctas de luz para este experimento, ya que, aunque se ha realizado un estudio previo de la luz ambiente, necesitamos poder diferenciar correctamente la luz que proviene de nuestra bombilla de cualquier otra fuente de luz (una lámpara, la luz solar, etc… ). Por lo que se aconseja un entorno lo más oscuro posible.

                                                                                En el siguiente vídeo se pueden apreciar los resultados del experimento

                                                                                Cabe destacar que todos nuestros robots llevan exactamente el mismo montaje y programa, con la excepción del primero (que es controlado remotamente).
                                                                                Además se ha introducido un pequeño cambio de última hora en el programa que veíamos más arriba, todos los robots seguidores esperan un sonido fuerte (por ejemplo una palmada) antes de comenzar la ejecución del bucle principal.

                                                                                Sin embargo, tal y como se aprecia en el vídeo, cuando los giros son muy cerrados, o el vehículo que va delante se mueve demasiado rápido, podemos llegar a perder la referencia.

                                                                                Manejo simultaneo de varios NXT en LabVIEW

                                                                                Artículo nº 6 de la serie de 14 artículos sobre LabVIEW

                                                                                MiniNXTEn el artículo de hoy vamos a ver cómo conectarnos y manejar varios NXT desde LabVIEW. Es un proceso algo complicado, y debido a las limitaciones propias del Bluetooth, poco fiable. Por tanto la mejor manera de que todo funcione bien es que el PC sea quién realice las conexiones y los cálculos, por lo que trabajaremos en LabVIEW orientado a PC y no a NXT (revisad el artículo LabVIEW orientado a PC o NXT. SubVIs). También explicaré al final cómo renombrar los NXT y descargar el firmware con LabVIEW.

                                                                                Multicontrol:

                                                                                Para poder manejar varios NXT con LabVIEW necesitamos hacer uso del módulo Specify NXT. Necesitamos este módulo porque es el que nos va a permitir seleccionar a cuál de los NXT con los que estamos trabajando queremos que los siguientes comandos hagan referencia. Dicho módulo podéis encontrarlo haciendo click derecho en el Block Diagram y accediendo a: NXT Robotics → NXT I/O → Advanced → Specify NXT. El bloque Specify NXT tiene el siguiente aspecto:

                                                                                Specify NXT

                                                                                En NXT Name elegiremos el nombre de nuestro NXT, y en Connection el tipo de conexión (Bluetooth en este caso, puesto que queremos trabajar con varios simultáneamente). Es muy importante que tengáis los NXT en la lista de dispositivos Bluetooth de vuestro PC, ya que sino LabVIEW no podrá conectarse a ellos. Por supuesto el nombre de NXT que debéis utilizar es el que aparece en Mis sitios de Bluetooth (el administrador de bluetooth de Windows). Os recuerdo que debéis de tener actualizado el firmware del NXT en todos los NXT que vayáis a utilizar, y tener ya descargada la Shell de LabVIEW en el NXT*. Por último, la salida de este bloque es el flujo del programa que queréis que ejecute el NXT, así que lo tenéis que conectar a la parte del programa que queréis que ejecute este NXT.

                                                                                *Para descargar la Shell de LabVIEW en el NXT en un NXT no tenéis mas que haber usado otro programa orientado a PC con este NXT. Si el de multicontrol es el primer programa orientado a PC que usáis con los NXT, LabVIEW tardará muchísimo en descargar la Shell en todos ellos y muy probablemente dará error de conexión.

                                                                                Una vez tengáis terminado el programa y le deis a ejecutar tardará bastante en realizar las conexiones, y además que irá uno por uno. Por esto es recomendable tener un botón de control en el Front Panel de LabVIEW que impida que los NXT ejecuten nada de código hasta que lo aprietes. Así podrás esperar tranquilamente a que todos los NXT se conecten, y una vez conectados iniciar el programa apretando dicho botón. Si no ponéis este botón cada NXT empezará a ejecutar el programa según se conecte, con lo que empezarán totalmente a destiempo.

                                                                                Programa de multicontrol: control simultaneo de seis NXT:

                                                                                ¿Por qué manejar dos o tres NXT si podemos manejar muchos más?… Para probar el experimento hemos hecho un multicontrol con nada menos que seis NXT. Debido a las limitaciones del Bluetooth no actúan de manera completamente simultanea como sí pasaría con un menor número de NXT, pero sin embargo el resultado es más impresionante.

                                                                                MultiNXT

                                                                                El programa consiste en un pequeño control remoto de los NXT desde el ordenador. Podemos ordenar a los NXT que se muevan marcha adelante o marcha atrás, giren hacia uno u otro lado, o frenen. El programa tarda varios minutos en realizar las seis conexiones, pero como el movimiento está controlado por botones no pulsaremos ninguno hasta que no estén todos conectados, cosa que podemos comprobar si en el displays de los NXT ya aparece el mensaje de la Shell. Hemos usado estructuras Case y un bucle cuya condición de salida es apretar otro botón creado por nosotros llamado Stop. El Block Diagram ha quedado de la siguiente manera:

                                                                                Block Diagram

                                                                                Como veis sigue una estructura lineal, es básicamente un Case que comprueba en cada iteración que botón esta presionado. La condiciones se podrían comprobar de manera simultánea en distintos bucles, lo cuál incrementaría la velocidad del programa, aunque esto no tendría un efecto visible en los NXT ya que el mayor retardo lo produce el Bluetooth.

                                                                                El Panel Frontal tiene este aspecto:

                                                                                Front Panel

                                                                                Y finalmente el vídeo de demostración. El modelo de tribot es en este caso un modelo ultraligero con muy pocas piezas inventado por mí. En algún momento subiré las instrucciones de montaje al igual que el modelo que podéis ver en otros vídeos (el tribot modular en el que puedes colocar los sensores rectos y a la distancia que quieras):

                                                                                Como veis no están completamente coordinados, y tampoco se mueven del todo igual. Esto es debido a varios factores: los retardos del Bluetooth sobre todo estando conectados a tantos NXT, que los robots no son exactamente iguales y con que tengan las ruedas algo separadas ya recorren distancias diferentes, y que no están perfectamente alineados al principio, lo que crea un error de navegación acumulativo. Resolver estos problemas no son el principal objetivo de nuestro experimento, sino el ser capaces de manejar varios NXT radio controlados mediante LabVIEW.

                                                                                Tenemos planteado manejar decenas de NXT mediante este sistema en algún momento (la tienda entera en movimiento), así que estad atentos al blog. Si tenéis alguna duda no tengáis reparos en preguntar en el foro que para eso está.

                                                                                Apéndice: Uso de la terminal de NXTs de LabVIEW:

                                                                                Existe una utilidad en LabVIEW para poder conectaros a cualquier NXT disponible, cambiarle el nombre, ver el estado de la batería, actualizar el firmware… Para acceder a esta utilidad tenéis que ir a: Tools → NXT Tools → NXT Terminal…. Os saldrá el siguiente panel:

                                                                                NXT Panel

                                                                                Como veis podéis realizar todas las operaciones que tenéis en el NXT-G, como renombrar el NXT, o manejar los ficheros que hay dentro del NXT. Este panel es bastante útil para actualizar el firmware, ya que normalmente tendrá una versión más actualizada que nuestro NXT-G.

                                                                                LabVIEW orientado a PC o NXT. SubVIs

                                                                                Artículo nº 4 de la serie de 14 artículos sobre LabVIEW

                                                                                subviEn el artículo de hoy vamos a ver qué diferencias hay a la hora de ejecutar un programa para NXT mediante el modo orientado a PC o a NXT. Trataremos además el tema de cómo crear un SubVI, que es la forma que se emplea en LabVIEW para crear funciones o métodos que después podamos usar fácilmente en otros programas.

                                                                                Modo orientado a NXT:

                                                                                En el modo orientado a NXT del LabVIEW tenemos una gran cantidad de funciones para manejar al NXT, y todas aquellas diseñadas para manejar los tipos de datos que usa el NXT (como boolean, integer, Port …). Podemos acceder rápidamente a toda esta funcionalidad haciendo click derecho en el Block Diagram. Muchas de estas funciones y métodos se encuentran también en el modo orientado a PC, pero no todas. Por ejemplo toda la funcionalidad de NXT Native I/O situada en la sección NXT I/O sólo la tenemos disponible en el modo orientado a NXT, y nos da gran precisión a la hora de manejar motores y sensores (podemos, por ejemplo, girar un motor los grados que queramos).

                                                                                Este modo funciona de manera parecida al NXT-G, una vez hemos creado el programa y le damos a ejecutar, éste es descargado al NXT y, una vez ahí, el NXT lo ejecutará. Por tanto en este caso no necesitamos que el NXT esté conectado al ordenador para ejecutar el programa, simplemente bastará con que esté descargado. Sin embargo, si trabajamos en este modo no podremos usar la funcionalidad del Panel Frontal de LabVIEW, ya que en el PC no se estará ejecutando nada.

                                                                                Modo orientado a PC:

                                                                                En el modo orientado a PC tenemos la desventaja de que por un lado las funciones para manejar el NXT no están tan accesibles y, por otro lado, de que no contamos con la funcionalidad de NXT Native I/O, que nos permitiría un control preciso sobre los motores y sensores. Otra desventaja de este modo es que, como el programa se ejecuta en el ordenador, y éste va enviando señales al NXT, necesitamos tener al NXT conectado con el PC. Sin embargo tenemos otras ventajas. La principal ventaja es que como el programa se ejecuta en el PC podemos hacer uso de toda la funcionalidad del Panel Frontal de LabVIEW (una de las características más útiles de LabVIEW). Así podremos mostrar gráficas y llevar un seguimiento del valor de las variables, así como contar con controles para manejar todo lo que queramos.

                                                                                Otra ventaja evidente de la ejecución en el ordenador es que su capacidad de procesado es muy superior, y puede hacer muchas más operaciones y de muchos más tipos que el NXT. Por ejemplo, podemos implementar cases que no dependan solo de True o False, sino de rangos de valores numéricos por ejemplo.

                                                                                Para cambiar entre un modo y otro no tenéis más que meteros en File (estándo en el Block Diagram por ejemplo) y elegir Target to Computer o Target to NXT según a cuál deseéis cambiar. Es posible ejecutar un mismo programa en los dos modos siempre que tenga componentes que ambos conozcan y puedan ejecutar:

                                                                                VI-2-d

                                                                                Cómo crear un subVI:

                                                                                Un subVI no es más que un programa que podemos utilizar dentro de otros programas. Si por ejemplo tenemos un programa que recibe una temperatura en grados Celsius y los transforma a Fahrenheit podríamos crear un SubVI, y así poder usarlo en otros programas. El objetivo fundamental que se persigue con ello es el de reutilizar código y, por otra parte, el de reducir y simplificar visualmente el vi. El aspecto de un SubVI es el de un bloque, como los que usamos para programar los NXT: tendrá sus entradas y sus salidas.

                                                                                Para crear nuestro SubVI lo primero que hacemos es crear el programa deseado. Una vez terminado conectaremos las entradas como Controles (por ejemplo en la siguiente imagen hemos creado tres controles de entrada: Un tipo Puerto, otro NXT, y otro entero). Las salidas sin embargo las pondremos como Indicator (como la salida del flujo de NXT de la siguiente imágen):

                                                                                Una vez terminado este proceso nos vamos al Front Panel donde podréis ver los controles e indicadores de vuestras variables de entrada y salida. Haced click derecho sobre el icono de LabVIEW que está situado en la parte superior derecha de la ventana y elegid Show Connector:

                                                                                VI-2-b

                                                                                Veréis unas pequeñas celdas, las de la izquierda representan las entradas y las de la derecha las salidas. Debéis clickear en cada celda, y posteriormente en el control o indicador con el que queráis enlazar. Deberíais enlazar por tanto todos los conectores de entrada con los controles, y los de salida con los indicadores, de forma que quedara algo así:

                                                                                Si volvéis a hacer click derecho sobre este panel de conexiones de la parte superior derecha podéis editar el icono que queréis que tenga vuestro SubVI. Una vez hayáis terminado con todo este proceso guardad el archivo como si se tratará de un VI normal. Para usarlo vuestro SubVi en otro programa no tenéis más que clickear con el botón derecho en el Block Diagram y elegir Select a VI… Se abrirá una ventana donde podréis elegir el VI que habéis creado anteriormente. Veréis que os crea un cuadrado con el icono que habéis dibujado antes, y con las entradas y salidas que le pusisteis:

                                                                                VI-2-c

                                                                                Espero que este artículo os sea de utilidad. Comentarios bienvenidos en el foro.

                                                                                Sigue líneas en LabVIEW: Máquinas de estado

                                                                                Artículo nº 3 de la serie de 14 artículos sobre LabVIEW

                                                                                maquina_de_estado_finitoContinuamos con la serie de artículos de LabView, esta vez con un programa un poco más complicado. Como sabréis, una máquina de estados finita o autómata finito es un sistema que dependiendo de unas entrada de datos, opera con ella y devuelve una salida. Hay muchas máquinas de estados que nos podemos encontrar en el día a día; por ejemplo las máquinas expendedoras son máquinas de estados, los semáforos son máquinas de estados, hasta los interruptores de luz de nuestra casa son una máquina de estados.

                                                                                Sigue líneas como máquina de estados:

                                                                                En este caso estamos hablando de máquinas de estados finitos porque tienen un número finito de estados. Por ejemplo, un interruptor tendrá dos estados: dejando pasar la corriente o cortando su paso. Un dispensador de bebidas tiene muchos estados, como: esperando pedido o devolviendo cambio. En el caso de la máquina de estados finitos que vamos a hacer hoy vamos a tener tres estados. Se trata de un robot sigue líneas que además esquiva obstáculos situados en su camino. Los estados posibles con los que vamos a trabajar serán: “dentro del blanco”, “dentro del negro”, y “obstáculo”.

                                                                                Tribot

                                                                                Dicha máquina de estados tendrá como entrada los valores de un sensor de ultrasonidos, y del sensor de luz en modo activo. Según sea el valor de esas entradas actuará de una manera u otra. Por ejemplo si esta en lo negro se moverá a lo blanco y viceversa. Sin embargo si detecta un obstáculo a menos de cierta distancia le dará prioridad y tratará de esquivarlo. Por tanto siempre que reciba por la entrada que un obstáculo está a menos de x cm. se moverá al estado “obstáculo”, independientemente del valor de luz captado por el otro sensor.

                                                                                Implementación en LabVIEW:

                                                                                En primer lugar vamos a explicar la estructura case. En LabVIEW se pueden crear estructuras case que podrán ejecutar distintos trozos del programa según qué entrada reciban. Todo esto es configurable, y al crearlo ya viene con una entrada que lee booleanos y con dos estados: uno para false y otro para true. Para crear una estructura case tenéis que situaros en el diagrama de bloques de vuestro VI, darle click derecho → Structures → Case Structure:

                                                                                Case

                                                                                Una vez seleccionado, estiramos para dejarlo del tamaño que queramos y hacemos click para ponerlo. Si clickeáis encima de donde pone true, podéis cambiar a la parte que ejecutaría si la entrada valiese False:

                                                                                Case2

                                                                                Podemos añadir nuevos casos además de True y False, para ello haced click derecho sobre el case y darle a Add Case After (después) o Before (antes) del caso actual:

                                                                                Case3

                                                                                Podemos movernos de uno a otro y ver que hace en cada caso. Por supuesto está vacío en un principio, para que lo rellenemos con nuestro código. Nuestro problema requiere de dos case: el primero comprobará que no haya obstáculos, y el segundo en que parte estamos (blanco o negro). Según en que estado nos encontremos debemos ejecutar solo la parte de código correspondiente, por ejemplo si detectamos un obstáculo es innecesario que comprobemos si estamos en blanco o en negro, ya que no nos sirve para nada, y además es un fallo conceptual (el estado es “obstáculo”, no “obstáculo y en negro” y “obstáculo y en blanco”, ya que si no nuestra máquina tendría cuatro estados y no tres).

                                                                                Una vez tengamos la máquina de estados, es necesario conectarle las entradas (en este caso las lecturas del sensor de ultrasonidos y de luz). Además debemos meterlo todo en un bucle para que se ejecute hasta que apretemos el botón ENTER. El programa quedaría de la siguiente manera:

                                                                                Estado “dentro del blanco”:

                                                                                blanco

                                                                                Estado “dentro del negro”:

                                                                                negro

                                                                                Estado “obstáculo”:

                                                                                obstáculo

                                                                                En este vídeo podéis observar su funcionamiento:

                                                                                Ya sabéis que si tenéis dudas podéis escribir en el foro.

                                                                                Navegación esquivando obstáculos: RobotC

                                                                                Artículo nº 4 de la serie de 8 artículos sobre Visión Artificial

                                                                                  DistanciaEn artículos anteriores como el artículo sobre reconocimiento básico de objetos con OpenCV y el artículo sobre segmentación básica en OpenCV hemos visto como detectar distintos objetos de color determinado en una imagen, e incluso calcular sus puntos medios y distancias entre ellos. En el artículo de hoy vamos a aplicar todos estos conocimientos para realizar un ejemplo práctico en el que gracias a dos imágenes tomadas por la cámara sabremos dónde se encuentran dos bolas rojas respecto al Robot, y lograremos esquivarlas con el robot mientras se mueve en línea recta.

                                                                                  Un poco de trigonometría:

                                                                                  Puesto que vamos a tomar dos fotos, y sabemos:

                                                                                  1) a qué altura está la cámara respecto del suelo, y
                                                                                  2) la inclinación de la cámara en grados,

                                                                                  podemos saber muchos datos, como por ejemplo la distancia hasta la que es capaz de ver la cámara. Teniendo en cuenta la siguiente imagen:

                                                                                  Trigonómetria

                                                                                  Si la cámara está a una altura b del suelo, mirando hacia abajo con un ángulo α, sabemos que la hipotenusa es:

                                                                                  c = b/cos(α)

                                                                                  y que la distancia a abarcada por la cámara es igual a:

                                                                                  a = c*sen(α)

                                                                                  por tanto la relación entre b y a será:

                                                                                  a = b*sen(α)/cos(α)

                                                                                  Así que con tan solo esos dos datos ya podemos saber muchas cosas. Pero, ¿cómo traducir las distancias que hemos hallado en píxeles a centímetros? Es fácil, ya que si sabemos la altura de la imagen (100 píxeles por ejemplo) y la distancia nos ha dado 50 píxeles, solo tenemos que dividir 50 entre 100, que serán 0.5, y multiplicarlo por a, de forma que nos dará más o menos la distancia equivalente en el mundo real (estamos suponiendo que la imagen abarca todo el plano horizontal cuando no es cierto). En cualquier caso es un método bastante preciso.

                                                                                  Programa de OpenCV:

                                                                                  Para procesar las imágenes usaremos OpenCV, y cuando terminemos guradaremos los datos necesarios en un archivo que moveremos al NXT, y que utilizará nuestro programa en RobotC para esquivar las pelotas. Hemos tomado mediante una cámara situada en el NXT las dos imágenes siguientes:

                                                                                  25º

                                                                                  Imagen030

                                                                                  con la cámara situada a 27 cm del suelo y con una inclinación de 25º, y:

                                                                                  50º

                                                                                  Imagen031

                                                                                  con la cámara situada a 30 cm del suelo (sube un poco al rotarla), y con una inclinación de 50º. De la primera imagen podemos sacar la distancia aproximada del robot a la primera pelota (supondremos que el robot está situado en el píxel central de la última linea de la imagen). De la segunda imagen podemos sacar la distancia entre las dos pelotas. Ya sabemos como calcular distancias y puntos medios de objetos de determinado color en OpenCV, así como distinguir dos objetos del mismo color. Incluso como escribir los datos en un fichero para que RobotC los entienda (revisad el artículo sobre manejo de ficheros en RobotC). El programa completo sería:

                                                                                  #include
                                                                                  #include
                                                                                  #include
                                                                                  #include
                                                                                  #include
                                                                                  #include
                                                                                  #include

                                                                                  using namespace std;

                                                                                  int main(int argc, char *argv[])
                                                                                  {
                                                                                  IplImage* img = 0;
                                                                                  int altura,anchura,anchura_fila,canales;
                                                                                  uchar *data;
                                                                                  int i,j;

                                                                                  if(argc<3){
                                                                                  printf("Uso: main \n\7");
                                                                                  exit(0);
                                                                                  }

                                                                                  // cargamos la imagen
                                                                                  img=cvLoadImage(argv[1]);
                                                                                  if(!img){
                                                                                  printf("No se ha podido cargar la imagen: %s\n",argv[1]);
                                                                                  exit(0);
                                                                                  }

                                                                                  // cogemos la información de la imagen
                                                                                  altura = img->height;
                                                                                  anchura = img->width;
                                                                                  anchura_fila = img->widthStep;
                                                                                  canales = img->nChannels;
                                                                                  data = (uchar *)img->imageData;
                                                                                  printf("Procesando una imagen de %dx%d píxeles con %d canales\n",
                                                                                  altura, anchura, canales);

                                                                                  // creamos una ventana
                                                                                  cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE);
                                                                                  cvMoveWindow("mainWin", 100, 100);

                                                                                  // maximos y minimos

                                                                                  int y_anterior = 0;

                                                                                  int x1_cont = 0;
                                                                                  int y1_cont = 0;

                                                                                  int x1_total = 0;
                                                                                  int y1_total = 0;

                                                                                  int x2_cont = 0;
                                                                                  int y2_cont = 0;

                                                                                  int x2_total = 0;
                                                                                  int y2_total = 0;

                                                                                  bool bola1 = false;
                                                                                  bool bola2 = false;

                                                                                  // recorremos la imagen

                                                                                  for(i=0;i

                                                                                  if ((data[i*anchura_fila+j*canales + 2] > 80) &&
                                                                                  !((data[i*anchura_fila+j*canales + 0] > data[i*anchura_fila+j*canales + 2]/2) ||
                                                                                  (data[i*anchura_fila+j*canales + 1] > data[i*anchura_fila+j*canales + 2]/2))){

                                                                                  if(!bola1 && !bola2){
                                                                                  bola1 = true;
                                                                                  y_anterior = i;
                                                                                  }
                                                                                  printf("Punto rojo en %d, %d, valor: %d\n",j,i,
                                                                                  data[i*anchura_fila+j*canales+2]);
                                                                                  if (y_anterior + 20 < i){
                                                                                  bola1 = false;
                                                                                  bola2 = true;

                                                                                  }
                                                                                  if (bola1){
                                                                                  printf("Bola1\n");
                                                                                  y1_total = y1_total + i;
                                                                                  x1_total = x1_total + j;
                                                                                  y1_cont++;
                                                                                  x1_cont++;
                                                                                  data[i*anchura_fila+j*canales] = 255;
                                                                                  data[i*anchura_fila+j*canales + 1] = 0;
                                                                                  data[i*anchura_fila+j*canales + 2] = 0;
                                                                                  } else if(bola2){
                                                                                  printf("Bola2\n");
                                                                                  y2_total = y1_total + i;
                                                                                  x2_total = x1_total + j;
                                                                                  y2_cont++;
                                                                                  x2_cont++;
                                                                                  data[i*anchura_fila+j*canales] = 0;
                                                                                  data[i*anchura_fila+j*canales + 1] = 255;
                                                                                  data[i*anchura_fila+j*canales + 2] = 0;
                                                                                  }
                                                                                  y_anterior = i;
                                                                                  }

                                                                                  }

                                                                                  int x1_medio = x1_total / x1_cont;
                                                                                  int y1_medio = y1_total / y1_cont;

                                                                                  printf("Punto medio bola1 en %d, %d, valor: %d\n",x1_medio, y1_medio,
                                                                                  data[i*anchura_fila+j*canales+2]);

                                                                                  int x2_medio = x2_total / x2_cont;
                                                                                  int y2_medio = y2_total / y2_cont;

                                                                                  printf("Punto medio bola2 en %d, %d, valor: %d\n",x2_medio, y2_medio,
                                                                                  data[i*anchura_fila+j*canales+1]);

                                                                                  float distancia = sqrt(pow(x1_medio - x2_medio,2) + pow(y1_medio - y2_medio,2));

                                                                                  // mostramos la imagen
                                                                                  cvShowImage("mainWin", img );
                                                                                  // esperamos apretar una tecla
                                                                                  cvWaitKey(0);

                                                                                  // liberar la imagen
                                                                                  cvReleaseImage(&img );

                                                                                  img=cvLoadImage(argv[2]);
                                                                                  if(!img){
                                                                                  printf("No se ha podido cargar la imagen: %s\n",argv[2]);
                                                                                  exit(0);
                                                                                  }

                                                                                  // cogemos la información de la imagen
                                                                                  altura = img->height;
                                                                                  anchura = img->width;
                                                                                  anchura_fila = img->widthStep;
                                                                                  canales = img->nChannels;
                                                                                  data = (uchar *)img->imageData;
                                                                                  printf("Procesando una imagen de %dx%d píxeles con %d canales\n",
                                                                                  altura, anchura, canales);

                                                                                  // creamos una ventana
                                                                                  cvNamedWindow("mainWin", CV_WINDOW_AUTOSIZE);
                                                                                  cvMoveWindow("mainWin", 100, 100);

                                                                                  // maximos y minimos

                                                                                  int x_cont = 0;
                                                                                  int y_cont = 0;

                                                                                  int x_total = 0;
                                                                                  int y_total = 0;

                                                                                  //recorremos la imagen

                                                                                  for(i=0;i

                                                                                  if ((data[i*anchura_fila+j*canales + 2] > 80) &&
                                                                                  !((data[i*anchura_fila+j*canales + 0] > data[i*anchura_fila+j*canales + 2]/2) ||
                                                                                  (data[i*anchura_fila+j*canales + 1] > data[i*anchura_fila+j*canales + 2]/2))){
                                                                                  printf("Punto rojo en %d, %d, valor: %d\n",j,i,
                                                                                  data[i*anchura_fila+j*canales+2]);
                                                                                  y_total = y_total + i;
                                                                                  x_total = x_total + j;
                                                                                  y_cont++;
                                                                                  x_cont++;
                                                                                  data[i*anchura_fila+j*canales] = 255;
                                                                                  data[i*anchura_fila+j*canales + 1] = 0;
                                                                                  data[i*anchura_fila+j*canales + 2] = 0;
                                                                                  }
                                                                                  }

                                                                                  int x_medio = x_total / x_cont;
                                                                                  int y_medio = y_total / y_cont;

                                                                                  printf("Punto medio rojo en %d, %d, valor: %d\n",x1_medio, y1_medio,
                                                                                  data[i*anchura_fila+j*canales+2]);

                                                                                  int xr_medio = anchura / 2;
                                                                                  int yr_medio = altura - 1;

                                                                                  printf("Punto medio robot en %d, %d, valor: %d\n",xr_medio, yr_medio,
                                                                                  data[i*anchura_fila+j*canales+1]);

                                                                                  float distancia2 = sqrt(pow(x_medio - xr_medio,2) + pow(y_medio - yr_medio,2));

                                                                                  printf("Distancia entre ambas pelotas: %6.2f\n", distancia);
                                                                                  printf("Distancia del robot a la pelota: %6.2f\n", distancia2);

                                                                                  ofstream fsalida("distancias.dat", ofstream::out | ios::binary);
                                                                                  fsalida.write(reinterpret_cast(&distancia), sizeof(distancia));
                                                                                  fsalida.write(reinterpret_cast(&distancia2), sizeof(distancia2));
                                                                                  fsalida.close();

                                                                                  // mostramos la imagen
                                                                                  cvShowImage("mainWin", img );
                                                                                  // esperamos apretar una tecla
                                                                                  cvWaitKey(0);

                                                                                  // liberar la imagen
                                                                                  cvReleaseImage(&img );
                                                                                  return 0;
                                                                                  }

                                                                                  Ya tenemos toda la información que necesitamos dentro de fichero.dat. Ahora necesitaremos mover ese fichero al NXT, para ello podemos utilizar el File Managment de RobotC.

                                                                                  IMG_2398

                                                                                  Programa de RobotC:

                                                                                  El programa de RobotC se encargará de, con los datos recibidos en el fichero de OpenCV, hacer los cálculos necesarios y moverse a lo largo de la línea esquivando las bolas. Para eso necesitamos los siguientes datos: altura y ángulo de la cámara en la primera foto, altura y ángulo de la cámara en la segunda foto, diámetro de la rueda (lo usaremos para saber cuántos cm se mueve el robot), altura de la imagen en píxeles, tamaño del robot en cm., y distancia de seguridad con la bola que necesitamos para realizar el giro para esquivarla. El programa quedaría de la siguiente manera:

                                                                                  const string sFileName = "distancias.dat";

                                                                                  TFileIOResult nIoResult;
                                                                                  TFileHandle hFileHandle;

                                                                                  int nFileSize;
                                                                                  float altura1 = 27;
                                                                                  float altura2 = 30;
                                                                                  float grados1 = 25;
                                                                                  float grados2 = 50;
                                                                                  float diametro_rueda = 17;
                                                                                  float altura_imagen = 280;
                                                                                  float tamano_robot = 18;
                                                                                  float distancia_seg = 10;
                                                                                  {
                                                                                  float FloatData;

                                                                                  OpenRead(hFileHandle, nIoResult, sFileName, nFileSize);

                                                                                  ReadFloat(hFileHandle, nIoResult, FloatData);
                                                                                  nxtDisplayCenteredTextLine(3, "Distancia:");
                                                                                  nxtDisplayCenteredTextLine(5, "%6.2f Pixeles" , FloatData);

                                                                                  float distancia = (FloatData/altura_imagen) *
                                                                                  (altura2*sinDegrees(grados2)/cosDegrees(grados2));
                                                                                  float rotacion2 = (distancia - distancia_seg - tamano_robot) *
                                                                                  360/diametro_rueda;

                                                                                  ReadFloat(hFileHandle, nIoResult, FloatData);
                                                                                  nxtDisplayCenteredTextLine(3, "Distancia:");
                                                                                  nxtDisplayCenteredTextLine(5, "%6.2f Pixeles" , FloatData);

                                                                                  distancia = (FloatData/altura_imagen) *
                                                                                  (altura1*sinDegrees(grados1)/cosDegrees(grados1));
                                                                                  float rotacion1 = (distancia - distancia_seg) *
                                                                                  360/diametro_rueda;

                                                                                  Close(hFileHandle, nIoResult);

                                                                                  while ((nMotorEncoder[motorC] < rotacion1) &&
                                                                                  (nMotorEncoder[motorA] < rotacion1)){
                                                                                  motor[motorC]= 30;
                                                                                  motor[motorA]= 30;
                                                                                  }

                                                                                  motor[motorA]=10;
                                                                                  motor[motorC]=40;
                                                                                  wait1Msec(1000);
                                                                                  motor[motorA]=40;
                                                                                  motor[motorC]=15;
                                                                                  wait1Msec(2500);
                                                                                  motor[motorA]=0;
                                                                                  motor[motorC]=40;
                                                                                  wait1Msec(800);

                                                                                  motor[motorA]=0;
                                                                                  motor[motorC]=0;

                                                                                  nMotorEncoder[motorA] = 0;
                                                                                  nMotorEncoder[motorC] = 0;

                                                                                  while ((nMotorEncoder[motorC] < rotacion2) &&
                                                                                  (nMotorEncoder[motorA] < rotacion2)){
                                                                                  motor[motorC]= 30;
                                                                                  motor[motorA]= 30;
                                                                                  }

                                                                                  motor[motorA]=0;
                                                                                  motor[motorC]=0;

                                                                                  motor[motorA]=10;
                                                                                  motor[motorC]=40;
                                                                                  wait1Msec(1000);
                                                                                  motor[motorA]=40;
                                                                                  motor[motorC]=15;
                                                                                  wait1Msec(2500);
                                                                                  motor[motorA]=0;
                                                                                  motor[motorC]=40;
                                                                                  wait1Msec(800);

                                                                                  motor[motorA]=0;
                                                                                  motor[motorC]=0;

                                                                                  return;
                                                                                  }

                                                                                  Ahora una breve explicación del programa:

                                                                                  1 a 14 - Declaración e inicialización de variables.

                                                                                  20 a 22 - Leemos del fichero y mostramos su contenido.

                                                                                  24 - Hacemos el cálculo de la distancia en cm, tal y como os he puesto en el apartado de trigonometría de este artículo.

                                                                                  26 - Para saber cuantos grados necesitamos rotar los motores para que el robot recorra determinada distancia, se multiplica la distancia por 360, y se divide entre el diámetro de las ruedas. Como en este caso es la distancia entre las dos bolas, a la distancia calculada anteriormente necesitamos restarle tanto la longitud del robot como la distancia de seguridad con la bola para poder realizar el giro necesario para esquivarla.

                                                                                  35 - En este caso es la distancia a la primera bola, por lo que solo le restaremos la distancia de seguridad para esquivarla.

                                                                                  40 a 44 - Aquí nos movemos hasta la primera bola, rotando los motores el número de grados calculados en la línea 35 del programa.

                                                                                  46 a 60 - Esta es la maniobra evasiva. Esta programada a mano, sin ningún cálculo, mediante ensayo y error para que el robot quedara justo pegado a la primera bola, entre la primera y la segunda bola. Esta maniobra de evasión variará mucho en función del objeto a esquivar, así como la distancia de seguridad en cm para realizarla.

                                                                                  62 a 66 - Aquí nos movemos hasta la segunda bola, rotando los motores el número de grados calculados en la línea 26 del programa.

                                                                                  71 a 79 - Otra maniobra para esquivar la segunda bola.

                                                                                  81 a 82 - Paramos el robot, y fin del programa

                                                                                  Y ahora os dejo un vídeo demostrativo del programa:

                                                                                  Espero que os haya gustado. Se que todo esto es un poco complicado, pero es solo una muestra de las cosas que se pueden hacer con una cámara. En un futuro los LEGO Mindstorms NXT tendrán cámaras, y su manejo será más sencillo que todo esto. Ante cualquier pregunta no dudéis en postear en el foro.

                                                                                  NXT Sonar 3D en RobotC con coordenadas esféricas

                                                                                  Artículo nº 13 de la serie de 14 artículos sobre RobotC

                                                                                    Coord_sysEl objetivo de este experimento es lograr una representación de la visión que obtiene el robot mediante un movimiento del sonar en dos ejes. Para ello se ha situado al sensor ultrasónico en una plataforma capaz de moverse con dos grados de libertad y se han realizado lecturas medidas en varias posiciones. En este caso haremos una representación transformando los datos obtenidos anteriormente a coordenadas esféricas.

                                                                                    El objetivo de este artículo es doble: por un lado realizar una representación más acorde a la realidad, puesto que los datos se están capturando mediante un sistema de coordenadas esféricas y la representación que hemos elegido en gnuplot es mediante representación cartesiana. Aprovechando el hecho de que hemos realizado previamente un artículo sobre tratamiento de ficheros en RobotC y de que debemos hacer una transformación de coordenadas, para trabajar ambos conceptos dividiremos el proceso en dos partes:

                                                                                    • En una primera parte haremos uso del programa implementado en el artículo anterior, NXT Sonar 3D en RobotC, modificándolo para que escriba la información de las lecturas realizadas en coordenadas esféricas como floats en un fichero.
                                                                                    • La segunda parte la realiza un segundo programa que se encarga de leer la información del fichero que ha creado el primero, convertir los datos a coordenadas de esféricas a cartesianas, y crear un segundo fichero que será el que gnuplot leerá y representará.

                                                                                    Coordenadas esféricas:

                                                                                    El sistema de coordenadas esféricas se utiliza para determinar la posición espacial de un punto mediante una distancia y dos ángulos. En este caso contamos con esos datos, ya que tenemos los grados que ha girado horizontalmente, la elevación (grados verticales), y la distancia, medida por el sensor de ultrasonidos. Teniendo entonces un punto de la siguiente manera:

                                                                                    Spherical_with_grid

                                                                                    Podríamos hallar las coordenadas cartesianas (x, y, z) del punto mediante las siguientes formulas:

                                                                                    Coordenas Esféricas

                                                                                    Puesto que RobotC dispone de funciones trigonométricas para hallar senos y cosenos es posible implementar esta conversión.

                                                                                    Modificación del primer programa:

                                                                                    Modificaremos el primer programa para que escriba los datos como float, y así practicaremos lectura y escritura de ficheros (revisad el artículo sobre manejo de ficheros en RobotC). La parte del código donde se escribe en el fichero es la siguiente:

                                                                                    WriteFloat(hFileHandle, nIoResult, (float)angle);
                                                                                    WriteFloat(hFileHandle, nIoResult, (float)elevation);
                                                                                    WriteFloat(hFileHandle, nIoResult, (float)distance_in_cm );
                                                                                    //sString = "" + angle + ".0 " + elevation + ".0 " + distance_in_cm + ".0\n";
                                                                                    //WriteText(hFileHandle, nIoResult, sString);

                                                                                    La parte que está comentada es lo que teníamos escrito anteriormente. Ahora ya podemos crear nuestro programa que leerá de este archivo y escribirá los datos ya procesados en uno nuevo.

                                                                                    Programa de conversión de coordenadas esféricas a cartesianas:

                                                                                    Este programa lee datos de tipo float de un archivo, calcula las correspondientes coordenadas (x, y, z) , y las escribe en un nuevo archivo de texto que gnuplot sea capaz de interpretar. El código es el siguiente:

                                                                                    const string nombre_fichero1 = "DatosRadar3D.dat";
                                                                                    const string nombre_fichero2 = "NDatosRadar3D.dat";
                                                                                    TFileIOResult error_IO;
                                                                                    TFileHandle fichero1;
                                                                                    TFileHandle fichero2;
                                                                                    int tamano = 40000;

                                                                                    task main() {
                                                                                    float x, y, z;
                                                                                    float distance_in_cm, elevation, angle;

                                                                                    Delete(nombre_fichero2, error_IO);
                                                                                    OpenWrite(fichero2, error_IO, nombre_fichero2, tamano);
                                                                                    OpenRead(fichero1, error_IO, nombre_fichero1, tamano);

                                                                                    for(int i = 1; i <= 1025; i++){
                                                                                    ReadFloat(fichero1, error_IO, angle);
                                                                                    ReadFloat(fichero1, error_IO, elevation);
                                                                                    ReadFloat(fichero1, error_IO, distance_in_cm);
                                                                                    if (distance_in_cm != 255){
                                                                                    x = distance_in_cm*cosDegrees(elevation)*cosDegrees(angle);
                                                                                    y = distance_in_cm*cosDegrees(elevation)*sinDegrees(angle);
                                                                                    z = distance_in_cm*sinDegrees(elevation);
                                                                                    string cadena;
                                                                                    cadena = (int)x + " " + (int)y + " " +(int)z + "\n";
                                                                                    WriteText (fichero2, error_IO, cadena);
                                                                                    }
                                                                                    }

                                                                                    Close(fichero1, error_IO);
                                                                                    Close(fichero2, error_IO);
                                                                                    }

                                                                                    Ahora una explicación de algunas líneas:

                                                                                    12 - Borramos el archivo, para que al abrirlo cree uno nuevo y vacío.

                                                                                    13 y 14 - Abrimos tanto para lectura el que tiene los datos, como para escritura el que vamos a crear.

                                                                                    16 - Este bucle nos asegura que leeremos el fichero entero, ya que sabiendo que tenemos 40 lecturas horizontales por 25 lecturas verticales.

                                                                                    20 - Aquí realizamos un filtrado que tiene por objeto eliminar las medidas que hayan podido ser erróneas (eliminamos los 255).

                                                                                    21 a 23 - Hacemos la transformación de coordenadas esféricas a cartesianas.

                                                                                    24 a 26 - Y finalmente las escribimos en el nuevo fichero.

                                                                                    Ya solo nos queda representar los datos que hemos calculado con el gnuplot.

                                                                                    Representación con el gnuplot:

                                                                                    Hemos puesto a nuestro radar enfrente de un enorme camión de LEGO, y después de pasarle el radar3D, y usar el nuevo programa para convertir las coordenadas esféricas estamos listos para representarlo. Lo primero que hay que hacer es descargar el archivo del NXT, y emplazarlo en la carpeta binary del gnuplot. Una vez hecho esto ejecutamos el wgnuplot.exe, se abrirá una ventana donde tenemos que copiar estos datos:

                                                                                    set title "Lecturas del sensor ultrasónico"
                                                                                    unset hidden3d
                                                                                    set ticslevel 2.0
                                                                                    set view 38,45
                                                                                    set autoscale
                                                                                    set parametric
                                                                                    set style data lines
                                                                                    set key box
                                                                                    set dgrid3d 30,30,100
                                                                                    set xlabel "www.electricBricks.com"
                                                                                    splot "NDatosRadar3D.dat"
                                                                                    pause -1 "Hit return to continue (1)"
                                                                                    reset

                                                                                    11 - Aquí es donde va el nombre de vuestro archivo.

                                                                                    Si todo sale bien os saldrá una gráfica, donde se verá más o menos representado el objeto que hubiese enfrente del NXT. En nuestro caso sale lo siguiente:

                                                                                    Representacion_Grafica

                                                                                    Se ve el perfil del camión, con su altura aproximada de 30cm y posicionado en diagonal.

                                                                                    Manejo de Ficheros en RobotC

                                                                                    Artículo nº 12 de la serie de 14 artículos sobre RobotC

                                                                                    Una de las funcionalidades más pobres, peor implementadas, y con menor documentación de RobotC es el manejo de ficheros. El manejo de ficheros es una herramienta muy útil que nos puede servir para guardar información y usarla con otros programas, independientemente de su lenguaje de programación. Por tanto es la universalidad de los ficheros la que nos permitiría comunicar RobotC con otro programa de cualquier lenguaje. Sin embargo los creadores de RobotC no vieron está utilidad cómo algo importante, y por tanto no han implementado la funcionalidad necesaria para el correcto manejo de ficheros.

                                                                                    Apertura y cierre de ficheros:

                                                                                    Antes de poder operar con un fichero es necesario abrirlo. En la mayoria de lenguajes de programación hay dos modos de apertura, modo lectura y modo escritura. Abrimos en modo lectura si queremos leer la información que hay en el archivo, sin modificarlo. Abrimos en modo escritura si queremos escribir en el archivo, modificando su contenido.

                                                                                    OpenRead(fichero, error, nombre_fichero, tamaño_fichero) :  Abre el fichero con el nombre indicado en modo lectura. En tamaño_fichero tenemos que indicar el tamaño del fichero. La variable error será distinta de 0 si ha habido algún error a la hora de abrir el fichero. A partir de que este abierto trabajaremos con el fichero usando el nombre que le hayamos dado a la variable fichero.

                                                                                    Nota: La variable fichero es de tipo TFileHandle, y la variable error es de tipo TFileIOResult.

                                                                                    OpenWrite(fichero, error, nombre_fichero, tamaño_fichero) : En este caso se abre el fichero en modo escritura. En caso de que el fichero no exista se crea automáticamente, y si existe se borra su contenido antes de empezar a escribir.

                                                                                    Close(fichero, error) : Cierra el fichero. Como siempre la variable error nos indicará si ha habido algún problema, en este caso a la hora de cerrar el fichero.

                                                                                    Es muy importante cerrar los ficheros cuando hemos acabado de usarlos, o podrían generar errores la próxima vez que queramos utilizarlos.

                                                                                    Lectura de ficheros:

                                                                                    En RobotC hay diversas funciones para leer de ficheros, se diferencian principalmente en el tipo de dato que leen. Para que la lectura sea correcta el dato tiene que haber sido escrito con el mismo tipo que vamos a leer. El principal problema de RobotC es que no tiene capacidad de leer strings, lo que es un gran problema a la hora de tratar de leer archivos con strings. Las funciones para leer de archivos en RobotC son las siguientes:

                                                                                    ReadByte(fichero, error, nParm): Lee una variable tipo byte (8-bit) del fichero especificado. El valor de la variable error es distinto de cero si ocurre algún error de lectura.

                                                                                    ReadFloat(fichero, error, fParm): Lee una variable tipo float del fichero especificado.  El valor de la variable error es distinto de cero si ocurre algún error de lectura.

                                                                                    ReadLong(fichero, error, nParm): Lee una variable tipo long (32-bit) del fichero especificado.  El valor de la variableerror es distinto de cero si ocurre algún error de lectura.

                                                                                    ReadShort(fichero, nIoResult, nParm): Lee una variable tipo short(16-bit) del fichero especificado.  El valor de la variableerror es distinto de cero si ocurre algún error de lectura.

                                                                                    Estas funciones solo nos servirán a la hora de leer variables que hayamos escrito con la función de escritura correspondiente que escriba el mismo tipo de dato en el fichero. Hay que ser muy cuidadoso a la hora de escribir ficheros en otros lenguajes de programación para que los lea RobotC, ya que muchas veces no funcionará. Los más compatibles serán lógicamente C y C++.

                                                                                    Escritura de ficheros:

                                                                                    Al contrario que con la lectura, sí se pueden escribir ficheros con strings, lo cuál nos ayudará bastante a la hora de usar datos capturados por RobotC con otro programa (por ejemplo dibujar gráficas con el gnuplot). Otros tipos de datos escritos por RobotC pueden ser leídos por RobotC con la función de lectura correspondiente, o por otros lenguajes de programación. Las funciones para la escritura en ficheros en RobotC son:

                                                                                    WriteByte(fichero, error, nParm): Escribe un byte en el fichero especificado. El valor de la variable error es distinto de cero cuando existe algún error de escritura.

                                                                                    WriteFloat(fichero, error, fParm): Escribe un float en el fichero especificado. El valor de la variable error es distinto de cero cuando existe algún error de escritura.

                                                                                    WriteLong(fichero, error, nParm): Escribe un long (32-bit) en el fichero especificado. El valor de la variable error es distinto de cero cuando existe algún error de escritura.

                                                                                    WriteShort(fichero, error, nParm): Escribe un short (16-bit) en el fichero especificado. El valor de la variable error es distinto de cero cuando existe algún error de escritura.

                                                                                    WriteString(fichero, error, sParm): Escribe una cadena de texto con el terminador null en el fichero especificado. El valor de la variable error es distinto de cero cuando existe algún error de escritura.

                                                                                    WriteText(fichero, error, sParm): Escribe una cadena de texto sin el terminador null en el fichero especificado. El valor de la variable error es distinto de cero cuando existe algún error de escritura.

                                                                                    La escritura de ficheros en RobotC esta más desarrollada que la lectura, lo que potencia la compatibilidad e itercambio de datos con otros programas. Aún así es curioso que el manejo de ficheros en RobotC sea más limitado que en NXT-G, cuando NXT-G debería ser mucho más limitado en este aspecto.

                                                                                    ¿Qué escribe RobotC en los ficheros?:

                                                                                    Para ver qué escribe RobotC en los ficheros hemos hecho unas pruebas: escribimos ficheros con las distintas funciones de RobotC, y luego vemos su contenido con un editor hexadecimal. Los resultados son los siguientes:

                                                                                    WriteByte(fichero, error, (byte)  ”a”):

                                                                                    hex1

                                                                                    Veamos cómo efectivamente escribe un byte (8 bits)

                                                                                    ff

                                                                                    WriteFloat(fichero, error, (float) 17.0):

                                                                                    hex2

                                                                                    Vemos que los float ocupan unos 32 bits (4 bytes).

                                                                                    WriteFloat(fichero, error, (float) 1.5):
                                                                                    WriteFloat(fichero, error, (float) 1.5):

                                                                                    hex3

                                                                                    Entre float y float no se escribe ningún byte, sino que van seguidos. A la hora de leerlos simplemente se leerán 32 bits y se interpretarán. Esto sucede también con todos los demás tipos de escritura.

                                                                                    WriteLong(fichero, error, (long) 17):

                                                                                    hex4

                                                                                    Los long también se componen de 32 bits como vimos en su definición.

                                                                                    WriteShort(fichero, error, (short) 17):

                                                                                    hex5

                                                                                    Así como los short se componen de solo 16 bits (la mitad).

                                                                                    WriteString(fichero, error, “hola”):

                                                                                    hex6

                                                                                    Se escribe hola, a byte por carácter, añadiendo el null al final (el byte 00).

                                                                                    WriteText(fichero, error, “hola”):

                                                                                    hex7

                                                                                    Aquí podemos ver cómo WriteText no escribe el carácter null al final de cada cadena de texto.

                                                                                    Cómo escribir ficheros en C++ para leerlos en RobotC:

                                                                                    Para escribir datos en un fichero de tipo float, long … necesitaremos crear un archivo de tipo binario. Luego necesitaremos escribir en dicho fichero haciendo un casting especial, indicando el tipo de archivo que vamos a escribir, y su tamaño. Por último cerraremos el fichero. El código que escribe un float en un fichero binario capaz de ser leído por RobotC sería así:

                                                                                    float numero = 10;
                                                                                    ofstream fsalida("numero.dat", ofstream::out | ios::binary);
                                                                                    fsalida.write(reinterpret_cast<char *>(&numero), sizeof(numero));
                                                                                    fsalida.close();

                                                                                    Espero que os haya gustado el artículo, ante cualquier duda podéis preguntar en el foro, que para eso estamos.

                                                                                    P.D.: Este artículo hace referencia a la versión 2.02 de RobotC, la versión más actualizada a fecha de publicación de este artículo.

                                                                                    LEGO Mindstorms NXT-G Programming Guide

                                                                                    Artículo nº 13 de la serie de 14 artículos sobre Libros

                                                                                      En breve podremos disponer de la segunda edición de LEGO MINDSTORMS NXT-G Programming Guide. Esta edición cubre tanto al NXT-G 1.0 como al 2.0 e incluye ejercicios y sugerencias en cada capítulo orientados a los educadores que quieren emplear el NXT-G como un vehículo para la enseñanza de robótica o de los principios de programación.

                                                                                      LEGO MINDSTORMS NXT-G Programming Guide, 2nd Ed. está centrado en la programación mediante el lenguaje gráfico NXT-G. Se aplica el NXT-G a problemas reales como desplazarse y girar, localizar objetos basándose en su color, toma de decisiones, etc. Este libro es adecuado para los mayores de 10 años que se inician en el mundo de la programación, puesto que no sólo se cubre el lenguaje, sino también las matemáticas empleadas o la forma de calibrar y ajustar el robot para que los programas se ejecuten según esperamos.

                                                                                      • Proporciona técnicas de programación y sencillos ejemplos para cada bloque de programación.
                                                                                      • Incluye ejercicios que pueden ser empleados por educadores.
                                                                                      • Proporciona las instrucciones de montaje de un robot que se emplea para probar los ejemplos.

                                                                                      Este libro permite el aprendizaje de:

                                                                                      • Conocimientos básicos de programación.
                                                                                      • Programación gráfica del robot mediante NXT-G 1.0 y 2.0.
                                                                                      • El nuevo sensor de color que se incluye en el conjunto NXT 2.0.
                                                                                      • Control manual de un robot desde un dispositivo de control remoto.
                                                                                      • Principios básicos de matemáticas que mejorarán tus capacidades como programador.

                                                                                      NXT Sonar 3D en RobotC

                                                                                        Gráfica_El objetivo de este experimento es lograr una representación de la visión que obtiene el robot mediante un movimiento del sonar en dos ejes. Para ello se ha situado al sensor ultrasónico en una plataforma capaz de moverse con dos grados de libertad y se han realizado lecturas medidas en varias posiciones.

                                                                                        hyperboloideDado que la posición del robot que sostiene el sensor es fija y sólo se trabaja con movimientos de ángulo horizontal y vertical del mismo, si tomamos las lecturas del robot enfrentado a una pared y las representamos deberíamos obtener una figura similar a una de las hojas de un hiperboloide de dos hojas. Como el alcance del sensor es limitado, esta figura será truncada por el alcance del sensor. Existirán además otras distorsiones derivadas de la dependencia que tiene la medida de la distancia con ángulo con el que la superficie se enfrenta al sensor.

                                                                                        Desafortunadamente la teoría anterior no se traduce tan fácilmente en resultados acordes. El motivo es que la distancia devuelta por el sensor ultrasónico se basa en el principio del tiempo de vuelo, pero las ondas de sonido emitidas por el sonar no siempre rebotan en la superficie más cercana y vuelven directamente. La dirección de reflexión dependerá de varios factores, como las características de la superficie sobre la que se refleja el sonido y el ángulo de incidencia entre el haz de sonido y la superficie. Si el ángulo entre el haz y la superficie es muy bajo y la superficie es lisa, es probable que el haz no vuelva al emisor sino que siga otro camino alejándose (es lo que se denomina reflexión especular), por lo que si vuelve en algún momento al emisor, la distancia que interpretará el sonar no será la distancia correcta existente entre el sonar y la superficie, sino mayor. En el peor de los casos el haz se perderá y no tendremos ninguna lectura de distancia. Este problema puede desaparecer si la superficie con la que se encuentra es rugosa porque al llegar el haz a la misma se producen reflexiones irregulares y en este contexto es más probable que alguno de esos haces dispersos sea devuelto directamente al emisor, proporcionando una lectura correcta.

                                                                                        Vista la limitación anterior, una forma de obtener medidas estadísticamente más correctas sería alterar el medio en el que opera el robot y cubrirlo con una capa rugosa. Podríamos emplear para ello papel de lija, por ejemplo, forrando las superficies sobre las esperamos que incida el haz ultrasónico. Esto puede ser factible en un entorno conocido y controlado o con fines educativos, como cuando hacemos experimentos dentro de un laberinto, pero en un entorno “real” será prácticamente imposible, como por ejemplo si tratamos de medir el fondo marino.

                                                                                        Como la sugerencia de alterar el entorno no es posible en la práctica, nos vemos forzados a solucionar el problema de dos modos posibles, bien incrementando el hardware y trabajando con un array de sensores desfasados que realizan medidas sobre un mismo objeto, o bien complicando el programa que interpreta los datos recibidos: la idea en este segundo caso sería realizar un procesado posterior sobre las medidas que el robot fuera tomando y, almacenando dichas medidas, aceptar como válidas aquellas que fuesen variando de forma paulatina. Esta técnica puede ser combinada con ciertas acciones, como forzando al robot a tomar medidas sobre un mismo punto desde diferentes ángulos en zonas con discontinuidades o incongruencias [1]. Por ejemplo: si nos acercamos hacia una supuesta pared, las medidas recibidas deberán ir reduciéndose y, si no es así, podríamos tomar las medidas como incorrectas o revisar la suposición de la existencia de dicha pared.

                                                                                        Otra forma posible de elevar la garantía de las medidas en un proceso de localización del robot es combinando las lecturas del sonar con las de un segundo dispositivo, como por ejemplo un sistema de visión.

                                                                                        scaner_2d_2_

                                                                                        La siguiente es una vista típica de un sonar en dos dimensiones en la que el robot estaría situado en el triángulo rojo. Nos fijamos en que muchas de las dimensiones crecen en líneas que siguen la dirección de un radio que partiría del robot.

                                                                                        vista de sonar

                                                                                        En el diagrama anterior podemos ver varias zonas en las que se producen saltos bruscos de medidas de distancia, como el señalado con la flecha azul: es bastante sensato pensar que la discontinuidad en la medida entre la parte superior de la zona señalada por el extremo de la línea azul y la parte inferior de la misma sea debida a una reflexión especular. Para confirmar la medida de esa zona podría ser necesario desplazar el robot a una posición como la señalada por el triángulo verde y volver a tomar la medida desde un angulo diferente.

                                                                                        Para el siguiente programa, implementado en robotC, se debe tener en cuenta que el motor A es el encargado de realizar el barrido horizontal, mientras que el motor B es el que proporciona la elevación. En cada barrido horizontal se realizan tantas lecturas como indique el parámetro nr_h_readings, mientras que el parámetro nr_v_readings indica el número de barridos horizontales realizar. Si int nr_v_readings = 1 transformamos el radar 3D en un radar 2D.

                                                                                        Cada una de las lecturas del sensor ultrasónico, situado en el puerto 1 según el programa, se almacena en una línea del fichero de texto testfile.dat. Para que la representación mediante gnuplot sea correcta, en cada una de las líneas del fichero mencionado se incluyen, junto al valor de la lectura de distancia del sensor, las coordenadas de ángulo y elevación.

                                                                                        El siguiente es el inicio del fichero con unas lecturas a modo de ejemplo:

                                                                                        1.0 1.0 39.0
                                                                                        2.0 1.0 39.0
                                                                                        3.0 1.0 39.0
                                                                                        4.0 1.0 39.0
                                                                                        5.0 1.0 38.0


                                                                                        #pragma config(Sensor, S4, sonarSensor, sensorSONAR)
                                                                                        //*!!Code automatically generated by 'ROBOTC' configuration wizard!!*//
                                                                                        //
                                                                                        // www.electricbricks.com
                                                                                        //

                                                                                        const string sFileName = "testfile.dat";
                                                                                        TFileIOResult nIoResult;
                                                                                        TFileHandle hFileHandle;
                                                                                        int nFileSize = 40000;
                                                                                        string sString;

                                                                                        task main() {
                                                                                        int distance_in_cm;
                                                                                        int increment_h = 2; // step increment for angle and elevation
                                                                                        int increment_v = 2; // step increment for angle and elevation
                                                                                        int nr_h_readings = 40; // number of horizontal readings
                                                                                        int nr_v_readings = 25; // number of horizontal lines

                                                                                        nMotorEncoder[motorA] = 0; // Reset Motor A Encoder
                                                                                        nMotorEncoder[motorB] = 0; // Reset Motor B Encoder

                                                                                        Delete(sFileName, nIoResult);
                                                                                        OpenWrite(hFileHandle, nIoResult, sFileName, nFileSize);

                                                                                        for (int angle = 0; angle <= nr_h_readings*increment_h; angle = angle + increment_h) {
                                                                                        for (int elevation = 0; elevation <= nr_v_readings*increment_v; elevation = elevation + increment_v) {
                                                                                        while (nMotorEncoder[motorA] < elevation) {
                                                                                        motor[motorA] = 20;
                                                                                        }
                                                                                        motor[motorA] = 0; // Motor A is sincinc
                                                                                        wait1Msec(50);
                                                                                        distance_in_cm = SensorValue[sonarSensor];
                                                                                        nxtDisplayCenteredTextLine(4, "Distancia: %d", distance_in_cm);
                                                                                        wait1Msec(50);
                                                                                        sString = "" + angle + ".0 " + elevation + ".0 " + distance_in_cm + ".0\n";
                                                                                        WriteText(hFileHandle, nIoResult, sString);
                                                                                        }

                                                                                        while (nMotorEncoder[motorB] < angle){
                                                                                        motor[motorB] = 20;
                                                                                        }
                                                                                        motor[motorB] = 0;

                                                                                        //return motor A:
                                                                                        while (nMotorEncoder[motorA] > 0) {
                                                                                        motor[motorA] = -10;
                                                                                        }
                                                                                        motor[motorA] = 0;

                                                                                        }
                                                                                        //return motor B:
                                                                                        while (nMotorEncoder[motorB] > 0) {
                                                                                        motor[motorB] = -10;
                                                                                        }
                                                                                        motor[motorB] = 0;

                                                                                        Close(hFileHandle, nIoResult);
                                                                                        }

                                                                                        Los datos se graban en un fichero que, finalizada la ejecución del programa, debe ser enviado posteriormente al PC para su representación gráfica. La aplicación que hemos empleado para representar los datos leídos es gnuplot y el programa encargado de ello es el siguiente:

                                                                                        #
                                                                                        # Representación de las lecturas del lector ultrasónico
                                                                                        #
                                                                                        # www.electricbricks.com
                                                                                        #
                                                                                        set title "Lecturas del sensor ultrasónico"
                                                                                        unset hidden3d
                                                                                        set ticslevel 2.0
                                                                                        set view 38,45
                                                                                        set autoscale
                                                                                        set parametric
                                                                                        set style data lines
                                                                                        set key box
                                                                                        set dgrid3d 30,30,100
                                                                                        set xlabel "www.electricBricks.com"
                                                                                        splot "testfile.dat"
                                                                                        pause -1 "Hit return to continue (1)"
                                                                                        reset

                                                                                        Os ponemos un vídeo del proceso de escaneo, acelerado 3x. El robot de la parte inferior es el que realiza el escaneo del modelo TETRIX que tiene enfrente.

                                                                                        Y un vídeo que muestra los resultados del escáner en gnuplot. La depresión que vemos en el gráfico representa la zona en la que se está encontrando al robot Tetrix. La parte plana superior se corresponde a las medidas devueltas con valor 255, valor que devuelve el sensor cuando la distancia está fuera de rango, por lo que esas zonas medidas hay que descartarlas o interpretarlas como fuera de rango.

                                                                                        Nota: Dado que todos los datos se toman desde el mismo punto, lo ideal sería representar la gráfica en coordenadas esféricas y filtrando las mediciones erróneas de 255, pero por simplicidad se ha realizado una representación cartesiana, cuyos valores habría que corregir. Como una primera representación aproximada puede ser suficiente, puesto que no es ése el objetivo de este artículo.

                                                                                        Información adicional:

                                                                                        • A Method of Ultrasonic Sensor Data Integration for Floorplan Recognition, Joong Hyup Ko, et al.

                                                                                        Vehículos de Braitenberg en LeJOS

                                                                                        Artículo nº 6 de la serie de 21 artículos sobre LeJOS

                                                                                        LateralTal y como vimos en el artículo de RobotC Vehículos de Braitenberg en RobotC los vehículos de Braitenberg fueron concebidos por el investigador Italo-Autriaco Valentino Braitenberg para ilustrar de manera evolutiva las habilidades de agentes simples. En el artículo de hoy implementaré un par de comportamientos de vehículos de Braitenberg en LeJOS

                                                                                        Repaso a los ejemplos de vehículos de Braitenberg:

                                                                                        Repetiré aquí los ejemplos más claros y sencillos de vehículos de Braitenberg :

                                                                                        Un primer agente tiene un sensor detector de luz que estimula directamente a su única rueda, implementado las siguientes reglas:

                                                                                        • Más luz produce un movimiento más rápido.
                                                                                        • Menos luz produce un movimiento más lento.
                                                                                        • La oscuridad produce que se quede quieto.

                                                                                        Este comportamiento puede ser interpretado como una criatura que teme a la luz y que se mueve rápido para salir de ella. Su objetivo es encontrar un hueco oscuro en el que ocultarse.

                                                                                        Un segundo agente algo más complicado tiene dos sensores de luz (izquierdo y derecho), cada uno estimulando a la rueda de su mismo lado. Obedece las siguientes reglas:

                                                                                        • Más luz en la derecha → la rueda derecha gira más rapido → gira hacia la izquierda, fuera de la luz.
                                                                                        • Más luz en la izquierda → la rueda izquierda gira más rapido → gira hacia la derecha, fuera de la luz.

                                                                                        Este es más eficiente como un comportamiento para escapar de la fuente de luz, ya que la criatura puede moverse en diferentes direcciones, y tiene a orientarse hacia la dirección desde la que procede menos luz.

                                                                                        En otra variación las conexiones son negativas o innovadoras:

                                                                                        • Más luz en la derecha → la rueda derecha gira más lento → gira hacia la derecha, hacia la luz.
                                                                                        • Más luz en la izquierda → la rueda izquierda gira más lento → gira hacia la izquierda, hacia la luz.

                                                                                        En este caso el agente huye de la oscuridad, moviéndose hacia la luz.

                                                                                        Configuración de motor y sensores de luz:

                                                                                        Aunque hasta ahora hemos estado usando los motores en LeJOS de la manera genérica (accediendo al atributo A, B, o C de la clase Motor), es posible definir los motores con el nombre que elijamos, los cual nos facilitará las cosas a la hora de manejarlos. Para ello usaremos el método constructor de la clase de objetos Motor. Esto se hace de la siguiente manera:

                                                                                        Motor nombre_motor = new Motor(MotorPort.puerto): Creamos un objeto de la clase Motor, cuyo su nombre será el indicado, y que estará anclado al puerto que hayamos elegido(A, B o C).

                                                                                        Respecto a los sensores de luz es importante decir que cuando se declaran (tal y como vimos en el artículo 6 de la serie LeJOS) están en modo activo (con el LED encendido. En estos programas no nos interesa que tengan el LED encendido, ya que nos interfiere a la hora de captar la luz ambiente. Para activarlo o desactivarlo existe el siguiente método de la clase LightSensor:

                                                                                        void setFloodlight(boolean encender): Enciende el LED en caso de que encender sea true, y lo apaga en caso de que encender sea false.

                                                                                        Con estos dos apuntes ya podemos programar nuestro vehículo de Braitenberg.

                                                                                        Vehículo de Braitenberg en LeJOS:

                                                                                        Como ya habréis supuesto los vehículos de Braitenberg no son problemas muy complejos de resolver con LeJOS. En este caso vamos a implementar el segundo comportamiento:

                                                                                        • Más luz en la derecha → la rueda derecha gira más rapido → gira hacia la izquierda, fuera de la luz.
                                                                                        • Más luz en la izquierda → la rueda izquierda gira más rapido → gira hacia la derecha, fuera de la luz.

                                                                                        Por tanto será un robot estilo cucaracha, que huye de la luz y se refugia en la oscuridad. Necesitaremos construir el robot con dos sensores de luz y dos motores. Es importante que los sensores de luz miren más o menos hacia los lados y no hacia adelante, o no podremos medir bien la cantidad de luz que proviene de cada lado. Con un ángulo de 45º es suficiente, como el robot que hemos construido nosotros:

                                                                                        Frontal

                                                                                        A la hora de configurar los sensores de luz es importante que los pongáis en modo pasivo como os indiqué en el apartado anterior del artículo, para que la luz del LED no nos entorpezca en nuestro cometido. Ponedle nombres intuitivos a los motores y a los sensores, como motorDerecho, sensorIzquierdo… Así se os hará mucho más fácil programarlos, y ver qué es lo que falla en caso de problemas. El código del programa sería así:

                                                                                        import lejos.nxt.Button;
                                                                                        import lejos.nxt.LCD;
                                                                                        import lejos.nxt.LightSensor;
                                                                                        import lejos.nxt.Motor;
                                                                                        import lejos.nxt.MotorPort;
                                                                                        import lejos.nxt.SensorPort;

                                                                                        public class Braitenberg {

                                                                                        public static void main(String[] args) {

                                                                                        LightSensor sluzizq = new LightSensor(SensorPort.S4);
                                                                                        LightSensor sluzder = new LightSensor(SensorPort.S1);
                                                                                        Motor motorizq = new Motor(MotorPort.C);
                                                                                        Motor motorder = new Motor(MotorPort.A);
                                                                                        int umbral = 18;

                                                                                        sluzizq.setFloodlight(false);
                                                                                        sluzder.setFloodlight(false);
                                                                                        motorizq.forward();
                                                                                        motorder.forward();
                                                                                        LCD.drawString("Valor de sluzizq:", 0, 1);
                                                                                        LCD.drawString("Valor de sluzder:", 0, 5);

                                                                                        while(!Button.ESCAPE.isPressed()){
                                                                                        LCD.drawInt(sluzizq.readValue(), 5, 3);
                                                                                        LCD.drawInt(sluzder.readValue(), 5, 7);
                                                                                        if (sluzder.readValue() > umbral){
                                                                                        motorder.setSpeed((sluzder.readValue()*sluzder.readValue()) - 200);
                                                                                        } else{
                                                                                        motorder.setSpeed(0);
                                                                                        }
                                                                                        if (sluzizq.readValue() > umbral){
                                                                                        motorizq.setSpeed((sluzizq.readValue()*sluzizq.readValue()) - 200);
                                                                                        } else{
                                                                                        motorizq.setSpeed(0);
                                                                                        }
                                                                                        }
                                                                                        }
                                                                                        }

                                                                                        Una pequeña explicación de algunas líneas:

                                                                                        8 - Aquí definimos un valor umbral de oscuridad, para el que creamos que nuestro robot-cucaracha está en un sitio suficientemente oscuro como para sentirse seguro y pararse. Este valor lo usaremos posteriormente en las líneas28 y 33.

                                                                                        18 y 19 - Recordad apagar los LED.

                                                                                        29 y 34 - Pondremos que la velocidad de los motores sea directamente proporcional a la cantidad de luz que recibimos, tal y como dice Braitenberg. En este caso será igual al cuadrado del valor leído menos un número que elijamos nosotros para moderar la velocidad, por ejemplo 200 o 300.

                                                                                        Y aquí tenéis este vídeo que nos ha quedado bastante chulo:

                                                                                        Ante cualquier duda podéis visitar el foro.

                                                                                        Control mediante joystick de un gráfico en el PC con LabVIEW

                                                                                        Artículo nº 2 de la serie de 14 artículos sobre LabVIEW

                                                                                          El objetivo de este pequeño proyecto es crear en el ordenador una gráfica en dos dimensiones en la que presentaremos un punto cuyas coordenadas estarán controladas dinámicamente por los sensores de rotación de dos servomotores del NXT. Para ello introduciremos varios conceptos nuevos, que serán necesarios para entender el funcionamiento del programa. Aunque este nuevo programa no tiene ninguna finalidad en sí mismo, nos abrirá las puertas para la creación de proyectos más complejos.

                                                                                          Creamos un nuevo VI en blanco y seleccionamos el panel frontal. Vamos a diseñar el panel que veremos como usuarios de la aplicación. Dado que queremos presentar una gráfica en dos dimensiones, necesitamos un indicador que pueda mostrarnos la información de ese modo. Se trata en concreto del Gráfico XY. Lo podemos localizar en varias paletas. Los iconos disponibles pueden ser muchos y puede darse el caso de que en un momento dado sepamos el nombre del icono que buscamos pero no sepamos en qué paleta se encuentra. Para ello podemos hacer uso de la función de búsqueda. En nuestro caso seleccionamos el icono de la siguiente forma:

                                                                                          Indicador XY-Graph

                                                                                          El icono de esta gráfica nos crea en el panel la imagen del siguiente indicador. Podemos modificar sobre el mismo tanto los fondos de escalas como el nombre de los mismos sin más que pinchando sobre ellos. Estas modificaciones también pueden hacerse en tiempo real, mientras se está ejecutando la aplicación. Es útil probar todas las opciones para familiarizarnos con los indicadores. En nuestro caso hemos seleccionado los fondos de escala de la figura, entre -360 y 360, y hemos desactivado el autoescalado en ambos ejes. Podemos modificar la forma de presentación del punto, su tamaño, color, etc. Las posibilidades de presentación son enormes.

                                                                                          xy-indicador

                                                                                          Si abrimos el diagrama de bloques vemos que automáticamente se nos ha creado el símbolo correspondiente a dicho indicador. Como consejo, es conveniente tener activada la ayuda contextual, que nos presenta información del icono sobre el que se encuentra el cursor en cada instante.

                                                                                          Ayuda contextual XY-Graph

                                                                                          Esta ayuda nos está indicando cuales son las entradas con las que debemos alimentar el gráfico. Vemos que es posible emplear esta gráfica para la presentación múltiple de varios gráficos, aunque en nuestro caso es suficiente con la presentación individual.

                                                                                          La entrada de este gráfico debe ser del tipo cluster. Un cluster no es más que una agrupación de datos de cualquier tipo. En muchas ocasiones el objetivo de agrupar los datos no es otro que el de simplificar el gráfico y evitar que se convierta en un amasijo de buses y cables. Lo que debe contener este cluster son dos arrays, uno con las coordenadas X de los puntos que queremos representas, y otro con las coordenadas Y de los mismos. Es decir, si quisiéramos representar N puntos, la longitud de los dos arrays del cluster sería N (de 0 a N-1). Pero nuestro caso es el más simple de todos, porque sólo queremos representar un único punto. La longitud de cada uno de los arrays del cluster debe ser 1.

                                                                                          Ahora que ya sabemos qué tipo de datos espera el XY-Graph, debemos generarlos. Para agrupar los dos arrays en el cluster necesitamos hacer uso de un icono que se denomina bundle. Si no sabemos dónde se encuentra el icono, lo más práctico es hacer la búsqueda en la casilla de búsqueda de las funciones del menú flotante.

                                                                                          bundle

                                                                                          El siguiente paso es conectar la entrada del icono XY-Graph con la salida del bundle. Realizada la conexión el programa marcará el hilo como erróneo, indicando que hemos realizado una conexión entre dos clusters con elementos diferentes. El problema que existe ahora mismo es que todavía no hemos definido cuáles son las entradas de bundle. Cuando las definamos el error desaparecerá, por lo que de momento haremos caso omiso de esta advertencia.

                                                                                          Para crear el array disponemos de un icono denominado Initialize Array. Como sólo queremos representar un punto el array será de tamaño unidad. Este valor es una constante. La forma más rápida de introducir el bloque de constante es, en lugar de buscarlo, pinchando con el botón derecho sobre el icono de Initialize Array cuando el ratón está posicionado en la entrada de la dimensión 0 y seleccionar create, constant.
                                                                                          Esto no sólo presenta el icono de constante, sino que lo cablea y nos pregunta directamente el valor de la constante.

                                                                                          Después de haber realizado varios cableados, nos damos cuenta de que el color de los mismos no es siempre el mismo. Al trabajar con datos en LabVIEW, es importante entender los tipos de datos diferentes, ya que algunas funciones sólo funcionará con un cierto tipo.

                                                                                          • Los terminales de color Naranja representar números en coma flotante, que son números que pueden tener una cifra decimal.
                                                                                          • Los terminales de color azul representar enteros, que son números que no pueden tener una cifra decimal.
                                                                                          • Los terminales de color rosa representan las cadenas (strings), que pueden incluir letras y números y algunos caracteres.
                                                                                          • Los terminales verdes representan booleanos, que son verdadera o falsa.
                                                                                          • Los terminales marrones representan las agrupaciones, que resultan de la combinación de varios conjuntos de datos.

                                                                                          El siguiente paso del programa es introducir el valor del array. Queremos controlar la posición del punto presentado mediante los sensores de rotación de los motores. Haremos uso en concreto de un joystick que ya hemos comentado en otros artículos. Este joystick tiene los motores conectados en los puertos A y B.

                                                                                          selección del icono de lectura de sensor

                                                                                          Debemos especificar que la lectura que debemos realizar es, en concreto, de un sensor de rotación. También debemos definir los puertos del NXT en los que están situados los servo motores porque el programa trabaja por defecto con el puerto A. Hemos añadido además unos indicadores para poder visualizar el valor de la lectura numérica del sensor, un indicador por cada sensor de rotación. Cableando todo lo anterior tenemos el siguiente programa, que además introducimos dentro de un bucle while (con su respectivo botón de parada) para que el programa se ejecute de forma contínua hasta que presionemos el botón de stop.

                                                                                          programa completo

                                                                                          El funcionamiento de la aplicación lo podeis ver en el video siguiente.

                                                                                          De nuevo volvemos a ver que la versatilidad y potencia del LabVIEW es muy superior a la del NXT-G. Se trata de una aplicación en la que el NXT está funcionando como sistema de adquisición de datos, que están siendo procesados y presentados en el PC.

                                                                                          Para cualquier duda, podeis comentar en el foro.

                                                                                          Vehículos de Braitenberg en RobotC

                                                                                          Artículo nº 10 de la serie de 14 artículos sobre RobotC

                                                                                          FrontalLos vehículos de Braitenberg fueron concebidos por el investigador Italo-Autriaco Valentino Braitenberg para ilustrar de manera evolutiva las habilidades de agentes simples. Los vehículos representan la forma más simple de inteligencia artificial basada en comportamientos o conocimiento corporal, por ejemplo el comportamiento inteligente que surje de la interacción sensor-motor entre el agente y su entorno, sin necesidad de una memoria interna, representación del entorno, o inferencia.

                                                                                          ¿Qué es un vehículo de Braitenberg?:

                                                                                          Un vehículo de Braitenberg es un agente que puede moverse de forma autónoma por un entorno. Tiene sensores primitivos (capaces de medir estímulos), y ruedas (cada una manejada por su propio motor) que funcionan como actuadores o efectores. Un sensor, en su forma más simple, está directamente conectado a un efector, de manera que una señal o estímulo en dicho sensor produce una respuesta inmediata en el motor (moviendo la rueda, por ejemplo).

                                                                                          Dependiendo de la forma en que estén conectados los sensores y motores, el vehículo mostrará distintos comportamientos. Basándonos en esta idea podemos definir dichas conexiones con objeto de cumplir cierto objetivo o comportamiento. Parecerá que el vehículo intenta lograr llegar a ciertas situaciones y evadir otras, cambiando su trayectoria cuando la situación cambia.

                                                                                          Ejemplos de vehículos de Braitenberg:

                                                                                          Los siguientes ejemplos son algunos de los vehículos de Braitenberg más simples:

                                                                                          Un primer agente tiene un sensor detector de luz que estimula directamente a su única rueda, implementado las siguientes reglas:

                                                                                          • Más luz produce un movimiento más rápido.
                                                                                          • Menos luz produce un movimiento más lento.
                                                                                          • La oscuridad produce que se quede quieto.

                                                                                          Este comportamiento puede ser interpretado como una criatura que teme a la luz y que se mueve rápido para salir de ella. Su objetivo es encontrar un hueco oscuro en el que ocultarse.

                                                                                          Un segundo agente algo más complicado tiene dos sensores de luz (izquierdo y derecho), cada uno estimulando a la rueda de su mismo lado. Obedece las siguientes reglas:

                                                                                          • Más luz en la derecha → la rueda derecha gira más rapido → gira hacia la izquierda, fuera de la luz.
                                                                                          • Más luz en la izquierda → la rueda izquierda gira más rapido → gira hacia la derecha, fuera de la luz.

                                                                                          Este es más eficiente como un comportamiento para escapar de la fuente de luz, ya que la criatura puede moverse en diferentes direcciones, y tiene a orientarse hacia la dirección desde la que procede menos luz.

                                                                                          En otra variación las conexiones son negativas o innovadoras:

                                                                                          • Más luz en la derecha → la rueda derecha gira más lento → gira hacia la derecha, hacia la luz.
                                                                                          • Más luz en la izquierda → la rueda izquierda gira más lento → gira hacia la izquierda, hacia la luz.

                                                                                          En este caso el agente huye de la oscuridad, moviéndose hacia la luz.

                                                                                          Configuración de motor y sensores mediante el menú de RobotC:

                                                                                          Una manera cómoda de configurar los sensores y motores en RobotC es mediante el menú de configuración de motores y sensores (Motors and Sensores Setup). Para acceder al mismo seguid la siguiente ruta: Robot → Motors and Sensores Setup y os saldrá el siguiente menú:

                                                                                          Braitenberg-conf1

                                                                                          En la pestaña de motores podremos elegir qué motores vamos a usar y ponerles el nombre que deseemos, y si queremos que tenga el PID (Controlador Proporcional Integral Derivativo) activado. En la pestaña de sensores podemos elegir qué tipo de sensores queremos anclar a cada puerto, así como el nombre que deseemos darles. Una vez configurado todo le dais a aceptar y ya os escribe el código directamente:

                                                                                          Braitenberg-conf2

                                                                                          Vehículo de Braitenberg en RobotC:

                                                                                          Como ya habréis supuesto los vehículos de Braitenberg no son problemas muy complejos de resolver con RobotC. En este caso vamos a implementar el último de los ejemplos que he puesto:

                                                                                          • Más luz en la derecha → la rueda derecha gira más lento → gira hacia la derecha, hacia la luz.
                                                                                          • Más luz en la izquierda → la rueda izquierda gira más lento → gira hacia la izquierda, hacia la luz.

                                                                                          Necesitaremos construir un robot con dos sensores de luz y dos motores. Es importante que los sensores de luz miren más o menos hacia los lados y no hacia adelante, o no podremos medir bien la cantidad de luz que proviene de cada lado. Con un ángulo de 45º es suficiente, como el robot que hemos construido nosotros:

                                                                                          lateral

                                                                                          A la hora de configurar los sensores de luz es importante que los pongáis en modo pasivo, o sea sin que emitan luz, ya que en este caso dicha luz nos entorpecería en nuestro cometido. Ponedle nombres intuitivos a los motores y a los sensores, como motorDerecho, sensorIzquierdo… Así se os hará mucho más fácil programarlos, y ver qué es lo que falla en caso de problemas. El código del programa sería así:


                                                                                          #pragma config(Sensor, S1, Sizq, sensorLightInactive)
                                                                                          #pragma config(Sensor, S4, Sder, sensorLightInactive)
                                                                                          #pragma config(Motor, motorB, Mder, tmotorNormal, PIDControl,)
                                                                                          #pragma config(Motor, motorC, Mizq, tmotorNormal, PIDControl,)
                                                                                          //*!Code automatically generated by ROBOTC configuration wizard!*//

                                                                                          #pragma platform(NXT)

                                                                                          task main(){
                                                                                          while(true){
                                                                                          nxtDisplayCenteredTextLine(2, "Sizq: %d", SensorValue[Sizq]);
                                                                                          nxtDisplayCenteredTextLine(4, "Sder: %d", SensorValue[Sder]);
                                                                                          if (SensorValue[Sizq] < SensorValue[Sder]){
                                                                                          motor[Mder] = 30;
                                                                                          motor[Mizq] = 70;
                                                                                          } else if (SensorValue[Sizq] > SensorValue[Sder]){
                                                                                          motor[Mder] = 70;
                                                                                          motor[Mizq] = 30;
                                                                                          } else {
                                                                                          motor[Mizq] = 70;
                                                                                          motor[Mder] = 70;
                                                                                          }
                                                                                          }
                                                                                          }

                                                                                          Y aquí os dejo un vídeo de muestra para que veáis como funciona:

                                                                                          Ante cualquier duda podéis visitar el foro.

                                                                                          Bump&Go en RobotC

                                                                                          Artículo nº 8 de la serie de 14 artículos sobre RobotC

                                                                                          Bump&Go2En el artículo de hoy voy a explicar el Bump&Go en RobotC. El Bump&Go es un programa en el que el robot se mueve aleatóriamente por una habitación esquivando obstáculos cuando los detecta. Puesto que el movimiento es aleatorio llegará un momento en que se haya movido por toda la habitación. Este tipo de robot ya existe, y tiene uso comercial, como la aspiradora Roomba, que es capaz de aspirarte la casa automáticamente.

                                                                                          El Bump&Go es un programa bastante sencillo que se puede implementar de varias maneras, en nuestro caso lo haremos con sensores de contacto. A diferencia del de LeJOS, este lo haremos con dos sensores de contacto, para tener una mayor precisión. Como montaje recomiendo un tribot modificado, de manera que el parachoques quede mirando hacia adelante y no no en diagonal como el montaje estandar de tribot del del LEGO Mindstorms NXT. pero no es muy difícil de implementar.

                                                                                          Manejo básico de los motores:

                                                                                          Vamos a hacer un pequeño recordatorio del manejo de motores en RobotC. La forma más sencilla de manejar los motores era mediante la función motor:

                                                                                          motor[nombre_motor] = int potencia: Ponemos el motor deseado (MotorA, MotorB, MotorC, o motores que hayamos definido nosotros mismos) a la potencia indicada. Potencias negativas significan que el motor rotará en sentido contrario, y una potencia de 0 parará el motor.

                                                                                          Si queremos que el motor A se mueva hacia adelante a máxima potencia sería:

                                                                                          motor[motorA] = 100;

                                                                                          No serán necesarias más funciones para este programa.

                                                                                          Manejo del sensor de contacto:

                                                                                          El sensor de contacto es el más simple de los sensores del NXT. Básicamente tiene dos estados, pulsado o sin pulsar. Por tanto la función que se utiliza para determinar su estado devolverá un booleano con true en caso de que esté presionado, o false en caso contrario. Lo primero que necesitamos hacer es definir los puertos a los que van anclados dichos sensores, esto se hace de la siguiente manera:

                                                                                          #pragma config(Sensor, puerto, nombre, tipo_sensor): Anclamos el sensor del tipo deseado al puerto que elijamos, utilizaremos el nombre que le hemos dado aquí para referirnos a el dentro del programa.

                                                                                          Si quisiésemos poner dos sensores de contacto, en los puertos 1 y 2, se haría de la siguiente manera:

                                                                                          #pragma config(Sensor, S1, contacto1, sensorTouch)
                                                                                          #pragma config(Sensor, S2, contacto2, sensorTouch)

                                                                                          Una vez definidos podremos utilizarlos. Se usa la función SensorValue(nombre_sensor) para tomar el valor del sensor. En este caso nos devolverá un booleano, por lo que podríamos utilizarlo en las condiciones de los bucles o de las sentencias selectivas if. Un ejemplo sería:

                                                                                          if (SensorValue[contacto1]){

                                                                                          Bump&Go1

                                                                                          Bump&Go:

                                                                                          Bump&Go es un robot móvil que se mueve erráticamente por una habitación o piso, esquivando cualquier obstáculo que encuentre a su paso. En este caso, y dado que usaremos el sensor de contacto, podemos decir que más que esquivar lo que el robot irá haciendo cada vez que colisione con un obstáculo es dar marcha atrás y girar para encontrar otra posible ruta sin obstáculos. Puesto que contamos con dos sensores de contactos podemos tener en cuenta si se ha presionado uno, otro o los dos, para saber a donde girar para esquivar dicho obstáculo. Si queremos además que el giro sea un número aleatorio de grados, usaremos la función matemática random(max) de RobotC, que nos devuelve un número aleatorio entre 0 y max. Suponiendo que queramos un rango entre un número distinto de 0 y el máximo que elijamos no tendríamos más que sumarle nuestro valor min, por ejemplo:

                                                                                          int aleatorio = 1000 + random(5000);

                                                                                          Nos devolveria en aleatorio un valor entre 1000 y 6000.

                                                                                          Puesto que su comportamiento va a ser aleatorio, está claro que será capaz de recorrer una habitación entera. Es solo cuestión de tiempo el que lo logre. Implementado así es un tipo de navegación muy simple, y tiene la ventaja de no exigir la necesidad de ningún sistema de localización, puesto que al tratarse de un movimiento aleatorio no se requiere conocer la posición del robot. (Versiones más avanzadas permiten que el robot pueda volver a la base para recargar las baterías. El problema de volver a una ubicación determinada sí requiere de un sistema más evolucionado).

                                                                                          El programa en RobotC sería:

                                                                                          #pragma config(Sensor, S1, contacto1, sensorTouch)
                                                                                          #pragma config(Sensor, S4, contacto4, sensorTouch)

                                                                                          #pragma platform(NXT)

                                                                                          task main(){
                                                                                          int tiempoMA, tiempoGiro;

                                                                                          while(true){
                                                                                          motor[motorB] = 100;
                                                                                          motor[motorC] = 100;

                                                                                          if (SensorValue[contacto1]){
                                                                                          tiempoMA = 200 + random(300);
                                                                                          motor[motorB] = -100;
                                                                                          motor[motorC] = -100;
                                                                                          wait1Msec(tiempoMA);
                                                                                          tiempoGiro = 200 + random(400);
                                                                                          motor[motorB] = 0;
                                                                                          wait1Msec(tiempoGiro);
                                                                                          }

                                                                                          if (SensorValue[contacto4]){
                                                                                          tiempoMA = 200 + random(300);
                                                                                          motor[motorB] = -100;
                                                                                          motor[motorC] = -100;
                                                                                          wait1Msec(tiempoMA);
                                                                                          tiempoGiro = 200 + random(400);
                                                                                          motor[motorC] = 0;
                                                                                          wait1Msec(tiempoGiro);
                                                                                          }

                                                                                          }
                                                                                          }

                                                                                          A continuación explicaré las lineas más importantes:

                                                                                          13 - En mi caso el contacto1 esta en el lado derecho del parachoques, teniendo en cuenta esto:

                                                                                          14 – Calculamos un tiempo aleatorio de milisegundos para dar marcha atrás, este número estará entre 200 y 500 milisegundos.

                                                                                          15, 16 y 17 - Damos marcha atrás durante el tiempo deseado.

                                                                                          18 - Calculamos un tiempo aleatorio de giro en milisegundos, este número estará entre 200 y 600 milisegundos. Por supuesto los tiempos los elegís vosotros, quizás os venga mejor otro número para vuestro robot.

                                                                                          19 y 20 - IMPORTANTE, aquí giramos el robot hacia la izquierda (motorC es dondo está mi rueda izquierda, al rotar marcha atrás el robot girará hacia la izquierda). Hacemos esto lógicamente porque nos hemos chocado por la derecha, por lo que es la mejor manera de esquivar el obstáculo.

                                                                                          23 a 31 - Repetimos el proceso, pero en este casi si nos hemos chocado por la izquierda. Por tanto el robot después de dar marcha atrás girará hacia la derecha.

                                                                                          Todo esto está dentro de un búcle infinito. En caso de que se presionen los dos sensores a la vez se ejecutará el primer if, como si hubiera chocado por la derecha. En realidad nos es indiferente en este caso.

                                                                                          En resumidas cuentas lo que hace este programa es que el robot ande marcha adelante hasta chocarse con un obstáculo; entonces dará marcha atrás, girará en una dirección determinada según que sensor de contacto haya sido presionado un número aleatorio de grados, y seguirá otra vez moviéndose adelante. El robot solo detendrá la ejecución del programa cuando presionemos el botón ESCAPE del NXT. Aquí os pongo un vídeo demostrativo con el mismo programa que os he escrito en el artículo (recordad hacer un buen parachoques o puede que se os atasque):

                                                                                          Si tenéis alguna duda podéis postearla en el foro.

                                                                                          Sonar mejorado en RobotC

                                                                                          Artículo nº 9 de la serie de 14 artículos sobre RobotC

                                                                                          Sonar1En el artículo anterior vimos como implementar el sonar básico. En este artículo voy a expandir el sonar para que tenga una mayor funcionalidad, y se puedan observar mejor los objetos por pantalla. Para ello usaremos nuevas funciones del LCD del NXT, y seguiremos utilizando la trigonometría para mostrar de forma precisa objetos por pantalla.

                                                                                          Funciones para el LCD:

                                                                                          La nueva función del LCD que vamos a utilizar es nxtDrawLine, que dibuja una línea entre dos puntos píxeles). La sintaxis es:

                                                                                          nxtDrawLine(punto1x, punto1y, punto2x, punto2y): Dibuja la recta que une los píxeles cuyas coordenas son las de punto1 y punto2.

                                                                                          Usaremos esta función para dibujar líneas negras entre donde detecta el objeto y el borde de la circunferencia del sonar. De esta manera los objetos quedarán representados por píxeles de color negro, mientras que el espacio libre será todo lo que esté en blanco.

                                                                                          Dibujo de objetos:

                                                                                          En el programa anterior, cada vez que detectábamos un objeto, lo representábamos como un punto, dibujándolo a la misma altura y en la que hubiéramos dibujado el borde de la circunferencia:

                                                                                          nxtSetPixel(distancia*cosDegrees(i) + 50, 31*sinDegrees(i) + 32);

                                                                                          Sin embargo es una representación muy poco precisa, ya que en realidad el objeto se debería dibujar en una coordenada y distinta, ya que es un punto intermedio de la recta que une el punto central de la circunferencia con su borde:

                                                                                          seno

                                                                                          Por tanto habría que modificar la coordenada y, de forma que su valor sea igual a la distancia por el seno, igual que la coordenada x es la distancia por el coseno:

                                                                                          nxtSetPixel(distancia*cosDegrees(i) + 50, distancia*sinDegrees(i) + 32);

                                                                                          Para dibuja la recta necesitamos dos puntos. El otro punto será por supuesto el borde de la circunferencia de radio 32 píxeles donde estamos representando el sonar. Por tanto para pintar el recta sería:

                                                                                          nxtDrawLine(distancia*cosDegrees(i) + 50, distancia*sinDegrees(i) + 32,
                                                                                          31*cosDegrees(i) + 50, 31*sinDegrees(i) + 32);

                                                                                          A la hora de detectar un objeto grande, dibujará varias lineas negras, una por cada grado que lo detecte, quedando como resultado un bloque negro representando dicho objeto.

                                                                                          Sonar

                                                                                          Eliminación de errores:

                                                                                          Es sabido que el sensor de ultrasonidos falla bastante, generando una gran cantidad de lecturas erróneas con valor 255. Por ello es importante eliminar estos errores de medida para que no aparezcan representados en el dibujo (por ejemplo que este dibujando en el sonar una caja que tenga enfrente y de repente dibuje una línea de color blanco en medio de todas las negras).

                                                                                          Una buena idea es controlar cuando el el sensor marque 255 y actuar en consecuencia. Pero, ¿qué se podría hacer?, tratar de dibujar una línea negra cuando el objeto quizás se encuentre a más de 2 metros es igual de erróneo. Por tanto nos debemos fijar más bien en las medidas tomadas recientemente, y basarnos en ellas para saber si dibujar o no una línea, y de que longitud. Para ello tenemos que guardar todas las distancias medidas anteriormente.

                                                                                          Una vez tenemos las distancias anteriores guardadas podemos hacer que cada vez que detecte 255 cm, tome en cuenta un pequeño espectro de medidas anteriores (por ejemplo las diez medidas anteriores), calcule una media de esas medidas, y la use como referencia. Suponiendo que tengamos un vector llamado distancias, quedaría de la siguiente manera:

                                                                                          if (valor == 255){
                                                                                          aux = 0;
                                                                                          for (int j = 1; j <= 10; j++){
                                                                                          aux = aux + distancias[actual-j];
                                                                                          }
                                                                                          valor = aux / 10;
                                                                                          }

                                                                                          Con estos dos conceptos podemos crear un radar bastante mas eficaz, aunque como siempre dependemos de la poca precisión del sensor de ultrasonidos de LEGO.

                                                                                          Programa del Sonar:

                                                                                          Aplicando los conceptos vistos anteriormente, el programa del sonar ampliado gana precisión en la representación de los objetos que detecta. Todo esto nos facilitará comprender más el funcionamiento del sensor de ultrasonidos. Recordad repasar el artículo sobre ultrasonidos, para tener más claro el uso avanzado de motores. El programa completo es el siguiente:

                                                                                          #pragma config(Sensor, S4, sonarSensor, sensorSONAR)

                                                                                          task main()
                                                                                          {
                                                                                          int grado, valor, aux, alcance = 50, espectro = 10;
                                                                                          nMotorEncoder[motorB] = 0;
                                                                                          int distancias[360];

                                                                                          for(int i = 1; i <= 359; i++){
                                                                                          while(nMotorEncoder[motorB] < i)
                                                                                          {
                                                                                          motor[motorB] = 30;
                                                                                          }
                                                                                          motor[motorB] = 0;
                                                                                          valor = SensorValue[sonarSensor];
                                                                                          wait1Msec(50);
                                                                                          distancias[i] = valor;
                                                                                          if ((valor == 255) && (i <= espectro)){
                                                                                          aux = 0;
                                                                                          for (int j = 1; j <= espectro; j++){
                                                                                          aux = aux + distancias[i-j];
                                                                                          }
                                                                                          valor = aux / espectro;
                                                                                          }

                                                                                          if (valor <= alcance){
                                                                                          int aux = valor*32/alcance;
                                                                                          nxtDrawLine(aux*cosDegrees(i) + 50, aux*sinDegrees(i) + 32,
                                                                                          31*cosDegrees(i) + 50, 31*sinDegrees(i) + 32);
                                                                                          }
                                                                                          else{
                                                                                          nxtSetPixel(31*cosDegrees(i) + 50, 31*sinDegrees(i) + 32);
                                                                                          }

                                                                                          }

                                                                                          while(nMotorEncoder[motorB] > 0)
                                                                                          {
                                                                                          motor[motorB] = -30;
                                                                                          }
                                                                                          motor[motorB] = 0;
                                                                                          wait1Msec(5000);
                                                                                          }

                                                                                          5 - Creamos bastantes variables que nos ayudarán a manejar nuestro programa, como rango del sonar, el valor actual actual, o espectro para realizar la media en caso de lectura errónea.

                                                                                          7 - Así es como se define una tabla de 360 elementos, en ella guardaremos todas las distancias.

                                                                                          18 a 24 - Aquí controlamos los valores 255.

                                                                                          26 a 33 - Aquí realizamos el dibujo.

                                                                                          Y aquí está el video que os prometí:

                                                                                          Por favor visitad el foro si tenéis alguna duda.

                                                                                          Sonar básico en RobotC

                                                                                          Artículo nº 11 de la serie de 14 artículos sobre RobotC

                                                                                          sonar_an_sqS31El sonar (del inglés SONAR, acrónimo de Sound Navigation And Ranging, ‘navegación y alcance por sonido’) es una técnica que usa la propagación del sonido bajo el agua (principalmente) para navegar, comunicarse o detectar otros buques.

                                                                                          El sonar puede usarse como medio de localización acústica, funcionando de forma similar al radar, con la diferencia de que en lugar de emitir señales de radiofrecuencia se emplean impulsos sonoros. De hecho, la localización acústica se usó en aire antes que el radar, siendo aún de aplicación el SODAR (la exploración vertical aérea con sonar) para la investigación atmosférica.

                                                                                          El término «sonar» se usa también para aludir al equipo empleado para generar y recibir el sonido. Las frecuencias usadas en los sistemas de sonar van desde las infrasónicas a las ultrasónicas.

                                                                                          Aunque algunos animales (como delfines y murciélagos) han usado probablemente el sonido para la detección de objetos durante millones de años, el uso por parte de humanos fue registrado por vez primera por Leonardo Da Vinci en 1490. Se decía que se usaba un tubo metido en el agua para detectar barcos, poniendo un oído en su extremo. En el siglo XIX se usaron campanas subacuáticas como complemento a los faros para avisar del peligro a los marineros.

                                                                                          Manejo de sensores:

                                                                                          El manejo básico del sensor de ultrasonidos lo vimos en el artículo Manejo del sensor de ultrasonido en RobotC. En este artículo no añadiremos más funcionalidad al ultrasonido , aunque si veremos una nueva función para mostrar datos por pantalla. En este caso usaremos una función para dibujar píxeles, ya que necesitaremos dibujar obstáculos. La función es:

                                                                                          nxtSetPixel(int x, int y): Dibuja el píxel de la posición indicada. Recordad que la pantalla tiene 100 píxeles de ancho (del 0 al 99) por 64 de alto (del 0 al 63).

                                                                                          Un poco de trigonometría:

                                                                                          Para dibujar el sonar en forma de círculo y los objetos que detectemos necesitamos utilizar senos y cosenos. Suponiendo que queramos crear un circulo de radio 32 (la mitad de 64 que es el ancho de la pantalla), centrado en medio de la pantalla (coordenadas x = 50 e y = 32 aprox) necesitaremos dibujar 360 píxeles. La posición de cada píxel será igual a:

                                                                                          x = 50 + 32*cos(número de píxel)
                                                                                          y = 32 + 32*sen(número de píxel)

                                                                                          9846 NXT Ultra Sonic Sensor

                                                                                          En RobotC existen funciones para calcular senos y cosenos:

                                                                                          sinDegrees(angulo_en_grados): Devuelve el seno del ángulo cuyos grados son los indicados.
                                                                                          cosDegrees(angulo_en_grados): Devuelve el coseno del ángulo cuyos grados son los indicados.

                                                                                          Programa de prueba:

                                                                                          Lo que hará este programa es realizar un giro completo de 360º mientras va tomando mediciones de distancia con el sensor de ultrasonidos, y a la vez dibujando un círculo y dentro de el todos los objetos que se encuentren a menos de 100 cm en este caso. Una vez finalizado el recorrido el sensor de ultrasonidos volverá a su posición. Aquí tenéis el código:

                                                                                          #pragma config(Sensor, S4, sonarSensor, sensorSONAR)

                                                                                          task main()
                                                                                          {
                                                                                          int grado, valor, min = 255;
                                                                                          nMotorEncoder[motorB] = 0;

                                                                                          for(int i = 1; i <= 360; i++){
                                                                                          while(nMotorEncoder[motorB] < i)
                                                                                          {
                                                                                          motor[motorB] = 30;
                                                                                          }
                                                                                          nxtSetPixel(31*cosDegrees(i) + 50, 31*sinDegrees(i) + 32);
                                                                                          motor[motorB] = 0;
                                                                                          valor = SensorValue[sonarSensor];
                                                                                          if (valor < 100){
                                                                                          int aux = valor*32/100;
                                                                                          nxtSetPixel(aux*cosDegrees(i) + 50, 31*sinDegrees(i) + 32);
                                                                                          }
                                                                                          wait1Msec(50);
                                                                                          }

                                                                                          while(nMotorEncoder[motorB] > 0)
                                                                                          {
                                                                                          motor[motorB] = -30;
                                                                                          }
                                                                                          motor[motorB] = 0;
                                                                                          wait1Msec(5000);
                                                                                          }

                                                                                          En breve colgaremos un vídeo de muestra.

                                                                                          Espero que os guste el programa. Si tenéis dudas, preguntad en el foro.

                                                                                          Robot seguidor básico con sensor de ultrasonidos

                                                                                          Artículo nº 7 de la serie de 14 artículos sobre RobotC

                                                                                          Sigue Objetos 1Actualmente es posible crear Robots avanzados que sean capaces de seguir a una persona guiándose por determinados patrones: como por el color de la ropa, su altura, … Aunque estos robots no tienen gran utilidad en la actualidad, es una funcionalidad que podría ser útil para complementar otras en un futuro. Con los LEGO Mindstorms NXT no es posible ser tan preciso, pero se puede conseguir un comportamiento parecido. Para ello usaremos el sensor de ultrasonido.

                                                                                          Programa de prueba:
                                                                                          Puesto que hoy no dispongo de mucho tiempo, el artículo no es tan largo como de costumbre. Antes de poneros con este programa os recomiendo que os leáis mis anteriores artículos de RobotC, especialmente el del manejo del sensor de ultrasonidos.

                                                                                          Este robot lo que hará principalmente es seguir a un objeto que se encuentre en línea con el. Cuando el objeto avance el robot lo seguirá avanzando también. Si el objeto se acerca demasiado el robot dará marcha atrás para no chocar con el.

                                                                                          Sigue Objetos 2

                                                                                          Es importante definir unos buenos umbrales de distancia para saber cuando el robot tiene que seguir al objeto, o cuando tiene que dar marcha atrás. Teniendo en cuenta que el sensor de ultrasonidos suele fallar bastante en distancias menores a los 15cm, es aconsejable que la distancia para empezar a dar marcha atrás sea mayor (unos 25 cm por ejemplo). También es buena idea que no sean la misma distancia, que haya un margen de más de 10cm entre ellas. Por ejemplo si estoy diciendo que el robot de marcha atrás cuando me acerque a el a menos de 30 cm, le tendré que decir que se mueva para adelante solo cuando me encuentre a más de 40 cm. Si el robot se encuentra dentro de ese intervalo deberá estar parado.

                                                                                          Es también recomendable que cuando el sensor de utrasonidos detecte 255 cm (que en la mayoría de casos significa error) se quede quieto. Así evitaremos que el robot acelere cuando nos acercamos demasiado a el y las lecturas fallan. El código del programa no es muy largo, os lo escribo a continuación:

                                                                                          #pragma config(Sensor, S4, sonar1, sensorSONAR)

                                                                                          task main(){
                                                                                          int min = 25, max = 35;
                                                                                          nxtDisplayCenteredTextLine(1, "Distancia al");
                                                                                          nxtDisplayCenteredTextLine(3, "objetivo: ");
                                                                                          while(true){
                                                                                          nxtDisplayCenteredTextLine(5, "%d cm", SensorValue[sonar1]);
                                                                                          if (SensorValue[sonar1] != 255){
                                                                                          if (SensorValue[sonar1] > max){
                                                                                          motor[motorA] = 100;
                                                                                          motor[motorC] = 100;
                                                                                          }
                                                                                          else if (SensorValue[sonar1] < min){
                                                                                          motor[motorA] = -100;
                                                                                          motor[motorC] = -100;
                                                                                          }
                                                                                          else{
                                                                                          motor[motorA] = 0;
                                                                                          motor[motorC] = 0;
                                                                                          }
                                                                                          }
                                                                                          else{
                                                                                          motor[motorA] = 0;
                                                                                          motor[motorC] = 0;
                                                                                          }
                                                                                          }
                                                                                          }

                                                                                          Aquí os dejo un vídeo bastante orientativo de como se comportaría el robor con dicho programa:

                                                                                          Por último añadir que todas las dudas que os surjan os las resolveremos sin problemas en el foro.

                                                                                          Calculadora en RobotC

                                                                                          Artículo nº 6 de la serie de 14 artículos sobre RobotC

                                                                                          Calculadora1Una calculadora es un artefacto que se utiliza para realizar cálculos, generalmente de tipo aritméticos. Las calculadoras modernas incorporan a menudo un ordenador de propósito general, que ayuda a realizar otro tipo de funciones, como cálculos avanzados, o una mejor memoria.

                                                                                          Existen varios tipos de calculadoras, como las calculadoras gráficas especializadas en campos matemáticos gráficos como la trigonometría y la estadística, o calculadoras conversoras de medidas, como calculadoras de cambio de moneda, que se actualizan automáticamente gracias a su capacidad de conectarse online. También suelen ser más portátiles que la mayoría de los computadores, si bien algunas PDAs tienen tamaños similares a los modelos típicos de calculadora.

                                                                                          En el pasado, se utilizaban como apoyo al trabajo numérico ábacos, comptómetros, ábacos neperianos, tablas matemáticas, reglas de cálculo y máquinas de sumar. El término «calculador» se usaba para aludir a la persona que ejercía este trabajo, ayudándose también de papel y lápiz. Este proceso de cálculo semimanual era tedioso y proclive a errores. Actualmente, las calculadoras son electrónicas y son fabricadas por numerosas empresas en tamaños y formas variados. Se pueden encontrar desde modelos muy baratos del tamaño de una tarjeta de crédito hasta otros más costosos con una impresora incorporada.

                                                                                          Calculadora en RobotC:

                                                                                          En este programa vamos a implementar una calculadora básica. Para ello hemos usado manejo de botones (leeros el artículo sobre botones y LCD), así como manejo de LCD, manejo de sensores de contacto (cuyo manejo es parecido al de cualquier sensor, revisar este artículo para ver como se definen), y la estructura switch() de C++, la cuál nos permite elegir que trozo de código queremos ejecutar según el valor de una variable.

                                                                                          IMG_2243

                                                                                          Dicha calculadora nos permitirá sumar, restar, multiplicar, dividir, hallar el resto y calcular la potencia. Puesto que la potencia no existe en RobotC, crearemos nosotros mismos una función que la calcule. Además los cuatro sensores de contactos nos ayudarán para elegir mejor que números vamos a operar. El código es el siguiente (aunque lo haya partido en trozos para explicarlo mejor en el programa va todo seguido):

                                                                                          Definición de los sensores:

                                                                                          #pragma config(Sensor, S1, contacto1, sensorTouch)
                                                                                          #pragma config(Sensor, S2, contacto2, sensorTouch)
                                                                                          #pragma config(Sensor, S3, contacto3, sensorTouch)
                                                                                          #pragma config(Sensor, S4, contacto4, sensorTouch)

                                                                                          Aquí definimos a los nombres y puertos de cada sensor.

                                                                                          Función potencia:

                                                                                          float potencia(int x, int y){
                                                                                          float aux = x;
                                                                                          if (y == 0)
                                                                                          aux = 1;
                                                                                          else
                                                                                          for(int i = 2; i <= y; i++)
                                                                                          aux = aux*x;
                                                                                          return aux;
                                                                                          }

                                                                                          La potencia x elevado a y no es mas que x multiplicado por si mismo y - 1 veces. Si el valor de y es 0, la potencia siempre valdrá 1.

                                                                                          Definición de variables:

                                                                                          #pragma platform(NXT)

                                                                                          task main(){
                                                                                          nNxtButtonTask = -2;
                                                                                          bool n1 = false, n2 = false, op = false, positivo = true;
                                                                                          int boton, j = 0, i = 0, opid = 0;
                                                                                          float resultado;

                                                                                          4 - Con este comando decimos al NXT que vamos a usar los botones, de manera que no queremos que enseñe lo que mostraría habitualmente al pulsarlos (como los valores del Bluetooth...). Dependiendo de la versión de RobotC que tengáis esto lo hará automático, o necesitaréis poner esta línea.

                                                                                          5 a 7 - Definimos e inicializamos las variables. Creamos variables booleanas (que pueden tomar valor verdadero (true) o falso (false) para usarlas como condiciones de salida de los bucles.

                                                                                          Elección del primer número:

                                                                                          while(!n1){
                                                                                          if ((boton = nNxtButtonPressed) > 0){
                                                                                          wait1Msec(200);
                                                                                          switch(boton){
                                                                                          case 1: positivo = !positivo;
                                                                                          break;
                                                                                          case 2: positivo = !positivo;
                                                                                          break;
                                                                                          case 3: n1 = true;
                                                                                          break;
                                                                                          }
                                                                                          }
                                                                                          if (SensorValue[contacto1]){
                                                                                          if(positivo)
                                                                                          i++;
                                                                                          else
                                                                                          i--;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          if (SensorValue[contacto2]){
                                                                                          if(positivo)
                                                                                          i = i + 10;
                                                                                          else
                                                                                          i = i - 10;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          if (SensorValue[contacto3]){
                                                                                          if(positivo)
                                                                                          i = i + 100;
                                                                                          else
                                                                                          i = i - 100;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          if (SensorValue[contacto4]){
                                                                                          if(positivo)
                                                                                          i = i + 1000;
                                                                                          else
                                                                                          i = i - 1000;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          eraseDisplay();
                                                                                          nxtDisplayCenteredTextLine(2, "Eliga un numero: ");
                                                                                          nxtDisplayCenteredTextLine(4, "%d", i);
                                                                                          if(positivo)
                                                                                          nxtDisplayCenteredTextLine(6, "Modo positivo");
                                                                                          else
                                                                                          nxtDisplayCenteredTextLine(6, "Modo negativo");
                                                                                          wait1Msec(100);
                                                                                          }

                                                                                          2 - Guardamos el identificador del botón que hemos pulsado. El hecho de que tenga que ser mayor que 0 nos libra de que pueda ser un fallo o el botón ESCAPE

                                                                                          3 - Es importante poner un pequeño retardo entre pulsado y pulsado de botón, o cambiaremos varias veces de número ya que el NXT interpretará que hemos apretado el botón muchas veces por haberlo mantenido apretado aunque sea durante muy pocos milisegundos.

                                                                                          4 - Los switch() irán de la siguiente forma: En caso de que apretemos el botón derecho e izquierdo pasaremos a modo positivo o negativo dependiendo del que estemos actualmente, y aprentando el ENTER elegímos el número actual. Por tanto en el case 3: asignamos true a la variable de condición de salida del búcle.

                                                                                          43 - Recordad que %d significa que vamos a escribir un número decimal entero en ese lugar, cuyo valor es el de la siguiente variable que pongamos en esa misma función (en este caso es i).

                                                                                          13, 20, 27 y 34 - Dependiendo de que botón de contacto apretemos aumentaremos (en modo positivo), o disminuiremos (en modo negativo) de más en más el número que estamos eligiendo actualmente.

                                                                                          Elección de la operación:

                                                                                          wait1Msec(300);
                                                                                          eraseDisplay();
                                                                                          nxtDisplayCenteredTextLine(1, "Eliga la ");
                                                                                          nxtDisplayCenteredTextLine(3, "Operacion :");
                                                                                          nxtDisplayCenteredTextLine(5, "Suma");
                                                                                          while(!op){
                                                                                          if ((boton = nNxtButtonPressed) > 0){
                                                                                          wait1Msec(300);
                                                                                          switch(boton){
                                                                                          case 1: if(opid < 5){
                                                                                          opid++;
                                                                                          }else{
                                                                                          opid = 0;
                                                                                          }
                                                                                          break;
                                                                                          case 2: if(opid > 0){
                                                                                          opid--;
                                                                                          }else{
                                                                                          opid = 5;
                                                                                          }
                                                                                          break;
                                                                                          case 3: op = true;
                                                                                          break;
                                                                                          }
                                                                                          eraseDisplay();
                                                                                          nxtDisplayCenteredTextLine(1, "Eliga la ");
                                                                                          nxtDisplayCenteredTextLine(3, "Operacion :");
                                                                                          switch(opid){
                                                                                          case 0: nxtDisplayCenteredTextLine(5, "Suma");
                                                                                          break;
                                                                                          case 1: nxtDisplayCenteredTextLine(5, "Resta");
                                                                                          break;
                                                                                          case 2: nxtDisplayCenteredTextLine(5, "Multiplicacion");
                                                                                          break;
                                                                                          case 3: nxtDisplayCenteredTextLine(5, "Division");
                                                                                          break;
                                                                                          case 4: nxtDisplayCenteredTextLine(5, "Resto");
                                                                                          break;
                                                                                          case 5: nxtDisplayCenteredTextLine(5, "Potencia");
                                                                                          break;
                                                                                          }
                                                                                          }
                                                                                          }

                                                                                          1 - Es también importante poner un pequeño retardo entre selección de número y operación u otro número, ya que si mantenemos el botón ENTER apretado aunque sea por poco tiempo podríamos seleccionar sin querer Suma como la operación deseada.

                                                                                          9 - Como en este switch() queremos que opid valga 0, 1, 2, 3, 4 o 5 si tocamos el botón derecho cuando opid es igual a 5 lo cambiaremos a 0, y si le damos al izquierdo cuando vale 0 lo cambiaremos a 5.

                                                                                          Elección del segundo número:

                                                                                          wait1Msec(300);
                                                                                          while(!n2){
                                                                                          if ((boton = nNxtButtonPressed) > 0){
                                                                                          wait1Msec(200);
                                                                                          switch(boton){
                                                                                          case 1: positivo = !positivo;
                                                                                          break;
                                                                                          case 2: positivo = !positivo;
                                                                                          break;
                                                                                          case 3: n2 = true;
                                                                                          break;
                                                                                          }
                                                                                          }
                                                                                          if (SensorValue[contacto1]){
                                                                                          if(positivo)
                                                                                          j++;
                                                                                          else
                                                                                          j--;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          if (SensorValue[contacto2]){
                                                                                          if(positivo)
                                                                                          j = j + 10;
                                                                                          else
                                                                                          j = j - 10;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          if (SensorValue[contacto3]){
                                                                                          if(positivo)
                                                                                          j = j + 100;
                                                                                          else
                                                                                          j = j - 100;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          if (SensorValue[contacto4]){
                                                                                          if(positivo)
                                                                                          j = j + 1000;
                                                                                          else
                                                                                          j = j - 1000;
                                                                                          wait1Msec(150);
                                                                                          }
                                                                                          eraseDisplay();
                                                                                          nxtDisplayCenteredTextLine(2, "Eliga un numero: ");
                                                                                          nxtDisplayCenteredTextLine(4, "%d", j);
                                                                                          if(positivo)
                                                                                          nxtDisplayCenteredTextLine(6, "Modo positivo");
                                                                                          else
                                                                                          nxtDisplayCenteredTextLine(6, "Modo negativo");
                                                                                          wait1Msec(100);
                                                                                          }

                                                                                          Prácticamente simétrico a la elección del primer número. Se le ha añadido un retardo delante por la misma razón que a la elección de operación.

                                                                                          Cálculo del resultado:

                                                                                          wait1Msec(300);
                                                                                          switch(opid){
                                                                                          case 0: resultado = i + j;
                                                                                          break;
                                                                                          case 1: resultado = i - j;
                                                                                          break;
                                                                                          case 2: resultado = i * j;
                                                                                          break;
                                                                                          case 3: resultado = (float)i / (float)j;
                                                                                          break;
                                                                                          case 4: resultado = i % j;
                                                                                          break;
                                                                                          case 5: resultado = potencia(i,j);
                                                                                          break;
                                                                                          }
                                                                                          eraseDisplay();
                                                                                          nxtDisplayCenteredTextLine(1, "El resultado de ");
                                                                                          nxtDisplayCenteredTextLine(3, "operar %d y %d es: ",i , j);
                                                                                          nxtDisplayCenteredTextLine(5, "%6.2f", resultado);
                                                                                          wait1Msec(5000);
                                                                                          }

                                                                                          En esta última parte calculamos y mostramos el resultado.

                                                                                          9 - Para que el resultado de la división sea con decimales es importante hacer un casting(conversión) a tipo float de los dos valores, ya que los int son números enteros. Para hacer un casting se pone delante de la variable que deseamos convertir el tipo a la que queremos convertirlo entre paréntesis.

                                                                                          13 - Aquí es donde llamamos a la función potencia que nos creamos al principio.

                                                                                          18 - Como veis es posible añadir varios decimales a una línea de texto. El orden será el mismo que el indicado en la parte de texto.

                                                                                          19 - En este caso utilizamos %6.número_decimalesf, que es el símbolo para representar tipos float. Número decimales indica la cantidad de decimales que se mostrarán por pantalla (dos en este caso).

                                                                                          Por ultimo añadir que lo importante de este programa es el uso de los búcles y de las sentencias switch(). Ante cualquier duda podéis escribir en el foro, como siempre.

                                                                                          Cerradura electrónica en NXT-G

                                                                                          cerraduraLa cerradura es un elemento mecánico, generalmente metálico que impide el acceso al contenido que protege si se carece del elemento que permite abrirlo, tradicionalmente una llave y en la actualidad cada vez más tarjetas magnéticas que deben pasar por una banda o transpondedores pasivos – elementos que son identificados por escáneres, robots u ordenadores. Es necesario que interactúe con un sensor que decodifique la información que contiene y la transmita al centro de datos. Tienen un alcance muy limitado-. Aunque también existen cerraduras que tienen un panel y requieren de una contraseña de acceso. Este es el caso de nuestro experimento de hoy.

                                                                                          Este es el modelo que hemos usado para probar nuestra cerradura

                                                                                          cerradura

                                                                                          Tiene 4 sensores de contacto en la parte derecha y la cerradura, accionada por un motor en la izquierda.

                                                                                          En una primera versión trabajaremos suponiendo que, como desarrolladores, conocemos la clave que nos permite abrir el cerrojo y comprobaremos si el usuario la introduce correctamente.

                                                                                          Programa cerradura completo

                                                                                          Cerradura básica

                                                                                          Si os fijáis, a la izquierda del programa, junto al punto de inicio hemos anotado la clave que tiene codificada esa cerradura por defecto, a continuación comprobamos si el usuario pulsa los botones (sensores de presión) en el orden correcto, si es así escribimos la variable dígito correspondiente (dígito 1.. dígito 4) con un 1, si no con un 0.
                                                                                          Es decir, si nosotros esperamos como primer botón de la clave el sensor 2 (como es el caso de este programa) y el usuario presiona un botón distinto del 2, nuestro dígito 1 será 0.

                                                                                          Una vez el usuario ha introducido todos los dígitos de la clave (son 4) debemos compara la clave introducida con la buena. Si la clave en correcta, todas las variables dígito X (X= 1 ..4) deben estar a 1, y por lo tanto tras la transformación del bloque Suma Dígitos compararemos el resultado con el número 1111.

                                                                                          Detalle del bloque Pedir Cierre

                                                                                          Detalle Cierre

                                                                                          Este bloque se encarga de comprobar si la cerradura ya está cerrada , en cuyo caso nos preguntará si queremos abrirla, o si está abierta, y nos preguntará entonces si queremos cerrarla.
                                                                                          para saber el estado de la cerradura utilizamos la variable flag, que cambia su valor dependiendo del estado de la cerradura al final de cada bucle.

                                                                                          Detalle del bloque Suma Dígitos

                                                                                          Detalle Suma

                                                                                          Este bloque se encarga de hacer el sumatorio que nos permitirá comprobar si la clave es correcta o no. Para ello convierte los 4 dígitos de la clave en un sólo número y devuelve el resultado para que pueda ser comparado con la respuesta correcta, tal y como se explica más arriba.

                                                                                          La transformación a un sólo número consiste en multiplicar el valor de cada dígito – 0 o 1 – por su posición.
                                                                                          digito1 – digito2 – digito3 – digito4 representan: unidades de millar – centenas – decenas – unidades respectivamente. Luego si tenemos la clave 1-0-0-1 haremos 1*1000+0*100+0*10+1*1 = 1001
                                                                                          ¿Es 1001 el valor correcto? No, tal y como hemos comprobado arriba, el valor correcto es 1111.

                                                                                          Ahora partimos de la base de que la contraseña debe introducirla el usuario, no la conocemos cuando hacemos el programa, para lo que necesitaremos preguntarle los 4 dígitos de la contraseña antes de cerrar la caja por primera vez.

                                                                                          Si en la versión anterior nos bastaba con colocar los sensores en el mismo orden en el que estaba la clave, ahora no sabemos el orden a priori, pues la clave la introduce el usuario al comenzar la ejecución, con lo que tendremos que variar ligeramente el programa.

                                                                                          Lo primero es pedir la clave al usuario, para ello, basta con añadir este bloque al principio del programa anterior.

                                                                                          Detalle Clave

                                                                                          La primera parte del bloque son instrucciones en pantalla para que el usuario sepa qué debe hacer. Tras las instrucciones esperamos a que el usuario presione el botón Intro del NXT.

                                                                                          ¡ATENCIÓN! El bloque no está completo ya que es muy muy largo, aunque al tratarse de un bloque muy repetitivo (tenemos que pedir 4 valores para la clave), es también fácil saber qué falta.
                                                                                          Si observáis el bloque expandido, veréis que en el bucle estamos escribiendo el valor de clave1 en función de qué sensor de contacto presione el usuario – guardaremos el valor 1 para puerto 1, 2 para puerto 2, etc – , simplemente tendríamos que repetir el mismo bucle para cada dígito de la clave, en nuestro caso hemos repetido ese bucle 4 veces. Debemos acordarnos de cambiar el valor de la variable correcta en cada bucle: clave1, clave2..

                                                                                          Una vez el usuario nos ha dado la clave basta un simple cambio para poder usarla en el programa anterior.

                                                                                          Con clave Usuario

                                                                                          Como podéis comprobar, hemos usado la variable clave1 del bloque anterior como entrada de puerto para realizar la comprobación.

                                                                                          Aquí tenéis el vídeo de su funcionamiento.

                                                                                          Hemos añadido una funcionalidad adicional, y es que si el usuario no está de acuerdo con la clave que ha introducido, antes de cerrar puede rectificarla.

                                                                                          Esperamos que os guste, para cualquier duda, podéis visitar el foro.

                                                                                          Manejo de Bluetooth en RobotC

                                                                                          Artículo nº 5 de la serie de 14 artículos sobre RobotC

                                                                                          bluetoothEl Bluetooth es un protocolo de comunicaciones muy útil que permite conectar distintos dispositivos a distancia. Se emplea en la actualidad para el intercambio de información entre móviles, aunque también lo podemos encontrar en otros dispositivos como micrófonos inalámbricos, ordenadores, o la misma PS3, etc. Los LEGO Mindstorms NXT también cuentan con Bluetooth, el cual sirve generalmente para subir programas al NXT desde el ordenador. Sin embargo las posibilidades son mucho mayores.

                                                                                          Manejo sencillo del Bluetooth en RobotC:

                                                                                          La conexión Bluetooth funciona como la mayoría de comunicaciones en informática: el dispositivo que desea enviar los datos busca al destinatario, establece conexión con el, y va enviando conjuntos de información a través de un buffer (una línea de información). El dispositivo que va a recibir los datos estará siempre a la espera de recibir una petición de conexión, y una vez aceptada y establecida la conexión irá leyendo del buffer dicha información, actuando en consecuencia a lo que reciba. Una vez finalizada la tarea ambos cerrarán la conexión, y el dispositivo de recepción quedará a la espera de una nueva conexión.

                                                                                          En RobotC un programa puede realizar varias tareas al mismo tiempo, por lo que podemos crear una tarea dedicada a enviar mensajes y otra a recibirlos. Estas tareas se denominan task, y si os habéis fijado en programas de artículos anteriores de RobotC, siempre creamos una tarea principal llamada main.

                                                                                          Para crear una conexión con un dispositivo necesitamos su nombre y el puerto en el que lo queremos conectar (recordad que el Bluetooth del NXT permite un máximo de cuatro conexiones, una de maestro y tres de esclavos). Para ello usaremos la función:

                                                                                          btConnect(numeroPuerto, nombre): Nos conectamos con el dispositivo cuyo nombre sea el indicado, en el puerto deseado.

                                                                                          Si un programa crea conexiones Bluetooth es importante que ese mismo programa las deshaga al final. Para ello se usan las funciones:

                                                                                          btDisconnect(numeroPuerto): Nos desconectamos del NXT que este anclado a dicho puerto.

                                                                                          btDisconnectAll(): Nos desconectamos de todos los NXT;

                                                                                          Una vez establecida la conexión ya podemos enviar información. Una forma muy útil es mediante la funcion:

                                                                                          sendMessageWithParm(short mensaje, ….): Nos permite enviar enviar información en forma de enteros. Se pueden enviar varios valores.

                                                                                          Para recibir un mensaje primero necesitamos leerlo, para saber si hay o no mensaje podemos usar la variable message, que nos devolverá un número distinto de cero en caso de que lo haya. Para leer el mensaje usaremos:

                                                                                          messageParm[numParametro]: Nos devuelve el parámetro que haya en la posición numParametro (empezando desde 0). En el programa de prueba podéis ver un ejemplo de esto.

                                                                                          Para limpiar el mensaje despues de leerlo usar la función:

                                                                                          ClearMessage(): Limpia el mensaje, lo que nos permite poder recibir nuevos mensajes.

                                                                                          Programa de prueba:

                                                                                          El programa de prueba consiste en un control remoto para manejar un NXT con otro NXT. En ambos tendremos dos task ejecutandose, una que recibe información y otra que la recibe, de forma que se pueden controlar mutuamente. Para que este programa funcione es importante que enlaceís los NXT mediante el menú de Bluetooth. Este programa lo deberán tener por tanto ambos NXT. A continuación os pongo el código (aunque vaya por partes cuando lo escribáis tiene que ir todo junto):

                                                                                          Inicio del programa y definición de variables:

                                                                                          #pragma platform(NXT)

                                                                                          int nBoton;
                                                                                          int nAccion;
                                                                                          int nMensaje = 0;

                                                                                          task enviarMensajes();
                                                                                          void leerMensajes();
                                                                                          void comprobarConexion();

                                                                                          Task main:

                                                                                          task main()
                                                                                          {
                                                                                          comprobarConexion();
                                                                                          eraseDisplay();
                                                                                          bNxtLCDStatusDisplay = true;
                                                                                          wait1Msec(500);
                                                                                          StartTask(enviarMensajes);
                                                                                          leerMensajes();
                                                                                          return;
                                                                                          }

                                                                                          Task enviarMensajes:

                                                                                          task enviarMensajes()
                                                                                          {
                                                                                          while (true){
                                                                                          if((nBoton = nNxtButtonPressed) > -1)
                                                                                          sendMessageWithParm(nBoton);
                                                                                          }
                                                                                          return;
                                                                                          }

                                                                                          Función leerMensaje:

                                                                                          void leerMensajes()
                                                                                          {
                                                                                          while (true){
                                                                                          nMensaje = message;
                                                                                          if (nMensaje != 0){
                                                                                          nAccion = messageParm[0];
                                                                                          switch(nAccion){
                                                                                          case 1 : motor[motorA] = 30;
                                                                                          motor[motorC] = 70;
                                                                                          break;
                                                                                          case 2 : motor[motorA] = 70;
                                                                                          motor[motorC] = 30;
                                                                                          break;
                                                                                          case 3 : motor[motorA] = 70;
                                                                                          motor[motorC] = 70;
                                                                                          break;
                                                                                          }
                                                                                          ClearMessage();
                                                                                          }
                                                                                          }
                                                                                          }

                                                                                          Función comprobarConexion:

                                                                                          void comprobarConexion()
                                                                                          {
                                                                                          if (nBTCurrentStreamIndex >= 0)
                                                                                          return;
                                                                                          PlaySound(soundLowBuzz);
                                                                                          PlaySound(soundLowBuzz);
                                                                                          eraseDisplay();
                                                                                          nxtDisplayCenteredTextLine(3, "No esta");
                                                                                          nxtDisplayCenteredTextLine(4, "Conectado");
                                                                                          wait1Msec(3000);
                                                                                          StopAllTasks();
                                                                                          }

                                                                                          Nota: Esta última función se encarga de comprobar si la conexión Bluetooth es correcta. En caso contrario el robot emitirá un pitido.

                                                                                          En breve incorporaremos el vídeo de muestra del programa.

                                                                                          Para cualquier duda no dudéis en escribir en el foro.

                                                                                          Información adicional:

                                                                                          NetBeans, opción gráfica para LeJOS

                                                                                          Artículo nº 15 de la serie de 21 artículos sobre LeJOS

                                                                                            NetbeansNetbeans es una plataforma que permite la creación de interfaces gráficas, y que es completamente compatible con LeJOS. Existe de hecho un plugin (complemento) de LeJOS para NetBeans, y además las carpetas de ejemplos de LeJOS viene ya preparadas para su compilación y ejecución en NetBeans. Gracias a NetBeans se pueden hacer aplicaciones como un mando radio-control para manejar nuestro robot desde el ordenador.

                                                                                            ¿Qué es NetBeans?:

                                                                                            La plataforma NetBeans permite que las aplicaciones sean desarrolladas a partir de un conjunto de componentes de software llamados módulos. Un módulo es un archivo Java que contiene clases de java escritas para interactuar con las APIs (interfaces de programación de aplicaciones) de NetBeans y un archivo especial (manifest file) que lo identifica como módulo. Las aplicaciones construidas a partir de módulos pueden ser extendidas agregándole nuevos módulos. Debido a que los módulos pueden ser desarrollados independientemente, las aplicaciones basadas en la plataforma NetBeans pueden ser extendidas fácilmente por otros desarrolladores de software.

                                                                                            Compatibilizar NetBeans con LeJOS:

                                                                                            Lo primero que necesitamos es descargar e instalar NetBeans, para ello iremos a la página de descargas de NetBeans y nos descargaremos el paquete de Java, que contiene ya todo lo necesario para desarrollar nuestras aplicaciones:

                                                                                            Página de descargas de NetBeans

                                                                                            Una vez descargado e instalado empezaremos por abrir y probar los programas de ejemplo que vienen ya en la carpeta de ejemplo de LeJOS preparados para su uso en NetBeans. Para ello elegimos Archivo, Abrir Proyecto… :

                                                                                            NetBeans1

                                                                                            y buscamos las carpetas samples y pcsamples que estarán en la carpeta de ejemplos de LeJOS (por defecto será …\Mis Documentos\\leJOSNXJProjects . Una vez abiertos deberia quedar algo así:

                                                                                            NetBeans2

                                                                                            Ahora vamos a probar uno normal y otro de PC. Si os dais cuenta dentro de cada carpeta de proyecto hay un archivo llamado build.xml. Si clickeaís con el botón derecho en él y le dais a Ejecutar destino veréis que os muestra todas las opciones de de compilación, linkado, subida … tal y como podéis ver en la imagen anterior.

                                                                                            Vamos a probar un programa de samples, el BlueStats por ejemplo. Es importante que tengáis el robot conectado por USB, para saber como podéis revisar mis artículos anteriores sobre Bluetooth. Hacéis click derecho en el build.xml y le dais a a Ejecutar destino, uploadandrun. El programa entonces se compilará, linkará, subirá y ejecutará en el NXT. Este programa lo que hace es mostrarte las estadísticas de la conexión Bluetooth con el NXT.

                                                                                            Ahora probaremos otro de los de pcsamples, el que se llama TachoCount. Los programas de pcsamples funcionan de forma diferente, primero necesitas hacer click derecho en el build.xml de la carpeta global de pcsamples, y le dais a Ejecutar destino, build. Una vez hecho esto os metéis en la carpeta individual del programa (TachoCount en este caso) y le dais click derecho, Ejecutar archivo:

                                                                                            NetBeans3

                                                                                            Para este programa necesitaréis un motor en el puerto A y otro en el puerto C. Lo que hace dicho programa es mostrar el valor del tacómetro de A y C, rotar luego cada motor, y finalmente volver a mostrar el valor de los tacómetros.

                                                                                            Instalación del Plugin de LeJOS:

                                                                                            Aunque mi recomendación es que creéis vuestros programas dentro de carpetas de programas de ejemplos, ya que tenéis asegurado que se puedan compilar y ejecutar bien, es posible crear nuestros propios programas por separado, y para ello necesitamos el plugin de LeJOS para NetBeans. Para instalar dicho plugin tenéis que hacer lo siguiente: meteros en Herramientas, Complementos y en la ventana que os sale darle a Descargado, Añadir Plugin… y buscáis el archivo NXJPlugin/build/nxjplugin.nbm que esta en la carpeta de ejemplos de LeJOS:

                                                                                            NetBeans5

                                                                                            Le dais a instalar, y decís que si a todo. Una vez instalado podremos crear Proyectos de LeJOS de la siguiente manera: le dais a Archivo, Proyecto Nuevo, Ejemplos, NXJProject:

                                                                                            NetBeans4

                                                                                            Es importante que creéis el proyecto en la carpeta de ejemplos de LeJOS. Una vez finalizada la creación tendremos un proyecto plantilla con el programa HelloWorld.java y el archivo build.xml con las opciones de compilación y ejecución. Es importante que añadáis la ruta de las librerías de LeJOS. Para ello haced click derecho en vuestro proyecto, Propiedades…, Ruta de clases de fuentes Java, Añadir JAR/Carpeta… y seleccionas classes.jar de vuestra carpeta de instalación de LeJOS (por defecto estará en C:\Archivos de programa\leJOS NXJ\lib:

                                                                                            NetBeans6

                                                                                            También deberéis variar las rutas escritas dentro del archivo build.xml para que coincidan con las de vuestra instalación.

                                                                                            Una vez hecho todo esto podréis modificar esta plantilla como os plazca para crear vuestro propio programa. Daos cuenta que no se pueden hacer programas para pc de LeJOS mediante este método, solo los de NXT.

                                                                                            ¿Cómo crear un programa para LeJOS con interfaz gráfica?: Un pequeño control remoto

                                                                                            Para crear un programa con interfaz gráfica usaremos una carpeta ya preparada de las de ejemplos para PC, como por ejemplo la de TachoCount. En ella crearemos lo que se denomina un Form, para lo que haremos click derecho en la carpeta TachoCount, Nuevo.., Formularios de Intefaz gráfica de Swing, Formulario JDialog:

                                                                                            NetBeans7

                                                                                            Una vez tengamos el form le pondremos varios botones: Uno para conectar, otro para desconectar, otro para mover el motor A, otro para el C, y otros dos para pararlos. Para añadir botones solo los tenéis que arrastrar de la paleta:

                                                                                            NetBeans8

                                                                                            Y podéis renombrarlos en Propiedades después de seleccionarlo, que se encuentra debajo de la paleta:

                                                                                            NetBeans9

                                                                                            Una vez añadidos los botones vamos a modificar el código. Para ello darle a Fuente:

                                                                                            NetBeans11

                                                                                            Y lo primero que pondremos son la importación librerías que necesitamos (podéis copiar las de TachoCount). Abajo del todo nos crearemos también la variable privada conn que usaremos como conexión:

                                                                                            private NXTConnector conn = new NXTConnector();

                                                                                            Una vez hecho esto volver a darle a Diseño. Para poner que hace cada botón al clickear en el damos click derecho en el botón, Eventos, Action, actionPerformed…; esto creará un método y nos llevará a Fuente para que lo programemos. A continuación os pondré el código para cada botón:

                                                                                            Conectar:

                                                                                            private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
                                                                                            // TODO add your handling code here:

                                                                                            conn.addLogListener(new NXTCommLogListener() {
                                                                                            public void logEvent(String message) {
                                                                                            System.out.println(message);
                                                                                            }

                                                                                            public void logEvent(Throwable throwable) {
                                                                                            System.err.println(throwable.getMessage());
                                                                                            }
                                                                                            });
                                                                                            conn.setDebug(true);
                                                                                            if (!conn.connectTo("NXTTorna", NXTComm.LCP)) {
                                                                                            System.err.println("Fallo de conexiónt");
                                                                                            System.exit(1);
                                                                                            }
                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            }

                                                                                            14 – Recordad poner el nombre de vuestro NXT (NXTTorna es el nombre del que estoy usando yo).

                                                                                            Desconectar:

                                                                                            private void jButton4ActionPerformed(java.awt.event.ActionEvent evt) {
                                                                                            // TODO add your handling code here:
                                                                                            try{
                                                                                            conn.close();
                                                                                            }catch(Exception e){
                                                                                            e.printStackTrace();
                                                                                            }
                                                                                            }

                                                                                            Motor A:

                                                                                            private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
                                                                                            // TODO add your handling code here:
                                                                                            Motor.A.forward();
                                                                                            }

                                                                                            Motor C:

                                                                                            private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {
                                                                                            // TODO add your handling code here:
                                                                                            Motor.C.forward();
                                                                                            }

                                                                                            Parar A:

                                                                                            private void jButton5ActionPerformed(java.awt.event.ActionEvent evt) {
                                                                                            // TODO add your handling code here:
                                                                                            Motor.A.stop();
                                                                                            }

                                                                                            Parar C:

                                                                                            private void jButton6ActionPerformed(java.awt.event.ActionEvent evt) {
                                                                                            // TODO add your handling code here:
                                                                                            Motor.C.stop();
                                                                                            }

                                                                                            Para compilar recordad hacer click derecho en el build.xml de la carpeta global de pcsamples, y darle a Ejecutar destino, build. Una vez hecho esto os metéis en la carpeta individual del programa y le dais click derecho, Ejecutar archivo al Form. Os debería salir una ventana de este estilo:

                                                                                            NetBeans10

                                                                                            Con la que podéis controlar a vuestro robot.

                                                                                            Sé que el artículo de hoy es extenso y complicado, así que os pido que si tenéis alguna duda por favor escribáis en el foro.

                                                                                            Manejo Remoto simultáneo de varios NXT desde PC

                                                                                            Artículo nº 14 de la serie de 21 artículos sobre LeJOS

                                                                                            Robots_gemelos_En el artículo anterior vimos que es posible comunicar PC y NXT gracias a las librerías de LeJOS. Hoy vamos a mostrar cómo conectar y manejar varios NXT simultáneamente, y la diferencia respecto a manejar varios NXT desde otro NXT, como su mucha menor latencia.

                                                                                            Comunicación con el PC:

                                                                                            Como puse en el artículo anterior, para comunicar cualquier dispositivo Bluetooth con el ordenador necesitamos un adaptador Bluetooth para el ordenador, que puede estar integrado en la placa base, o se puede adquirir por separado. Para ver cómo echadle un vistazo a dicho artículo.

                                                                                            Las ventajas de conectar varios NXT a un PC en lugar de a otro NXT maestro son claras: no tenemos problemas de latencias tan altas como las comunicaciones NXT-NXT, tenemos un número prácticamente ilimitado de conexiones en lugar de las tres máximas que acepta un NXT, y además la potencia de cálculo de un ordenador no es comparable con la de un NXT. Podríamos incluso crear un interfaz gráfico para manejar un NXT radio-control desde el PC.

                                                                                            Lo mejor del tipo de comunicaciones que vamos a usar es que no necesitamos subir ningún programa al NXT, todo lo hacemos desde el PC, lo cual nos facilita muchas cosas, desde las comunicaciones hasta la velocidad para realizar cálculos complejos.

                                                                                            En el artículo anterior ya mostré qué métodos se utilizan para realizar la conexión, por tanto si queremos conectarnos con varios NXT sólo tendremos que repetir el proceso de conexión varias veces, asegurándonos de que todos los NXT se han conectado correctamente con el PC. Es importante dejar un retardo después de realizar varias conexiones, de forma que nos aseguraremos que todos están listos para recibir ordenes.

                                                                                            Una vez estén todas las conexiones listas podemos dar ordenes a uno u otro NXT indistintamente. Recordad que para elegir a cuál vais a dar una instrucción es necesario usar antes el método:

                                                                                            NXTCommand.getSingleton().setNXTComm(nombre_conexión.getNXTComm());

                                                                                            poniendo en nombre_conexión el nombre que le hayamos dado a la conexión que queramos utilizar (Ej.: conn1, conn3, …). Usad este método cada vez que queráis cambiar el objetivo de vuestras ordenes. Por ejemplo, si quiero mover el motor A con dos NXT, cuyas conexiones son conn1 y conn2 sería:

                                                                                            NXTCommand.getSingleton().setNXTComm(conn1.getNXTComm());
                                                                                            Motor.A.forward();
                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.forward();

                                                                                            Es MUY importante que al final del programa cerréis las conexiones con el método close() o el PC no os conectará con los NXT la segunda vez que queráis usar el programa (tendrías que apagar y encender los NXT en su lugar).

                                                                                            Programa de ejemplo:

                                                                                            En el programa de ejemplo vamos a hacer que el ordenador ordene a dos NXT moverse simultáneamente describiendo un cuadrado. Es importante que la forma de los robots sea la misma para que quede bien, ya que si la distancia entre las ruedas es mayor, o uno pesa más que otro ya no se moverían igual.

                                                                                            Robots_gemelos2_

                                                                                            No estamos utilizando la librería Navigation de LeJOS, por lo que el cálculo del cuadrado lo hemos hecho aproximado, rotando los motores un número determinado de grados. Lo importante es que este programa consigue mover simultáneamente los dos robots desde el PC, cosa que sería imposible desde otro NXT debido a las latencias. El código sería el siguiente:

                                                                                            import lejos.nxt.*;
                                                                                            import lejos.nxt.remote.NXTCommand;
                                                                                            import lejos.pc.comm.*;

                                                                                            /*
                                                                                            ElectricBricks Entertainment S.L.
                                                                                            Autor: Álvaro Peláez Santana
                                                                                            Fecha: 13/04/2010
                                                                                            Nombre: MultiControl
                                                                                            Descripción: Programa que controla remotamente dos NXT desde un PC,
                                                                                            ordenándoles que realicen una figura geométrica simultáneamente.
                                                                                            */

                                                                                            public class MultiControl {

                                                                                            public static void main(String [] args) throws Exception {
                                                                                            NXTConnector conn = new NXTConnector();

                                                                                            if (!conn.connectTo("NXT", NXTComm.LCP)) {
                                                                                            System.err.println("Conexión Fallida");
                                                                                            System.exit(1);
                                                                                            }
                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());

                                                                                            NXTConnector conn2 = new NXTConnector();

                                                                                            if (!conn2.connectTo("NXTTorna", NXTComm.LCP)) {
                                                                                            System.err.println("Conexión Fallida");
                                                                                            System.exit(1);
                                                                                            }

                                                                                            Thread.sleep(1000);

                                                                                            int x = 720;
                                                                                            int y = 520;

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(y, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(y);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(y, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(y);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(y, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(y);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x, true);

                                                                                            NXTCommand.getSingleton().setNXTComm(conn2.getNXTComm());
                                                                                            Motor.A.rotate(x, true);
                                                                                            Motor.B.rotate(x);

                                                                                            Thread.sleep(1000);

                                                                                            conn.close();
                                                                                            conn2.close();
                                                                                            }
                                                                                            }

                                                                                            Este tampoco es un programa muy complejo, si tenéis alguna duda sobre el manejo de motores en LeJOS os recomiendo el este artículo. Aquí os dejo un vídeo demostración del funcionamiento de este programa, que como podéis ver hace que los NXT se comporten de manera casí simultánea:

                                                                                            Por último, insistir en que si tenéis alguna duda posteadla en el foro. Un saludo y hasta otro artículo.

                                                                                            Manejo Remoto del NXT desde PC: LeJOS

                                                                                            Artículo nº 13 de la serie de 21 artículos sobre LeJOS

                                                                                              c0018_bluetooth_usb_01Hasta ahora hemos visto cómo comunicar varios NXT entre sí y enviar información de uno a otro, pero también es posible comunicarnos con un NXT mediante el PC, dándole instrucciones ya sea mediante mediante comandos, o creando un UI (Interfaz de Usuario) con botones y consola propia. Es cierto que estas comunicaciones son más complejas que las que se realizan habitualmente entre los propios NXT, pero también son muchas las posibilidades que nos proporcionan.

                                                                                              ¿Cómo comunicarnos con el PC?:

                                                                                              Para comunicar cualquier dispositivo Bluetooth con el ordenador necesitamos un adaptador bluetooth para el ordenador, que puede estar integrado en la placa base, o se puede adquirir por separado (normalmente del tipo mochila USB). Si ya tenéis uno seguramente lo habréis usado alguna vez para descargar programas al NXT en vez de usar el USB.

                                                                                              Una vez tengamos un adaptador USB es importante agregar el dispositivo Bluetooth a la lista de “Mis sitios Bluetooth”. Para ello encender el NXT, hacer doble click en el simbolo de Bluetooth de la barra de herramientas de Windows y en el menú que se despliega darle a “Agregar dispositivo Bluetooth”. Una vez lo haya encontrado lo seleccionamos, metemos la clave del NXT (por defecto 1234) y nos conectamos. Llegado a este paso ya estaremos listos para la comunicación entre el PC y el NXT.

                                                                                              ConBluetooth

                                                                                              Comunicación entre PC y NXT en LeJOS:
                                                                                              Los protocolos de comunicación en LeJOS entre PC y NXT se encuentran en una libreria llamada pccomm.jar, que será la que en este caso tendremos que añadir a nuestro proyecto en lugar de classes.jar (Mirar artículo de instalación de LeJOS para saber cómo). Luego tendremos que importar las siguiente librería dentro de nuestro programa:

                                                                                              import lejos.nxt.remote.NXTCommand;
                                                                                              import lejos.pc.comm.*;

                                                                                              Ahora podremos iniciar la comunicación. Para ello lo primero es crear un objeto de la clase NXTConnector:

                                                                                              NXTConnector nombre_conexión = new NXTConnector();

                                                                                              De esta forma ya nos podemos conectar con el NXT: para ello utilizaremos el protocolo de comunicaciones de LEGO LCP (LEGO MINDSTORMS NXT Communications Protocol). El método sería el siguiente:

                                                                                              boolean connectTo(“nombre_NXT”, NXTComm.LCP): Conecta el PC con el NXT usando el protocolo de comunicaciones de LEGO MINDSTORMS NXT. Devuelve false en caso de que la conexión falle.

                                                                                              Una vez conectado necesitaremos ser capaces de usar métodos como si de un programa normal se tratase, y que el ordenador los envíe y el NXT los entienda. Para ello usaremos siguiente método:

                                                                                              NXTCommand.getSingleton().setNXTComm(nombre_conexión.getNXTComm());

                                                                                              Este método es muy importante, porque traduce todos los comandos que se encuentran a continuación en el programa al LCP, para que puedan ser ejecutados en el dispositivo remoto, en este caso un NXT. Es decir, estas líneas de código que se están ejecutando en el PC están enviando los comandos que se encuentran en las siguientes líneas de programa, a continuación de ésta, al NXT para su ejecución en él. Una vez hecho esto ya podremos usar métodos como si de un programa de NXT se tratara como, por ejemplo, Motor.A.forward(). El último paso necesario es el de cerrar la conexión existente entre el PC y el NXT. Para ello haremos uso del método close().

                                                                                              Compilar programas para PC de LeJOS:

                                                                                              Los programas en LeJOS que se van a ejecutar en el PC se compilan de forma especial (no con javac como la mayoría de programas en java). Si os acordáis de el artículo de instalación de LeJOS explique cómo crear configuraciones propias de compilación y ejecución. De hecho en ese tutorial se enseña cómo crear las dos que después hemos usado para todos los programas.

                                                                                              Para compilar y ejecutar programas para PC en LeJOS necesitaremos crear otras dos nuevas configuraciones. Estás configuraciones son tal y como se muestran en las siguientes imágenes:

                                                                                              Eclipse2

                                                                                              Eclipse3

                                                                                              Por tanto las usaremos para compilar y ejecutar nuestro programa de PC en LeJOS.

                                                                                              Programa de ejemplo:

                                                                                              En el programa de ejemplo vamos ha hacer que el ordenador muestre por pantalla el valor del tacómetro (recordad el manejo del tacómetro en el artículo sobre el sensor de ultrasonido), ordene al NXT que gire el motor A y B, y una vez finalizado muestre los nuevos valores de los tacómetros. Este programa es bastante sencillo y se ve el funcionamiento básico del protocolo de comunicaciones, así como escribir información por la consola del PC (de eclipse en este caso). El código del programa es el siguiente:

                                                                                              import lejos.nxt.*;
                                                                                              import lejos.nxt.remote.NXTCommand;
                                                                                              import lejos.pc.comm.*;

                                                                                              public class ContadorGrados {

                                                                                              public static void main(String [] args) throws Exception {
                                                                                              NXTConnector conn = new NXTConnector();

                                                                                              if (!conn.connectTo("NXT", NXTComm.LCP)) {
                                                                                              System.err.println("Conexión Fallida");
                                                                                              System.exit(1);
                                                                                              }
                                                                                              NXTCommand.getSingleton().setNXTComm(conn.getNXTComm());

                                                                                              Motor.A.resetTachoCount();
                                                                                              Motor.B.resetTachoCount();
                                                                                              System.out.println("Tacómetro A: " + Motor.A.getTachoCount());
                                                                                              System.out.println("Tacómetro B: " + Motor.B.getTachoCount());
                                                                                              Motor.A.rotate(3600, true);
                                                                                              Motor.B.rotate(-3600);
                                                                                              Thread.sleep(1000);
                                                                                              System.out.println("Tacómetro A: " + Motor.A.getTachoCount());
                                                                                              System.out.println("Tacómetro B: " + Motor.B.getTachoCount());
                                                                                              conn.close();
                                                                                              }
                                                                                              }

                                                                                              Ahora explicaré brevemente algunas líneas:

                                                                                              10 - Si la conexión resulta fallida (! es el símbolo de negación en Java):

                                                                                              11 - Muestra el mensaje de error Conexión Fallida por consola, y

                                                                                              12 - Detiene la ejecución del programa (el hecho de salir con “1″ significa que ha habido error en el prográma, es un estándar universal en programación.

                                                                                              14 - Se trata de la línea más importante, que hemos explicado anteriormente, y es la que envía el código posterior a esta línea para su ejecución remota en el NXT al que nos hemos conectado previamente.

                                                                                              18, 19, 23 y 24 - Esta es la manera universal de mostrar información por consola (pantalla) en Java. Entre comillas para las cadenas de texto, y usando el símbolo + para concatenar elementos.

                                                                                              Como veis no es un programa muy complejo, el resultado por consola debería quedar algo así:

                                                                                              Eclipse1

                                                                                              Por último insistir en que si tenéis alguna duda posteadla en el foro. Un saludo y hasta otro artículo.

                                                                                              Manejo del Bluetooth en LeJOS: Parte 2

                                                                                              Artículo nº 12 de la serie de 21 artículos sobre LeJOS

                                                                                                IMG_2230_edit_En esta segunda parte del artículo voy a explicar la forma de conectar varios NXT por Bluetooth (más de dos), su utilidad, y sus limitaciones. Veremos principalmente el problema de la latencia (el tiempo que tarda en establecer conexión con otro NXT).

                                                                                                Manejo de Varias Conexiones:

                                                                                                En el artículo anterior vimos que es posible conectar varios NXT mediante el Bluetooth, sin embargo existe una limitación implícita: un ladrillo NXT sólo puede tener una conexión de maestro y tres de esclavo. El maestro es el encargado de enviar información a los esclavos, y los esclavos se encargarán de actuar según las ordenes que hayan recibido. Todo esto lo podéis ver con claridad en nuestro artículo sobre robots repetidores en NXT-G. El problema principal es que para comenzar una nueva conexión debe cerrarse la anterior, y el NXT tarda varios segundos (entre 1s y 2s) en realizar las conexiones, lo que impide ciertas aplicaciones como de control simultáneo de varios NXT desde un NXT maestro. Las conexiones Bluetooth entre ordenadores son muchísimo más rápidas.

                                                                                                Programa de pruebas:

                                                                                                Gracias a este programa podemos ver la latencia (tiempo que tardan las conexiones que el Bluetooth en realizarse entre los NXT). Tenemos tres programas (uno para cada ladrillo NXT): el primer programa esperará a que pulsemos el botón ENTER, y una vez pulsado se conectará al segundo NXT, enviándole la información de que hemos pulsado el botón. El segundo esperará a recibir esta información, actuará moviendo el motor y haciendo un sonido, y enviará la información al tercero. Finalmente el tercero recibirá esta información y actuará en consecuencia. El código del primer NXT es:

                                                                                                import lejos.nxt.Button;
                                                                                                import lejos.nxt.LCD;
                                                                                                import lejos.nxt.Motor;
                                                                                                import lejos.nxt.Sound;
                                                                                                import lejos.nxt.comm.BTConnection;
                                                                                                import lejos.nxt.comm.Bluetooth;
                                                                                                import java.io.*;

                                                                                                import javax.bluetooth.*;

                                                                                                public class LatenciasMain {

                                                                                                public static void main(String[] args) throws Exception {
                                                                                                String nombre = "NXTTorna";
                                                                                                LCD.drawString("Conectando...", 2, 1);
                                                                                                LCD.refresh();

                                                                                                LCD.drawString("Pulsa un boton", 2, 1);
                                                                                                int i = Button.waitForPress();
                                                                                                Sound.beep();
                                                                                                Motor.A.forward();

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conectando", 2, 1);
                                                                                                LCD.refresh();

                                                                                                RemoteDevice bt2 = Bluetooth.getKnownDevice(nombre);

                                                                                                if (bt2 == null){
                                                                                                LCD.clear();
                                                                                                LCD.drawString("No existe ese dispositivo", 0, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(2000);
                                                                                                System.exit(1);
                                                                                                }

                                                                                                BTConnection btc = Bluetooth.connect(bt2);

                                                                                                if (btc == null){
                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conexión fallida", 1, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(2000);
                                                                                                System.exit(1);
                                                                                                }

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conectado", 2, 1);
                                                                                                LCD.refresh();

                                                                                                DataOutputStream dos = btc.openDataOutputStream();

                                                                                                dos.writeInt(i);
                                                                                                dos.flush();

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Cerrando conexion", 0, 1);
                                                                                                LCD.refresh();
                                                                                                dos.close();
                                                                                                btc.close();

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Finalizado", 0, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(20000);
                                                                                                }
                                                                                                }

                                                                                                14 - El nombre del NXT al que se va a conectar tiene que ser el correcto. En nuestro hemos empleado estos nombres puesto que son como se llaman los NXT que estoy utilizando en el experimento.

                                                                                                El del segundo es:

                                                                                                import lejos.nxt.LCD;
                                                                                                import lejos.nxt.Motor;
                                                                                                import lejos.nxt.Sound;
                                                                                                import lejos.nxt.comm.BTConnection;
                                                                                                import lejos.nxt.comm.Bluetooth;
                                                                                                import java.io.*;

                                                                                                import javax.bluetooth.RemoteDevice;

                                                                                                public class LatenciaSlave2 {
                                                                                                public static void main(String[] args) throws Exception {

                                                                                                String nombre = "NXT3";

                                                                                                LCD.drawString("Esperando...", 2, 1);
                                                                                                LCD.refresh();

                                                                                                BTConnection btc = Bluetooth.waitForConnection();

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conectado", 2, 1);
                                                                                                LCD.refresh();

                                                                                                DataInputStream dis = btc.openDataInputStream();

                                                                                                int i = dis.readInt();
                                                                                                dis.close();
                                                                                                btc.close();
                                                                                                LCD.clear();

                                                                                                if (i == 1){
                                                                                                Sound.beep();
                                                                                                Motor.A.forward();
                                                                                                RemoteDevice bt2 = Bluetooth.getKnownDevice(nombre);

                                                                                                if (bt2 == null){
                                                                                                LCD.clear();
                                                                                                LCD.drawString("No existe ese dispositivo", 0, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(2000);
                                                                                                System.exit(1);
                                                                                                }

                                                                                                BTConnection btc2 = Bluetooth.connect(bt2);

                                                                                                if (btc2 == null){
                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conexion fallida", 0, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(2000);
                                                                                                System.exit(1);
                                                                                                }

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conectado", 2, 1);
                                                                                                LCD.refresh();
                                                                                                DataOutputStream dos = btc2.openDataOutputStream();
                                                                                                dos.writeInt(i);
                                                                                                dos.flush();
                                                                                                dos.close();
                                                                                                btc2.close();
                                                                                                }

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Cerrando conexion", 0, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(20000);
                                                                                                }
                                                                                                }

                                                                                                El del tercero sería:

                                                                                                import lejos.nxt.LCD;
                                                                                                import lejos.nxt.Motor;
                                                                                                import lejos.nxt.Sound;
                                                                                                import lejos.nxt.comm.BTConnection;
                                                                                                import lejos.nxt.comm.Bluetooth;
                                                                                                import java.io.*;

                                                                                                public class LatenciasSlave3 {
                                                                                                public static void main(String[] args) throws Exception {

                                                                                                LCD.drawString("Esperando...", 2, 1);
                                                                                                LCD.refresh();

                                                                                                BTConnection btc = Bluetooth.waitForConnection();

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Conectado", 2, 1);
                                                                                                LCD.refresh();

                                                                                                DataInputStream dis = btc.openDataInputStream();

                                                                                                int i = dis.readInt();
                                                                                                dis.close();
                                                                                                btc.close();
                                                                                                LCD.clear();

                                                                                                if (i == 1){
                                                                                                Sound.beep();
                                                                                                Motor.A.forward();
                                                                                                }

                                                                                                LCD.clear();
                                                                                                LCD.drawString("Cerrando conexion", 0, 1);
                                                                                                LCD.refresh();
                                                                                                Thread.sleep(20000);
                                                                                                }
                                                                                                }

                                                                                                Y por último y como siempre, un video de demostración:

                                                                                                No os olvidéis de publicar cualquier duda que tengáis al respecto en el foro.

                                                                                                Manejo de Bluetooth en LeJOS

                                                                                                Artículo nº 11 de la serie de 21 artículos sobre LeJOS

                                                                                                  bluetoothEl Bluetooth es un protocolo de comunicaciones muy útil que permite conectar distintos dispositivos a distancia. Se emplea en la actualidad para el intercambio de información entre móviles, aunque también lo podemos encontrar en otros dispositivos como micrófonos inalámbricos, ordenadores, o la misma PS3, etc. Los LEGO Mindstorms NXT también cuentan con Bluetooth, el cual sirve generalmente para subir programas al NXT desde el ordenador. Sin embargo las posibilidades son mucho mayores.

                                                                                                  Manejo del Bluetooth:

                                                                                                  La conexión Bluetooth funciona como la mayoría de comunicaciones en informática: el dispositivo que desea enviar los datos busca al destinatario, establece conexión con el, y va enviando conjuntos de información a través de un buffer (una línea de información). El dispositivo que va a recibir los datos estará siempre a la espera de recibir una petición de conexión, y una vez aceptada y establecida la conexión irá leyendo del buffer dicha información, actuando en consecuencia a lo que reciba. Una vez finalizada la tarea ambos cerrarán la conexión, y el dispositivo de recepción quedará a la espera de una nueva conexión.

                                                                                                  En primer lugar necesitaremos importar las librerias necesarias, principalmente serán lejos.nxt.comm.* y las librerias de java java.io.* y javax.bluetooth.*.

                                                                                                  Para crear una conexión con un dispositivo, antes necesitamos buscar ese dispositivo, y ver si está disponible, esto lo podemos hacer mediante:

                                                                                                  RemoteDevice id = Bluetooth.getKnownDevice(nombre del dispositivo): Buscamos el dispositivo cuyo nombre hayamos elegido, si existe nos devolverá su identificador, y en caso contrario nos devolverá null.

                                                                                                  Teniendo ya el id del dispositivo podemos generar ya una conexión mediante el siguiente método:

                                                                                                  BTConnection nombre_conexion = Bluetooth.connect(id): Creamos una conexión bluetooth con el dispositivo cuya id sea la indicada. Esta conexion tendrá el nombre que queramos ponerle. Si el resultado de la conexión es el valor null, nos está indicando que la conexión ha fallado.

                                                                                                  Una vez establecida la conexión es importante crear canales a través de los cuales podamos enviar y recibir información. Se puede crear uno de cada, por ejemplo:

                                                                                                  DataOutputStream canal_salida = btc.openDataOutputStream(): Creamos un canal de salida en el que escribiremos y enviaremos información para que el dispositivo al que estemos conectados pueda leerla.

                                                                                                  DataInputStream canal_entrada = btc.openDataInputStream(): Creamos un canal de entrada del que leeremos la información que el dispositivo al que estemos conectados nos haya enviado.

                                                                                                  Una vez terminados los envios de datos (para ver como enviar datos ver el programa de prueba) necesitamos cerrar tanto los canales como las conexiones, para ello usaremos el método close():

                                                                                                  canal_salida.close();
                                                                                                  canal.entrada.close();
                                                                                                  nombre_conexion.close();

                                                                                                  Ahora ilustraré todo esto con un programa de prueba.

                                                                                                  Programa de prueba:

                                                                                                  El programa de prueba consiste en realidad en dos programas: uno lo tendrá un NXT que usaré de mando radiocontrol, y el otro lo tendrá el vehículo con cambios de marchas que habéis visto en artículos anteriores. Con el NXT “manejador” conseguiremos hacer que el vehículo cambie de marcha, pare, o se mueva marcha adelante o marcha atrás. El dódigo del manejador es el siguiente:

                                                                                                  import lejos.nxt.Button;
                                                                                                  import lejos.nxt.LCD;
                                                                                                  import lejos.nxt.comm.BTConnection;
                                                                                                  import lejos.nxt.comm.Bluetooth;
                                                                                                  import java.io.*;
                                                                                                  import javax.bluetooth.*;

                                                                                                  public class BlueTest {

                                                                                                  public static void main(String[] args) throws Exception {
                                                                                                  String nombre = "NXTTorna";
                                                                                                  LCD.drawString("Conectando...", 2, 1);
                                                                                                  LCD.refresh();

                                                                                                  RemoteDevice bt = Bluetooth.getKnownDevice(nombre);

                                                                                                  if (bt == null){
                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("No existe ese dispositivo", 0, 1);
                                                                                                  Thread.sleep(2000);
                                                                                                  System.exit(1);
                                                                                                  }

                                                                                                  BTConnection btc = Bluetooth.connect(bt);

                                                                                                  if (btc == null){
                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Conexión fallida", 1, 1);
                                                                                                  Thread.sleep(2000);
                                                                                                  System.exit(1);
                                                                                                  }

                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Conectado", 2, 1);
                                                                                                  Thread.sleep(2000);

                                                                                                  DataOutputStream dos = btc.openDataOutputStream();

                                                                                                  LCD.drawString("Pulsa un boton", 2, 1);
                                                                                                  while(!Button.ESCAPE.isPressed()){
                                                                                                  int i = Button.waitForPress();
                                                                                                  dos.writeInt(i);
                                                                                                  dos.flush();
                                                                                                  }

                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Cerrando conexion", 0, 1);
                                                                                                  dos.close();
                                                                                                  btc.close();

                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Finalizado", 0, 1);
                                                                                                  Thread.sleep(2000);
                                                                                                  }
                                                                                                  }

                                                                                                  17 y 26 - Podéis ver como hacemos las comprobaciones para ver si se ha podido encontrar el dispositivo, y si se ha podido establecer la conexión.

                                                                                                  42 - Aquí se ve como escribir en un canal o buffer de salida. En este caso, como queremos enviar un entero usaremos el método writeInt(), escribiendo en para ello el identificador del botón que hemos pulsado.

                                                                                                  43 - El método flush() obliga a que se envíen los datos.

                                                                                                  Código del actuador:

                                                                                                  import lejos.nxt.Button;
                                                                                                  import lejos.nxt.LCD;
                                                                                                  import lejos.nxt.Motor;
                                                                                                  import lejos.nxt.comm.BTConnection;
                                                                                                  import lejos.nxt.comm.Bluetooth;
                                                                                                  import java.io.*;

                                                                                                  public class BlueRec {

                                                                                                  public static void main(String[] args) throws Exception {
                                                                                                  int nummarcha = 1;
                                                                                                  Marchas m = new Marchas();
                                                                                                  while(!Button.ESCAPE.isPressed()){
                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Esperando...", 2, 1);
                                                                                                  LCD.refresh();

                                                                                                  BTConnection btc = Bluetooth.waitForConnection();

                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Conectado", 2, 1);

                                                                                                  DataInputStream dis = btc.openDataInputStream();

                                                                                                  while(!Button.ESCAPE.isPressed()){
                                                                                                  int i = dis.readInt();
                                                                                                  LCD.clear();
                                                                                                  switch (i) {
                                                                                                  case 1:
                                                                                                  LCD.drawString("Cambio de Marcha", 1, 4);
                                                                                                  if (nummarcha == 1){
                                                                                                  m.marchaUp(Motor.C);
                                                                                                  nummarcha++;
                                                                                                  }else{
                                                                                                  m.marchaDown(Motor.C);
                                                                                                  nummarcha--;
                                                                                                  }
                                                                                                  break;
                                                                                                  case 2:
                                                                                                  LCD.drawString("Marcha alante", 1, 4);
                                                                                                  Motor.A.backward();
                                                                                                  break;
                                                                                                  case 4:
                                                                                                  LCD.drawString("Marcha atras", 1, 4);
                                                                                                  Motor.A.forward();
                                                                                                  break;
                                                                                                  case 8:
                                                                                                  LCD.drawString("Stop", 1, 4);
                                                                                                  Motor.A.stop();
                                                                                                  break;
                                                                                                  }
                                                                                                  }
                                                                                                  Thread.sleep(2000);
                                                                                                  LCD.clear();
                                                                                                  LCD.drawString("Cerrando conexion", 0, 1);
                                                                                                  dis.close();
                                                                                                  btc.close();
                                                                                                  Thread.sleep(2000);
                                                                                                  }
                                                                                                  }
                                                                                                  }

                                                                                                  26 - Como queremos leer el int con el identificador del botón pulsado que nos ha enviado el manejador, usaremos el método readInt().

                                                                                                  Y aquí tenéis el vídeo de muestra del programa:

                                                                                                  Para cualquier duda no dudéis en escribir en el foro.

                                                                                                  Información adicional:

                                                                                                  Robótica con LabVIEW, LEGO Mindstorms NXT y Tetrix

                                                                                                  labVIEW y LEGO MINDSTORMS NXTLa investigación en el mundo de la robótica implica trabajar con aplicaciones complejas que permitan probar nuevas teorías o algoritmos. En este contexto son típicas las aplicaciones que deben capturar los datos procedentes de los sensores y procesarlos para, dependiendo de la aplicación, poder descubrir su ubicación en el entorno que rodea al robot y poder navegar en el mismo, controlando los motores, y evitando colisionar con los objetos que le rodean.

                                                                                                  El nivel de dificultad de aplicaciones como la anterior puede dispararse sin límite: la posibilidad de trabajar con un sistema de visión, por ejemplo, multiplica las posibilidades pero complica la aplicación. Si bien lenguajes como C, C++ o Java están muy extendidos para ello y son empleados con frecuencia, no son óptimos en todas las circunstancias: la parte de bajo nivel puede desarrollarse con C++, mientras que el interfaz de usuario será más fácil de desarrollar en Java.

                                                                                                  Si bien el desarrollo del software puede ser una parte importante del problema, no debemos olvidar que el problema conjunto suele requerir también de habilidades en otros campos como la electrónica, ingeniería mecánica, cálculo, matemáticas o física.

                                                                                                  LabVIEW puede ser una ayuda a la hora de reducir el tiempo de desarrollo o facilitar el desarrollo en alguno de los campos en los que no seamos tan hábiles. Pero… ¿Qué es LabVIEW?

                                                                                                  LabVIEW es un poderoso entorno de desarrollo gráfico con funciones integradas para realizar adquisición de datos, control de instrumentos, análisis de medida y presentaciones de datos. El lenguaje utilizado por este entorno es denominado Lenguaje G (recordemos que el NXT-G es una versión muy reducida de las posibilidades de LabVIEW): es un lenguaje que describe de forma visual los elementos y el funcionamiento del programa sumándole simplicidad a su elaboración, de forma que es posible escribir programas altamente complejos haciendo uso de una interfaz de usuario completa y a medida. Una implementación de este tipo en lenguajes convencionales sería muy complicada de desarrollar por personas con una mínima experiencia en programación. Sin embargo esta simplicidad no resta poder a los usuarios experimentados.

                                                                                                  LabVIEW en educación

                                                                                                  Teniendo en cuenta la complejidad de algunos de los problemas a resolver dentro del ámbito de robótica y del poco tiempo disponible para ello, varias han sido las opciones que el mundo académico ha adoptado:

                                                                                                  • Los alumnos trabajan sobre robots pre-construidos y desarrollan algún componente del mismo. Esto tiene el inconveniente de que alguien ha debido preparar el conjunto, a la par que se exige de un presupuesto muy elevado, el de robots completos, para trabajar exclusivamente alguna parte del mismo.
                                                                                                  • Una opción alternativa es la de trabajar en entornos simulados, pero esto tiene también el problema de que una simulación realista exige un gran tiempo de desarrollo.
                                                                                                  • Una solución que han impartiendo universidades de prestigio como el MIT es la de trabajar con la solución de LEGO. Por lo que respecta al hardware, las versiones iniciales de hace diez años como el RCX han sido ampliamente superadas por el actual NXT y expandidas con los componentes metálicos Tetrix. Al software también le ha sucedido algo similar: el Robolab ha sido reemplazado por el NXT-G para aplicaciones simples o el LabVIEW para niveles avanzados, aunque las posibilidades de programación son muy variadas.

                                                                                                  LabVIEW permite tanto controlar robots, como crear aplicaciones avanzadas para controlar experimentos de laboratorio y mostrar los resultados, o desarrollar programas matemáticos para la enseñanza a estudiantes universitarios. LabVIEW Education Edition incorpora funciones especiales para conectarse con el LEGO MINDSTORMS NXT.

                                                                                                  Ejemplo de controles con LabVIEW

                                                                                                  Los programas desarrollados con LabVIEW se llaman Instrumentos Virtuales, o VIs. El control de instrumentos se dividen en dos partes: Panel Frontal y Diagrama de Bloques. El Panel Frontal es la interfaz con el usuario, en la cual se definen los controles e indicadores que se muestran en pantalla. El Diagrama de Bloques es el programa propiamente dicho, donde se define su funcionalidad, aquí se colocan iconos que realizan una determinada función y se interconectan.

                                                                                                  La programación con LabVIEW permite el prototipado rápido para probar nuevas ideas o algoritmos. LabVIEW dispone de librerías especiales para el procesado de señales o visión artificial, por citar algunas pero, además, si una función o tarea determinada no está soportada por LabVIEW, tenemos la posibilidad de escribirla en C y crear un nuevo instrumento virtual que podemos emplear en nuestros programas LabVIEW.

                                                                                                  El objetivo de esta pequeño artículo es servir de introducción para una nueva serie de artículos cuyo objetivo no es otro que el de ir descubriendo las posibilidades de la potente combinación entre tres plataformas: NXT, LabVIEW y Tetrix.

                                                                                                  GPS con el NXT y LabVIEW

                                                                                                  El GPS (Global Positioning System) es un sistema que permite determinar en todo el mundo la posición del receptor que recibe dichas señales. Dicho receptor puede formar parte del sistema de navegación de un coche, un avión, un ciclista, o un excursionista, y lo hace con una precisión habitual de varios metros, por lo que tiene tanto aplicaciones civiles como militares. Es posible incrementar la precisión del sistema mediante el uso del denominado GPS diferencial, aunque dicha posibilidad es normalmente empleada de forma exclusiva por sistemas militares.

                                                                                                  La base de este sistema de localización reside en la medida de las distancias hasta un mínimo de 3 satélites, convenientemente 4. Esto es lo que se conoce como triangulación. Una forma de determinar la posición de un objeto es medir su distancia a otros cuya posición es conocida (en nuestro caso sería la de los satélites). Si trabajamos en dos dimensiones, una circunferencia de radio R1 define la ubicación de todos los posibles puntos que se encuentran a una distancia R1 del centro C1 de dicha circunferencia. La intersección de la anterior con una segunda circunferencia produce normalmente dos posibles puntos de ubicación, los que distan R1 de C1 y R2 del nuevo centro C2. Habitualmente es necesaria una tercera medida o información adicional que nos permita discernir en cuál de los dos puntos anteriores nos encontramos. Del mismo modo, cuando trabajamos en el espacio, la información que recibimos de un satélite nos indica la distancia existente entre nosotros y dicho satélite, lo que supone una esfera de posibles ubicaciones. La intersección de las esferas producidas por las distancias a 3 satélites produce dos posibles puntos. Para determinar cuál de ellos es nuestra posición verdadera, podríamos efectuar una nueva medición a un cuarto satélite. Pero normalmente uno de los dos puntos posibles resulta ser muy improbable por su ubicación demasiado lejana de la superficie terrestre y puede ser descartado sin necesidad de mediciones posteriores.

                                                                                                   

                                                                                                  constelacion GPS, por El Pak
                                                                                                   

                                                                                                  La anterior es una simulación que indica con líneas verdes cuáles de los 24 satélites (4 satélites por cada una de las 6 órbitas) son los que serían visibles para nuestro GPS si consideramos que la visibilidad se entiende como que podemos trazar una recta entre ellas hasta un ángulo máximo de 45º.

                                                                                                  La medida de distancias a los satélites se basa en el cálculo del tiempo empleado en la propagación de ondas de radio entre el satélite y nuestro sistema. Con este objetivo se requiere que tanto satélite como el sistema generen una misma señal (pseudoaleatoria): el desfase entre ambas medido en el receptor proporciona la medida de la distancia. Teniendo en cuenta que dicha propagación se produce a la velocidad de la luz, es necesaria una imprescindible sincronización entre los relojes de nuestro sistema y el de los satélites, así como el uso de relojes muy precisos.

                                                                                                  Aunque parece obvio, las medidas anteriores parten de la base de que es necesario conocer dónde están los satélites en el espacio. El sistema GPS fue desarrollado e instalado por el Departamento de Defensa de los Estados Unidos, que mantiene 24 satélites a unas órbitas cercanas a los 20.000 km y cuyas posiciones son conocidas. La antigua Unión Soviética tenía un sistema similar (GLONASS), mientras que la Unión Europea está desarrollando en la actualidad su propio sistema de posicionamiento por satélite (Galileo).

                                                                                                  Teniendo en cuenta que la precisión habitual del GPS es de varios metros si no consideramos sistemas de corrección diferencial (DGPS), el uso de un GPS en un MINDSTORMS no suele ser de utilidad alguna en un entorno cerrado, puesto que el propio error de medida es del tamaño igual o mayor que el espacio en el que nos encontramos, y esta información no nos aporta nada: en cualquier punto de la habitación recibiremos seguramente la misma señal. Pero si llevamos el NXT al exterior sí podemos disponer de un sistema de ayuda al posicionamiento y navegación. Alternativamente podemos compaginar la información del GPS con la proporcionada por otros sensores y crear un sistema más robusto.

                                                                                                  Existen varios ejemplos de uso del GPS con el NXT. La arquitectura más simple es la de hacer uso de un receptor GPS comercial externo que nos proporcione la información de localización, como una PDA. En este caso, la forma habitual de conexión de nuestro NXT con el receptor GPS será habitualmente bluetooth. Los sistemas GPS hacen uso de un protocolo de comunicaciones serie denominado NMEA 0183, o NMEA, que define cómo debe transmitirse la información en formato ASCII desde un transmisor hasta los posibles escuchas.

                                                                                                  El aspecto de lo que nos puede devolver un receptor GPS, de acuerdo al formato NMEA, es similar al siguiente:

                                                                                                  $GPRMC,145055,V,4453.6083,N,00944.9533,E,000.0,000.0,070399,,,N*7F

                                                                                                  en donde cada uno de los elementos anteriores debe ser parseado teniendo en cuenta que:


                                                                                                  $GPRMC indica el tipo de cadena NMEA
                                                                                                  145055 corrección de posición del tiempo GPS (segundos)
                                                                                                  V calidad de los datos: A = posición válida, V = aviso de alarma
                                                                                                  4453.6083 latitud “ddMM.mmnn”
                                                                                                  N latitud N o S hemisferio
                                                                                                  00944.9533 longitud “DddMM.mmnn”
                                                                                                  E longitud E o W hemisferio
                                                                                                  000.0 velocidad sobre tierra (nudos)
                                                                                                  000.0 rumbo sobre tierra (0-359.9 grados)
                                                                                                  070399 fecha de la correción de posición “ddmmyy”
                                                                                                  - no empleado
                                                                                                  - no empleado
                                                                                                  N N = Dato no válido; A = Modo Autónomo etc.
                                                                                                  * delimitador
                                                                                                  7F byte de checksum (= byte1 eor byte2 eor byte3 eor … byte62)


                                                                                                  La cadena finaliza con los códigos ASCII “13″ (retorno de carro) y “10″ (line feed). 

                                                                                                  La rutina para parsear la información proveniente de los receptores tendrá en cuenta por tanto los caracteres de inicio, que siempre son del tipo $GP y extraerá por tanto toda la cadena existente hasta ubicación del siguiente string $GP. Una vez extraída la información de localización del string que recibimos del GPS, lo que hagamos con esa información depende de nuestra aplicación: seguir una ruta, etc.

                                                                                                  Una opción más atrevida es la de desarrollar nuestro propio sensor GPS, tal como hizo Martijn ten Bhömer. Dicho receptor GPS se conecta al NXT mediante un cable a unos de los puertos convencionales haciendo uso del protocolo I2C. En el interior de este sensor existe un microcontrolador que hace de puente entre el NXT y el sensor GPS USGlobalSat EM-406A. Este micro se comunica con el sensor mediante el protocolo NMEA-0183, y con el NXT a través del I2C. Una de las ventajas de esta arquitectura, frente a la anterior, es que el micro intermedio permite liberar al NXT de la sobrecarga que impone el parsing del código recibido por el GPS. La desventaja es la necesidad de crear el hardware. La aplicación elegida para demostrar la utilidad de este sensor consistía en desarrollar un robot capaz de seguir una ruta. El software que se ejecutaba en el NXT corría en LeJOS y Martijn se resiente de las imprecisiones matemáticas en esta plataforma, en concreto para las raíces cuadradas necesarias para el cálculo de distancias. Podemos ver dicha aplicación funcionando en el siguiente video.

                                                                                                  Otra implementación distinta es la realizada por convict. En este caso también implementaron su propio hardware, e hicieron uso del módulo GPS SkyTraq Venus 6 ST22 de perthold. La filosofía de este proyecto es exactamente la misma que la del anterior, pero en este caso programaron el NXT mediante LabVIEW Education Edition, y el micro encargado de comunicarse con el módulo GPS fue un PIC16F88.

                                                                                                  Información adicional:

                                                                                                  Cambio de Marchas Automático NXT en LeJOS

                                                                                                  Artículo nº 10 de la serie de 21 artículos sobre LeJOS

                                                                                                    detalleLos motores presentan un máximo y mínimo de posible velocidad. Pero muchas veces nos encontramos en situaciones en las que necesitamos más potencia o más velocidad de la que nuestros motores pueden ofrecer. Una solución poco práctica es utilizar diferentes motores, pero esto exige complicar la mecánica excesivamente. Una posible solución es hacer uso de una caja de cambios.

                                                                                                    Las cajas de cambios permiten disponer de una velocidad de giro diferente en las ruedas del vehículo pese a mantener la misma velocidad en el motor. Consecuencia de ello es la modificación de la relación existente entre la velocidad y el par del motor. Esta relación es inversamente proporcional, es decir, si disminuimos la velocidad, aumentamos el par, lo cual viene muy bien para poner el vehículo en marcha, y cuando ya no necesitamos tanta potencia debido a la inercia, podemos aumentar considerablemente la velocidad.

                                                                                                    Las cajas de cambios están formadas por un árbol primario que recibe el giro del motor, un árbol intermedio o intermediario, formado por engranajes de diferentes tamaños y que se desplaza para engranar el árbol primario con el árbol secundario, formado también por engranajes diferentes y que transmite el movimiento al árbol de transmisión del vehículo, y éste a las ruedas. La diferente relación entre los engranajes del intermediario y del árbol secundario es la que causa la variación de velocidad y par.

                                                                                                    Hemos realizado un pequeño experimento con una caja de cambios de dos marchas. Y lo hemos acoplado a un vehículo sencillo. Uno de los motores se emplea en tracción. El segundo motor es el encargado de gestionar la relación de cambio entre el árbol primario y el secundario, para lo que desplaza el árbol de engranajes intermedio.

                                                                                                    vehículo

                                                                                                    Para probar el vehículo y aprovechando nuestros recientes artículos relacionados con el giróscopo decidimos hacer un programa que cambia automáticamente de marchas según la inclinación que detecte. El objetivo es emular lo que hacemos nosotros manualmente cuando subimos una cuesta con el coche: reducir a una marcha más corta para ganar potencia, y alargarla cuando necesitamos más velocidad. Por tanto el vehículo irá en marcha rápida en llano, mientras que cambiará a la marcha corta pero más potente en las cuestas. Puesto que el programa reviste de cierta complejidad, hemos decidido programarlo en LeJOS (la plataforma Java para Mindstorms). Se ha programado en dos clases diferentes, la clase Principal (que es la que ejecutará el NXT), y la clase Marchas. El código es el siguiente:

                                                                                                    import lejos.nxt.Button;
                                                                                                    import lejos.nxt.LCD;
                                                                                                    import lejos.nxt.Motor;
                                                                                                    import lejos.nxt.SensorPort;
                                                                                                    import lejos.nxt.addon.GyroSensor;

                                                                                                    public class Principal {

                                                                                                    /**
                                                                                                    * @param args
                                                                                                    */
                                                                                                    public static void main(String[] args) throws Exception{
                                                                                                    // TODO Auto-generated method stub
                                                                                                    GyroSensor gyro = new GyroSensor(SensorPort.S4);
                                                                                                    Marchas m = new Marchas();
                                                                                                    float med = 0;
                                                                                                    int ginicial, gactual, nummarcha = 1;

                                                                                                    for(int i = 0; i <= 9; i++){
                                                                                                    med = med + gyro.readValue();
                                                                                                    }
                                                                                                    med = med / 10;
                                                                                                    med = med + 600;

                                                                                                    gyro.setOffset((int)med);
                                                                                                    ginicial = gyro.readValue();
                                                                                                    LCD.drawInt(ginicial, 6, 4);
                                                                                                    Motor.A.backward();
                                                                                                    m.marchaUp(Motor.C);
                                                                                                    nummarcha++;
                                                                                                    while(!Button.ESCAPE.isPressed()){
                                                                                                    gactual = gyro.readValue();
                                                                                                    LCD.drawInt(gactual, 6, 4);
                                                                                                    if ((gactual < (ginicial - 30)) || (gactual > (ginicial + 30))){
                                                                                                    if (nummarcha == 1){
                                                                                                    m.marchaUp(Motor.C);
                                                                                                    nummarcha++;
                                                                                                    }else{
                                                                                                    m.marchaDown(Motor.C);
                                                                                                    nummarcha--;
                                                                                                    }
                                                                                                    }
                                                                                                    }
                                                                                                    }
                                                                                                    }

                                                                                                    Y éste para la clase Marchas:

                                                                                                    import lejos.nxt.Motor;

                                                                                                    public class Marchas {

                                                                                                    public void marchaUp(Motor m1) throws Exception{
                                                                                                    m1.rotateTo(m1.getTachoCount() + 52);
                                                                                                    Thread.sleep(500);
                                                                                                    }

                                                                                                    public void marchaDown(Motor m1) throws Exception{
                                                                                                    m1.rotateTo(m1.getTachoCount() - 58);
                                                                                                    Thread.sleep(500);
                                                                                                    }
                                                                                                    }

                                                                                                    La primera función que realiza el programa es calibrar el giróscopo, y a continuación pone el motor en marcha y cambiar a marcha rápida, puesto que empezamos en llano. En cuanto detecta un cambio en el giróscopo (lo que significa un desnivel) cambia a la marcha corta. Podéis ver en estos vídeos el funcionamiento del vehículo y de la caja de cambios:



                                                                                                    Y aquí un zoom de la caja de cambios:



                                                                                                    Manejo del sensor de ultrasonido en RobotC

                                                                                                    Artículo nº 4 de la serie de 14 artículos sobre RobotC

                                                                                                      IMG_2177El sensor de ultrasonido puede ser muy útil, aunque su poca precisión debe de tenerse en cuenta para evitar ciertos problemas. Así mismo, un uso mejorado de los motores puede incrementar nuestra precisión a la hora de realizar movimientos complicados. En este artículo ahondaré en temas ya vistos, como los motores, e introduciré el sensor de ultrasonido. Finalmente haré un programa de prueba para que se vea todo esto.

                                                                                                      Sensor de ultrasonido:

                                                                                                      Basado en un sistema parecido al que usan los murciélagos, el sensor de ultrasonidos lanza ondas de sonido a la espera de que reboten en objetos y vuelvan, y finalmente halla la distancia al objeto calculando el tiempo que ha tardado en regresar dicha onda. La precisión del sensor de ultrasonidos del NXT es bastante reducida, pero a pesar de ello se le puede sacar partido. En el caso de RoboC la cantidad de funciones es bastante reducida, y en este caso solo usaremos una, la que nos sirve para tomar la distancia al supuesto obstáculo:

                                                                                                      SensorValue(sonarSensor): Toma la medida de distancia al objeto en centímetros. El máximo valor es de 255, que muchas veces puede ser una medición mal tomada.

                                                                                                      Al igual que los demás sensores, el sensor de ultrasonido se debe definir, indicando el puerto en el que va a estar conectado. Se definiría de la siguiente manera:

                                                                                                      #pragma config(Sensor, S4, sonarSensor, sensorSONAR);

                                                                                                      Una vez definido ya estará listo para su uso.

                                                                                                      Manejo avanzado de motores:

                                                                                                      Los motores del NXT tienen un encoder interno que es capaz de decirles cuanto grados ha girado un motor desde su ultimo reseteo. Esto nos puede ser útil para girar una cantidad de grados determinada, o para volver a la posición inicial del motor. En RobotC hay diversas formas de manejar estos encoders, la más simple y útil es modificando la variable del encoder:

                                                                                                      nMotorEncoder[motor_deseado]: Esta variable contiene la cantidad de grados que ha rotado el motor (motorA, motorB, o motorC) desde la ultima vez que se puso a cero.

                                                                                                      Para resetear el encoder no tenemos más que poner su valor a 0, por ejemplo:

                                                                                                      nMotorEncoder[motorC] = 0;

                                                                                                      En el programa de prueba veréis la utilidad de esta variable.

                                                                                                      IMG_2176

                                                                                                      Programa de prueba: Radar sencillo

                                                                                                      En este programa el robot será un robot estático, solo usaremos un motor. Lo que hará es girar el sensor de ultrasonidos 180º mientras este va tomando medidas, por lo que tomaremos 180 medidas. Calcularemos en que grado hemos detectado el objeto más cercano, y lo mostraremos por pantalla, al igual que a que distancia esta situado del robot. Este programa sirve como preparación para posteriores programas de mayor complejidad que publicaré en un futuro. El código sería el siguiente:

                                                                                                      #pragma config(Sensor, S4, sonarSensor, sensorSONAR)

                                                                                                      task main()
                                                                                                      {
                                                                                                      int grado, valor, min = 255;
                                                                                                      nMotorEncoder[motorB] = 0;

                                                                                                      for(int i = 1; i <= 180; i++){
                                                                                                      while(nMotorEncoder[motorB] < i)
                                                                                                      {
                                                                                                      motor[motorB] = 30;
                                                                                                      }
                                                                                                      motor[motorB] = 0;
                                                                                                      valor = SensorValue[sonarSensor];
                                                                                                      if (valor < min){
                                                                                                      min = valor;
                                                                                                      grado = i;
                                                                                                      }
                                                                                                      wait1Msec(50);
                                                                                                      }

                                                                                                      while(nMotorEncoder[motorB] > 0)
                                                                                                      {
                                                                                                      motor[motorB] = -30;
                                                                                                      }
                                                                                                      motor[motorB] = 0;

                                                                                                      nxtDisplayCenteredTextLine(3, "Grado: %d", grado);
                                                                                                      nxtDisplayCenteredTextLine(5, "Distancia: %d cm", min);
                                                                                                      wait1Msec(5000);
                                                                                                      }

                                                                                                      Ahora explicaré algunas líneas del código para aclararlas:

                                                                                                      05 - La variable min es inicializada a 255, ya que 255 es el máximo valor que puede devolver el sensor de ultrasonidos, y no hay ningún otro por encima.

                                                                                                      06 – Reseteamos el valor del encoder del motor que vamos a utilizar, en este caso es el motor B.

                                                                                                      08 - Creamos un búcle de 180 repeticiones.

                                                                                                      09 – Mientras el motor no haya girado hasta el grado i (1, 2, 3,…, 180):

                                                                                                      11 - Pon el motor en marcha al 30% de potencia.

                                                                                                      13 - Una vez alcanzado el grado deseado el motor se detendrá.

                                                                                                      14 – Tomamos la medida del sensor de ultrasonido

                                                                                                      15 – Si esta medida es menor que la mínima que ya teníamos:

                                                                                                      16 - Lo guardamos como la nueva mínima y,

                                                                                                      17 - Guardamos en que grado estamos.

                                                                                                      19 – Dejamos unos milisegundos para que el giro no sea brusco y podamos tomar bien los valores del ultrasonido.

                                                                                                      22 - Ahora que hemos terminado devolveremos el motor B a su posición inicial. por tanto mientras el motor no este en su posición inicial:

                                                                                                      24 - Lo ponemos marcha atrás al 30% de potencia.

                                                                                                      26 – Una vez que ha alcanzado el punto inicial paramos el motor.

                                                                                                      29 – Finalmente mostramos por pantalla en que grado hemos detectado el valor mínimo,

                                                                                                      30 - Y cual ha sido.

                                                                                                      31 – Dejamos unos segundos antes de que se acabe el programa para poder ver los resultados por pantalla.

                                                                                                      También podríamos buscar el hueco más grande por ejemplo, incluso mover el robot hacía el hueco más grande, pero eso ya lo pondré en otro artículo. Aquí os dejo un video de ejemplo para que veáis su funcionamiento:

                                                                                                      Por último recordad que para cualquier duda estamos atentos al foro.

                                                                                                      Giróscopo

                                                                                                      giroscopoEl giróscopo fue inventado en 1852 por Léon Foucault, quien también le dio el nombre, montando una masa rotatoria en un soporte de Cardano (o cardan) para un experimento de demostración de la rotación de la tierra. La rotación ya había sido demostrada con el péndulo de Foucault (péndulo esférico que puede oscilar libremente en cualquier plano vertical y capaz de oscilar durante mucho tiempo).

                                                                                                      Sin embargo, Foucault, no comprendía el porqué la velocidad de rotación del péndulo era más lenta que la velocidad de rotación de la tierra por un factor sin (λ) , donde λ representa la latitud en que se localiza el péndulo. Se necesitaba otro aparato para demostrar la rotación de la tierra de forma más simple.

                                                                                                      …Simplificando mucho, podemos imaginarnos un giróscopo como un objeto con forma de disco o, en general, como cualquier objeto que produzca el mismo efecto giroscópico: buscamos que la mayoría de la masa del objeto esté lo más alejada posible del centro del mismo. Podría ser, por ejemplo, una rueda de bicicleta, que concentra casi toda su masa en la parte externa. Para lograr el efecto de giróscopo necesitamos que este objeto se encuentre en rotación, a ser posible a gran velocidad, porque si no rota desparece el efecto giroscópico. Y… ¿En qué consiste este efecto? Pues en que girando a gran velocidad ofrece resistencia a movimientos en ciertas direcciones.

                                                                                                      ¿Qué sucede cuando circulamos en coche a gran velocidad y pisamos el freno? El coche no frena en seco, sino que lo hace de forma paulatina porque la energía que tiene almacenada debido a su gran velocidad tiene que ser liberada por completo antes de detenerse. Esa liberación de energía se produce emitiendo el clásico ruido de la frenada, en el calentamiento de las ruedas, en el dibujo de las mismas sobre la calzada, etc. Esto es, en definitiva, una muestra de lo que Newton denominó como su primera Ley del movimiento, que manifiesta que todo cuerpo tiende a mantenerse en su actual estado de movimiento, a menos que una fuerza lo impida. En el caso del giróscopo, que está girando a gran velocidad, esta gran energía de rotación tiende a obligarle a mantenerse en este estado (del mismo modo que el coche a gran velocidad tiende a seguir así). Por ello, cuando algo tiende a modificar el estado del giróscopo, éste se opone a ello generando una fuerza que lo contrarresta. Esta fuerza se denomina precesión.

                                                                                                      Los giróscopos tienen por tanto dos características principales:

                                                                                                      • Tienden a resistir cambios en su orientación para conservar su momento angular, una magnitud física intrínseca a los cuerpos en rotación, que depende del radio (distancia del extremo al eje de giro), de la masa y de la velocidad de giro.
                                                                                                      • Siempre se orientan hacia el norte geográfico, es decir, hacia el eje de inclinación de la tierra, y no al magnético como las brújulas convencionales.


                                                                                                      Gyroscope_operation

                                                                                                      De acuerdo con la mecánica del sólido rígido, además de la rotación alrededor de su eje de simetría, un giróscopo presenta en general dos movimientos principales: la precesión y la nutación.

                                                                                                      precesionLa precesión es el movimiento asociado con el cambio de dirección en el espacio que experimenta el eje instantáneo de rotación de un cuerpo, es decir, es aquel movimiento del eje de rotación que mantiene su nutación constante.

                                                                                                      La nutación se define como el movimiento de oscilación transitorio que se produce cuando el momento que causa la precesión cambia de valor, lo que hará que la velocidad de precesión también cambie de valor. Es aquel movimiento del eje de rotación que mantiene su precesion constante.

                                                                                                      En la imagen de la izquierda Precesión, Nutación y Rotación.

                                                                                                      Esta es la base de lo que ocurre en el vídeo


                                                                                                      gyro-forces

                                                                                                    • En la figura 1 la rueda está girando sobre su eje.
                                                                                                    • En la figura 2 se aplica una fuerza para intentar rotar el eje de giro.
                                                                                                    • En la figura 3 el giróscopo está reaccionando a la fuerza aplicada a lo largo de un eje que es perpendicular a dicha fuerza.

                                                                                                      gyro-points Cuando se aplican fuerzas al eje, los dos puntos que aparecen marcados en rojo, intentarán moverse en las direcciones indicadas, es decir,al aplicar una fuerza al eje, la sección de la parte superior intentará moverse a la izquierda y la sección de la parte inferior intentará moverse a la derecha.

                                                                                                      La Primera Ley del Movimiento de Newton (“todo cuerpo mantiene su estado de reposo o movimiento uniforme y rectilíneo a no ser que sea obligado a cambiar su estado por fuerzas que actuen sobre él”) da como resultado que la sección superior se ve afectada por la fuerza aplicada en el eje y comienza a moverse hacia la izquierda. Continúa intentando moverse hacia la izquierda por la citada Ley de Newton, pero el giro de la rueda hace que varíe su posición en función del tiempo, así:


                                                                                                      gyro-force2

                                                                                                      Este efecto es la causa de la precesión (explicada más arriba). Las diferentes secciones del giróscopo reciben fuerzas, pero entonces rotan a una nueva posición, manteniendo el deseo de moverse hacia la dirección inicial – la sección superior hacia la izquierda y la inferior hacia la derecha-. Por lo tanto, si el movimiento continúa, las fuerzas se anulan entre si, lo que hace que la rueda quede suspendida en el aire.

                                                                                                      El efecto de esto es que, una vez que haces girar un giróscopo, su eje quiere seguir apuntando a la misma dirección, por lo que si montas el giróscopo en una estructura con cardan que permitan que siga apuntando en esa dirección lo hará, esta es la base de la brújula giroscópica.

                                                                                                      Si montas dos giróscopos con sus ejes en un ángulo de 90º uno con respecto del otro en una plataforma y colocas esa plataforma en una estructura con cardan, la plataforma se mantendrá completamente estática mientras que los cardan rotan en cualquier dirección, esta es la base de los Sistemas de Navegación Inercial (INS: Inertial Navigation Systems).

                                                                                                      Giróscopos en robótica

                                                                                                      Este tipo de giróscopos con un volante giratorio además de voluminosos son muy caros, por lo que son válidos para elementos muy grandes o proyectos industriales. Se utilizan, por ejemplo, en aviones y satélites.

                                                                                                      Pero nuestros robots tienen dimensiones más reducidas y requieren de otro tipo de sensor más pequeño. La mayoría de los sensores actuales de pequeño tamaño están basados en circuitos integrados cuyo núcleo son diminutas lenguetas vibratorias, construidas directamente sobre el chip de silicio. Su detección se basa en que las piezas cerámicas en vibración están sujetas a una distorsión que se produce por los cambios en la velocidad angular.

                                                                                                      MEMS-ASIC, de Analog Devices

                                                                                                      La tecnología empleada en la construcción de estos micosensores se denomina MEMS (Micro-Electro-Mechanical Systems) y su objetivo es integrar elementos mecánicos, sensores, actuadores y electrónica en un mismo sustrato de silico mediante la tecnología de microfabricación.

                                                                                                      El giróscopo para Mindstorms NXT contiene un sensor giroscópico de un eje que detecta la rotación y devuelve un valor que representa el número de grados por segundo de rotación. Es decir, el giróscopo nos devuelve un valor de velocidad de rotación.

                                                                                                      El eje de medida está en el plano vertical con la parte oscura del sensor mirando hacia arriba.

                                                                                                      eje

                                                                                                      En muchas ocasiones nos interesa conocer cuánto ha girado un objeto, más que a qué velocidad lo hace. Afortunadamente es posible extraer esa información sin más que recordar las ecuaciones más básicas de cinemática. Del mismo modo que la derivada relaciona la velocidad v y posición x:

                                                                                                      v=dx/dt

                                                                                                      existe también la misma relación entre la velocidad angular o de rotación w (la que nos devuelve el sensor) y el ángulo theta:

                                                                                                      w={d{theta}}/dt

                                                                                                      Por tanto, para obtener el ángulo integramos la salida del giróscopo:

                                                                                                      {theta}= int{}{}{w dt}

                                                                                                      Desafortunadamente la inocente ecuación anterior tiene la desventaja de que la velocidad instantánea de rotación es completamente desconocida. Es cierto que el giróscopo nos devuelve el valor de la velocidad de rotación en un instante determinado, en concreto en el instante en el que realizamos la lectura de la misma, pero no nos devuelve la función que podríamos emplear para calcular la integral anterior. Lo único que podemos hacer es tomar muestras del sensor cada cierto intervalo de tiempo, e intentar calcular una función que se aproxime en el intervalo de trabajo. Según el fabricante, la velocidad de rotación puede ser leída hasta unas 300 veces por segundo.

                                                                                                      La forma más sencilla de integración I desde la primera lectura hasta la lectura enésima, g(n), es:

                                                                                                      I(n)= I(n-1) + g(n)

                                                                                                      Podemos hacer uso de una versión más avanzada de integrado, como el de runge-kutta, que además reduce también el jitter:

                                                                                                      I(n)= I(n-1) + 1/6(g(n-3)+ 2g(n-2)+2g(n-1)+g(n))

                                                                                                      Por si fuera poco, este tipo de sensores tienen además bastante ruido, deben ser calibrados para eliminar el offset y la posible deriva (drift). Si nos limitamos a integrar directamente los valores de velocidad angular obtenidos del giróscopo veremos que el resultado acaba por derivar. Eliminar dicha deriva exige de algún tipo de compensación y filtrado.

                                                                                                      El offset es la medida que tiene el giróscopo cuando se encuentra en reposo. Si todo fuera “correcto” en reposo deberíamos obtener un valor de velocidad de rotación nulo, pero no es así: ese efecto se debe a defectos en el proceso de fabricación, variaciones de temperatura o envejecimiento. El segundo efecto a tener en cuenta es la deriva (drift), un desvío que se produce a lo largo del tiempo y que hace que el valor que devuelve el giróscopo en reposo (el offset mencionado anteriormente) difiera de su valor inicial.

                                                                                                      giróscopo en NXT-G

                                                                                                      Atendiendo a las indicaciones del fabricante el valor devuelto por el sensor depende también del lenguaje en el que estemos programando. El rango de trabajo de este sensor es de 1024: devuelve un valor entre 0 y 1023. Este sensor puede ser programado mediante un bloque gráfico en NXT-G, Labview, RobotC, LeJOS, pbLUA, etc. Por la forma en la que ha sido creado, el bloque gráfico devuelve un valor de cero cuando la velocidad de rotación es 600 dentro del rango anterior. Es decir, si trabajando en NXT-G leemos un valor de 4, el valor en crudo del giróscopo que leeríamos en robotC sería de 604. Por ello el offset a compensar sería de 4 en NXT-G o de 604 en robotC.

                                                                                                      Iremos viendo a lo largo de una serie de artículos posteriores ejemplos de su programación en varios lenguajes.

                                                                                                      Información adicional:

                                                                                                    • Concurso de excavadoras con base fija 1 de Mayo de 2010

                                                                                                      excavadoraQueremos presentaros el nuevo concurso que hemos organizado para el Sábado 1 de Mayo. Se trata de construir una excavadora con base fija que sea capaz de transportar la mayor carga de ladrillos LEGO posible en un intervalo de tiempo dado.

                                                                                                      ¿Os apuntáis? Para inscribiros o preguntar cualquier duda, podéis acudir al foro.

                                                                                                      Sigue Líneas en RobotC

                                                                                                      Artículo nº 2 de la serie de 14 artículos sobre RobotC

                                                                                                      RobotC1En este artículo voy a explicar el uso básico del sensor de luz y a ponder un par de programas de prueba: el sigue líneas básico y el sigue líneas proporcional. Ambos programas ya han sido explicados en NXT-G y en LeJOS, por lo que podréis compararlos.

                                                                                                      Uso básico del sensor de luz:

                                                                                                      El sensor de luz tiene un manejo bastante sencillo en RobotC, primero se necesita declarar y luego ya se puede usar para tomar el valor de la luz que le entra. Es recomendable poner un pequeño retardo al principio del programa (unos 100ms por ejemplo) para dejar que se inicialize. Para inicializar el sensor es necesario poner la siguiente linea al principio del programa:

                                                                                                      #pragma config(Sensor, num_sensor, lightSensor, sensorLightActive)

                                                                                                      donde num_sensor es S1, S2, S3 o S4 según que puerto de sensores vayamos a utilizar. Para leer el valor de luz del sensor usaremos la función:

                                                                                                      SensorValue[lightSensor]: Lee el valor de luz y lo devuelve en un entero. Su valor es parecido al devuelto en NXT-G.

                                                                                                      No usaremos más funciones para el sigue líneas.

                                                                                                      Uso básico del motor:

                                                                                                      Hay bastantes funciones en RobotC para el manejo de motores, sin embargo me centraré solo en dos para este artículo y ya explicaré funcionamientos más avanzados en artículos posteriores. Los motores no hace falta declararlos, si no que ya están listos para su uso. La función para regular la potencia de un motor es:

                                                                                                      motor[motorLetra] = %potencia: Regula la potencia del motorLetra (motorA, motorB o motorC) al % indicado (10, 20, 75, …).

                                                                                                      La potencia sin embargo no regula la velocidad máxima. Para regular la velocidad máxima a la que queremos que vayan nuestros motores tendremos que modificar la siguiente variable:

                                                                                                      nMaxRegulatedSpeedNxt = velocidad_max:Regula la velocidad máxima al valor indicado; hay que tener en cuenta que por defecto es 1000, por lo que si queremos reducirla a la mitad velocidad_max será 500.

                                                                                                      No usaremos más funciones para el sigue líneas.

                                                                                                      Sigue líneas básico en zigzag:

                                                                                                      El sigue líneas es un problema que consiste en que el robot camine por una línea sin salirse. Esto se puede resolver mediante el uso de diferentes sensores, y de multitud de formas dentro de cada sensor. Una de las formas básicas de solucionarlo es que el robot se dirija hacia el borde de la linea negra esté en lo negro o en lo blanco. Como resultado parecerá que va haciendo un pequeño zigzag, ya que nunca será capaz de ir exactamente por el borde.

                                                                                                      RobotC2

                                                                                                      La primera parte del programa será, como en todos los sigue líneas, el calibrado. Este calibrado sirve para que el robot distinga entre que es negro, que es blanco, y que sería el borde de la línea. Lo que haremos en este caso para calcular ese borde de la linea será: med = (max + min)/2. Este punto medio seria el sitio ideal por donde debería ir el robot. Para calibrarlo dejaríamos al robot en el centro de la línea e iniciaríamos el programa; el programa tomaría el valor del negro, luego giraría hasta el blanco y tomaría su valor, calcularía el valor medio, volvería a girar a la posición inicial (o a una que calculéis más cercana al borde de la línea), y finalmente empezaría con la parte de sigue líneas. Dicho todo esto la parte de calibrado quedaría de la siguiente manera:

                                                                                                      nMaxRegulatedSpeedNxt = 500;
                                                                                                      int negro = SensorValue[lightSensor];
                                                                                                      nxtDisplayCenteredTextLine(4, "%d", negro);
                                                                                                      wait1Msec(1000);
                                                                                                      motor[motorB] = 60;
                                                                                                      motor[motorC] = -60;
                                                                                                      wait1Msec(300);

                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 0;
                                                                                                      int blanco = SensorValue[lightSensor];
                                                                                                      nxtDisplayCenteredTextLine(4, "%d", blanco);
                                                                                                      wait1Msec(1000);
                                                                                                      int media = (negro + blanco)/2;

                                                                                                      motor[motorB] = -60;
                                                                                                      motor[motorC] = 60;
                                                                                                      wait1Msec(200);

                                                                                                      nxtDisplayCenteredTextLine(4, "%d", media);
                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 0;
                                                                                                      wait1Msec(1000);
                                                                                                      nMaxRegulatedSpeedNxt = 1000;

                                                                                                      Una vez calibrado sabemos cual es el valor medio hacia el que tiene que moverse el robot, por tanto lo ponemos en marcha y vamos leyendo los valores del sensor de luz, haciendo que si el valor es superior al valor medio paremos el motor derecho (en este caso el MotorB) y aceleremos con el izquierdo (MotorC en este caso), lo cual nos hará rotar hacia la izquierda, donde estará la línea. Si en caso contrario estamos muy dentro de la línea y leemos un valor menor al valor medio con el sensor de luz, hay que hacer la operación inversa (parar el motor izquierdo, y mover el motor derecho). Todo esto metido en un bucle infinito hace que el robot se mueva por el borde de la línea. La parte de código de sigue líneas sería la siguiente:

                                                                                                      while(true)
                                                                                                      {
                                                                                                      if(SensorValue[lightSensor] < media){
                                                                                                      motor[motorB] = 60;
                                                                                                      motor[motorC] = 0;
                                                                                                      }
                                                                                                      else
                                                                                                      {
                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 60;
                                                                                                      }
                                                                                                      }

                                                                                                      El programa completo sería (calibrado más sigue líneas):

                                                                                                      #pragma config(Sensor, S1, lightSensor, sensorLightActive)

                                                                                                      task main()
                                                                                                      {
                                                                                                      wait1Msec(100);
                                                                                                      nMaxRegulatedSpeedNxt = 500;
                                                                                                      int negro = SensorValue[lightSensor];
                                                                                                      nxtDisplayCenteredTextLine(4, "%d", negro);
                                                                                                      wait1Msec(1000);
                                                                                                      motor[motorB] = 60;
                                                                                                      motor[motorC] = -60;
                                                                                                      wait1Msec(300);

                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 0;
                                                                                                      int blanco = SensorValue[lightSensor];
                                                                                                      nxtDisplayCenteredTextLine(4, "%d", blanco);
                                                                                                      wait1Msec(1000);
                                                                                                      int media = (negro + blanco)/2;

                                                                                                      motor[motorB] = -60;
                                                                                                      motor[motorC] = 60;
                                                                                                      wait1Msec(200);

                                                                                                      nxtDisplayCenteredTextLine(4, "%d", media);
                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 0;
                                                                                                      wait1Msec(1000);
                                                                                                      nMaxRegulatedSpeedNxt = 1000;
                                                                                                      while(true)
                                                                                                      {
                                                                                                      if(SensorValue[lightSensor] < media){
                                                                                                      motor[motorB] = 60;
                                                                                                      motor[motorC] = 0;
                                                                                                      }
                                                                                                      else
                                                                                                      {
                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 60;
                                                                                                      }
                                                                                                      }
                                                                                                      }

                                                                                                      Y aquí os pongo un video demostrativo de como funcionaría un NXT con este programa:

                                                                                                      Espero que os haya gustado. Ahora vamos a probar algo más complejo.

                                                                                                      Sigue líneas proporcional:

                                                                                                      Una manera más avanzada de resolver el problema del sigue líneas es mediante el método proporcional. En este método lo que haremos es aplicarle a los motores una potencia proporcional al error que tengamos (diferencia entre el valor medio hallado en la calibración y el valor actual del sensor de luz). De esta manera nos quedará un movimiento mucho más suave y preciso. Por supuesto la parte complicada es calcular la relación entre el error y la potencia que tenemos que dar a cada motor.

                                                                                                      La parte del calibrado es la misma que la del sigue líneas anterior, por lo que no es necesario repetirla. La parte de sigue líneas será de la forma siguiente: una vez calibrado, el robot se pone en marcha; dentro de un búcle se calcula el error (valor medio - lectura actual) y se guarda en una variable; se modifica ese error para que pueda ser coherente a la hora de restárselo y sumarselo a la potencia de los motores*, y por último se resta ese valor a la velocidad de un motor (en este caso el izquierdo (MotorC)) y se le suma al otro (en este caso el derecho (MotorB)). Finalmente se repite este bucle infinitas veces.

                                                                                                      *Por ejemplo supongamos que estamos completamente en lo negro y el error es de 3, no tiene sentido que si el motor B tiene una potencia de 80 le restemos solo 3, porque no va a modificar casi su trayectoria. Sin embargo si multiplicamos 3 por 6 tendremos 18, que ya si es una reducción bastante notable de potencia en ese motor. Por esto hay que tener en cuenta de que magnitud van a ser las unidades de este error. Tampoco conviene elevar mucho este valor, o acabará haciendo mucho Zig Zag y pareciendose al sigue línseas anterior. En este caso las lecturas de SensorValue[lightSensor] son de entre 64 lo más blanco, y unos 34 lo más negro, por lo que la media serán de aproximadamente 48. Los errores serán como mucho del orden de 20 (Ej.: 48 - 64 = -16), por lo que podemos restarlos directamente a las potencias de los motores si usamos potencias medias (Ej.: 50 - 1*(-16) = 66 , este motor acelerará claramente para tratar de corregir el error y situarse en el borde de la línea, mientras que el otro motor frenará: 50 + 1*(-16) = 34).

                                                                                                      El programa completo con la parte de calibrado incluida quedaría de la siguiente manera:

                                                                                                      #pragma config(Sensor, S1, lightSensor, sensorLightActive)

                                                                                                      task main()
                                                                                                      {
                                                                                                      wait1Msec(100);
                                                                                                      nMaxRegulatedSpeedNxt = 500;
                                                                                                      int negro = SensorValue[lightSensor];
                                                                                                      nxtDisplayCenteredTextLine(4, "%d", negro);
                                                                                                      wait1Msec(1000);
                                                                                                      motor[motorB] = 60;
                                                                                                      motor[motorC] = -60;
                                                                                                      wait1Msec(300);

                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 0;
                                                                                                      int blanco = SensorValue[lightSensor];
                                                                                                      nxtDisplayCenteredTextLine(4, "%d", blanco);
                                                                                                      wait1Msec(1000);
                                                                                                      int media = (negro + blanco)/2;

                                                                                                      motor[motorB] = -60;
                                                                                                      motor[motorC] = 60;
                                                                                                      wait1Msec(200);

                                                                                                      nxtDisplayCenteredTextLine(4, "%d", media);
                                                                                                      motor[motorB] = 0;
                                                                                                      motor[motorC] = 0;
                                                                                                      wait1Msec(1000);
                                                                                                      nMaxRegulatedSpeedNxt = 600;
                                                                                                      int error;
                                                                                                      while(true)
                                                                                                      {
                                                                                                      error = media - SensorValue[lightSensor];
                                                                                                      motor[motorB] = 70 + 1.2*error;
                                                                                                      motor[motorC] = 70 - 1.2*error;
                                                                                                      }
                                                                                                      }

                                                                                                      Se ha regulado la velocidad máxima a 600 para una mayor precisión. Recordad que según como tengáis construido el robot necesitaréis calibrar el multiplicador del error y las potencias para que os funcione bien. Y aquí tenéis un vídeo demostración de este programa en ejecución:

                                                                                                      Finalmente deciros que si tenéis alguna duda o pregunta podéis escribirla en el foro.

                                                                                                      Manejo básico del LCD y los botones en RobotC

                                                                                                      Artículo nº 1 de la serie de 14 artículos sobre RobotC

                                                                                                        ROBOTCskewlogoRobotC es un lenguaje para robótica educacional basado en C, con un entorno de desarrollo bastante sencillo. RobotC da soporte a diversas plataformas de robótica, entre ellas a LEGO Mindstorms NXT. El objetivo de esta nueva serie de artículos sobre RobotC es acercar a la gente a este entorno, que nos permite crear programas más complejos que NXT-G con bastante facilidad.

                                                                                                        Manejo básico de los botones:

                                                                                                        En RobotC no hay muchas maneras de controlar dos botones, de hecho la forma de controlarlos es más bien mediante variables en vez de funciones. En cualquier caso estas variables son suficiente para controlar los botones y darles casi cualquier uso que necesitemos. Las dos variables que usaremos serán:

                                                                                                        nNxtButtonPressed: Contiene un identificador del botón que está siendo apretado (puede ser ninguno). Mediante esta variable podemos saber que botón acabamos de apretar.

                                                                                                        nNxtExitClicks: Puesto que al apretar el botón Exit los programas se abortan, no podremos usar un programa que usa el botón Exit a no ser que cambiemos el valor de esta variable. Esta variable nos indica el número de veces que hay que apretar el botón Exit para abortar un programa (que por defecto será 1).

                                                                                                        Con estás dos variables podremos hacer programas que realicen acciones según que botón haya sido apretado.

                                                                                                        nxt-brick-labeled

                                                                                                        Manejo básico del LCD:

                                                                                                        El LCD tiene una gran cantidad de funciones en RobotC, que sirven para mostrar texto, números, o incluso figuras geométricas. En este artículo os pondré las dos más útiles para un manejo básico:

                                                                                                        eraseDisplay(): Limpia el LCD, borrando todo lo que hubiera anteriormente escrito.

                                                                                                        nxtDisplayTextLine(num_linea, “Texto y tipos de variables extra”, var_extra1, var_extra2, …) : Muestra una frase (o cadena de texto) en la pantalla en la línea indicada, y el valor de las variables extra en el lugar indicado. Los tipos de variables son de la siguiente manera:

                                                                                                        - Entero decimal: “%d”
                                                                                                        - Notación científica (mantisa/exponente) usando el caracter e “%e”
                                                                                                        - Notación científica (mantisa/exponente) usando el caracter E “%E”
                                                                                                        - Decimal de coma flotante “%f”
                                                                                                        - Octal “%o”
                                                                                                        - Cadena de caracteres “%s”
                                                                                                        - Entero hexadecimal sin signo “%x”
                                                                                                        - Entero hexadecimal sin signo (letras mayúsculas)”%X”
                                                                                                        - Caracter “%c”

                                                                                                        Todo esto se ve más claro en el programa de prueba que viene a continuación, donde hay un ejemplo de cadena de texto con una variable tipo integer en medio.

                                                                                                        nxtDisplayCenteredTextLine(num_linea, “Texto y tipos de variables extra”, var_extra1, var_extra2, …) : Lo mismo que la función anterior, pero centrado en la línea.

                                                                                                        Con estas funciones podemos escribir cualquier tipo por pantalla, no solo texto, por lo que tenemos la mayoría de funcionalidad que necesitamos. Es importante usar el eraseDisplay() para limpiar la pantalla cada vez que queramos escribir una cosa nueva, si no queremos que se nos mezcle con lo que hubiera escrito anteriormente.

                                                                                                        Programa de prueba:

                                                                                                        El objetivo del siguiente programa es mostrar qué botón está siendo apretado en cada momento. Además, para que el programa no se aborte cuando aprietes el botón Exit se ha modificado el valor de nNxtExitClicks a 3, y se ha creado un contador que va indicando al usuario cuantas veces tiene que apretar el botón Exit para que se acabe el programa. El programa quedaría así:

                                                                                                        {
                                                                                                        nNxtExitClicks = 3;
                                                                                                        int contador = 3;
                                                                                                        while(contador > 0){
                                                                                                        nxtDisplayTextLine(1, " Pulse cualquier ");
                                                                                                        nxtDisplayCenteredTextLine(3, "boton");
                                                                                                        nxtDisplayTextLine(5, "%d Clicks en EXIT", contador);
                                                                                                        nxtDisplayTextLine(7, " para salir ");
                                                                                                        switch (nNxtButtonPressed)
                                                                                                        {
                                                                                                        case kLeftButton: eraseDisplay();
                                                                                                        nxtDisplayTextLine(3, "Boton izquierdo");
                                                                                                        nxtDisplayTextLine(5, " pulsado");
                                                                                                        wait1Msec(2000);
                                                                                                        eraseDisplay();
                                                                                                        break;
                                                                                                        case kRightButton: eraseDisplay();
                                                                                                        nxtDisplayTextLine(3, " Boton derecho");
                                                                                                        nxtDisplayTextLine(5, " pulsado");
                                                                                                        wait1Msec(2000);
                                                                                                        eraseDisplay();
                                                                                                        break;
                                                                                                        case kEnterButton: eraseDisplay();
                                                                                                        nxtDisplayTextLine(3, " Boton Enter");
                                                                                                        nxtDisplayTextLine(5, " pulsado");
                                                                                                        wait1Msec(2000);
                                                                                                        eraseDisplay();
                                                                                                        break;
                                                                                                        case kExitButton: eraseDisplay();
                                                                                                        nxtDisplayTextLine(3, " Boton Exit");
                                                                                                        nxtDisplayTextLine(5, " pulsado");
                                                                                                        contador--;
                                                                                                        wait1Msec(2000);
                                                                                                        eraseDisplay();
                                                                                                        break;
                                                                                                        }
                                                                                                        }
                                                                                                        return;
                                                                                                        }

                                                                                                        Puesto que es el primer programa que voy a explicar en RobotC, lo haré linea por linea para que lo podáis entender todo (número de linea – explicación):

                                                                                                        02 - Modificamos el valor de nNxtExitClicks para que valga 3, por lo que será necesario apretar 3 veces el botónExit para salir del programa.

                                                                                                        03 - Creamos un contador cuyo valor sea 3, el cual iremos reduciendo cada vez que apretemos el botón Exit, para saber cuantos clicks nos quedan.

                                                                                                        04 - Creamos un bucle que se repetirá hasta que contador valga 0.

                                                                                                        05 a 08 - Escribimos Pulse cualquier boton %d Clicks en Exit para salir en el LCD. Cuando muestre por pantalla esto, no mostrará “%d”, sino el valor de la variable contador incluida esa misma función. Por ejemplo la primera vez que se muestre mostrará algo como:

                                                                                                        Pulse cualquier
                                                                                                        boton
                                                                                                        3 Clicks en Exit
                                                                                                        para salir

                                                                                                        09 - Switch es una estructura de programación que lo que hace es mirar que valor hay en la variable (nNxtButtonPressed en este caso), y ejecutar la parte de programa que corresponda con el valor de esa variable. En este caso es:

                                                                                                        11 - Si el botón izquierdo ha sido pulsado ejecutará estás líneas hasta el break:
                                                                                                        11 - Limpiamos la pantalla para poder escribir.
                                                                                                        12 y 13 - Mostramos por el LCD que el botón izquierdo a sido pulsado.
                                                                                                        14 - Dejamos el programa parado durante 2000 milisegundos (2 segundos), por lo que se mostrará por pantalla lo escrito anteriormente.
                                                                                                        15 y 16 - Volvemos a limpiar la pantalla y salimos del switch con un break();

                                                                                                        17 a 22 - Lo mismo con el botón derecho.

                                                                                                        23 a 28 - Lo mismo con el botón Enter.

                                                                                                        29 a 35 - Lo mismo con el Exit. Notar que en la línea 34 reducimos el valor de contador en 1 (contador– es lo mismo que decir contador = contador -1).

                                                                                                        Los corchetes “{” y “}” significan que todo lo que haya en medio pertenece a quién los ha abierto. Por ejemplo el switch (nNxtButtonPressed) { abre corchetes, y los cierra después de todos los case, ya que pertenecen a él. Igualmente pasa con el bucle while(true) o con el mismo programa entero, que esta contenido todo dentro de unos corchetes significando que pertenecen al cuerpo principal del programa.

                                                                                                        Por último decir que si os surgen dudas no tengáis reparo en escribir en el foro.

                                                                                                        Tetrix: Mantis

                                                                                                        Artículo nº 2 de la serie de 4 artículos sobre Tetrix

                                                                                                        mantis lateralEn este nuevo artículo sobre TETRIX (el anterior fue TETRIX: Primeras Impresiones) quiero centrarme en la experiencia de construcción con este nuevo tipo de pieza, que no tiene demasiado en común con la pieza a la que LEGO nos tiene acostumbrados. Aunque los elementos estructurales son muy ligeros (están hechos de aluminio), debemos tener en cuenta que la electrónica de estos modelos es muy pesada, entre otros motivos porque requiere de una batería adicional.

                                                                                                        Primero quiero centrarme en los ejes, en este caso ya no se trata de ejes en forma de cruz, son ejes cilíndricos. Para poder hacer uso de estos ejes debemos utilizar un axle hub o soporte para eje, un elemento de sujeción que mantiene el eje fijo en una postura y está dotado de 4 agujeros para tornillos.

                                                                                                        eje-hub

                                                                                                        Haciendo uso de estos 4 agujeros podremos unir nuestro eje con otras piezas.

                                                                                                        eje-rueda

                                                                                                        Las uniones entre elementos estructurales se realiza mediante tornillos y tuercas, lo que supone un inmenso cambio con respecto a la facilidad de unión de las piezas Technic mediante pines. Sin embargo, puede ser que simplemente necesitemos un periodo de adaptación.

                                                                                                        Después de esta breve introducción, vamos a centrarnos en la construcción de un modelo básico que vendría a ser como un tribot de gran tamaño: la mantis. Este modelo tiene dos motores 12V para tracción y un servo adicional para controlar los movimientos de las patas delanteras. Además hace uso de las ruedas omnidireccionales que harán las veces de la rueda loca que monta el tribot en su parte posterior.

                                                                                                        Vamos a ver algunas imágenes del proceso de construcción.

                                                                                                        Esta es la base

                                                                                                        base

                                                                                                        El lateral

                                                                                                        lateral

                                                                                                        Base ensamblada

                                                                                                        base 2

                                                                                                        Detalle de la parte trasera

                                                                                                        Detalle trasera

                                                                                                        Modelo completo

                                                                                                        mantis lateral

                                                                                                        Es un modelo bastante grande y muy pesado cuyo tiempo de construcción se aleja bastante de los 30 minutos (teóricos) para construir el tribot. Para ser más concretos se tardaron 2 horas en terminar este primer robot de TETRIX. Puede también que parte de esa demora se deba a la adaptación al nuevo tipo de pieza. Aunque la verdad es que mientras lo estás montando lo único que piensas es en terminar y en verlo funcionar, ya que impresiona mucho. Más en vivo que en foto.

                                                                                                        Programación del robot TETRIX

                                                                                                        Para controlar los elementos de TETRIX hacemos uso del NXT, pero ahora tenemos electrónica nueva, con lo que no podremos programarlo de la forma habitual, necesitaremos hacer uso de las nuevas herramientas de programación adaptadas a este sistema.

                                                                                                        Podéis descargar el bloque para NXT-G (descarga TETRIX), aunque este bloque sólo nos permite controlar los motores 12V que conectamos al Mindstorms a través del controlador HiTechnic, no nos permite controlar los servos.

                                                                                                        Bloque

                                                                                                        Así que, si queremos hacer uso de todas las posibilidades de control, tendremos que programarlo con RobotC, que en su versión 2.x ya incluye soporte para TETRIX.

                                                                                                        Para poder hacer uso de la plataforma TETRIX en RobotC, lo primero que debemos hacer es configurar el programa, para ello tendremos que ir a:

                                                                                                        Robot-> Platform Type -> LEGO Mindstorms NXT+TETRIX

                                                                                                        RobotC

                                                                                                        Una vez hecho esto tendremos que configurar los motores/servos/controladores, para ello RobotC tiene una herramienta que nos permite configurarlo gráficamente incluyendo directamente el código en nuestro programa.

                                                                                                        Robot-> Motors and Sensors Setup

                                                                                                        RobotC_2

                                                                                                        Y nos abrirá esta ventana

                                                                                                        RobotC_3

                                                                                                        Aquí tendremos que configurar el puerto del controlador, los motores que tenemos conectados y los servos. Al aceptar la configuración, aparecerá en pantalla un código como este:


                                                                                                        #pragma config(Hubs,S1,HTMotor,HTServo,none, none)
                                                                                                        #pragma config(Sensor,S4,sonic,sensorSONAR)
                                                                                                        #pragma config(Motor,mtr_S1_C1_1,motorD,tmotorNormal,openLoop)
                                                                                                        #pragma config(Motor,mtr_S1_C1_2,motorE,tmotorNormal,openLoop,reversed)
                                                                                                        #pragma config(Servo,srvo_S1_C2_1,servo1,tServoNormal)
                                                                                                        /*!Code automatically generated by 'ROBOTC' configuration wizard!*/

                                                                                                        NOTA: Atención a la nomenclatura necesaria para declarar los motores y sensores, aunque RobotC escriba el código por nosotros, no está de más saber qué hace.

                                                                                                        mtr_S1_C1_2

                                                                                                      • Indicamos en tipo de elemento: mtr -> motor
                                                                                                      • Puerto de sensores del NXT donde está conectado el controlador : S1 -> puerto de sensor 1
                                                                                                      • Controlador al que se conecta el motor: C1 -> controlador de motores 1
                                                                                                      • Dónde está conectado el motor al controlador: 2 -> puerto 2 del controlador

                                                                                                        Los controladores de motores HiTehcnic controlan 2 motores 12V DC y tienen entrada para 2 encoders. además tienen 2 entradas/salidas para la batería.

                                                                                                        srvo_S1_C2_1

                                                                                                      • Indicamos en tipo de elemento: srvo -> servo
                                                                                                      • Puerto de sensores del NXT donde está conectado el controlador : S1 -> puerto de sensor 1
                                                                                                      • Controlador al que se conecta el motor: C2 -> controlador 2, si ambos controladores está conectados al mismo puerto de sensor del NXT, es porque uno de ellos C1 se conecta directamente al puerto y el otro C2 está conectado a través de C1.
                                                                                                      • Dónde está conectado el servo al controlador: 1 -> canal 1 del controlador.

                                                                                                        Los controladores de servos HiTechnic tienen 6 canales para servos y 2 entradas/salidas para la batería.

                                                                                                        Una vez hecho esto ya podremos escribir el programa propiamente dicho. El ejemplo de programa que tenemos a continuación hace de nuestra mantis un Bump&Go algo más sofisticado, ya que no necesita chocar con los objetos, basta con que se encuentren a poca distancia de su sensor ultrasónico.

                                                                                                        Para ser más exactos, cuando detecta un objeto a menos de 30 cm se para y lo amenaza haciendo subir y bajar sus patas delanteras hasta 3 veces, si el objeto se retira antes de completar el ciclo de 3 amenazas el robot seguirá su camino. Si por el contrario, el objeto no se aparta, la mantis dará la vuelta y seguirá avanzando hasta detectar otro obstáculo.

                                                                                                        Aquí tenéis el código de este programa.


                                                                                                        #pragma config(Hubs,S1,HTMotor,HTServo,none,none)
                                                                                                        #pragma config(Sensor,S4,sonic,sensorSONAR)
                                                                                                        #pragma config(Motor,mtr_S1_C1_1,motorD,tmotorNormal,openLoop)
                                                                                                        #pragma config(Motor,mtr_S1_C1_2,motorE,tmotorNormal,openLoop,reversed)
                                                                                                        #pragma config(Servo, srvo_S1_C2_1, servo1, tServoNormal)
                                                                                                        /*!Code automatically generated by 'ROBOTC' configuration wizard!*/
                                                                                                        task main()
                                                                                                        {
                                                                                                        int delta = 4;
                                                                                                        int flag=0;
                                                                                                        while (true) //Bucle infinito
                                                                                                        {
                                                                                                        while (flag<=1) // flag que controla cuantas veces repetimos el movimiento
                                                                                                        {
                                                                                                        while(SensorValue[sonic]>30) //Mientras que la distancia sea menor que 30 cm
                                                                                                        {
                                                                                                        motor[motorD]=40; // Avanzamos en línea recta
                                                                                                        motor[motorE]=40;
                                                                                                        }
                                                                                                        motor[motorD]=0; // Paramos los motores
                                                                                                        motor[motorE]=0;
                                                                                                        wait1Msec(1000); // Esperamos 1 segundo
                                                                                                        servoChangeRate[servo1] = delta;// servoChangeRate =nº de posiciones por actualización
                                                                                                        if(ServoValue[servo1] < 80) //Si no hemos llegado a los 80º, nos movemos hasta alcanzarlos
                                                                                                        {
                                                                                                        while(ServoValue[servo1] < 80)
                                                                                                        {
                                                                                                        servo[servo1] = 80;
                                                                                                        }
                                                                                                        }
                                                                                                        wait1Msec(1000); // Espera 1 segundo
                                                                                                        if(ServoValue[servo1] >= 80) // Si hemos llegado a los 80º...
                                                                                                        {
                                                                                                        while(ServoValue[servo1] > 0)
                                                                                                        {
                                                                                                        servo[servo1] = 0; // ... nos movemos hasta 0
                                                                                                        }
                                                                                                        }
                                                                                                        flag=flag+1; // flag nos permite controlar el nº de veces que repetimos
                                                                                                        } // el movimiento del servo
                                                                                                        motor[motorD]=20; // Giramos
                                                                                                        motor[motorE]=-20;
                                                                                                        wait1Msec(1000); // Esperamos 1 segundo
                                                                                                        motor[motorD]=0; // Paramos los motores
                                                                                                        motor[motorE]=0;
                                                                                                        flag=0; // Reseteamos el valor de flag y como es un bucle infinito
                                                                                                        } // nos movemos de nuevo en busca de otro objetivo
                                                                                                        }

                                                                                                        Y para que lo veáis en funcionamiento, un pequeño vídeo.

                                                                                                      • Concurso de Cruza Puentes electricBricks 2010

                                                                                                          vehiculosEl pasado Domingo día 21 de Marzo tuvo lugar la competición de cruza puentes. Se presentaron 4 vehículos, todos ellos radiocontrolados. Parece que los vehículos autónomos no gozan de popularidad en la comunidad. Sin embargo el reto fue muy difícil, y el nivel muy alto.

                                                                                                          Los 5 valientes que se presentaron a la competición

                                                                                                          Grupo

                                                                                                          Y sus vehículos

                                                                                                          Sheepo Puente

                                                                                                          Un enorme vehículo con un puente desplegable. El sueño de todos los participantes, largo, bien diseñado… insuperable. PF puro.

                                                                                                          SheepoPuente

                                                                                                          CSMEgea

                                                                                                          De dimensiones algo más modestas se nu puedo superar con éxito la prueba por una rueda loca en la parte trasera que siempre sen enganchaba donde no debía y la dificultad de manejar 2 canales distintos con un sólo mando. Sistema de control PF, motores NXT.

                                                                                                          CSMEgea

                                                                                                          LanzaPuentes

                                                                                                          Pqueño y con posibilidades, la dificultad de control y la falta de corrección de errores le relegaron a la última posición, sin embargo fue el competidor más divertido con diferencia, y en su especialidad, lanzar puentes, no tuvo rival. NXT puro.

                                                                                                          lanzapuentes

                                                                                                          Ballestero

                                                                                                          De los 4 participantes sólo 2 lograron atravesar el abismo, y este compacto modelo fue uno de ellos. Unos muy meritorios 50 cm y dos intentos, colocaron a este vehículo en la segunda posición de la clasificación. PF puro.

                                                                                                          ballestero

                                                                                                          Las normas eran simples: el vehículo debía ser capaz de colocar el puente, cruzarlo y recogerlo. Cada participante indicaba la medida que su vehículo era capaz de superar, y luego debía demostrarlo.


                                                                                                          Clasificación FINAL

                                                                                                          Puesto
                                                                                                          Distancia
                                                                                                          Vehículo
                                                                                                          Intento 1
                                                                                                          Intento 2
                                                                                                          Intento 3
                                                                                                          1
                                                                                                          105 cm
                                                                                                          SheepoPuente
                                                                                                          Superado
                                                                                                          2
                                                                                                          50 cm
                                                                                                          Ballestero
                                                                                                          2/3
                                                                                                          Superado
                                                                                                          3
                                                                                                          39 cm
                                                                                                          CSMEgea
                                                                                                          2/3
                                                                                                          X
                                                                                                          2/3
                                                                                                          4
                                                                                                          30 cm
                                                                                                          LanzaPuentes
                                                                                                          X
                                                                                                          X
                                                                                                          1/3

                                                                                                          1/3 indica que el participante fue capaz de colocar el puente correctamente.
                                                                                                          2/3 indica que el participante colocó el puente y lo cruzó.
                                                                                                          Y, por supuesto, Superado indica que el vehículo atravesó el puente y luego lo recogió correctamente.

                                                                                                          Resumen CSMEgea

                                                                                                          Resumen Sheepo Puente

                                                                                                          Resumen Ballestero

                                                                                                          Resumen Lanza Puentes