34- Arduino, Escribir su propia Biblioteca


¿Cómo escribo mi propia biblioteca Arduino?

En esta lección, aprenderemos a escribir nuestras propias bibliotecas para Arduino y analizaremos algunas preguntas típicas sobre la interacción del código en la biblioteca y el código en el boceto (en el archivo principal del programa). Esta es la tercera lección relacionada con las bibliotecas: asegúrese de leer y dominar la lección sobre objetos y clases del bloque de programación, y la lección sobre el uso de bibliotecas del bloque de lecciones básicas, así como la lección sobre la creación de funciones. En este tutorial usaremos todos nuestros conocimientos previos, por lo que te recomiendo que te ocupes de todo lo que no tienes claro.

Es muy conveniente escribir bibliotecas en el editor de texto Notepad ++ ( sitio oficial ), el llamado cuaderno del programador. Este cuaderno reconoce y resalta la sintaxis, es capaz de autocompletar texto y búsqueda avanzada, y mucho más. Recomiendo encarecidamente trabajar en él si no sabe cómo usar Microsoft Visual Studio u otros entornos de desarrollo serios.


Tratar con archivos.

Una biblioteca es principalmente un archivo de texto con código que podemos conectar a nuestro boceto y usar los comandos disponibles allí. Una biblioteca puede tener varios archivos o incluso carpetas con archivos, pero siempre se incluye solo uno: el archivo de encabezado principal con la extensión .h , que a su vez extrae el resto de los archivos necesarios.

En general, la biblioteca tiene la siguiente estructura (el nombre de la biblioteca es testLib ):

  • testLib – carpeta de la biblioteca
    • ejemplos – carpeta con ejemplos
    • testLib.h – archivo de encabezado
    • testLib.cpp – archivo de implementación
    • keywords.txt – mapa de resaltado de sintaxis

A veces los archivos .h y .cpp se pueden encontrar en la carpeta  src. Todos los archivos y carpetas, excepto el encabezado .h, son opcionales y pueden estar ausentes, es decir, una biblioteca solo puede constar de un archivo de encabezado.

De esta forma, la biblioteca está en la carpeta con todas las demás bibliotecas y se puede incluir en el boceto usando el comando #include. En general, hay dos lugares donde el programa buscará la biblioteca (es decir, el archivo de la biblioteca):

  • Carpeta de bocetos
  • Carpeta de la biblioteca

En consecuencia, el comando de inclusión tiene dos opciones para encontrar un archivo, depende si nombre está encerrado entre <> o «» :

  • #include <file.h> : buscará un archivo en la carpeta de la biblioteca
  • #include “file.h” – intentará encontrar un archivo en la carpeta con el boceto, si no lo encuentra, irá a la carpeta de bibliotecas

Núcleo de la biblioteca

Llenemos nuestro archivo testLib.h, nuestra biblioteca de prueba, con el código mínimo para trabajar:

#ifndef testLib_h
#define testLib_h
#include <Arduino.h>
// código de biblioteca
#endif 

La construcción de las directivas del preprocesador prohíbe volver a vincular la biblioteca y generalmente es opcional, pero es mejor no ser vago y escribir así. El archivo de biblioteca testLib.h se encuentra en la carpeta testLib en la carpeta con todas las demás bibliotecas. También incluimos el archivo principal Arduino.h para usar las funciones de arduino en nuestro código. Si no hay ninguno, no tiene que conectarlo. También incluimos testLib.h en nuestro boceto de prueba, como en la captura de pantalla del último capítulo.

Puede encontrar la construcción ifndef-define de C # en casi todas las bibliotecas. En las versiones actuales del IDE, puede hacer esto:

#pragma once
// conecta Arduino.h
// código de biblioteca

La directiva #pragma once le dice al compilador que este archivo necesita ser incluido solo una vez, esta es solo una pequeña alternativa a # ifndef-define. Lo usaremos más.


Escribir una clase.

