Arduino orientado a objetos y creación de librerías

Arduino orientado a objetos y creación de librerías

tecnoingenia / noviembre 14, 2019

Qué tal estimados lectores en está ocasión vengo a hablarles de un tema enfocado meramente al software de nuestros proyectos, se trata de un tema que nos ayudará en gran medida a dar soluciones más «elegantes», profesionales y más fáciles de entender al código que creemos.

 

A partir de un comentario hecho en la entrada anterior acerca de evitar el uso de la función «delay()«, se me ocurrió que podíamos crear una librería que nos ayudara a manejar un timer con el mínimo de código.

 

Por lo cual haremos un pequeño acercamiento a la programación orientada a objetos, para esto es necesario entender el concepto de «Clase».

 

Una clase, es un extracto de código en el que se modela o se definen las variables y métodos con los que opera un objeto. A partir de dicha clase se pueden instanciar (crear) objetos que «heredarán» esas variables y métodos.

 

Bueno, después del verbo mareador, vayamos a la práctica, para crear una clase se utiliza la palabra reservada «class» seguida de el nombre de la clase, en este ejemplo crearemos una clase llama autoTimer que tendrá como atributos 4 variables y 5 métodos.

 

class AutoTimer
{
  private:
    unsigned long lastUpdate; // tiempo de la última ejecución
    boolean       inicialWait; //Ejecutar inmediatamente o esperar al primer timer
    void          (*funcion)(void); //Función que Ejecuta el timer
    unsigned long delayTime; //Tiempo de retardo
    
  public:
    AutoTimer(void(*f)(void), unsigned long d){
    } //Constructor

    AutoTimer(void(*f)(void), unsigned long d, boolean iW){
    } //Constructor opción 2

    boolean evaluate(){
    }

    boolean setDelayTime(unsigned long d){
    }

    unsigned long getDelayTime(){
    }
    
};
 
Nótese lo siguiente, dentro de nuestra clase, tenemos 2 secciones, la «private:» que se refiere a las variables y métodos privados, es decir que solo podrán ser usados dentro de la misma clase y «public:» que indica que estos podrán ser utilizados en ámbitos externos a la clase. 
La idea es que a través de la función millis() podamos llamar a ejecutar una función cualquiera en el intervalo de tiempo que definamos, para esto declaramos 4 variables:
  • lastUpdate: que guardará el tiempo en milisegundos de la ultima ejecución de la función.
  • inicialWait: un booleano que indicará si queremos que la función se ejecute inmediatamente la primer vez o queremos que espere.
  • *funcion: es un apuntador a la función que queremos ejecutar.
  • delayTime: que es el tiempo en milisegundos que queremos esperar entre cada ejecución.

 

Y los siguientes métodos:
  • evaluate(): es el método que usaremos en el loop, este estará constantemente evaluando si se ha cumplido el tiempo de espera.
  • setDelayTime(unsigned long d): esta función nos servirá para cambiar el delayTime de un objeto autoTimer y recibe como parámetros una variable tipo unsigned long.
  • getDelayTime(): y está regresará el valor de delayTime de un objeto autoTimer 
Como lo habrán notado solo hemos definido los métodos pero tenderemos que escribir su código todavía para que realicen la función que queremos.
También hemos creado un constructor que es el que define la forma en que se crearán los objetos y los parámetros necesarios para hacerlo. El constructor debe tener siempre el mismo nombre que la clase y se pueden declarar tantos constructores como sea prudentemente necesario y claro que estos tengan una utilidad. Dentro de cada constructor podemos escribir código también, en este caso hemos declarado dos constructores.

 

    AutoTimer(void(*f)(void), unsigned long d){
    } //Constructor

    AutoTimer(void(*f)(void), unsigned long d, boolean iW){
    } //Constructor opción 2

 

El tener dos constructores significa que se pueden crear autoTimers de dos maneras diferentes:
caso 1: teniendo los parámetros de f (función) y d (delayTime), se tomará por default que el parámetro inicialWait es falso.
caso 2: teniendo los parámetros de f (función), d(delayTime) y iW(inicialWait).

 

Finalmente cerramos nuestra clase con «}» seguido de «;«.

 

