Objetivos

 

  • Comprender que a medida que los programas C crecen, cada vez es mas dificil  corregir errores.
  • Prsentaremos una forma de organizar tus proyectos en varios ficheros
  • Veremos como se definen esos ficheros estandard y porqué hacerlo así.

Material Requerido

 

Vista principal  Un Arduino

 

Organizando tus programas Arduino

 

Vale, llevas un tiempo escribiendo programas para Arduino y hasta has empezado a escribir algunos más largos que incluyen múltiples sensores, por ejemplo, y has visto que a medida que tu programa crece, moverte arriba y debajo de tu código, buscando algo, empieza a suponer cada vez más tiempo y peor aún, un mareo completo.

Sospechas que debería haber alguna manera de organizar este lio porque de lo contrario en cuanto tu programa crezca un poco más la cosa tiene pinta de complicarse más de lo que nuestra salud mental recomienda. ¿Cómo demonios hacen esos enormes programas que ves en las librerías (Y que asustan cantidad) y como consiguen no volverse locos?

Bien si estas en ese punto, has venido al sitio correcto por que pretendemos mostrarte de que forma puedes organizar tus programas para evitar que su complejidad te devore .Comprenderás que no eres el primero en encontrarse con este problema y como siempre decimos: Si hay un problema, hay mucha gente buscando soluciones antes que tú. Mas aun, hace años que los programadores de C encontraron una forma razonable de organizar sus programas más o menos grandes.

En esta sesión veremos cómo hacerlo y espero que veas que merece la pena a pocas líneas que tu programa vaya creciendo. Vamos a hacer un ejemplo simple con un humilde led y te mostraremos como organizar tus programas de un modo estándar y ampliamente extendido en mundo de C++, hasta el punto de ser una norma.

 

Un programa sencillo

 

Cada vez que escribimos un programita con Arduino acabamos escribiendo de forma repetitiva varias funciones o al menos líneas de código que inicializan el led, lo encienden, lo apagan, lo hacen parpadear… etc.

Imagínate que hacemos un programa simple para manejar un led con la intención de no tener que volver a bregar con todas las cosas que decíamos arriba. Queremos escribir un programa que podamos estandarizar y usarlo cuando nos convenga sin tener que pensar mucho en ello, es decir, mas menos hacer una librería estándar (Una, particularmente simple, pero es que nos encantan las cosas simples)

Bien podríamos empezar planteando que funciones vamos a necesitar para nuestro led, algo como:

Init(pin)                                 //Inicializar
On(void)                                  //Encender
Off(void)                                 //Apagar
blink(int repeat, int pause)        //Blink n veces con una pausa en milliseconds

Y una vez que lo tenemos claro, si has osado llegar hasta aquí, el programa de abajo debería ser inmediato de comprender:

int pin = 13;

void setup()
{  Init(pin) ;}

void loop()
{   on() ;
    delay(500);
    off();
   delay(500);
}

void Init(int pin)
{ pinMode(pin, OUTPUT);    }

void on(void)
{ digitalWrite(pin, HIGH);  }

void off(void)
{ digitalWrite(pin, LOW);   }

void blink(int repeat, int pause)
{ for (int i=0; i< repeat; i++)
    { on();
      delay(pause);
      off();
     delay(pause);
    }
}

Si pruebas este programa, el resultado será un bonito blinking led (Por enésima vez).  Y si quieres probar la función blink basta con que lo pruebes con un loop como este:

void loop()
{   blink(5, 150); }

Bien, aunque nuestro programa no tiene ninguna dificultad, hasta ahora no hemos hecho nada nuevo y este tuto va de organizar nuestro código… en varios ficheros de modo que sea mucho más cómodo acceder a nuestro código cuando este crezca. Por eso vamos a ver la forma estándar en C y C++ de organizar tu programa en 3 ficheros básicos. Vamos al lio.

 

Organizando tu programa en 3 ficheros

 

La idea básica es que tengas un fichero donde esta el programa principal, que en Arduino será el habitual fichero.ino como siempre, pero que llevemos las funciones que hemos definido a un fichero de extensión .cpp (C plus plus) y otro fichero de cabeceras (O headers en inglés) con extensión .h. Vamos por pasos

¿Cómo creamos estos ficheros adicionales? Pues muy sencillo, pincha en el botón de la derecha:

Nuevo fichero relacionado

Si pulsas en nueva pestaña, te aparecerá una ventanita amarilla pidiéndote el nombre del archivo que deseas crear, yo le voy a llamar Led.h.:

NUevo fichero asociado

Bien dale al OK y te creará ese nuevo fichero en el mismo directorio donde guardas el programa principal y ahora verás dos pestañas, en mi caso una con el el programa led 3 y otra (En blanco, aun) con Led.h :