Usemos lo aprendido en la lección objetos y las clases e insertemos la versión final de la clase en testLib.h:

testlib.h

#pragma once
#include <Arduino.h>
// descripción de la clase
class Color {   // clase Color
public:
 Color(byte color = 5, byte bright = 30);
 void setColor(byte color);
 void setBright(byte bright);
 byte getColor();
 byte getBright();
private:
 byte _color;  // variable color
 byte _bright; // variable brillo
};
// implementación de métodos
Color::Color(byte color = 5, byte bright = 30) {
 _color = color;   // recuerda
 _bright = bright;
}
void Color::setColor(byte color) {_color = color;}
void Color::setBright(byte bright) {_bright = bright;}
byte Color::getColor() {return _color;}
byte Color::getBright() {return _bright;}

testsketch.h

#include <testLib.h>
Color myColor ( 10 ) ; // crea un objeto myColor con _color (obtenemos 10, 30)
Color myColor2 ( 10, 20 ) ; // ¡establece color y brillo! (obtenemos 10, 20)
Color myColor3;
void setup () {  
}
void loop () {  
}

En realidad, así es como colocamos nuestra clase en un archivo separado, lo conectamos al programa principal y usamos el código: acabamos de crear varios objetos. Comprobemos si funciona: enviamos los métodos de retorno al puerto:

testsketch.h

#include <testLib.h>
Color myColor ( 10 ) ; // crea un objeto myColor con _color (obtenemos 10, 30)
Color myColor2 ( 10, 20 ) ; // ¡establece color y brillo! (obtenemos 10, 20)
Color myColor3; 
void setup() {
  Serial.begin(9600);
  Serial.println(myColor.getColor());   // 10
  Serial.println(myColor2.getBright()); // 20
  Serial.println(myColor3.getColor());  // 5
}
void loop() {
}

El código genera los valores de la clase, los genera correctamente. De hecho, ¡aquí hemos escrito nuestra propia biblioteca! A continuación, puede separar la descripción y la implementación creando un archivo testLib.cpp:

testLib.h

#pragma once
#include <Arduino.h>
// descripción de la clase
class Color {   // clase Color
public:
 Color(byte color = 5, byte bright = 30);
 void setColor(byte color);
 void setBright(byte bright);
 byte getColor();
 byte getBright();
private:
byte _color; // variable de color
byte _bright; // variable brillo 
} ;

testLib.cpp

#include <testLib.h>  // debemos incluir el encabezado
// implementación de métodos
Color::Color(byte color = 5, byte bright = 30) { // conmstructor
 _color = color;   // recuerda
 _bright = bright;
}
void Color::setColor(byte color) {_color = color;}
void Color::setBright(byte bright) {_bright = bright;}
byte Color::getColor() {return _color;}
byte Color::getBright() {return _bright;}

testSketch

#include <testLib.h>
Color myColor ( 10 ) ; // crea un objeto myColor 
Color myColor2 ( 10, 20 ) ; // ¡establece color y brillo! (obtenemos 10, 20)
Color myColor3; // sin inicialización
void setup() {
  Serial.begin(9600);
  Serial.println(myColor.getColor());   // 10
  Serial.println(myColor2.getBright()); // 20
  Serial.println(myColor3.getColor());  // 5
}
void loop() {
}

Un punto importante: si la biblioteca tiene un archivo name.cpp de biblioteca, entonces la implementación de métodos y funciones debe estar ahí. No puede especificar la implementación en el archivo name.h de la biblioteca, habrá un error.

Si la biblioteca consta solo del archivo de encabezado name.h de la biblioteca, entonces la implementación se puede escribir en ella.

Y ahora tenemos (con los ejemplos) una biblioteca completa, dividida en archivos. Puede complementarlo con el archivo keywords.txt para que nuestros métodos estén resaltados en el código.


Keywords.txt

