La robótica educativa es un tema que me apasiona ya que permite potenciar el desarrollo de habilidades y competencias en niños y jóvenes.
Una de las soluciones comerciales que me parece muy interesante y muy bien conseguida es la que está haciendo la empresa Makeblock, concretamente con el robot mbot.
El tutorial que aquí os presento hace referencia a la creación de extensiones en el entorno de programación mBlock. mBlock 3 es un entorno gráfico de programación basado en el editor Scratch 2.0 creado por la empresa Makeblock que permite programar no solo los propios robots de Makeblock con Scratch si no también robots basados en Arduino (de hecho los robots de la empresa Makeblock están basados en Arduino).
Las extensiones nos permiten crear nuestros propios bloques y por tanto nos dará la posibilidad de poder programar nuestro propio robot basado en Arduino mediante este entorno gráfico. En este tutorial vamos a crear bloques para programar nuestro robot Mario (enlace tutorial robot Mario) en este entorno.
En este tutorial programaremos nuestro robot utilizando el “modo Arduino”. Este modo nos permite cargar el programa en nuestro robot como lo haríamos con el IDE de Arduino utilizando un cable USB.
¿Por dónde empezamos? bueno pues lo primero es pensar qué bloques son los que queremos crear. Estos bloques tienen que tener unos nombres lo suficientemente descriptivos para que cualquier persona y más especialmente un niño pueda entender la acción que sucederá mediante su utilización a la hora de programar el robot.
En nuestro caso vamos a programar los siguientes bloques:
Como podéis ver en la imagen anterior estos bloques son lo suficiente claros y descriptivos para que una persona pueda entender la acción que producirá en el robot.
Una vez tengamos los bloques creados programaremos mediante este entorno gráfico algo sencillo como lo que se muestra en la siguiente imagen:
Los bloques de la imagen anterior se traducirán en las siguientes acciones:
Si se detecta un obstáculo a 30 cm se pondrá la luz del robot de color “ROJO” y girará a la izquierda a velocidad “RAPIDO” y esperá media segundo, por el contrario si no detecta un obstáculo a esta distancia la luz se pondrá de color “VERDE” y se hará avanzar al robot a velocidad “RAPIDO”.
Creando la clase RobotMario
Antes de empezar a programar las extensiones y con el objetivo de simplificar la creación de éstas crearemos una clase que contenga todas las acciones necesarias para nuestro robot.
La creación de una clase nos permitirá a posteriori abstraernos de todas las tareas que implican una acción en nuestro robot, es decir, en el robot Mario la acción de Avanzar implicaría programar las siguientes acciones:
const int pinENA = 10; const int pinIN1 = 9; const int pinIN2 = 8; const int pinIN3 = 7; const int pinIN4 = 6; const int pinENB = 5; const int pinMotorA[3] = { pinENA, pinIN1, pinIN2 }; const int pinMotorB[3] = { pinENB, pinIN3, pinIN4 }; pinMode(pinIN1, OUTPUT); pinMode(pinIN2, OUTPUT); pinMode(pinENA, OUTPUT); pinMode(pinIN3, OUTPUT); pinMode(pinIN4, OUTPUT); pinMode(pinENB, OUTPUT); analogWrite(pinMotorA[0], 255); digitalWrite(pinMotorA[1], HIGH); digitalWrite(pinMotorA[2], LOW); analogWrite(pinMotorB[0], 255); digitalWrite(pinMotorB[1], HIGH); digitalWrite(pinMotorB[2], LOW);
Mediante la creación de una clase todo esto quedará simplificado de la siguiente manera:
#include <RobotMario.h> RobotMario robot; ... robot.avanzar(255); ...
En nuestro caso crearemos una clase llamada RobotMario que tenga las siguientes funciones (métodos):
class RobotMario { public: RobotMario(); ~RobotMario(); void avanzar(int velocidad); void retroceder(int velocidad); void izquierda(int velocidad); void derecha(int velocidad); void parar(); void desactivarLed(); void activarLed(String color); bool lecturaPulsador(); void melodia1(); void melodia2(); bool obstaculoDetectado(int distanciaMinima); private: void melodia(const int notas_melodia[], const int duracion_notas[], const int total_notas); NewPing *sonar; };
avanzar(int velocidad). Hace avanzar el robot a una velocidad determinada.
retroceder(int velocidad). Hace retroceder el robot a una velocidad determinada.
izquierda(int velocidad). Hace girar a la izquierda el robot a una velocidad determinada.
derecha(int velocidad). Hace girar a la derecha el robot a una velocidad determinada.
parar(). Hace parar al robot.
desactivarLed(). Hace que el LED esté apagado y no emita ningún color.
activarLed(String color). Permite activar el LED para que muestre uno de los siguientes colores : rojo, verde y azul.
melodia1(), melodia2(). Permite hacer sonar una melodía.
obstaculoDetectado(int distanciaMinima). Esta función devolverá TRUE si el sensor de movimiento detecta un obstáculo dentro de la distancia mínima especificada, en caso contrario devolverá FALSE.
En el siguiente bloque de código se puede apreciar la implementación del constructor, destructor y el método avanzar de la clase RobotMario:
#include "RobotMario.h" // Constructor RobotMario::RobotMario() { // CONTROLADORA MOTOR L298N pinMode(pinIN1, OUTPUT); pinMode(pinIN2, OUTPUT); pinMode(pinENA, OUTPUT); pinMode(pinIN3, OUTPUT); pinMode(pinIN4, OUTPUT); pinMode(pinENB, OUTPUT); // PULSADOR pinMode(pinPulsador, INPUT); // LED RGB KY-016 pinMode(pinRGBAzul, OUTPUT); pinMode(pinRGBVerde, OUTPUT); pinMode(pinRGBRojo, OUTPUT); // ZUMBADOR KY-006 pinMode(pinBUZZER, OUTPUT); // SENSOR ULTRASONICO DISTANCIA HC-SR04 pinMode(pinTRIGGER, OUTPUT); pinMode(pinECHO, INPUT); sonar = new NewPing (pinTRIGGER, pinECHO, MAX_DISTANCIA); } // Destructor. RobotMario::~RobotMario() { // Se debe borrar el objeto sonar creado dinmicamente if (sonar) { delete sonar; sonar = NULL; } } // Hace avanzar el robot a la velocidad especificada void RobotMario::avanzar(int velocidad) { analogWrite(pinMotorA[0], velocidad); digitalWrite(pinMotorA[1], HIGH); digitalWrite(pinMotorA[2], LOW); analogWrite(pinMotorB[0], velocidad); digitalWrite(pinMotorB[1], HIGH); digitalWrite(pinMotorB[2], LOW); }
La clase completa con todos los métodos la tenéis disponible para descargar en el siguiente enlace (descargar clase)
Antes de pasar a crear las extensiones podemos realizar un simple sketch con el IDE de Arduino para probar que todas los métodos creados funcionan correctamente.
#include <RobotMario.h> RobotMario robot; bool activar = false; void setup() { } void loop() { if (robot.lecturaPulsador()) { if (activar == false) { robot.melodia1(); activar = true; } else { robot.parar(); robot.activarLed("ROJO"); robot.melodia2(); activar = false; } } if (activar == true) { if (robot.obstaculoDetectado(50)) { robot.activarLed("ROJO"); robot.izquierda(255); } else { robot.activarLed("VERDE"); robot.avanzar(255); } } }
El código anterior hace que al apretar el botón de la parte superior del robot suene la melodía 1 y se ponga el robot a correr (avanzar hacia adelante) con el LED en color verde hasta que se detecte un obstáculo a 50 cm que hará girar a la izquierda el robot poniendo el LED en color rojo. Si se vuelve a pulsar el botón de la parte superior, el robot se parará haciendo sonar la melodía 2 y poniendo el LED en color rojo.
Creando extensiones
Estructura de archivos y directorios
Para poder programar extensiones tendremos que leer la documentación facilitada por Makeblock que adjunto en el siguiente PDF donde explica cómo crearlas (descargar PDF tutorial crear extensiones)
A la hora de crear una extensión debemos crear una estructura de ficheros y directorios como la que se muestra en la foto:
Esta estructura está formada por:
-
- js: Carpeta que contiene el archivo javascript (*.js) utilizado para trabajar en “modo Scratch”. En nuestro caso no utilizaremos este modo (*).
- src: Carpeta que contiene las librerías y archivos adicionales que utilizamos en la extensión. En nuestro caso tendrá la librería NewPing utilizada para el sensor de distancia, la librería NewTone y el archivo pitches.h utilizados para las melodías y por último nuestra propia librería RobotMario que hemos creado.
- *.s2e: Archivo principal de la extensión que contiene la información básica y los bloques que constituyen nuestra extensión así como el código que se genera cuando estamos en “modo Arduino”.
(*) NOTA: Aunque en nuestro caso solo utilizaremos el “modo Arduino” y por tanto no sería necesario crear el archivo *.js del directorio js sí que es necesario que este directorio se encuentre creado puesto que de no ser así a la hora de crear el archivo zip que contiene todos los elementos de la extensión produciría un error a la hora de importarlo como extensión en mBlock.
Creación del archivo s2e
Este archivo consta de 5 partes o bloques:
-
- Información básica de la extensión.
- Definición de bloques.
- Definición de menús.
- Bloque donde se relaciona los ítems de los menús con sus respectivos valores numéricos cuando estamos en “modo Arduino”.
- Bloque de traducciones.
En la siguiente imagen podemos ver los bloques anteriormente descritos en nuestro archivo:
Ahora cogeremos de todos los bloques programados el bloque que genera la acción de “Avanzar” para ver el significado de cada uno de los elementos que lo forman:
El primer parámetro (1) indica el tipo de bloque y puede tener los siguientes valores:
-
- w: bloque de escritura → se manda un comando y no se espera respuesta.
- r: bloque de lectura → se espera que la función devuelva un valor.
- b: bloque binario. Es idéntico al bloque “r” pero el valor que se espera que tiene que devolver es un valor binario “true” o “false”.
- h: bloque de cabecera.
El segundo parámetro (2) es el texto que aparecerá en el bloque. Las palabras que empiezan por % indicarán que se trata de un parámetro. Los posibles valores de estos parámetros son:
-
- %n: parámetro numérico. Este parámetro aparecerá en el bloque con forma redondeada.
- %s: parámetro de tipo string (cadena). Este parámetro aparecerá en el bloque con forma rectangular.
- %d.nombre: parámetro que será un desplegable con valores. El contenido de ese desplegable lo marcará el nombre del menú especificado. Este parámetro aparecerá en el bloque con forma redondeada.
Ejemplo: En nuestro bloque de ejemplo tenemos el parámetro %d.velocidad que indica que los valores que aparecen en el desplegable pertenecen al menú “velocidad” y si miramos el bloque que contiene los menús dentro del archivo s2e podemos ver que dicho menú contiene los ítems “RAPIDO” y “LENTO”. A su vez estos textos se traducirán en valores numéricos especificados en el bloque que relaciona los textos del menú con los valores numéricos.
-
- %m.nombre: parámetro que será un desplegable con valores. El contenido de ese desplegable lo marcará el nombre del menú especificado. Este parámetro aparecerá en el bloque con forma rectangular.
Ejemplo: En nuestro archivo s2e tenemos un bloque que tiene el parámetro %m.colores que indica que los valores que aparecen en el desplegable pertenecen al menú “colores” y si miramos el bloque que contiene los menús podemos ver que dicho menú contiene los ítems “ROJO” “VERDE” y “AZUL”. A diferencia del parámetro %d.nombre ese mismo texto será el que se insertará en el código para el Arduino y no está asociado a ningún valor numérico.
El tercer parámetro (3) representa el nombre de la función javascript utilizado en modo “Scratch” que aunque en nuestro caso no lo utilizamos se tiene que poner.
El cuarto parámetro (4) representa el valor por defecto del parámetro del bloque. Si no hubiera parámetro no se pondría nada y en caso de que el bloque tuviera más de un parámetro se pondrían tantos valores por defecto como parámetros existan.
Los siguientes parámetros representan el código Arduino generado por el bloque a la hora de utilizarlo:
(5) setup: código que se insertará una sola vez dentro de la función setup de nuestro sketch.
(6) inc: includes que se insertarán en nuestro sketch.
(7) def: variables globales que se insertarán en nuestro sketch.
(8) work: código que se insertará donde utilicemos el bloque.
(9) loop: código que se insertará una sola vez dentro de la función loop de nuestro sketch.
Dentro del código que generan los bloques, los textos {0}, {1}, {2} … representarán los parámetros especificados dentro del bloque utilizado, siendo {0} el primer parámetro, {1} el segundo parámetro y así sucesivamente.
El bloque “Avanzar” de nuestro ejemplo tiene que generar el siguiente código Arduino:
#include “RobotMario.h” RobotMario robot; robot.avanzar(255);
Por tanto rellenaremos los parámetros anteriormente descritos de la siguiente manera:
[ "w", "avanzar %d.velocidad", "Avanzar", "RAPIDO", { "setup":"", "inc":"#include \"RobotMario.h\"", "def":"RobotMario robot; \n", "work":"robot.avanzar({0}); \n", "loop":"" } ]
Donde:
-
- El parámetro {0} representa la velocidad cuyo valor por defecto es “RAPIDO” y tendrá un valor numérico asociado de 255.
- \» Lo utilizaremos para poder poner una comilla doble dentro del código generado.
- \n Lo utilizaremos para hacer un retorno de carro.
NOTA: Como se puede apreciar en nuestra extensión todos los bloques generan el mismo código Arduino dentro de los parámetros “inc” y “def”. Podríamos pensar que al ir añadiendo estos bloques se repetirían los mismos códigos Arduino dentro de nuestro sketch ¿no?… pues estáis equivocados … ya que el sistema es muy “listo” y solo se insertará una sola vez. Tanto si insertamos el mismo bloque repetidamente como bloques diferentes, los parámetros asociados a esos bloques que generen un código Arduino exactamente igual al que ya ha generado otro bloque dentro de nuestro sketch no duplicarán el código y por tanto ese código solo se insertará una vez. Esto solo es válido para los parámetros “setup”, “inc”, “def” y “loop”, es decir, todos a excepción del parámetro “work” cuyo código asociado se insertará tantas veces como se utilice el bloque.
En la siguiente imagen se puede apreciar la relación entre los bloques que el usuario vería dentro del entorno gráfico de programación y su código respectivo dentro del archivo s2e:
Una vez creado todos los bloques dentro del archivo s2e y puestas todas las librerías utilizadas en nuestra extensión dentro de la carpeta src bastará crear un archivo zip del directorio principal que contiene todos los elementos de nuestra extensión.
En el siguiente enlace tenéis disponible el zip con todos los elementos de la extensión (descargar extensión robot Mario)
Una vez realizado esto podremos importar la extensión al entorno gráfico de programación mediante la opción de menú : Extensiones → Administrar Extensiones. Con la ventana de “Administrar extensiones” abierta pulsaremos el botón “Añadir extensión” y buscaremos y seleccionaremos el archivo zip que acabamos de crear.
Una vez importada la extensión ya tendremos disponibles en mBlock todos los bloques que hemos creado y por tanto ya podremos programar en “modo Arduino” el ejemplo sencillo que vimos al inicio de este tutorial:
En la siguiente imagen podemos ver el código Arduino que se genera al utilizar el conjunto de bloques de la imagen anterior:
Por último bastará pulsar el botón “Subir Arduino” para poder cargar el código generado en nuestro robot Mario (configurando previamente el puerto y la placa que utiliza nuestro robot).