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 鈥渇ile.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 鈥渂iblioteca鈥 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