keywords.txt es un archivo que contiene un «mapa» de resaltado de sintaxis, es decir, de qué color resaltar qué palabras. La sintaxis para construir este archivo es muy simple: los nombres de las funciones / métodos se enumeran en una nueva línea y el tipo de palabra clave está separado por tabulaciones (presionando la tecla TAB).

  • KEYWORD1 : naranja en negrita, resaltado para tipos de datos y nombres de clases
  • KEYWORD2 – naranja, para métodos y funciones
  • LITERAL1 – azul, para constantes

Así es como se verá keywords.txt en nuestra biblioteca:

# comentario
testLib KEYWORD1
Color KEYWORD1
setColor KEYWORD2
setBright KEYWORD2
getColor KEYWORD2
getBright KEYWORD2

Puedes dejar comentarios, aquí comienzan con un hash #. No tenemos constantes, así que no usé LITERAL1. Veamos cómo se ve el código con el resaltado de nuestros comandos de la biblioteca. Un punto importante: para que los cambios surtan efecto, debe cerrar todas las ventanas del IDE de Arduino y volver a abrir el boceto.


Ejemplos de implementación.

Hemos analizado la estructura de la creación de una biblioteca, veamos algunas opciones privadas con ejemplos. Haré ejemplos con clases, no con funciones, porque la mecánica de trabajar con una clase, con una biblioteca, es mucho más complicada, y aquí estamos aprendiendo a escribir bibliotecas.

En todos los ejemplos, he creado la biblioteca de prueba testLib.h y la pruebo en testSketch.

Biblioteca sin clase

La biblioteca no necesita tener una clase, puede ser solo un conjunto de funciones:

testLib.h

#pragma once
#include <Arduino.h>
void printLol() {
  Serial.println("lol");
}

tesSketch

#include <testLib.h>
void setup() {
  Serial.begin(9600);
  printLol(); // imprime lol
}
void loop() {
}

Variaciones obvias: tiene más sentido escribir la declaración por separado de la implementación de la función. O incluso poner la implementación en un archivo .cpp.

testLib.h

#pragma once
#include <Arduino.h>
// declaracion
void printLol () ; 

testLib.cpp

#include <testLib.h>  // / debemos incluir el encabezado
// implementacion
void printLol() {
  Serial.println("lol");
}

tesSketch

#include <testLib.h>
void setup() {
  Serial.begin(9600);
  printLol(); // muestra lol
}
void loop() {
}

Vamos a envolverlo en el espacio de nombres

Este ejemplo se refiere al ejemplo anterior: en la “biblioteca” que creamos funciones, los nombres de estas funciones pueden coincidir con otras funciones en el esquema, lo que generará problemas. En lugar de escribir una clase, las funciones se pueden incluir en un namespace. Mira este ejemplo, creo que todo se aclarará:

testLib.h

#pragma once
#include <Arduino.h>
// espacio de nombres myFunc
namespace myFunc {
  void printLol () ; 
} ;
// implementación
void myFunc :: printLol () { 
  serial.println ( "lol" ) ;
}

El uso del espacio de nombres le permite separar funciones con los mismos nombres de diferentes documentos, llamar a una función desde un espacio de nombres se ve exactamente igual que una clase: nombre de namespace :: nombre de función.

tesSketch

#include <testLib.h>
void setup () {  
   serial.beguin( 9600 ) ;
  // saca kek de la función printLol de este modulo
  printLol () ;
  // saldrá lol de la función de biblioteca
  myFunc :: printLol () ;
}
void printLol () {  
  serial.println ( "kek" ) ;
}
void loop () {  
}

Pasar y enviar un valor a una clase

Considere este ejemplo: debe pasar un cierto valor a la clase, procesarlo y devolver el resultado al boceto. Como ejemplo, devolvamos un número multiplicado por 10. O mejor aún, considere una situación más compleja: necesita tomar un valor en una clase, escribirlo en una variable privada y obtenerlo usando un método separado:

testLib.h

#pragma once
#include <Arduino.h>
class testClass {
  public :
    void setValue ( int val ) ; 
    int getValue () ; 
  private :
    int _value = 0;
} ;

testLib.cpp

#include <testLib.h> // debemos incluir el encabezado
void testClass :: setValue ( int val ) { 
  // toma el valor externo y escribe en nuestro _value
  _value = val;
}
int testClass :: getValue () { 
  return _value; // devuelve la variable de la clase
}

tesSketch

#include <testLib.h>
testClass testObject;
void setup() {
  Serial.begin(9600);
  testObject.setValue(666);
  Serial.println(testObject.getValue()); // imprime 666
}
void loop() {
}

Modificar una variable de una clase

Considere esta situación: necesitamos cambiar el valor de una variable en el boceto usando un método / función de biblioteca. Aquí hay dos opciones: asignar directamente o usar un puntero. Consideremos ambas opciones en un ejemplo:

testLib.h

#pragma once
#include <Arduino.h>
class testClass {
  public :
    int multTo5 (int value) ; 
    void multTo7 ( int * value ) ; 
  private :
    
} ;

testLib.cpp

int testClass::multTo5(int value) {
  // devuelve el valor multiplicado por 5
  return value * 5;
}
void testClass::multTo7(int* value) {
  // multiplicar la variable por 7
  *value = *value * 7;
}

tesSketch

#include <testLib.h>
testClass testObject;
void setup() {
  int a = 10;
  a = testObject.multTo5(a);
  // a == 50
  
  testObject.multTo7(&a);
  // a == 350
}
void loop() {
}

En la primera versión, pasamos el valor de la variable, dentro del método lo multiplicamos por 5 y lo devolvemos, y podemos igualar la misma variable en el boceto al nuevo valor. En el caso de un puntero, todo funciona de manera más interesante: pasamos la dirección de la variable al método, multiplicamos esta variable por 7 dentro de la clase, y listo. En términos generales, en este ejemplo, *value es un muñeco vudú para la variable a: lo que haremos con * valor dentro del método: se reflejará inmediatamente en a.

Este tema se puede desarrollar con esta opción: podemos almacenar la dirección de una variable en la clase, y la clase siempre tendrá acceso directo al valor de la variable, ¡no será necesario pasarlo cada vez!

testLib.h

#pragma once
#include <Arduino.h>
class testClass {
  public :
    void takeControl ( int* value) ; 
    void multTo6 () ; 
  private :
    int * _value; // almacena la dirección
} ;

testLib.cpp

#include <testLib.h> // debemos incluir el encabezado
void testClass :: takeControl ( int * value ) { 
  _value = value;
}
void testClass :: multTo6 () { 
  * _value = * _value * 6;
}

tesSketch

#include <testLib.h>
testClass testObject;
int a = 10;
void setup () {  
  // pasó la dirección a
  testObject.takeControl ( & a ) ;
  // ahora a == 10
  testObject.multTo6 () ; // aquí a se convierte en 60
  a = 5;
  testObject.multTo6 () ; // aquí a se convierte en 30
  testObject.multTo6 () ; // aquí a se convierte en 180
}
void loop () {  
}

De esta forma, la clase y sus métodos pueden tener un control completo sobre la variable en el programa principal.

Pasando una matriz a una clase

Intentemos pasar la matriz a la clase para que los métodos de la clase puedan, por ejemplo, sumar los elementos de la matriz y devolverla.

testLib.h

#pragma once
#include <Arduino.h>
class testClass {
  public:
    long getSum(int *array, byte length);
  private:
};

testLib.cpp

#include <testLib.h>  //debemos incluir el encabezado
long testClass::getSum(int *array, byte length) {
  long sum = 0;
  //calcula la longitud de la matriz
  length = length / sizeof(int);
  
  for (byte i = 0; i < length; i++) {
    sum += array[i];
  }
  return sum;
}

