Creando una librería para Arduino
Cada vez que nos adentramos a un nuevo proyecto con Arduino, y empezamos a seleccionar nuestros periféricos empezamos a ver que cada componente tiene su librería, por ejemplo si queremos integrar una LCD, Arduino tiene una librería, si queremos manejar un Servo motor, Arduino ya tiene una librería para ello. Y es que existe infinidad de librerías listas para usar con cualquier actuador o sensor que se nos venga en mente.
Ahora bien, ¿por que quisiéramos crear una librería en para Arduino? en lo personal yo creé una librería para poder experimentar en este campo y de paso mejorar el código con técnicas propias de la ingeniería de software. Además disfruto de crear tanto software como hardware.
Sin embargo un beneficio que le veo a crear tu propia librería es que ganas control, ya que puedes hacer que una librería se comporte como tu o tu proyecto necesita y no como un desarrollador pensó que sería lo mejor para el publico en general.
Dicho esto procedamos a crear nuestra librería, este proceso lo he separado en dos parte, lo primero sería la fase de definición, que es donde vamos a capturar todos los requerimientos y decidir que va a hacer nuestra librería y la segunda fase será la implementación.
Definiendo que vas a "encapsular"
Para empezar debemos conocer muy bien el funcionamiento del elemento al que le vamos crear la librería, un muy buen lugar para empezar suele ser la hoja de datos (datasheet) para este ejemplo usaré el encoder que hemos usado ya en la segunda parte del proyecto Pong
Como recordaran en el artículo pasado exploramos a fondo el funcionamiento de un encoder, como existe una secuencia de activación y como esta secuencia nos puede indicar el sentido de giro. toda esta información la obtuve de la hoja de datos de uno de los fabricantes de estos dispositivos.
A continuacion veremos la gráfica de la secuencia de activacion junto con su tabla lógica para recordar como era su funcionamiento.