multiples pestañas de programa
Verás que puedes conmutar entre las dos pestañas, para ver el programa inicial (Led 3, en mi caso) y la nueva pestaña .h; Muy bien, créate otra ventana que se llame Led.cpp

  • En principio puedes dar el nombre que quieras, pero es muy recomendable que el fichero.h y el fichero.cpp tengan el mismo nombre. De ese modo sabes de un vistazo que van juntos

Si salvas los ficheros, aun en blanco, y pulsas la opción //Programa/Mostrar Carpeta de programa:

Mostrar los multiples ficheros del programa

Podrás ver en tu directorio algo parecido a esto:

Imagen del explorador de archivos

La idea consiste en organizar los programas de un cierto tamaño en tres ficheros separados:

  • h : Contendrá la descripción de las funciones (Prototipos) , pero no su implementación.
  • cpp : Contendrá las funciones propiamente e incluirá en su encabezado el fichero.h
  • ino: Contendrá el programa principal, pero no las funciones ya que estas tienen su propio fichero al que puedes conmutar cambiando de pestañas.

De este modo, si queremos ver que funciones hay definidas y como usarlas, nos bastará con estudiar el fichero.h sin tener que preocuparnos de cómo funcionan. Si queremos ver el funcionamiento interno de una función o si queremos añadir mas funciones lo haremos en fichero cpp que es donde se encuentra la implementación

¿Un poco confuso? Bueno, en realidad no, pero como todo en la vida hay que acostumbrarse (Y al final te gusta). Veamos como reescribiríamos nuestro mismo programa de Led con este nuevo sistema de organización.

Rellenando los ficheros

 

Una vez que hemos creado los tres ficheros, tenemos que cortar las tres funciones que hemos definido y pasarlas al fichero cpp que contiene la programación de las mismas, de modo que quedará así:

#include "Led.h"               //Incluimos las definiciones que haremos en el fichero Led.h

void Init(int pin)
   { pinMode(pin, OUTPUT);    }

void on(void)
   { digitalWrite(pin, HIGH);  }

void off(void)
   { digitalWrite(pin, LOW);   }

void blink(int repeat, int pause)
{ for (int i=0; i< repeat; i++)
     { on();
       delay(pause);
       off();
       delay(pause);
     }
}

Lo único nuevo es que hemos añadido una primera línea que hace un include (Importa) el fichero de definiciones .h, del que hablaremos a continuación, el resto es una corta pega del programa principal, por lo que el Led.cpp quedaría así:

#include "Led.h"
int pin = 13;

void setup()
  {  Init(pin) ;
  }
void loop()
  {   on() ;
      delay(500);
      off(); 
      delay(500);
  }

Donde de nuevo hemos incluido la primera línea con el led.h y poco más. Veras que estamos usando las funciones on() y off() porque están definidas en el fichero led.h

Ahora sí, vamos a ver como queda el fichero Led.h

El fichero Led.h

 

La idea de organizar en tres ficheros nuestro programa es que si hacemos un include del fichero .h, en un programa este se encargará de cargar también el fichero .cpp (El compilador lo hace en automatico porque conoce la costumbre) y de este modo, las funciones que incluyen en esta pareja de ficheros, las podremos usar en otros programas más adelante sin complicarnos la vida de como se hace. Si nuestro programa está afinado podremos incluirlo en todos los programas futuros en los que necesitemos.

Para ello nuestro fichero .h se parecerá a esto:

#include <arduino.h>

void Init(int pin) ;
void on(void) ;
void off(void) ;
void blink(int repeat, int pause) ;

Y lo primero que tenemos que comprender es, que en este fichero deben estar todas las declaraciones de las funciones que más adelante programaremos en nuestro fichero cpp, No se pretende que en el fichero .h puedas revisar las funciones que has escrito, sino solamente lo que se llaman prototipos de las funciones,  su descripción, donde puedes ver el nombre de estas, así como los parámetros que requiere y que devuelven esas funciones.

Esto permite que comprendas con facilidad lo que hacen y los parámetros que requieren /devuelven sin necesidad de revisar código, si además añades algunos comentarios describiendo la utilidad de la función pues mejor que mejor para todos ¿Vale?

Vale, nuestro fichero .h incluye una primera línea, que puede sorprender:

#include <arduino.h>

La realidad es que en un fichero .ino, de Arduino incluye esta línea por ti (Aunque no te des cuenta) y así podrás usar cosas particulares de Arduino como: OUTPUT, INPUT, HUGH, LOW & byte,. por ejemplo, a los que ya te has acostumbrado, pero que no forman parte de C++. Pero Arduino no hará eso por ti en los ficheros .cpp y .h y por eso al compilar te dará un error en cosas que te va a costar comprender por qué, así que debes acostumbrarte a añadir ese include en el fichero.h