tesSketch

#include <testLib.h>
testClass testObject;
void setup() {
  //hacer matriz
  int myArray[] = {5, 33, 58, 251, 91};
  //  pasar la matriz y su tamaño (en bytes)
  long arraySum = testObject.getSum((int*)myArray, sizeof(myArray));
  // arraySum == 438
}
void loop() {
}

Creo que el mecanismo principal está claro, aquí dejo otro ejemplo de cómo transferir una estructura.

Pasando una estructura por puntero:

// pasar la estructura por puntero
struct foo_param_t
{
 float *u; int n; float b; float c;
}
void foo(foo_param_t *p)
{
 for (int i=0; i<p->n; i++)
 {
  float x = i*M_PI;
  p->u[i] = 1.0+p->b*x+p->c*x*x;
 }
}
void bar()
{
 const int N = 10;
 float a[N];
 foo_param_t p = {(float*)&a, N, 1.23, -4.56};
 foo(&p);
}

Pasando una estructura por referencia:

// pasar la estructura por referencia
struct foo_param_t
{
 float *u; int n; float c; float b;
}
void foo(foo_param_t& p)
{
 for (int i=0; i<p.n; i++)
 {
  float x = i*M_PI;
  p.u[i] = 1.0+p.b*x+p.c*x*x;
 }
}
void bar()
{
 const int N = 10;
 float a[N];
 foo_param_t p = {(float*)&a, N, 1.23, -4.56};
 foo(p);
}

Pasar una función a una clase

Creo que recuerdas cómo funcionan cosas como attachInterrupt: especificamos el nombre de una función que se puede llamar desde otra función. Esto se hace mediante un puntero de función. Veamos un ejemplo simple sin una clase:

testLib.h

#pragma once
#include <Arduino.h>
// la función adjunta se almacena aquí
void (*atatchedF)();
// conecta la función
void attachFunction(void (*function)()) {
 atatchedF = *function;
}
//  llamar a la función adjunta
void callFunction() {
 (*atatchedF)();
}

tesSketch

#include <testLib.h>
void setup() {
  Serial.begin(9600);
  // conectó la función printKek
  attachFunction(printKek);
  // llamó a la función conectada
  callFunction();
  // llamar a printKek
}
void printKek() {
  Serial.println("kek");
}
void loop() {
}

Ahora pongámoslo todo en la clase y almacenemos la dirección de la función adjunta dentro de la clase. 

testLib.h

#pragma once
#include <Arduino.h>
class testClass {
  public:
    void attachFunction(void (*function)());
    void callFunction();
  private:
    void (*atatchedF)();
};

testLib.cpp

#include <testLib.h>  //  debemos incluir el encabezado
void testClass::attachFunction(void (*function)()) {
 atatchedF = *function;  
}
void testClass::callFunction() {
 (*atatchedF)();
}

tesSketch

#include <testLib.h>
testClass testObj;
void setup() {
  Serial.begin(9600);
  // conectó la función printKek
  testObj.attachFunction(printKek);
  //llamó a la función conectada
  testObj.callFunction();
  // llama printKek
}
void printKek() {
  Serial.println("kek");
}
void loop() {
}

Creación automática de objetos

Crear una clase también significa crear un objeto, pero a veces se escribe una biblioteca para un solo objeto (por ejemplo, una biblioteca para trabajar con una interfaz) y crear un objeto en un boceto parece un código adicional. Pero, si abre cualquier ejemplo usando la biblioteca Wire.h, no encontrará una creación de objeto Wire allí, ¡pero se usa! Por ejemplo:

#include <Wire.h>
void setup() {
  Wire.begin();
}
// ...........

Estamos usando un objeto Wire , ¡pero no lo creamos! A veces puede ser adecuado, le mostraremos cómo hacerlo: solo necesita agregar una línea al archivo de encabezado:

extern ombre_clase nombre_objeto;

Y en .cpp, si hay uno, agregue:

nombre_clase nombre_objeto = nombre_clase () ;

Así, el objeto se creará dentro de la biblioteca y podremos usarlo desde el boceto. Tomemos el primer ejemplo de la lección, del capítulo «Pasar y enviar un valor a una clase», y eliminemos la creación de objetos innecesarios:

testLib.h

#pragma once
#include <Arduino.h>
class testClass {
  public:
    long get_x10(int value);
  private:
};
extern testClass testObject;

testLib.cpp

#include <testLib.h> // incluir encabezado
long testClass::get_x10(int value) {
  return value*10;
}
testClass testObject = testClass();

tesSketch

#include <testLib.h>
//  ¡no creas un objeto!
void setup() {
  Serial.begin(9600);
  Serial.println(testObject.get_x10(450)); //  imprime 4500
}
void loop() {
}

Establecer el tamaño de una matriz al crear un objeto

Debe recordar de la lección sobre matrices que se debe conocer el tamaño de la matriz antes de iniciar la ejecución del programa. Pero, ¿qué pasa si en una clase necesitamos una matriz con la capacidad de establecer su tamaño? Si hay un objeto en el programa, o para todos los objetos, el tamaño de la matriz será el mismo, entonces obviamente puede hacerlo así:

#define ARRAY_LEN 20
lass myClass {
  public:
    byte vals[ARRAY_LEN];
  private:       
} ;
myClass obj1; // obj1.vals tendrá 20 celdas aquí
myClass obj2; // obj2.vals tendrá 20 celdas aquí
myClass obj3; // obj3.vals tendrá 20 celdas aquí

Si queremos poder establecer el tamaño de la matriz para cada objeto, existen opciones:

Vía plantilla:

Al crear objetos, <parámetro> aparecerá en consecuencia:

template < int ARRAY_LEN >
class myClass {
  public:
    byte vals[ARRAY_LEN];
  private:    
};
myClass obj1; // obj1.vals tendrá 20 celdas aquí
myClass obj2; // obj2.vals tendrá 20 celdas aquí
myClass obj3; // obj3.vals tendrá 20 celdas aquí

También puede escribir el tamaño de la matriz en una variable para usar en más código:

template < int ARRAY_LEN >
class myClass {
  public:
    byte vals[ARRAY_LEN];
    byte arrSize = ARRAY_LEN;
  private:
};
myClass<30> obj1;
// obj1.vals tiene 30 celdas
// obj1.arrSize es 30

Cuando un objeto se crea globalmente, dicha matriz se almacenará en el área de variables globales y el compilador podrá calcular su tamaño.

A través de new y puntero:

Puede asignar una «matriz» de forma dinámica y almacenarla como un puntero. Como valor «constante», un truco de C ++ llamado lista de inicialización (dos puntos después myClass ( int x ) ):

class myClass {
  public:
    int* arr;
    myClass(int x) : arr(new int[x]) {
      // constructor
    }
  private:
};
myClass obj(5);
void setup() {
  Serial.begin(9600);
  obj.arr[0] = 1;
  obj.arr[1] = 2;
  obj.arr[2] = 3;
  obj.arr[3] = 4;
  obj.arr[4] = 5;
  for (byte i = 0; i < 5; i++) {
    Serial.println(obj.arr[i]);
    //  imprimirá 1 2 3 4 5 
  }
}
void loop() {
}

Incluso con la creación global de un objeto, dicha matriz se almacenará en la memoria dinámica y el compilador no podrá calcular su tamaño.

Haciendo constantes:

Probablemente vio a menudo en bibliotecas pasar una constante a una función, no tiene que ir muy lejos: digitalWrite (13, HIGH )¿Dónde está HIGH? ¿Qué es? Si abre Arduino.h, encontrará HIGH allí, esta es una constante, defina:

#define HIGH 0x1

Y en keywords.txt aparece como LITERAL1, lo que le da color azul. Hagamos una biblioteca que genere texto según la constante especificada:

testLib.h

#pragma once
#include <Arduino.h>
// constantes
#define KEK 0
#define LOL 1
#define AZAZA 2
#define HELLO 3
class testClass {
  public:
    void printer(byte value);
  private:
};ct;

testLib.cpp

#include <testLib.h> //debemos incluir el encabezado
void testClass::printer(byte value) {
 switch (value) {
 case 0: Serial.println("kek");
  break;
 case 1: Serial.println("lol");
  break;
 case 2: Serial.println("azaza");
  break;
 case 3: Serial.println("hello");
  break;  
 }  
}

tesSketch

#include <testLib.h>
testClass testObject;
void setup() {
  Serial.begin(9600);
  testObject.printer(KEK);  // imprime kek
  testObject.printer(LOL);  // imprime lol
  testObject.printer(AZAZA);  // imprime azaza
  testObject.printer(HELLO);  // imprime hello
}
void loop() {
}

Así es como puede pasar una palabra en lugar de un valor, y será más conveniente trabajar con dicha biblioteca. Tenga en cuenta que usamos constantes (define), esto no es muy correcto: si en otro documento conectado a continuación o en el propio boceto, nuestra definición coincide con el nombre de otra variable, función u otra definición, entonces el programa no funcionará correctamente. define se aplica a otros documentos, incluido el programa principal (Sketch).

¿Qué hacer? Puede nombrar sus constantes de manera tan única que nadie interfiera con ellas, por ejemplo, agregue un prefijo con el nombre de la biblioteca: MYLIB_ CONSTANT.

También puede reemplazar la definición con una enumeración, entonces su biblioteca no afectará a otras y al documento principal, pero otras bibliotecas y definiciones externas también pueden ingresar a su biblioteca.

Interferencia de compilación:

A continuación, considere esta situación: sabemos cómo utilizar las directivas del preprocesador y queremos influir en el proceso de compilación de la biblioteca sin tocar nada en el archivo de la biblioteca. ¿Es posible? Sí, es posible. Un punto importante: este truco solo funciona en el archivo de encabezado de la biblioteca, es decir, es muy probable que el archivo de implementación .cpp deba abandonarse.

Si realiza una definición antes de conectar el archivo de la biblioteca, esta definición será «visible» desde el archivo de encabezado de la biblioteca y se puede utilizar para declaraciones de compilación condicional.

Un punto importante: al crear una biblioteca, no se recomienda escribir código ejecutable en el archivo de encabezado fuera de la clase, porque esto dará lugar a errores al conectar la biblioteca en diferentes archivos. Para utilizar la «magia de las definiciones», debe formatear correctamente la implementación en el archivo de encabezado, vea un ejemplo:

Así es como puede hacerlo:

// lib.h
class testClass {
  public:
    int func() {return 10;}
  private:
};

Pero esto de abajo es imposible:

// lib.h
class testClass {
  public:
    int func();
  private:
};
int testClass::func() {
  return 10;
}

Bueno, aquí hay un ejemplo de cómo funciona una definición que «encaja» en la biblioteca:

testLib.h

#pragma once
#include <Arduino.h>
void printResult() {
// si SEND_NUDES está definido
#ifdef SEND_NUDES
 Serial.begin(9600);
 Serial.println("nudes");
#endif
}

tesSketch

// definir SEND_NUDES
// ANTES de conectar la biblioteca
#define SEND_NUDES
#include <testLib.h>
void setup() {
 // imprime "nudes" si SEND_NUDES está definido
  printResult();
}
void loop() {
}

¿Por qué es necesario? La compilación condicional le permite controlar la compilación de su código, es decir, codificar qué partes del código se compilarán y cuáles no. Para obtener más detalles sobre los peligros y las complejidades de trabajar con define, incluida la creación de bibliotecas, lea la lección anterior sobre las directivas de preprocesador.


Deja un comentario