| tiempo | pin A | pin B |
|---|---|---|
| t0 | 0 | 0 |
| t1 | 1 | 0 |
| t2 | 1 | 1 |
| t3 | 0 | 1 |
Recordemos que el encoder tiene 2 salidas que forman una señal cuadrada y que esta señal esta desplazada 90 grados con respecto a la otra. Y que según el estado actual y el estado anterior del las salidas del encoder podemos determinar si gira y en qué sentido.
Como parte del proceso de definición una parte importante es tener claro en que tipo de dato queremos guardar la cuenta que lleva el encoder. A continuación veremos una tabla con los tipos de datos que maneja Arduino y la cantidad de números decimales que podemos guardar en cada uno.
| Tipo | Tamaño | Rango |
|---|---|---|
| int | 2 byte | -32.768 a 32.767 |
| unsigned int | 2 byte | 0 a 65.535 |
| long | 4 byte | -2.147.483.648 a 2.147.483.647 |
| unsigned byte | 4 byte | 0 a 4.294.967.295 |
De esta tabla podemos ver que cada tipo de dato tiene un rango en el que podemos almacenar datos, algunos mas grandes que otros e incluso algunos manejan datos negativos y otros no. Por ejemplo si fueramos a almacenar los datos del encoder en un tipoint el encoder solo podría llegar incrementando hasta 32.767 y si fuese a incrementar una unidad más la variable pasaría a -32.768. Es como si tuvieramos un candado de clave, si giramos el cilindro solo podemos ir de 0 - 9 y volver a su punto inicial.
esta carácteristica nos pone una limitante, ¿que tanto planeamos que incremente nuestro encoder? además ¿necesitamos usar números negativos?
Para el caso de Pong podemos trabajar con un entero "int" por que realmente solo necesitamos que cuente de 0 a 30 o de 0 a 50, estos valores pueden ser para ti o para mi diferentes pero en cualquier caso no serán muy grandes.
Otros requerimientos que puedan surgir los podemos obtener respondiendo las siguientes preguntas
- ¿puede el usuario definir que puertos de la placa va a usar?
- ¿puede el usuario definir sus propios límites?
- ¿puede el usuario reiniciar los valores a uno por defecto?
- ¿que sucede cuando el encoder llega a un límite?
- ¿puede el usuario usar interrupciones (interrupts)?
Creando la librería
Con la información del paso anterior ya podemos empezar a crear la librería.
Las librerías en C++ estan divididas en dos archivos uno con extesion .h y otro con extension .cpp.
En el archivo con extension .h define una plantilla de como será el programa, y el archivo .cpp define la lógica del programa.
Veamos como esta definido el fichero ChineseEncoder.h que usamos en el proyecto de "Pong"
Disclaimer: he nombrado la librería como ChineseEncoder dado que usé un encoder comprado por aliexpress, además ya existen múltiples librerías para encoder y es necesario que nuestra librería tenga un nombre único
#ifndef ENCODER_LIB_H
#define ENCODER_LIB_H
#include
class ChineseEncoder
{
public:
ChineseEncoder(int intA, int intB, int reset);
void setup();
void read();
void setRange(int a, int b);
int getLowerBound();
int getUpperBound();
int getPosition();
private:
int _intA;
int _intB;
int _reset;
int _intARead;
int _intBRead;
int _previntA;
int _previntB;
int _position;
int _upperLimit;
int _lowerLimit;
};
#endif // ENCODER_LIB_H
Como podemos ver el archivo no contiene lógica alguna. pero si definiciones. De aquí podemos ver que se definen las funciones en un ambito público
- setup()
- read()
- setRange()
- getLowerBound()
- getUpperBound()
- getPosition()
y tambien tenemos diversas variables de ambito privado
lo que se encuentra dentro del ambito público es todo lo que el usuario final de la librería podrá utilizar. Y lo que se encuentra dentro del ambito privado estará disponible únicamente para la librería.
Hay una función especial ChineseEncoder que lleva el mismo nombre de la clase, y esto se hace así para definir que esta función es la que se usará cuando se quiera crear una "instancia". si no te suena familiar el termino instancia, no te preocupes. lo que quiero decir con esto es que esta será la función principal con el que crearemos la representacion de nuestro encoder dentro de arduino. La instancia se ve así
ChineseEncoder encoder(8,9,10);
Ya con la instancia encoder podremos acceder a los diversos métodos que expone la librería, es importante ver que esta instancia recive unos parametros, veremos luego en la implementación que estos valores corresponden a los pines de Arduino a los que vamos a conectar el encoder.
Finalmente, si te preguntas para que son las lineas que te muestro a continuación te diré que de momento no entraremos en ese terreno. pero si que es importante que estén definidas y por convencion llevan el mismo nombre del archivo pero todo mayusculas y separado por _
#ifndef ENCODER_LIB_H
#define ENCODER_LIB_H
#endif
Con nuestra "plantilla" definida procederemos a implementar la lógica, que si lo comparamos con el código desarrollado en Pong parte 2 veremos que es muy similar, pero con algunos extras
#include "ChineseEncoder.h"
ChineseEncoder::ChineseEncoder(int intA, int intB, int reset){
_intA = intA;
_intB = intB;
_reset = reset;
_intARead = 1;
_intBRead = 1;
_previntA = _intA;
_previntB = _intB;
_position = 0;
_upperLimit = 255;
_lowerLimit = 0;
}
void ChineseEncoder::setup(){
pinMode(_intA, INPUT);
pinMode(_intB, INPUT);
pinMode(_reset, INPUT);
}
void ChineseEncoder::read(){
_intARead = digitalRead(_intA);
_intBRead = digitalRead(_intB);
if(digitalRead(_reset) == LOW){
_position = 0;
}
if(
((_previntA == 0 && _previntB == 1 && _intARead == 0 && _intBRead == 0)
|(_previntA == 0 && _previntB == 0 && _intARead == 1 && _intBRead == 0)
|(_previntA == 1 && _previntB == 0 && _intARead == 1 && _intBRead == 1)
|(_previntA == 1 && _previntB == 1 && _intARead == 0 && _intBRead == 1))
&& _position < _upperLimit
) {
_position++;
}
if(
((_previntA == 0 && _previntB == 0 && _intARead == 0 && _intBRead == 1)
|(_previntA == 0 && _previntB == 1 && _intARead == 1 && _intBRead == 1)
|(_previntA == 1 && _previntB == 1 && _intARead == 1 && _intBRead == 0)
|(_previntA == 1 && _previntB == 0 && _intARead == 0 && _intBRead == 0))
&& _position > _lowerLimit
) {
_position--;
}
_previntA = _intARead;
_previntB = _intBRead;
}
int ChineseEncoder::getPosition(){
return _position;
}
void ChineseEncoder::setRange(int a, int b){
_lowerLimit = a;
_upperLimit= b;
}
int ChineseEncoder::getLowerBound() {
return _lowerLimit;
}
int ChineseEncoder::getUpperBound() {
return _upperLimit;
}
Del código anterior podemos observar que la lógica principal para contar los pasos e identificar el sentido de giro esta dentro de la función "read". Además hay otras funciones auxiliares como getPosition, setRange getUpperBound o getLowerBound. las funcionalidades aquí descritas han sido diseñadas para mi caso particular de uso, he ahí una de las ventajas de crear nuestra propia librería.
Como es de esperarse esta librería se pude enriquecer agregando más funcionalidades, por ejemplo cambiar la lectura digital por interrupts o darle la posibilidad al usuario de escoger si desea tener "overflow" o no. Otra posible funcionalidad podria ser que el botón de reset no se comparta de ese modo sino que envíe una señal para pausar el juego.
como podrás ver el crear tu librería te da control de controlar tus dispositivos según tus necesidades o incluso, si no quieres crear una de cero, puedes tomar cualquier librería de código abierto para modificarla del modo que cumpla con tus requerimientos.
Como siempre muchas gracias por haberme acompañado hasta aquí y nos vemos en la próxima entrega.