Uno de las principales razones por las cuales Arduino ha tenido tanto éxito a nivel internacional se debe a la facilidad con la que se puede programar. El lenguaje Arduino es un lenguaje de alto nivel que permite lograr excelentes resultados sin tener que invertir mucho tiempo en aprender nuevos paradigmas.
Personalmente considero que el lenguaje Arduino es un híbrido entre C/C++ y Java. Supuestamente es un lenguaje que está basado en C++ y de hecho Java nace de C. Si me preguntan yo diría que es un híbrido entre ambos lenguajes. Posiblemente mi percepción se debe a que nunca desarrollé mis habilidades en C++, sino que luego de aprender lo básico me dediqué a Java y es ahí donde desarrollé la mayor parte de mis conocimientos en programación.
Java es para mí una de mis principales herramientas de trabajo. En más de una ocasión durante mi carrera universitaria y profesional he utilizado esta herramienta para desarrollar herramientas personalizadas que me permitan resolver problemas en situaciones específicas. Siento que con el tiempo he aprendido mucho sobre lo que es programación orientada a objetos con todas las ventajas que ofrece este paradigma de programación. De hecho, hace algún tiempo escribí un artículo sobre este tema:
El propósito de este nuevo post que estoy presentando en esta ocasión es explicar como funciona la programación orientada a objetos en Arduino y cómo construir clases para lograr mejores resultados en nuestros algoritmos de programación. Hay muchas cosas que se pueden lograr de una manera más fácil y rápida si se sabe como aprovechar la orientación a objetos, lo cual será el principal objetivo de este aporte.
Clases y objetos en Arduino
Cuando se escribe sobre programación orientada a objetos, lo primero que se debe mencionar son las clases. En el post sobre clases en Java traté de explicar este tema de la forma más sencilla posible. Una clase es básicamente una plantilla para la creación de un objeto. En la clase se definen las características con las que contará un objeto. ¿Qué significa esto? Veamos un ejemplo.
Supongamos que en un Arduino UNO tenemos 6 sensores conectados a los 6 puertos analógicos (ver imagen). Digamos que necesitamos almacenar las lecturas de los 6 pines y el tiempo en el cual se produjo la lectura. Podemos pensar en un arreglo o matriz multidimensional que nos permita almacenar los datos para utilizarlos después. También es posible emplear una solución más elegante utilizando el paradigma de programación orientada a objetos.
Para poder utilizar la OOP (Object Oriented Programming) tendríamos que definir una clase que nos permita crear objetos en los cuales sea posible almacenar la información requerida por nosotros. La estructura de esta clase queda definida en el siguiente diagrama.
Esta clase, a la que he llamado PortsState, será la encargada de permitirme crear objetos en los cuales almacenaré la información. Los atributos son variables que están «empacadas» dentro de la clase. Estas variables son las encargadas de guardar los datos y devolverlos a petición del usuario.
Las clases, además de atributos, contienen métodos (o funciones). Uno de estos métodos en particular se conoce como «constructor». El constructor es un método que se ejecuta justo durante la creación de un objeto. Generalmente se utiliza para asignar valores iniciales a los atributos.
Junto con el constructor normalmente se incluyen los «getters» y «setters«. Es decir, métodos que permiten obtener y asignar valores a los atributos. El usuario nunca manipula los atributos de forma directa. Esto se hace a través de métodos como getSensor0() y setSensor0(int value). Los setters casi siempre son métodos tipo void con al menos un parámetro de entrada. Los getters casi siempre son métodos que devuelven un valor.
Junto con el constructor, los getters y setters también se pueden incluir métodos que para otros tipos de tareas. Por ejemplo, se puede agregar un método llamado getSensorsAverage() que calcule y devuelva el promedio de los valores de los sensores, almacenados en los atributos de la clase.
Construyendo la clase PortsState
Para construir una clase en Arduino se requiere de un proceso un poco extenso y complicado. Esto lo digo en base a mis gustos, dado que en Java resulta muy fácil de hacer.
Una clase en Arduino está formada por dos archivos: el header file (archivo cabecera, con extensión .h) y el source file (archivo fuente, con extensión .cpp). En el header file se escriben las definiciones de la clase, con todos los métodos y atributos. Este fichero no contiene más que eso, definiciones. El código de los métodos, su contenido como tal, se define en el archivo fuente.
Para escribir los archivos que forman la clase se utiliza el Bloc de Notas, con el cual se crean archivos con extensión txt, para luego renombrarlos con el nombre y la extension adecuada.
El archivo PortsState.h debe contener obligatoriamente las instrucciones #ifndef Morse_h y #define Morse_h al principio del fichero. También se debe colocar la instrucción #endif. También es obligatorio incluir la librería Arduino.h, a través de la instrucción #include «Arduino.h». Si es necesario utilizar recursos de otras clases, éstas deben ser agregadas utilizando la instrucción #include. A continuación se presenta una visión general de la estructura del fichero PortsState.h.
Los mismos métodos declarados en el fichero PortsState.h deben estar declarados en en PortsState.cpp. Este último fichero debe contener las instrucciones que se ejecutan al invocar determinado método.
La imagen de arriba muestra parte del fichero PortsState.cpp. Este fichero también contiene todos los getters y los setters de todos los atributos. También se incluye el método getPortsAverage(), el cual contiene el siguiente código:
1 2 3 4 5 6 |
double PortsState::getPortsAverage() { int sum = sensor0 + sensor1 + sensor2 + sensor3 + sensor4 + sensor5; double portsAverage = sum / 6; return portsAverage; } |
Este método debe devolver el promedio de los valores de todos los sensores. Aparte de los ficheros fuente y cabecera se suele incluir un archivo llamado keywords.txt, donde se declaran los métodos de la clase, en una interfaz que permite diferenciar los elementos de la clase cuando se está programando el Arduino IDE. Los métodos declarados en el keywords.txt permiten que las letras adquieran un color naranja o chocolate cuando se escribe el código. En el caso de nuestra clase, el contenido del fichero keywords es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
PortsState KEYWORD1 setSensor0 KEYWORD2 setSensor1 KEYWORD2 setSensor2 KEYWORD2 setSensor3 KEYWORD2 setSensor4 KEYWORD2 setSensor5 KEYWORD2 getSensor0 KEYWORD2 getSensor1 KEYWORD2 getSensor2 KEYWORD2 getSensor3 KEYWORD2 getSensor4 KEYWORD2 getSensor5 KEYWORD2 getPortsAverage KEYWORD2 |
Básicamente se listan todos los métodos y se le colocan las etiquetas KEYWORD1 si es el constructor de la clase y KEYWORD2 si es un método. Es importante tomar en cuenta que la separación entre los métodos y la palabra KEYWORD debe hacerse con la tecla TAB y no con la barra espaciadora.
Los tres archivos descritos en este artículo se encuentran disponibles en nuestro repositorio de Github. Éstos tres archivos deben ubicarse en una ruta específica para poder ser utilizados por el Arduino. Dentro de la carpeta «Documentos» (Windows) hay un directorio llamado «Arduino», en cuyo interior encontramos otro con el nombre «libraries». Aquí dentro creamos una carpeta llamada PortsState, en la cual colocamos los tres ficheros.
Para efectos prácticos, el proceso de crear una clase en Arduino es el mismo que el de crear una librería. De hecho las librerías son colecciones de código que podemos utilizar a nuestro favor para programar en Arduino aprovechando, precisamente, el concepto de orientación a objetos.
Probando la clase PortsState en Arduino
Una vez creada la nueva clase e «instalada» en el directorio de librerías de Arduino, procedemos a probar lo que hemos hecho. He subido un archivo de ejemplo a nuestro repositorio de Github. Subí el ejemplo a un Arduino y el resultado fue el siguiente:
Como vemos, se crea un objeto de la clase PortsState llamado port. A través del constructor se le asignan los valores de fecha y el de los sensores (10, 11, 12… 15). Luego, en el setup se imprimen los valores almacenados en el objeto port. En una sola variable hay almacenados 6 valores. Además, se ha podido calcular el valor del promedio de los sensores con solo escribir la instrucción getPortsAverage().
Cuando utilizamos correctamente el paradigma de la programación orientada a objetos podemos aprovechar al máximo las capacidades que ofrece este tipo de método. Podemos crear objetos que almacenen múltiples datos, o ejecutar procesos específicos en pocas líneas de código, a través de la encapsulación de instrucciones en clases (o librerías).
Espero que la información suministrada sea de utilidad para ustedes. Estaré pendiente a sus comentarios, dudas y sugerencias.
Hola Antony.
He leído el contenido de tu página y veo qu realmente estás motivado en el mundo de la POO, primero en java y luego en Arduino.
Quiero comentarte de forma respetuosa que la POO en Arduino es un poco más fácil de como lo muestras en esta página. Por ejemplo en está dirección web https://www.prometec.net/programacion-oop/# puedes encontrar material de POO básico y muy sencillo para implementarlo en Arduino.
Si tienes oportunidad te recomiendo que lo puedas leer y con los conocimientos que tienes de POO en Java estoy seguro que seguirás realizando aportaciones en esta área. Te lo comento de forma personal, yo doy clases de mecatrónica con POO en arduino en licenciatura, tenemos un shield para arduino llamado «Multi Function Shield» el cual nos ayuda de forma considerable para combinar el hermoso mundo de la POO con el poderoso ecosistema de hardware para Arduino.
Es muy buena la forma en que explicas estos conceptos que son tan complicados para los alumnos que inician en este tema. Sigue con este trabajo, que realmente se necesita.
Hola. Gracias por comentar. Tus opiniones son bienvenidas. Saludos.
Muy buen aporte. Lamento disentir con Miguel Angel, dar todos los detalles necesarios para que algo funcione no es complicar las cosas, sino todo lo contrario. Gracias amigo.