El código completo con un pequeño ejemplo de blink, queda de la siguiente manera:
class AutoTimer
{
  private:
    unsigned long lastUpdate; // última a actualización
    boolean       inicialWait; //Ejecutar inmediatamente o esperar al primer timer 
    void          (*funcion)(void); //Función que Ejecuta el timer
    unsigned long delayTime; //Tiempo de retardo
    
  public:
    AutoTimer(void(*f)(void), unsigned long d) //Constructor
    {
      inicialWait = false;
      lastUpdate = millis();
      funcion = f;
      delayTime = d;  
    }

    AutoTimer(void(*f)(void), unsigned long d, boolean iW) //Constructor opción 2
    {
      inicialWait = iW;
      funcion = f;
      delayTime = d;
      if(inicialWait)
      {
        lastUpdate = delayTime + millis();
      }
      else
      {
       lastUpdate = millis(); 
      }
    }

    boolean evaluate(){
      if(lastUpdate > millis()) //Esto es en caso de que ocurra un desbordamiento de la función millis
      {
        lastUpdate = millis();
      }
      
      if(millis() - lastUpdate >= delayTime){
        (*funcion)();
        lastUpdate = millis();
        return true;
      }

      return false;
    }
    
};

void toggleLED(){
  
  if(digitalRead(13))
  {
    digitalWrite(13, LOW);
  }
  else
  {
    digitalWrite(13, HIGH);
  }
  
}

AutoTimer parpadeo(toggleLED, 1000, false);

void setup() {

  pinMode(13, OUTPUT);
  
}

void loop() {
  
  parpadeo.evaluate();
  
}

 

Ahora vemos que después de la clase AutoTimer, declaramos una función toggleLED(), que lo que hace es cambiar el estado del pin digital 13 cada vez que se ejecuta. Instanciamos (creamos) un objeto de la clase AutoTimer llamado parpadeo.

 

AutoTimer parpadeo(toggleLED, 1000, false);

 

En el setup() usamos pinMode para definir el pin 13 como una salida digital:

 

void setup() {

  pinMode(13, OUTPUT);
  
}

 

 Y en el loop() usamos el método evaluate() para evaluar cuando se cumpla el tiempo de delay y mandar ejecutar la función toggleLED() cada 1000 milisegundos.

 

void loop() {
  
  parpadeo.evaluate();
  
}

 

Si hemos hecho todo bien esto dará como resultado que el led de nuestro arduino parpadeará cada segundo.

 

Ahora, el lector podra decir, ¡Pero tantas lineas de código solo para hacer parpadear un led!, bueno ahora viene lo interesante, cuando metemos todo esto en una librería y así podremos hacer parpadear un led, tomar una lectura, escribir en el puerto serie, encender otra salida y lo que se te ocurra, todo con diferente frecuencia sin que una cosa retrace a la otra, es decir en un casi Paralelo y digo casi, porque en realidad el arduino no realiza tareas en paralelo pero podemos dar la impresión de que si lo hace. 

 

Ahora hablemos de librerías… Una librería es en si solo un fragmento de código creado por nosotros o cualquier otra persona, que podemos incluir en nuestro proyecto sin tener que volver a redactor el código completo. Generalmente una librería consta mínimo de las siguientes partes:
 
  • Fichero .h: Es el «header» en el cual declaramos las clases con sus variables y métodos.
  • Fichero .cpp: Es un archivo de código fuente en el que escribimos el código de cada método.
  • Carpeta de Ejemplos: Generalmente en una librería se incluyen códigos de ejemplo para mostrar a otra persona como utilizarla.
  • Archivo keywords.txt: En este archivo se guardan las palabras claves utilizadas por la librería, es lo que hace que las palabras reservadas cambien de color en el código, como el nombre de la librería y los métodos.
 
Empezaremos creando un proyecto en el IDE de arduino, con el nombre de la librería, en mi caso «AutoTimer«. En este proyecto crearemos otras dos pestañas con el nombre de la librería pero una con .h y otra .cpp. Como se muestra en la imagen:

 

 

Como comentaba antes en la pestaña con la extensión .h declararemos nuestra clase con sus variables y métodos, OJO solamente declaramos:

 

//Fichero .h

#ifndef AutoAutoTimer_h
#define AutoTimer_h

#include "Arduino.h" // se debe incluir la librería Arduino.h

class AutoTimer
{
  private:
    unsigned long lastUpdate;
    boolean       inicialWait;
    void          (*funcion)(void);
    unsigned long delayTime;
    