Hay otro problema un poco mas insidioso aquí también, porque es para resolver un problema que aun no has tenido. Veamos: Todas al librerías incorporan un fichero .h y uno .cpp, pero esperamos que en una librería que se precie, sea lo bastante interesante para que la incluyas varias veces en tus programas y puede llegar un momento en que la incluyamos en el fichero principal y también en el fichero .cpp, como de hecho hemos hecho arriba y eso normalmente dará algún problema o no, dependiendo del compilador, podría añadir más de una vez tu librería de forma absurda haciendo crecer tontamente el tamaño del programa y por eso siempre debes controlar este problema empezando tu programa .h con las siguientes líneas:

#ifndef LED_H
#define LED_H

Y luego la ultima línea de tu fichero debe ser

#endif

Con todo lo que habíamos visto hasta ahora del fichero .h, Veamos que significa y como queda nuestro .h al final. Pero antes (Como en los anuncios)… Las instrucciones que empiezan por “#” son lo que se llaman directivas del compilador y no son propiamente instrucciones de C++ sino instrucciones para que el compilador haga alguna cosa. Por ejemplo, cuando dices cosas como:

#define pin 13

Algo a lo que estás muy acostumbrado y no pestañeas cuando lo ves por ahí, no es una instrucción C++, significa que instruyes al compilador para que sustituya la variable pin por el valor 15 allá donde lo encuentre en el programa ANTES de compilar. Las líneas:

#ifndef LED_H
#define LED_H

La segunda, define una variable del compilador llamada LED_H y la primera es lo que se llama una compilación condicional del trozo de código que viene después hasta el “#endif”. De ese modo lo que hacemos es comprobar si está definida la variable LED_H, si no lo está entonces compilamos todas las líneas hasta el #endif, incluyendo crear una variable LED_H para saber que ya hemos cargado este fichero.

Pero si ya está definida, es porque otro trozo de código ya ha incluido este fichero y por tanto no debemos compilarlo e incluirlo otra vez. Astuto ¿A que sí?

Es importante destacar aquí, que lo del nombre de la variable LED_H, viene de que nuestro fichero de headers se llama “Led.h” y la regla exige que se pasen a mayúsculas todos los caracteres del nombre del fichero (Sin acentos ni ñs porfa) y que en vez del punto se use un subrayado. Nuestro fichero Led.h quedará finalmente así: Led3

#ifndef LED_H
   #define LED_H

   #include <arduino.h>

   void Init(int pin);
   void on(void);
   void off(void) ;
   void blink(int repeat, int pause) ;

#endif

Hacedme caso cuando os digo que añadas siempre las líneas de #ifndef – #endif o acabarás encontrando problemas

Ya solo queda un último detallito, que consiste en que en el fichero Led.cpp hacemos referencia al valor de pin que hemos definido como un entero en el programa principal, pero que en este contexto, no está definido por lo que nuestro compilador ladrará una queja de la misma y se negará a seguir:

C:\Users\info\AppData\Local\Temp\arduino_modified_sketch_529654\Led.cpp: In function 'void on()':
Led.cpp:9:18: error: 'pin' was not declared in this scope
{ digitalWrite(pin, HIGH);  }
               ^~~

Por lo que al compilador respecta, el valor de pin esta sin definir así que se acabó la fiesta . Por eso nos conviene informarle de que estamos definiendo ese valor en otro módulo de nuestro programa y que todo tendrá sentido cuando se junten las piezas (Al llamar al linker). Para ello existe la instrucción:

extern int pin;

Donde le informamos al compilador que encontrará en algún lugar esa variable, pero le informamos que asigne espacio para un entero y eso es todo. Aquí os dejo el programa final completo que hasta compila religiosamente:  Led_3

 

Consideraciones finales

 

Alguno dirá eso de … Joer, teníamos un programa de lo mas sencillo y vaya taco que has montado para organizar un programita de nada. Si. Es verdad . Pero todo esto cobrará sentido a medida que tu programa vaya creciendo, porque ese programa está ahora separado en tres secciones con propósitos claramente diferenciadas:

  • El programa principal (.ino en Arduino) tiene el programa, pero no las funciones que usa. Esas se definen en otro lado.
  • El programa .h trae las definiciones de las funciones y los parámetros que recibe y que devuelve, (pero no el código propiamente dicho) y nos permite ver que funciones tenemos disponibles para trabajar: Nos da la información precisa para usar las funciones del .cpp
  • El fichero .cpp, tiene el código final de las funciones y aquí es donde podemos añadir o modificar funciones, siempre y cuando las definamos en el fichero.h

Vale, pues a grandes rasgos esta es la forma canónica de organizar tus programas en C y C++. Todas las librerías se organizan de esta manera, lo puedes comprobar en tu directorio de librerías, pero algunas de esas librerías pueden organizarse, además, en forma de objetos y clases… algo con lo que bregaremos en el próximo tuto, porque coma ya sospechabas, es el objetivo oculto de toda esta serie de tutos.

 

Deja una respuesta