  public:
    AutoTimer(void(*f)(void), unsigned long d); //Constructor
    AutoTimer(void(*f)(void), unsigned long d, boolean iW); //Constructor opción 2
    boolean evaluate();
    boolean setDelayTime(unsigned long d);
    unsigned long getDelayTime();
    
};

#endif

 

En el fichero .cpp escribimos el código de los métodos que declaramos anteriormente. Antes de cada método se escribe el nombre de la clase seguido de 2 doble puntos «AutoTimer::«, esto para indicar que pertenece a la clase AutoTimer.

 

#include "Arduino.h" //incluimos de nuevo la librería Arduino.h
#include "AutoTimer.h" //incluimos el fichero AutoTimer.h

    AutoTimer::AutoTimer(void(*f)(void), unsigned long d) //Constructor
    {
      inicialWait = false;
      lastUpdate = millis();
      funcion = f;
      delayTime = d;  
    }

    AutoTimer::AutoTimer(void(*f)(void), unsigned long d, boolean iW) //Constructor opción 2
    {
      inicialWait = iW;
      funcion = f;
      delayTime = d;
      if(inicialWait)
      {
        lastUpdate = delayTime + millis();
      }
      else
      {
       lastUpdate = millis(); 
      }
    }

    boolean AutoTimer::evaluate(){
      if(lastUpdate > millis()) //Esto es en caso de que ocurra un desbordamiento de la función millis
      {
        lastUpdate = millis();
      }
      
      if(millis() - lastUpdate >= delayTime){
        (*funcion)();
        lastUpdate = millis();
        return true;
      }

      return false;
    }

    boolean AutoTimer::setDelayTime(unsigned long d){
      delayTime = d;
      return true;
    }

    unsigned long AutoTimer::getDelayTime(){
      return delayTime;
    }

 

Creamos un bloc de notas llamado keywors.txt con las palabras claves de la librería, seguido de cada palabra va un tabulador y la palabra KEYWORD1 o KEYWORD2, la primera se refiere al los nombres de las clases y la segunda a los nombres de los métodos.

 

 

Ahora solo faltaría hacer un código de ejemplo, para esto creé dos códigos, en el primero se hace el parpadeo de un led, mientras se escribe en el puerto serie con un tiempo de delay diferente:

 

Pueden observar como las palabras claves aparecen resaltadas, el código es más conciso, entendible y lo mejor de todo sin delay()!! 

 

En el segundo ejemplo utilizó un AutoTimer para hacer parpadear el led y uso otro para que cada segundo cambie la frecuencia del primer AutoTimer, dando como resultado un led que sube y baja su frecuencia de parpadeo:

 

 

Les comparto el link de descarga de la librería desde mi drive con ambos ejemplos, espero que lo encuentren interesante y lo puedan aplicar en sus proyectos. Solo deben descomprimir y copiar la carpeta a C:\Users\«NombreDeUsuario»\Documents\Arduino\libraries y en la parte de ejemplos de su IDE ya debe de aparecer.

 

Descargar Librería

 

Les comento que estoy pendiente de subir el mini proyecto que les comentaba en la entrada anterior, estoy a la espera de que lleguen las PCBs que mande fabricar pero estoy tan desesperado que no tardo en ponerme a planchar… Espero tener noticias pronto, sigan ingeniando. 

3 Comments

    • tecnoingeniaPosted on : noviembre 14, 2019 at 4:50 am

      Se me ocurre hacer algun sensor que mida temperatura del Aire para checar lo Diario porque soy #viejita que le gusta saber del clima..

    • tecnoingeniaPosted on : junio 4, 2022 at 7:34 pm

      Puede ser que no funcione bien la clase? o no entendi el concepto de /Ejecutar inmediatamente o esperar al primer timer.
      if(lastUpdate > millis()) //Esto es en caso de que ocurra un desbordamiento de la función millis
      {
      lastUpdate = millis();
      }
      esto pone a lastUpdate a cero en caso que quiera esperar el primer timer no funciona

      tecnoingeniaPosted on : octubre 28, 2023 at 11:34 am

      si, yo creo que tenia que ser
      if(lastUpdate > (millis()+delayTime)) //Esto es en caso de que ocurra un desbordamiento de la función millis
      {
      lastUpdate = millis();
      }

Leave a Reply

TecnoIngenia | Blog personal -israel.barreras@tecnoingenia.com