Archivo de la categoría: Librerías

Librerías Arduino

Manejar Librerías Arduino

Las librerías son trozos de código hechas por terceros que usamos en nuestro sketch. Esto nos facilita mucho la programación y permite la abstracción haciendo que nuestro programa sea más sencillo de hacer y de entender. En este apartado también veremos cómo escribir o modificar librerías.

Disponemos de infinidad de librerías a nuestra disposición para facilitarnos el trabajo, todas ellas son open source y disponemos de su código.

Las librerías normalmente incluyen los siguientes archivos comprimidos en un archivo ZIP o dentro de un directorio. Estas siempre contienen:

  • Un archivo .cpp (código de C++)
  • Un archivo .h o encabezado de C, que contiene las propiedades y métodos o funciones de la librería.
  • Un archivo Keywords.txt, que contiene las palabras clave que se resaltan en el IDE (opcional).
  • Muy posiblemente la librería incluye un archivo readme con información adicional de lo que hace y con instrucciones de como usarla.
  • Directorio denominado examples con varios sketchs de ejemplo que nos ayudará a entender cómo usar la librería (opcional).

La distintas formas de instalar librerías se puede ver en https://aprendiendoarduino.wordpress.com/2016/06/27/librerias-3/

Para usar una librería que acabamos de instalar, lo que hay que hacer es leer la documentación de esa librería si es que está disponible y luego leer y probar los ejemplos que dispone la librerías.

Pero ahora que ya sabemos manejar clases y objetos, si queremos entrar a fondo en una librería para saber usarla, podemos abrir el fichero del encabezado (.h) y ver las propiedades y métodos, ver si hereda de otra librería, etc… y luego incluso ver cómo funciona la propia librería leyendo el código en el fichero .cpp e incluso si nos atrevemos añadir nuevos métodos o modificar un método que nos interese.

Problemas comunes con las librerías: https://learn.adafruit.com/adafruit-all-about-arduino-libraries-install-use/common-library-problems

Con el nuevo gestor de librerías es mucho más fácil gestionar las librerías instaladas así como su versión y actualización, algo que antes había que hacer manualmente

El listado de librerías se actualiza de internet y se guarda en:

  • C:\Users\usuario\AppData\Local\Arduino15\library_index.json

Las placas se actualiza de internet (del gestor de tarjetas) y se guarda en:

  • C:\Users\usuario\AppData\Local\Arduino15\package_index.json

El fichero library_index.json se actualiza al abrir el IDE y en caso de tener problemas, se puede borrar y se genera de nuevo al abrir el IDE.

Es importante darse de alta en github puesto que nos permite estar al día de las novedades en las librerías y recibir notificaciones cada vez que haya una modificación en las mismas. Además podemos fácilmente actualizar nuestro repositorio de librerías con el comando PULL de git y poner una u otra versión en nuestro repositorio con el comando checkout.

Librería Time y Timezone

Como ejemplo para aprender a usar una librería, veamos las librerías Time y Timezone.

Para aprender a manejarlas, simplemente leer el fichero readme que viene en el repositorio de github y luego los ejemplos.

Algunas funciones librería Time:

  • hour();            // the hour now  (0-23)
  • minute();          // the minute now (0-59)
  • second();          // the second now (0-59)
  • day();             // the day now (1-31)
  • weekday();         // day of the week, Sunday is day 0
  • month();           // the month now (1-12)
  • year();            // the full four digit year: (2009, 2010 etc)
  • hourFormat12();    // the hour now in 12 hour format
  • isAM();            // returns true if time now is AM
  • isPM();            // returns true if time now is PM
  • now();             // returns the current time as seconds since Jan 1 1970
  • setSyncProvider(getTimeFunction);  // set the external time provider
  • setSyncInterval(interval);         // set the number of seconds between re-sync

Algunas funciones librería Timezone:

  • time_t toLocal(time_t utc); Converts the given UTC time to local time, standard or daylight as appropriate.
  • TimeChangeRule myRule = {abbrev, week, dow, month, hour, offset};
    • abbrev is a character string abbreviation for the time zone; it must be no longer than five characters.
    • week is the week of the month that the rule starts.
    • dow is the day of the week that the rule starts.
    • hour is the hour in local time that the rule starts (0-23).
    • offset is the UTC offset in minutes for the time zone being defined.

Ejemplos:

  • TimeChangeRule usEDT = {“EDT”, Second, Sun, Mar, 2, -240};  //UTC – 4 hours
  • TimeChangeRule usEST = {“EST”, First, Sun, Nov, 2, -300};   //UTC – 5 hours

Una vez visto el manual de la librería, veamos cómo está escrita la librería Time de Arduino que nos ofrece funcionalidades para mantener la fecha y hora con un hardware externo o sin él. Nos permite obtener la fecha y hora como: segundo, minuto, hora, día, mes y año. También da el tiempo con el tipo de dato del estándar C time_t, siendo sencillo el cálculo del tiempo transcurrido.

Existe una nueva versión de la librería Time cuyo código está derivado librería DateTime del Arduino Playground pero está actualizada para ofrecer una API que es más flexible y fácil de usar.

Más información de la librería time: http://www.prometec.net/time-arduino/

La librería Time no requiere de ningún HW especial. Internamente depende de la función millis() de Arduino para llevar un registro del tiempo transcurrido. El tiempo se puede sincronizar con varios tipos de HW como GPS, NTP, RTC, etc…

Veamos cómo está escrita la librería. El código lo encontramos en https://github.com/PaulStoffregen/Time. En el readme.txt explica con detalle el uso de la librería. Explica las funciones disponibles, los ejemplos dentro del directorio examples y el funcionamiento interno de la librería. también hay un fichero keywords.txt con las palabras clave que se van a resaltar en el IDE.

También pueden verse los ficheros:

  • library.json – Información de la librería
  • library.properties – datos de la librería y clasificación que sale en el Gestor de Librerías

Los ficheros que componen la librería son:

  • Time.h — Simplemente incluye a TimeLib.h, se hace por temas de compatibilidad
  • TimeLib.h — Fichero de cabecera de la libreria Time.
  • Time.cpp — código de la librería time
  • DateStrings.cpp — contiene los strings de las fecha en inglés, pero se podría hacer una versión modificada para español.

Fichero TimeLib.h:

  • #ifdef __cplusplus permite a la sección del programa que está dentro ser compilado solo si la macro especificada como parámetro de ha definido. en este caso __cplusplus es una macro definida cuando el compilador de c++ está en uso. Esto se usa para comprobar si una cabecera está compilada bajo C o C++
  • #include <inttypes.h> incluye la cabecera de inttypes.h que está en C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include, cuyo propósito es proveer un conjunto de tipos de enteros cuyas definiciones sean consistentes en todas las máquinas e independientes de sistemas operativos. Define una serie de macros para usar con printf y scanf, así como funciones para trabajar con el tipo de dato intmax_t que es un entero con el máximo de anchura soportado. Ver http://www.cplusplus.com/reference/cstdint/
  • Se define el tipo de dato http://www.cplusplus.com/reference/ctime/time_t/ del estandar C++ que no dispone el avr libc y se “simula” la librería time.h de C++ que no tiene implementada AVR.
  • Se definen una serie de variables enumeradas con typedef enum y typedef struct
  • Se definen unas macros para convertir los años de tmYear a un valor de 4 dígitos
  • El valor de domingo es 1 como se puede ver en:
    typedef enum {dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday} timeDayOfWeek_t; y como lo define en la macro #define dayOfWeek(_time_)
  • Se definen luego las funciones y se puede ver que hay sobrecarga en las funciones. Por ejemplo en la función setTime que puede ajustar el tiempo pasando un time_t o los datos de hora, minuto, segundo, día, mes y año.

Ficheros Time.cpp y DateStrings.cpp:

  • La función más importante es now() que calcula el numero de segundo transcurridos desde la última llamada a now()
  • En la función now() es donde comprueba si toca sincronizar de nuevo la hora con (nextSyncTime <= sysTime)
  • En la variable sysTime se guarda la hora, es una variable privada.
  • Se definen primero las funciones públicas en Time.cpp y luego las funciones de bajo nivel.
  • Para sincronizar la hora externamente se definen las funciones:
    • setSyncProvider(getTimeFunction);  // set the external time provider
    • setSyncInterval(interval);         // set the number of seconds between re-sync
  • En DateStrings.cpp está la definición de los strings de fechas. Los strings los guarda en la memoria flash para no ocupar memoria RAM. Para ello usa PROGMEM y si no es para AVR, define el PROGMEM y las funciones de progmem de AVR.
  • Se modifican las funciones que devuelven el string del día y del mes pasando el entero con el número de día o mes.

Adicionalmente tenemos otras librería:

La librería Time no dispone de ajuste de hora por zona horaria (time zone o TZ) ni ajuste de DST (Daylight Saving Time): https://en.wikipedia.org/wiki/Daylight_saving_time. Se podría añadir estas funcionalidades a la librería Time o se puede crear una nueva librería que implemente estas funcionalidades y haga uso de la librería Time.

Para solucionar esta carencia disponemos de la librería Time zone https://github.com/JChristensen/Timezone. Esta librería no es accesible desde el gestor de librerías, por lo que habrá que hacer una instalación manual de la misma.

La librería Timezone está diseñada para trabajar en conjunto con la librería Time y debe ser referenciada en el sketch que se use con timezone. Esta librería convierte el Universal Coordinated Time (UTC) a la hora local correcta incluso si hay Daylight Saving Time (DST). La hora puede obtenerse de un GPS, NTP server o un RTC.

La librería Timezone implementa dos objetos para facilitar la conversión de zona:

  • Un objeto TimeChangeRule que describe cuando la hora local cambia de hora estándar a hora de verano y viceversa.
  • Un objeto Timezone que usa TimeChangeRule para hacer las conversiones y las funciones relacionadas. También puede escribir y leer de la EEPROM el TimeChangeRule. Es posible implementar varias zonas horarias definiendo varios objetos Timezone.

Para establecer la TimeChangeRule se hace mediante dos reglas por zona, una para definir cuando comienza el horario de verano y otra cuando comienza el horario estándar. En España el cambio de horario se produce el último domingo de octubre a las 3.00 y el último domingo de marzo a las 2.00 (https://es.wikipedia.org/wiki/Horario_de_verano_en_el_mundo)

Definir un Timezone:

  • TimeChangeRule SDT = {“SDT”, Last, Sun, Mar, 2, 60};  //Spain Daylight Time UTC + 0 hours
    TimeChangeRule SST = {“SST”, Last, Sun, Oct, 3, 180};   //Spain Standard Time UTC + 1 hours
  • Timezone spainTZ(SDT,SST)

Métodos asociados:

  • toLocal(time_t utc) — Convierte la hora obtenida en UTC a hora local

Ficheros librería:

  • Timezone.h
    • Implementa compatibilidad con IDEs Arduino anteriores a version 1.x comprobando la versión de ARDUINO
    • Incluye la librería Time
    • Define las constantes de los nombres con enum y el número de inicio. Define como día 1 el Domingo en lugar del Lunes
    • TimeChangeRule es un struct con el nombre (abbrev), 4 bytes para: semana, día de la semana, mes y hora y un entero para el offset de la zona horaria
    • Clase Timezone con dos constructores, 7 métodos públicos, 2 métodos privados y 6 variables privadas.
  • Timezone.cpp
    • __AVR__ es una macro que indica si el compilador puede usar procesadores AVR y en ese caso uso la librería EEPROM
    • Puedo construir un objeto Timezone con el par de reglas DST o si la tengo guarda en la EEPROM, pasando la dirección donde la tengo guardada.
    • calcTimeChanges() calcula el nuevo dst start y std start al llamarlo.
    • Al usar toLocal() ya autocalculo las fechas de dst start y std start. Ver ejemplo https://github.com/JChristensen/Timezone/blob/master/examples/Clock/Clock.pde

Más información del uso de la librería Timezone en:

NOTA: la librería timezone no soporta leapsecond https://es.wikipedia.org/wiki/Segundo_intercalar pero podría implementarse en la librería. Ver http://stackoverflow.com/questions/9168970/arduino-leap-second-clock

Crear Librerias Arduino

Las librerías son trozos de código hechos por terceros que usamos en nuestro sketch. Esto nos facilita mucho la programación y permite la abstracción haciendo que nuestro programa sea más sencillo de hacer y de entender. En este apartado veremos cómo escribir o modificar librerías.

Librerías Arduino: https://www.arduino.cc/en/Main/Libraries

El IDE de Arduino incluye una serie de librerías ya instaladas: https://www.arduino.cc/en/Reference/Libraries

Listado de librerías del playground de Arduino: http://playground.arduino.cc/Main/LibraryList, pero existen otras muchas librerías creadas por usuarios o por los fabricantes de HW para facilitar el uso de esos dispositivos con Arduino.

Este tutorial explica como crear una librería: http://arduino.cc/en/Hacking/LibraryTutorial. Explica cómo convertir la función morse en en una librería.

Ejemplo morse.ino

int pin = 13;

void setup()
{
  pinMode(pin, OUTPUT);
}

void loop()
{
  dot(); dot(); dot();
  dash(); dash(); dash();
  dot(); dot(); dot();
  delay(3000);
}

void dot()
{
  digitalWrite(pin, HIGH);
  delay(250);
  digitalWrite(pin, LOW);
  delay(250);
}

void dash()
{
  digitalWrite(pin, HIGH);
  delay(1000);
  digitalWrite(pin, LOW);
  delay(250);
}

Para convertir en una librería de código morse, vemos que hay dos funciones dot() y dash() para iluminar un led durante 250 ms y 1 segundo y  una variable que es ledPin que determina que pin usar. Este es un estilo de programación clásico usando funciones en lugar de objetos.

Para una librería se necesitan al menos dos ficheros:

  • Un fichero de cabecera con la extensión .h. Este fichero tiene las definiciones de la librería, básicamente un listado de todo lo que hay dentro de la librería
  • Un fichero fuente con la extensión .cpp. Este fichero el que contiene el código

En este caso la librería se va a llamar morse y generamos un fichero de cabecera llamado morse.h.

Veamos el código de morse.h donde se define la clase Morse donde tiene una línea por cada función o método y también una línea por cada variable o propiedad de la clase.

class Morse
{
  public:
    Morse(int pin);	//constructor
    void dot();
    void dash();
  private:
    int _pin;
};

Una clase es una colección de funciones (métodos) y variables (propiedades) que se guardan todas juntas en un solo lugar. Las funciones pueden ser públicas (public), es decir, pueden llamarse por quien usa la librería o privadas (private), es decir, que solo pueden llamarse desde dentro de la propia clase. Todas las clases tienen una función llamada constructor, que es usada para crear una instancia de la clase. El constructor tiene el mismo nombre que la clase y no tiene tipo de variable de devolución.

En el fichero de cabecera de una librería es necesario la declaración #include que de acceso a los tipos y constantes estándar del lenguaje de Arduino (esto se añade automáticamente en los sketches pero no a las librerías). Esta declaración debe ponerse antes de la definición de la clase. La declaración debe ser:

  • Versión IDE 1.x: #include “Arduino.h”
  • Versión IDE 0.x: #include “WProgram.h”

También es común poner todo el fichero de cabecera entre estas instrucciones:

#ifndef Morse_h
#define Morse_h
// the #include statment and code go here…
#endif

Esto evita problemas si alguien accidentalmente incluye dos veces la librería, lo que provocaría un error de compilación. A esto se llama guardián de inclusión múltiple o include guard

// Guardián de inclusión múltiple
#ifndef FICHERO_YA_INCLUIDO
#define FICHERO_YA_INCLUIDO

Así se evita que un compilador poco sofisticado abra otra vez el mismo conjunto de ficheros cuando se incluye un fichero de cabecera dos o más veces. Puede darse el caso de no poner las inclusiones en el inicio de un fichero.

La directiva #include existe en dos versiones. En una se pone el nombre de fichero entre comillas, en la otra entre paréntesis angulares (el signo menor y mayor como “comillas”).

#include “fichero_con_comillas.h”
#include <fichero_entre_menor_y_mayor.h>

La versión con los paréntesis angulares busca los ficheros en todos los directorios que se han especificado en la llamada al compilador – normalmente con la opción “-I”. Estos directorios se suelen rastrear por el fichero incluido en el orden en que aparecen en la línea de comando.

Cuando se incluye un fichero entre comillas, entonces el compilador busca este fichero primero en el mismo directorio que el fichero actualmente compilado y después en los demás directorios. Es decir, la versión con comillas se diferencia de la versión con paréntesis angulares únicamente por buscar primero en el directorio del fichero compilado. Tras no encontrarlo ahí actaa igual.

Cuando se crea una librería se debe documentar poniendo un comentario al comienzo de la librerías con el nombre, breve descripción, quien la ha escrito, fecha, licencia, etc…

El fichero de cabecera queda:

/*
  Morse.h - Library for flashing Morse code.
  Created by David A. Mellis, November 2, 2007.
  Released into the public domain.
*/
#ifndef Morse_h
#define Morse_h

#include "Arduino.h"

class Morse
{
  public:
    Morse(int pin);
    void dot();
    void dash();
  private:
    int _pin;
};

#endif

Una vez hecho el fichero de cabecera hay que codificar el fichero fuente Morse.cpp

Primero deben ponerse las declaraciones, esto da al resto de código acceso a las funciones estándar de Arduino y a las definiciones del fichero de cabecera:

#include “Arduino.h”
#include “Morse.h”

Lo siguiente es poner el constructor de la clase. Esto define que ocurre cuando se crea una instancia de la clase. En este caso el usuario debe especificar cual es el pin que se va a usar. Configuramos el pin como salida y los guardamos en una variable privada para usarlo desde otras funciones.

Morse::Morse(int pin)
{
  pinMode(pin, OUTPUT);
  _pin = pin;
}

El código “Morse::” antes del nombre de la función indica que la función es parte de la clase Morse. Esto se ve en todas las funciones de la clase. La variable llamada “_pin” es una variable privada tal y como se ha definido en el fichero de cabecera y se pone el simbolo “_” delante por convención para indicar que es privada y para diferenciarlo del argumento de la función, pero puede llamarse de cualquier forma mientras coincida con la definición en el fichero de cabecera.

Después de definir el constructor, se deben definir las funciones o métodos de la clase. Son las funciones que se habían definido anteriormente en el sketch:

void Morse::dot()
{
  digitalWrite(_pin, HIGH);
  delay(250);
  digitalWrite(_pin, LOW);
  delay(250);  
}

void Morse::dash()
{
  digitalWrite(_pin, HIGH);
  delay(1000);
  digitalWrite(_pin, LOW);
  delay(250);
}

También es habitual añadir el comentario del fichero al principio del fichero. El fichero Morse.cpp queda de la siguiente forma:

/*
  Morse.cpp - Library for flashing Morse code.
  Created by David A. Mellis, November 2, 2007.
  Released into the public domain.
*/

#include "Arduino.h"
#include "Morse.h"

Morse::Morse(int pin)
{
  pinMode(pin, OUTPUT);
  _pin = pin;
}

void Morse::dot()
{
  digitalWrite(_pin, HIGH);
  delay(250);
  digitalWrite(_pin, LOW);
  delay(250);  
}

void Morse::dash()
{
  digitalWrite(_pin, HIGH);
  delay(1000);
  digitalWrite(_pin, LOW);
  delay(250);
}

De esta forma ya tenemos una librería completa. Ahora para incluirla en nuestro IDE debemos crear un directorio Morse dentro del subdirectorio “libraries” del directorio de nuestro entorno de trabajo definido en las propiedades del IDE. Copiar Morse.h y Morse.cpp dentro del directorio Morse y abrir o reiniciar el IDE de Arduino. A partir de este momento veremos nuestra librería disponible en el IDE y podremos incluirla en los sketches con la declaración #include <Morse.h>. La librería será compilada por los sketches que la usen.

El anterior sketch quedaría ahora sustituido por:

#include <Morse.h>

Morse morse(13);

void setup()
{
}

void loop()
{
  morse.dot(); morse.dot(); morse.dot();
  morse.dash(); morse.dash(); morse.dash();
  morse.dot(); morse.dot(); morse.dot();
  delay(3000);
}

Podemos ver que primero se llama a la declaración de la librería Morse. Esto hace que la librería esté disponible en el sketch y lo incluye en el código enviado a la placa Arduino, lo que hace que si la librería es muy pesada, ocupe mucha más memoria nuestro sketch y si no voy a usar una librería es mejor no incluirla para ahorrar espacio.

También observamos que creamos una instancia de la clase Morse llamada “morse”. Al ejecutar esta línea el constructor de la clase es llamado pasando un argumento, creando así el objeto “morse” en nuestro sketch. Luego podemos llamar a los métodos dot() y dash() precedidos del prefijo morse del nombre del objeto.

Es posible tener múltiples instancias de la clase Morse, cada una un pin diferente guardado en la variable privada “_pin”.

Si creamos una librería es conveniente crear el fichero keywords.txt dentro del directorio Morse. De esta forma conseguiremos resaltar las palabras clave que definamos en el fichero keywords. En cada línea del fichero keywords.txt se indica el nombre de la palabra clave y seguido por un tabulador, el tipo de keyword.

  • Las clases deben ser del tipo KEYWORD1 que se resaltan en naranja.
  • Las funciones deben ser del tipo KEYWORD2 que se resaltan en marrón

Para que el fichero keywords.txt se aplique al IDE es necesario reiniciar el IDE.

El fichero keywords.txt quedaría:

Morse KEYWORD1
dash KEYWORD2
dot KEYWORD2

También es aconsejable ofrecer ejemplos de uso de la librería para que los posibles usuarios sepan usarla. Esto se hace creando un directorio “examples” dentro del directorio Morse y añadir en el subdirectorio los sketches de ejemplos que serán visibles desde el IDE.

La librerías morse con los ejemplos y el fichero keywords.txt se puede descargar en: http://www.arduino.cc/en/uploads/Hacking/Morse.zip

Para más información sobre la creación de librerías con un buen “estilo Arduino”, ver la Guía de Estilo de API. Guia de estilo para escribir librerías: http://arduino.cc/en/Reference/APIStyleGuide

Ver ejemplos simples de librerías:

Más información:

Guía de estilo para escribir una librería de Arduino

Para facilitar el entendimiento de las librerías o APIs de Arduino y hacer que el entorno de Arduino sea más homogéneo, desde arduino.cc dan unas pautas para escribir librerías al “estilo Arduino”. Algunas de estas prácticas van en contra de la programación programación profesional, pero esto hace posible a los principiantes comenzar a trabajar de forma sencilla con Arduino.

  • Se amable con el usuario. Tener una modelo claro del concepto que va tratar la librería y las funciones que se van a implementar.
  • Organizar las funciones pública alrededor de los datos y funcionalidades que el usuario quiere. Frecuentemente el conjunto de comandos para un módulo electrónico son demasiado complicado, pero podemos reorganizar las funciones desde un punto de vista de uso a alto nivel. Pensar en lo que hace la mayoría de las personas y organizar la API en función de ese uso. Un ejemplo es la librería https://github.com/adafruit/Adafruit-BMP085-Library. La función readPressure() hace todos los pasos para obtener la presión final, siendo transparente para el usuario todos los complejos pasos intermedios. Esto abstrae al usuario no solo de los comandos I2C necesarios, sino de de los cálculos intermedios.
  • Usar palabras claras para escribir la librerías. Usar nombres de funciones y variables claros que expresen claramente lo que son o hacen y no usar términos técnicos.
  • Evitar palabras que tengan diferente significado para el público en general.
  • Documentar todo. Al escribir ejemplos usar esta guía: http://www.arduino.cc/en/Reference/StyleGuide
  • Usar las librerías del core de Arduino y su estilo:
    • Usar read() para leer entradas y write() para escribir salidas
    • Usar las librerías Stream.h y print.h cuando se estén manejando byte streams. Si no es posible al menos intentar usar su modelo de API.
    • Para aplicaciones de redes, usar las librerías de Client y Server como base.
    • Usar begin() para inicializar unas instancia de una librería, normalmente con unos parámetros de configuración. Usar end() para finalizarla.
  • Usar funciones camel case, no con guión bajo. Por ejemplo analogRead en lugar de analog_read. Esto es una adopción de processing por motivos de facilidad la lectura.
  • No usar nombre de constantes largos que son difíciles de leer.
  • Evitar usar argumentos booleanos. En su lugar es preferible ofrecer dos funciones diferentes con nombres que describan la diferencia entre ellas.
  • No asumir conocimiento de punteros. Los principiantes en C suelen encontrar dificultades con el uso de & y *, en la medida de lo posible tratar de evitar el uso de punteros.
  • Al usar la comunicación serie de cualquier tipo, permitir al usuario especificar el objeto de stream en lugar de solo “Serial”, esto hará la librería compatible con los los puertos del Arduino Mega y Due y también poder usar interfaces alternativos como el SoftwareSerial. ver el uso de begin(Stream &serial) en las librerías https://github.com/firmata/arduino y https://github.com/andrewrapp/xbee-arduino
  • Cuando se escriba una librería que ofrezca comunicación byte-stream heredada de la la clase Stream de Arduino, de forma que la librería pueda ser usada por otras librerías que acepte objetos Stream: si es posible, el método read() inmediatamente accede a los datos del buffer sin esperar a que lleguen más datos y si es posible el método write() debe guardar los datos al buffer de transmisión, pero debe esperar si el buffer no tiene suficiente espacio para guardar inmediatamente todo los datos salientes. La función yield() debe llamarse mientras se espera. De esta forma mantenemos compatibilidad con el restos de librerías Stream.

Unos ejemplos de librerías que definen realmente bien las funciones de alto nivel son:

Viendo los métodos públicos que definen es muy fácil entender que es lo que hacen.

Otro buen ejemplo de abstracción de la librerías wire (I2C) se puede ver en: https://github.com/adafruit/RTClib donde con una serie de métodos obtengo los datos del RTC siendo totalmente transparente para el usuario el uso de la librería wire.

Es frecuente que nos encontremos que algunas librerías se quedan cortas para algunas funcionalidades avanzadas, puesto que como se ha visto en la guía de estilo se tiende a simplificar. Un ejemplo de esto es la librería ethernet donde muchas de las funcionalidades del Wiznet 5100 no están implementadas en la librería, pero esto no impide que nosotros podamos añadirlas.

Más información: https://www.arduino.cc/en/Reference/APIStyleGuide

Modificar librerías

Es habitual que al usar una librería necesitemos una propiedad o un método que no esté implementado o queramos modificar el comportamiento, para ello podemos modificar localmente la librería que tengamos instalada en nuestro IDE.

En el caso que queramos colaborar en una librería hecha, podemos hacer un fork de esa librería en nuestra cuenta de github y añadir una nueva funcionalidad o corregir ese fallo e incluso podemos hacer un pull request para que se añada en la librería original nuestra modificación. También podemos tener nuestra propia librería como un fork de la librería original.

Un pull request es una petición que el propietario de un fork de un repositorio hace al propietario del repositorio original para que este último incorpore los commits que están en el fork. Explicación de pull request: http://aprendegit.com/que-es-un-pull-request/

Para modificar la librería time y añadir una funcionalidad, lo primero es hacer un fork del código https://github.com/PaulStoffregen/Time en tu repositorio, en mi caso en https://github.com/jecrespo/Time y luego clonarlo en el ordenador para modificarlo.

Ejercicios Librerías

Modificar Librería Ethernet

Ver como averiguar la IP de quien se ha conectado y  modificar librería ethernet para implementar un método que de devuelva la IP de origen.

Librería ethernet modificada en https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Otros/Ethernet%20Modificada

Crear una librería para NTP

Ejercicio: A partir del ejemplo de Arduino para obtener la hora de un servidor NTP https://www.arduino.cc/en/Tutorial/UdpNtpClient, crear una librería para obtener de forma sencilla la hora con una llamada a la función readNTP().

Ver Ejercicio39-NTP para obtener fecha y hora en Arduino sin necesidad de un RTC usando NTP: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio39-NTP

Basado en:

Protocolo:

Las firmas de tiempo que se usan en NTP, son de 32 bits indicando la parte entera en segundos desde  el 1 de Enero de 1900, y una parte fraccionaria también de 32 bits. Por ello la resolución teórica de NTP sería de 232 segundos =136 años, y una resolución teórica de 2E-32 segundos, o sea 0,233 nanosegundos.

Nota: Ojo al llamar al constructor de algo p.e. ethernet sin hacer el begin ethernet antes.

Solución: https://github.com/jecrespo/simpleNTP  

Funciones definidas por usuario

Una función es un bloque de código que tiene un nombre y un conjunto de instrucciones que son ejecutadas cuando se llama a la función. Son funciones setup() y loop() de las que ya se ha hablado.

Las funciones de usuario pueden ser escritas para realizar tareas repetitivas y para reducir el tamaño de un programa. Segmentar el código en funciones permite crear piezas de código que hacen una determinada tarea y volver al área del código desde la que han sido llamadas.

Las funciones se declaran asociadas a un tipo de valor. Este valor será el que devolverá la función, por ejemplo ‘int’ se utilizará cuando la función devuelva un dato numérico de tipo entero. Si la función no devuelve ningún valor entonces se colocará delante la palabra “void”, que significa “función vacía”

Sintaxis:

tipo nombreFunción (parámetros) {

  instrucciones;

}

Para llamar a una función, simplemente:

nombreFunción(parámetros);

En una función que devuelve un valor siempre debe tener la instrucción Return, este termina una función y devuelve un valor a quien ha llamado a la función: http://arduino.cc/en/Reference/Return

Ventajas del uso de funciones:

  • Ayuda a tener organizado el código.
  • Una función codifica una tarea en un lugar de nuestro sketch, así que la función solo debe ser pensada una sola vez.
  • Reduce la probabilidad de errores al modificar el código.
  • Hacen que el tamaño del sketch sea menor porque el código de la función es reutilizado.
  • Facilita la lectura del código.
  • Hace más sencillo reutilizar código en otros sketches.

Más información: http://arduino.cc/en/Reference/FunctionDeclaration

Nombres de funciones

Generalmente los nombres de las funciones deben ser en minúscula, con las palabras separadas por un guión bajo, aplicándose éstos tanto como sea necesario para mejorar la legibilidad.

“mixedCase” (primera palabra en minúscula) es aceptado únicamente en contextos en donde éste es el estilo predominante con el objetivo de mantener la compatibilidad con versiones anteriores.

En el caso de las clases, los nombres deben utilizar la convención “CapWords” (palabras que comienzan con mayúsculas).

Las funciones en Arduino pueden estar dentro del mismo fichero .ino o en otro fichero con extensión .ino dentro del directorio del sketch.

Paso por Valor y Paso por Referencia

Hasta ahora siempre hemos declarado los parámetros de nuestras funciones del mismo modo. Sin embargo, éste no es el único modo que existe para pasar parámetros.

La forma en que hemos declarado y pasado los parámetros de las funciones hasta ahora es la que normalmente se conoce como “por valor”. Esto quiere decir que cuando el control pasa a la función, los valores de los parámetros en la llamada se copian a “objetos” locales de la función, estos “objetos” son de hecho los propios parámetros.

int funcion(int n, int m) { 
  n = n + 2; 
  m = m - 5; 
  return n+m; 
}

int a = 10;
int b = 20;
Serial.println(funcion(a,b));
Serial.println(funcion(10,20));

Empezamos haciendo a = 10 y b = 20, después llamamos a la función “funcion” con las objetos a y b como parámetros. Dentro de “funcion” esos parámetros se llaman n y m, y sus valores son modificados. Sin embargo al retornar al programa que lo llama, a y b conservan sus valores originales. Lo que pasamos no son los objetos a y b, sino que copiamos sus valores a los objetos n y m. Es lo mismo que hacer funcion(10,20), cuando llamamos a la función con parámetros constantes. Si los parámetros por valor no funcionasen así, no sería posible llamar a una función con valores constantes o literales.

Las referencias sirven para definir “alias” o nombres alternativos para un mismo objeto. Para ello se usa el operador de referencia (&).

Por ejemplo:

int a;
int &r = a;
a = 10;
Serial.println(r);

En este ejemplo los identificadores a y r se refieren al mismo objeto, cualquier cambio en una de ellos se produce en el otro, ya que son, de hecho, el mismo objeto. El compilador mantiene una tabla en la que se hace corresponder una dirección de memoria para cada identificador de objeto. A cada nuevo objeto declarado se le reserva un espacio de memoria y se almacena su dirección. En el caso de las referencias, se omite ese paso, y se asigna la dirección de otro objeto que ya existía previamente. De ese modo, podemos tener varios identificadores que hacen referencia al mismo objeto, pero sin usar punteros.

Si queremos que los cambios realizados en los parámetros dentro de la función se conserven al retornar de la llamada, deberemos pasarlos por referencia. Esto se hace declarando los parámetros de la función como referencias a objetos. Por ejemplo:

int funcion(int &n, int &m) {
   n = n + 2; 
   m = m - 5; 
   return n+m;
}

int a = 10;
int b = 20;

Serial.println(funcion(a,b));
Serial.println("a = " + String(a) + " b = " + String(b));
//es ilegal pasar constantes como parámetros cuando estos son referencias
Serial.println(funcion(10,20));

En este caso, los objetos “a” y “b” tendrán valores distintos después de llamar a la función. Cualquier cambio de valor que realicemos en los parámetros dentro de la función, se hará también en los objetos referenciadas. Esto quiere decir que no podremos llamar a la función con parámetros constantes, ya que aunque es posible definir referencias a constantes, en este ejemplo, la función tiene como parámetros referencias a objetos variables. Y si bien es posible hacer un casting implícito de un objeto variable a uno constante, no es posible hacerlo en el sentido inverso. Un objeto constante no puede tratarse como objeto variable.

Una const reference es una referencia a que no permite cambiar la variable a través de esa referencia. Por ejemplo const int &r = a; en r tengo el valor de a pero no puedo cambiar el valor de a usando r.

No confundir este concepto con el modificador de variable static, que es utilizado para crear variables que solo son visibles dentro de una función, sin embargo, al contrario que las variables locales que se crean y destruyen cada vez que se llama a la función, las variables estáticas mantienen sus valores entre las llamadas a las funciones.

Más información:

Sobrecarga de Funciones

Hay operadores que tienen varios usos, como por ejemplo *, &, << o >>. Esto es lo que se conoce en C++ como sobrecarga de operadores. Con las funciones existe un mecanismo análogo, de hecho, en C++, los operadores no son sino un tipo especial de funciones, aunque eso sí, algo peculiares.

Así que en C++ podemos definir varias funciones con el mismo nombre, con la única condición de que el número y/o el tipo de los argumentos sean distintos. El compilador decide cuál de las versiones de la función usará después de analizar el número y el tipo de los parámetros. Si ninguna de las funciones se adapta a los parámetros indicados, se aplicarán las reglas implícitas de conversión de tipos.

Las ventajas son más evidentes cuando debemos hacer las mismas operaciones con objetos de diferentes tipos o con distinto número de objetos. También pueden usarse macros para esto, pero no siempre es posible usarlas, y además las macros tienen la desventaja de que se expanden siempre, y son difíciles de diseñar para funciones complejas. Sin embargo las funciones serán ejecutadas mediante llamadas, y por lo tanto sólo habrá una copia de cada una.

Ejemplo:

int mayor(int a, int b);
char mayor(char a, char b); 
float mayor(float a, float b);
int mayor(int a, int b, int c, int d);

int mayor(int a, int b) { 
   if(a > b) return a; else return b; 
}
 
char mayor(char a, char b) { 
   if(a > b) return a; else return b; 
}
 
float mayor(float a, float b) { 
   if(a > b) return a; else return b; 
}

int mayor(int a, int b, int c, int d) { 
   return mayor(mayor(a, b), mayor(c, d)); 
}

Las llamadas a funciones sobrecargadas se resuelven en la fase de compilación. Es el compilador el que decide qué versión de la función debe ser invocada, después de analizar, y en ciertos casos, tratar los argumentos pasados en la llamadas. A este proceso se le llama resolución de sobrecarga.

Tener en cuenta que el tipo de retorno de la función no se considera en la sobrecarga de funciones. Consideremos el caso en el que desea escribir una función que devuelve un número aleatorio, pero se necesita una versión que devolverá un entero, y otra versión que devolverá un doble.

int getRandomValue();
double getRandomValue();

Pero el compilador toma esto como un error. Estas dos funciones tienen los mismos parámetros (ninguno) y en consecuencia, la segunda getRandomValue () serán tratada como una redeclaración errónea de la primera. En consecuencia, tendrán que ser dado diferentes nombres a estas funciones.

Más información:

Sobrecarga de operadores:

Ámbito de las variables

Una variable puede ser declarada al inicio del programa antes de la parte de configuración setup(), a nivel local dentro de las funciones, y, a veces, dentro de un bloque, como para los bucles del tipo if.. for.., etc. En función del lugar de declaración de la variable así se determinará el ámbito de aplicación, o la capacidad de ciertas partes de un programa para hacer uso de ella.

Una variable global es aquella que puede ser vista y utilizada por cualquier función y estamento de un programa. Esta variable se declara al comienzo del programa, antes de setup().

Recordad que al declarar una variable global, está un espacio en memoria permanente en la zona de static data y el abuso de variables globales supone un uso ineficiente de la memoria.

Una variable local es aquella que se define dentro de una función o como parte de un bucle. Sólo es visible y sólo puede utilizarse dentro de la función en la que se declaró. Por lo tanto, es posible tener dos o más variables del mismo nombre en diferentes partes del mismo programa que pueden contener valores diferentes, pero no es una práctica aconsejable porque complica la lectura de código.

En el reference de Arduino hay una muy buena explicación del ámbito de las variables:http://arduino.cc/en/Reference/Scope

La variables estáticas solo se crean e inicializan la primera vez que la función es llamada. Ver ejemplo en: http://arduino.cc/en/Reference/Static

Más información:

Inline

Cuando usamos el nombre de una función, indicando valores para sus argumentos, dentro de un programa, decimos que llamamos o invocamos a esa función. Esto quiere decir que el procesador guarda la dirección actual, “salta” a la dirección donde comienza el código de la función, la ejecuta, recupera la dirección guardada previamente, y retorna al punto desde el que fue llamada.

Esto es cierto para las funciones que hemos usado hasta ahora, pero hay un tipo especial de funciones que trabajan de otro modo. En lugar de existir una única copia de la función dentro del código, si se declara una función como inline, lo que se hace es insertar el código de la función, en el lugar (y cada vez) que sea llamada. Esta indica al compilador que cada llamada a la función inline deberá ser reemplazado por el cuerpo de esta función. En la práctica la función inline es utilizado sólo cuando las funciones son pequeñas para evitar generar un ejecutable de tamaño considerable.

La palabra reservada inline tiene la ventaja de acelerar un programa si éste invoca regularmente a la función inline. Permite reducir considerablemente el código, en particular para los accesadores de una clase. Un accesador de clase es típicamente una función de una línea.

Ejemplo:

inline int mayor(int a, int b) {
  if(a > b) return a; 
  return b; 
}

Más información de inline:

Prototipos de Funciones

Primero recordar que en el lenguaje de Arduino al contrario que en estandar C, no es necesario declarar los prototipos de las funciones, puesto que de eso se encarga el de incluirlo el arduino builder, al igual que de añadir el main..

En C++ es obligatorio usar prototipos. Un prototipo es una declaración de una función. Consiste en una presentación de la función, exactamente con la misma estructura que la definición, pero sin cuerpo y terminada con un “;”.

En general, el prototipo de una función se compone de las siguientes secciones:

  • Opcionalmente, una palabra que especifique el tipo de almacenamiento, puede ser extern o static. Si no se especifica ninguna, por defecto será extern.
  • El tipo del valor de retorno, que puede ser void, si no necesitamos valor de retorno.
  • Modificadores opcionales.
  • El identificador de la función. Es costumbre, muy útil y muy recomendable, poner nombres que indiquen, lo más claramente posible, qué es lo que hace la función, y que permitan interpretar qué hace el programa con sólo leerlos.
  • Una lista de declaraciones de parámetros entre paréntesis. Los parámetros de una función son los valores de entrada (y en ocasiones también de salida).

Un prototipo sirve para indicar al compilador los tipos de retorno y los de los parámetros de una función, de modo que compruebe si son del tipo correcto cada vez que se use esta función dentro del programa, o para hacer las conversiones de tipo cuando sea necesario.

Normalmente, los prototipos de las funciones se declaran dentro del fichero del programa, o bien se incluyen desde un fichero externo, llamado fichero de cabecera, (para esto se usa la directiva #include).

Ya lo hemos dicho más arriba, pero las funciones son extern por defecto. Esto quiere decir que son accesibles desde cualquier punto del programa, aunque se encuentren en otros ficheros fuente del mismo programa. En contraposición las funciones declaradas static sólo son accesibles dentro del fichero fuente donde se definen.

Más información:

Bibliotecas/Librerías en C++

Junto con los compiladores de C y C++, se incluyen ciertos archivos llamados bibliotecas más comúnmente librerías. Las bibliotecas contienen el código objeto de muchos programas que permiten hacer cosas comunes, como leer el teclado, escribir en la pantalla, manejar números, realizar funciones matemáticas, etc.

Las bibliotecas están clasificadas por el tipo de trabajos que hacen, hay bibliotecas de entrada y salida, matemáticas, de manejo de memoria, de manejo de textos y como imaginarás existen muchísimas librerías disponibles y todas con una función específica.

La declaración de librerías, tanto en C como en C++, se debe hacer al principio de todo nuestro código, antes de la declaración de cualquier función o línea de código, debemos indicarle al compilador que librerías usar, para el saber qué términos están correctos en la escritura de nuestro código y cuáles no. La sintaxis es la siguiente: #include <nombre de la librería> o alternativamente #include “nombre de la librería”. En tu código puedes declarar todas las librerías que quieras aunque en realidad no tienen sentido declarar una librería que no vas a usar en tu programa, sin embargo no existe límite para esto.

La directiva de preprocesador #include se usa en los lenguajes C y C++ para “incluir” las declaraciones de otro fichero en la compilación. Esta directiva no tiene más misterio para proyectos pequeños. En cambio, puede ayudar aprovechar bien esta directiva en proyectos con un gran número de subdirectorios.

Ejemplo:

#include “iostream”
#include “string”
#include <math.h>
using namespace std;

Lo único adicional, es la línea que dice using namespace std; esta línea nos ayuda a declarar un espacio de nombre que evita tener que usarlo cada que accedemos a alguna función específica de una librería. Teniendo este namespace declarado podemos llamar por ejemplo el comando cout >>, que pertenece a la librería iostream, sin embargo sin este namespace sería std::cout >>, imagina tener que hacer esto cada vez que uses algún comando o función de las librerías, sería bastante tedioso.

A continuación pondré algunas de las librerías de uso más común de C++ y que forman parte de las librerías estándar de este lenguaje.

  • fstream: Flujos hacia/desde ficheros. Permite la manipulación de archivos desde el programar, tanto leer como escribir en ellos.
  • iosfwd: Contiene declaraciones adelantadas de todas las plantillas de flujos y sus typedefs estándar. Por ejemplo ostream.
  • iostream: Parte del a STL que contiene los algoritmos estándar, es quizá la más usada e importante (aunque no indispensable).
  • math: Contiene los prototipos de las funciones y otras definiciones para el uso y manipulación de funciones matemáticas.
  • memory: Utilidades relativas a la gestión de memoria, incluyendo asignadores y punteros inteligentes (auto_ptr). “auto_ptr” es una clase que conforma la librería memory y permite un fácil manejo de punteros y su destrucción automáticamente.
  • ostream: Algoritmos estándar para los flujos de salida.
  • Librería stdio: Contiene los prototipos de las funciones, macros, y tipos para manipular datos de entrada y salida.
  • Librería stdlib: Contiene los prototipos de las funciones, macros, y tipos para utilidades de uso general.
  • string: Parte de la STL relativa a contenedores tipo string; una generalización de las cadenas alfanuméricas para albergar cadenas de objetos.
  • vector: Parte de la STL relativa a los contenedores tipo vector; una generalización de las matrices unidimensionales C/C++
  • list: Permite implementar listas doblemente enlazadas (listas enlazadas dobles) fácilmente.
  • iterator: Proporciona un conjunto de clases para iterar elementos.
  • regex: Proporciona fácil acceso al uso de expresiones regulares para la comparación de patrones.
  • thread: Útil para trabajar programación multihilos y crear múltiples hilos en nuestra aplicación.

Más información:

Ejercicios Funciones

Ver ejemplo sencillo de uso de funciones: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio02-Funciones

Comparar las funciones con librerías: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio03-Librerias

Ejercicio: Menú interactivo con Arduino. Con todo lo visto de Strings, operadores, estructuras de control y funciones, hacer un ejemplo de un menú interactivo donde se dan varias opciones y pulsando cada una de ellas se ejecuta una acción concreta. Si el valor pulsado no es ninguna de las opciones avisar y volver a mostrar el menú hasta que se pulse una opción correcta.

Solución: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio46-Estructuras_de_Control

Librerías vs Funciones en Arduino

Como se ha visto anteriormente, las librerías son trozos de código hechas por terceros que usamos en nuestro sketch. Esto nos facilita mucho la programación y hace que nuestro programa sea más sencillo de hacer y luego de entender. Más adelante veremos cómo hacer una librería.

Las librerías en Arduino incluyen los siguientes archivos comprimidos en un archivo ZIP o dentro de un directorio. Estas siempre contienen:

  • Un archivo .cpp (código de C++)
  • Un archivo .h o encabezado de C, que contiene las propiedades y métodos o funciones de la librería.
  • Un archivo Keywords.txt, que contiene las palabras clave que se resaltan en el IDE (opcional).
  • Muy posiblemente la librería incluye un archivo readme con información adicional de lo que hace y con instrucciones de como usarla.
  • Directorio denominado examples con varios sketchs de ejemplo que nos ayudará a entender cómo usar la librería (opcional).

Más información: https://aprendiendoarduino.wordpress.com/2016/06/27/librerias-3/

Una librería a diferencia de las funciones debe estar al menos en un fichero diferente con extensión .h y opcionalmente en otro .cpp y además debe ser llamada con #include desde el sketch de arduino y estar en una ruta accesible desde el IDE de Arduino, ya sea el mismo directorio del sketch o en algunas de las rutas configuradas para librerías.

La ventaja de usar librerías frente a las funciones es que no es necesario incluir el código cada vez que se va a reutilizar sino que con tener la librería instalada en el IDE y llamarla mediante #include ya la puedo usar en mi código.

Al llamar a una librería desde un sketch, la librería completa es cargada a la placa de Arduino incrementando el tamaño del espacio usado en el microcontrolador, tanto en la memoria flash como en la RAM.

NOTA: Como instalar una librería de github: http://scidle.com/install-github-libraries-on-arduino-ide/ habla de quitar caracteres no ASCII, pero comprueba también que no funcione porque haya subdirectorios.

Las librerías que usamos para los sketches tienen una versión, que se suelen actualizar con frecuencia. También tenemos un control de versiones en el nuevo IDE a partir de 1.6.4 que nos facilita la gestión de la versión de las librerías usadas. Este aspecto es importante porque un sketch que funciona con una versión de una librería es posible que al compilarlo con otra versión en otro IDE no funcione. Por ello es importante documentar con que versión de librería está hecho o distribuir el sketch con la librería con la que se ha creado. Generalmente las librerías tienen compatibilidad hacia atrás, pero puede que no ocurra o que el comportamiento de la librería sea diferente.

Al cambiar el IDE también nos podemos encontrar que nuestro sketch no es compatible con la versión de la librería que estemos usando, que es diferente con la que se diseñó originalmente el sketch.

Listado de librerías: http://playground.arduino.cc/Main/LibraryList

Ejercicio Librerías vs Funciones

Ya hemos visto lo que son las librerías y las funciones definidas por usuario, el uso de unas u otras dependerá de varios factores como la posibilidad de reutilización del código o la experiencia del programador con el uso de librerías.

Ejemplos simples de librerías:

Software Arduino a fondo

Programación Arduino

El lenguaje de programación de Arduino es C++, aunque es posible programarlo en otros lenguajes. No es un C++ puro sino que es una adaptación que proveniente de avr-libc que provee de una librería de C de alta calidad para usar con GCC (compilador de C y C++) en los microcontroladores AVR de Atmel, denominado avr-gcc, y otras muchas funciones específicas para las MCU AVR de Atmel.

El lenguaje de los AVR es un entorno C para programar los chips de Atmel, la mayor parte del lenguaje o core de Arduino está escrito con constantes y funciones AVR y hay bastantes cosas que no son fáciles de hacer con el lenguaje de Arduino sin el uso de código AVR de la avr-libc.

Como el entorno de Arduino usa GCC para compilar el código, todas las variables de los puertos y de los registros de una MCU de Atmel son soportadas por el IDE de Arduino.

Las herramientas necesarias para programar los microcontroladores AVR de Atmel son avr-binutils, avr-gcc y avr-libc y ya están incluidas en el IDE de Arduino, cuando compilamos y cargamos un sketch estamos usando estas herramientas.

Para usar un microcontrolador ATmega8  de la familia AVR de 8 bits de Atmel, sin usar el IDE de Arduino, podemos programarlos usando C y C++ mediante una versión muy extendida de GCC (http://es.wikipedia.org/wiki/GNU_Compiler_Collection) denominda AVR-GCC http://winavr.sourceforge.net/ y http://sourceforge.net/projects/winavr/files/.

Una vez compilado el programa de C/C++ en un binario para AVR con AVR-GCC, necesitaremos “quemarlo” en el microcontrolador. Esto puede hacerse con AVRDUDE http://www.nongnu.org/avrdude/ o UISP http://www.nongnu.org/uisp/ o Atmel Studio http://www.atmel.com/tools/atmelstudio.aspx

Para Linux son necesarias estas herramientas:

  • gcc-avr
  • binutils-avr
  • gdb-avr
  • avr-libc
  • avrdude

Más información: http://playground.arduino.cc/Main/AVR

Un ejemplo de uso de funciones AVR que no dispone el entorno de Arduino es cuando queremos hacer delays muy pequeños. La función delayMircoseconds() puede hacer el delay más pequeño con el lenguaje de Arduino que es de 2 microsegundos.

Para delays menores es necesario usar ensamblador y en concreto la función ‘nop’ (no operation. Cada llamada a ‘nop’ ejecuta un ciclo de reloj que para 16 MHz es un retraso de 62,5 ns.

__asm__(“nop\n\t”);
__asm__(“nop\n\t””nop\n\t””nop\n\t””nop\n\t”);  \\ gang them up like this

También como hemos visto anteriormente, manipular los puertos y pines con el código AVR es más rápido que usar digitalWrite(). Más información https://www.arduino.cc/en/Reference/PortManipulation

Otro ejemplo es el uso de los métodos AVR cbi y sbi que se usan para hacer set o clear de bits el PORT y otras variables. Puedes encontrar estos métodos de código AVR en muchos sitios y ambos deben ser definidos en el sketch de Arduino para ser usados. Pegar este código al inicio del sketch y para usarlo simplemente poner una variable del puerto y el pin para hacer set o clear.

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

Pero para programar el 99% de lo que necesitemos en proyectos sencillos con Arduino, todo esto se puede resumir en que para programar nuestro lenguaje de programación de Arduino es el “Arduino Reference”, que es donde debemos recurrir para cualquier duda de programación que surja. Puesto que Arduino provee de unas librerías que facilitan la programación del microcontrolador. http://arduino.cc/en/Reference/HomePage

A medida que nuestro programa se hace más complejo o necesitamos que sea más rápido o queremos que tenga un uso de memoria más eficiente, entonces es cuando tendremos que recurrir a los comando AVR de la avr-libc.

El gran éxito de Arduino en parte se debe a que el IDE nos permite programar una MCU sin tener que saber todo lo anterior y nos da unas herramientas sencillas y específicas para programar los microcontroladores que suministra en sus placas. Por ejemplo veamos las funciones que nos ofrece para comunicar por el puerto serie: http://arduino.cc/en/Reference/Serial

El IDE de Arduino está basado en Processing: https://processing.org/, es un lenguaje de programación y entorno de desarrollo integrado de código abierto basado en Java, de fácil utilización, y que sirve como medio para la enseñanza y producción de proyectos multimedia e interactivos de diseño digital.

La librería Arduino está basada en wiring: http://wiring.org.co/ y es de donde nacieron las funciones que conocemos como digitalRead(), Serial.println(), etc…

Web del proyecto de Proccesing: https://processing.org/

Processing en github: https://github.com/processing/processing

Web del proyecto Wiring: http://wiring.org.co/

Wiring en github: https://github.com/WiringProject

Processing es útil cuando queremos comunicar Arduino con un ordenador y mostrar datos o guardar datos, pero también podemos usar python, .NET o cualquier otro lenguaje de programación que conozcamos.

Arduino trae algunos ejemplos para trabajar con Processing en el apartado communication, por ejemplo para hacer una gráfica de datos en http://arduino.cc/en/Tutorial/Graph

Ver el reference y la librería Serial de processing: https://processing.org/reference/ y https://processing.org/reference/libraries/serial/

También disponemos de una librería de Arduino para processing que nos permite interactuar entre processing y arduino: http://playground.arduino.cc/interfacing/processing

Por supuesto Arduino se puede programar en otros lenguajes y desde otros entornos de programación. Cabe destacar scratch como un lenguaje visual que hace innecesario saber programación o Atmel Studio que es la herramienta que proporciona Atmel. Más información en: https://aprendiendoarduino.wordpress.com/2014/11/20/tema-4-conceptos-basicos-de-programacion/

Más información: http://wittyrobo.com/mcu328p/c01_p01.html

Fuses

Los fuses son una parte muy importante en la programación de un microcontrolador. Solo es necesario configurarlos una vez, pero si no se hace correctamente podemos tener problemas.

Ya sabemos lo que es la flash, EEPROM y RAM como parte de la MCU, pero no se ha mencionado anteriormente que hay 3 bytes de memoria permanente llamados fuses. Los fuses determinan cómo se va a comportar el microcontrolador, si tiene bootloader, a que velocidad y voltaje va a funcionar, etc… Notar que a pesar de llamarse fuses (fusibles) puede configurarse una y otra vez y no tienen nada que ver con la protección de sobrecorrientes.

Los fuses están documentados en los datasheets, para el Arduino UNO ver página 348 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Con los fuses seleccionamos el comportamiento de:

  • Clock Source: puede ser un reloj externo, un reloj interno o un cristal externo.
  • Clock Startup: es el tiempo que necesita la fuente de reloj para estabilizarse.
  • Clock Output: habilita una salida de reloj en el PORT C2
  • Clock Divide: Divide por 8 el reloj
  • Reset Disable: convierte el pin de reset en un pin normal, habilitar este fuse hace que no se pueda programar por ISP
  • Brown-out Detect (BOD): selecciona a que voltaje hace disparar la protección de brownout. Para una MCU brownout es el voltaje al que es demasiado bajo para que funcione de una forma fiable a la velocidad de reloj.
  • Selecciona si se usa bootloader o no.
  • Selecciona cuánta memoria se reserva para el bootloader
  • Deshabilitar programación por el puerto serie

Con los fuses podemos bloquear algunos aspectos importamte de la MCU y podríamos brickearlo.

Los 3 bytes de los fuses son:

  • low byte fuse
  • high byte fuse
  • extended fuse

También hay un cuarto byte que se usa para programar los lock bits. Los lock bits pueden ser usados para restringir la lectura/escritura en la memoria de programa (flash), la zona de boot y la de aplicación tienen sus propios lock bits y es posible controlar el acceso a ellos de forma separada.

Para los fuses el 1 significa no usado y 0 usado.

Low Byte Fuses

High Byte Fuses

Extended Fuse Bits

Los valores por defecto el ATmega328p son:

  • Low fuse = 0x62 (B01100010)
  • High fuse = 0xD9 (B11011001)
  • Extended fuse = 0xFF (B11111111)

Los valores de los fuses de Arduino con el ATmega328p son:

  • Low fuse = 0xFF (B11111111)
  • High fuse = 0xDA (B11011110)
  • Extended fuse = 0x05 (B00000101)

En caso de usar el Optiboot bootloader, cambia el valor en el high byte fuse porque el tamaño del bootloader es inferior.

Más información de cada fuse en: http://www.martyncurrey.com/arduino-atmega-328p-fuse-settings/

Pero la mejor forma de ver los fuses es examinar AVR fuse calculator: http://www.engbedded.com/fusecalc/

Luego con los datos de los 3 bytes de los fuses, se pasan como argumento en el avr-dude al cargar el sketch y configurar los fuses. La configuración de los fuses es parte del proceso de cargar un bootloader. Editando el fichero boards.txt en la carpeta del IDE de Arduino en la ruta C:\Program Files (x86)\Arduino\hardware\arduino\avr se puede configurar el valor de los fuses.

Discusion en foro sobre el uso de fuses para proteger el binario de un sketch:  http://www.todopic.com.ar/foros/index.php?topic=44794.0. Es tan simple como agregar “-Ulock :w:03c:h” al final del comando que se le envía a avrdude.exe. Se configuran los lock bits como 1100 0011 (ojo que cambian los bits más significativos y menos significativos)

fuses

Cargar un programa desde el ide de arduino al microcontrolador. Luego desde el programa que mencionado ejecutar el mismo comando enviado por el ide de arduino a avrdude, pero cambiando la ruta del archivo .hex por un programa distinto y agregando al final “-Ulock :w:03c:h” Avrdude muestra en pantalla el avance en el proceso, pero al tratar de verificar el archivo enviado con el que recibe del microcontrolador, la comprobación falla. El microcontrolador se reinicia y ejecuta el programa nuevo correctamente, lo que demuestra que el Arduino no entregará el programa y que avrdude no es capaz de extraerlo del microcontrolador, ni tampoco con esta configuración es posible cargar ningún programa.

Ver el estado de los fuses de un Arduino se puede hacer con avr-dude y un programador, para ello hay que ejecutar el comando “avrdude -c arduino -p m328p -P COM3 -b 19200 -v”

Con AVR studio se puede leer y escribir los valores de los fuses. Pero si queremos cambiar los valores de los fuses con el IDE de Arduino podemos hacerlo de dos formas:

  • Option 1. El “quemado del bootloader” configura los fuses. Para configurar los fuses el ATmega328p se puede hacer en el proceso de cargar bootloader y toma la configuración de los fuses que hay en el fichero boards.txt de la placa correspondiente.
  • Option 2. Configurar los fuses manualmente, sin cargar un bootloader. Para ello necesitaremos la herramienta avr-dude mediante el comando.
    avrdude -c stk500v2 -p m328p -P /dev/ttyACM0 -b 19200 -U lfuse:w:0xFF:m -U hfuse:w:0xDE:m -U efuse:w:0x05:m”

Más información en:

IDE Arduino

El IDE de Arduino es open source y por lo tanto tenemos su código fuente y también podemos hacer nuestra propia versión del IDE. El proyecto del IDE de Arduino en github es: https://github.com/arduino/Arduino y las instrucciones para construir el binario desde el fuente está en https://github.com/arduino/Arduino/wiki/Building-Arduino

El software de Arduino se puede descargar en https://www.arduino.cc/en/Main/Software

Para actualizar Arduino y mantener las configuraciones, en Windows simplemente ejecutar el instalador, este desinstala la versión anterior y mantiene las librerías y sketchs siempre y cuando no hayamos guardado o modificado algo en el directorio de instalación.

También es posible tener varias versiones del IDE instaladas, simplemente usar la instalación manual con el archivo .zip

Para añadir al IDE Arduino soporte de placas de terceros: https://learn.adafruit.com/add-boards-arduino-v164?view=all

Lista no oficial de placas de terceros soportadas por el IDE de Arduino:

https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls#list-of-3rd-party-boards-support-urls, que son ficheros json para añadir en el IDE y dar soporte con el nuevo gestor de placas.

Ruta donde se instalan los cores: C:\Users\enrique\AppData\Local\Arduino15\packages

La explicación de cada parámetro en configuración IDE: https://code.google.com/archive/p/arduino/wikis/Platforms.wikien la ruta: C:\Program Files (x86)\Arduino\hardware\arduino\avr

  • boards.txt: The Arduino environment supports multiple target boards with different chips (currently, only AVRs), CPU speeds, or bootloaders. These are defined in boards.txt. A board is specified by multiple variables prefixed with the same keyword (shown here as BOARD). The file consists of multiple lines with the format key=value. Lines that start with a # are comments and ignored.
  • programmers.txt: Programmers may be specified in the programmers.txt file in a platform. This file has a format similar to the boards.txt file, but with the other variables.
  • The bootloaders sub-folder of a platform contains the bootloaders for the boards in the platform. These should appear as .hex files in a sub-folder of the bootloaders folder. Often, the source code for the bootloader is also included, along with a Makefile for building the .hex file.
  • A core is a set of .c, .cpp, and .h files that implement the basic Arduino functions for a platform. Each core appears as a sub-folder of thecores sub-folder of a platform.

Guia para cambiar estilo IDE Arduino:

AVR libc

Es posible usar comandos estándar de C++ en la programación de Arduino siempre que estén incluidos en el avr libc, así como otros comandos propios para las MCUs AVR tambiñen incluidos en en avr-libc:

Dispositivos soportados por avr-libc: http://www.atmel.com/webdoc/AVRLibcReferenceManual/index_1supp_devices.html

Cuando creamos un programa en Arduino, automáticamente carga varios módulos como stdlib.h, math.h, etc…

Por ejemplo math.h:

El equivalente para linux de avr-libc es la glibc:

glibc como se puede ver tiene versiones para diferentes arquitecturas de los ordenadores como i386, i686, etc… para usar las instrucciones de cada una de sus arquitecturas.

Más información sobre MCUs AVR:

WinAVR

WinAVR es una herramienta importante para el desarrollo de MCUs AVR, incluso después de instalar Atmel Studio como IDE, es recomendable instalar WinAVR.

WinAVR (pronunciado “whenever”) es una suite compuesta de herramientas de desarrollo opensource para los microcontroladores AVR sobre plataforma Windows. Incluye el compilador GNU GCC para C y C++.

Herramientas WinAVR:

  • avr-gcc (compiler)
  • avrdude (programmer)
  • avr-gdb (debugger)
  • avr-libc (with a good HTML help Documentation)
  • and more …..

Web del proyecto:

Más información:

Tutoriales de programación AVR:

Interesante información sobre avr-lib en Wiring y Arduino: https://ccrma.stanford.edu/~juanig/articles/wiriavrlib/wiriavrlib.html

Proceso de compilación

GCC es un conjunto de compiladores que se considera el estándar para los Sistemas Operativos derivados de UNIX y requiere de un conjunto de aplicaciones conocidas como binutils que son unas herramientas de programación para la manipulación de código de objeto.

GCC soporta varios lenguajes y diferentes arquitecturas de HW::

Como GCC está construido para ejecutarse en un sistema como Linux, Windows o mac OS, pero para generar código para un microcontrolador AVR, entonces se denomina avr-gcc que es una versión de GCC solo para arquitecturas AVR. avr-gcc es el compilador que usa el IDE de arduino para convertir el sketch en C++ a un fichero binario (.hex) que es el que se carga en la flash del MCU y que ejecuta.

avr-gcc se puede usar directamente o a través de un IDE como el de Arduino o el Atmel Studio.

En el caso de Arduino el proceso de construcción para que el código escrito llegue a la placa es el siguiente:

  • El IDE verifica el código y comprueba que la sintaxis del código es correcta en C y C++
  • Luego pasa al compilador avr-gcc, que convierte el código en C++ en instrucciones que entiende el microcontrolador.
  • Después el código es combinado con las librerías estándar de Arduino, que son las que ofrecen las funciones básicas como digitalWrite() o Serial.println().
  • El resultado es un fichero hex que contiene los bytes que van a ser grabado en la memoria flash de Arduino.
  • Finalmente el fichero es cargado en la placa Arduino transmitiendolo sobre el USB o la conexión serie si hay un bootloader cargado o sino con un hardware externo (programador).

Cuando un sketch contiene múltiples ficheros, es compilado y los ficheros con extensiones .c o .cpp se compilan de forma separada y los ficheros con otras extensiones se concatenan juntos al fichero del sketch principal antes de pasarlo al compilador. Para usar ficheros con extensión .h, es necesario incluirlos con #include usando “comillas dobles” y no <>

El entorno de Arduino, antes de compilar también hace algunas modificaciones como añadir al principio del sketch #include “Arduino.h”. También el entorno de Arduino busca las definiciones de las funciones en el sketch principal y crea las declaraciones (prototypes) para ellas, estas se insertan después de los comentarios y las directivas de preprocesamiento, pero antes del resto de código, esto significa que si se quiere usar un tipo de dato como argumento de una función, debe declararse dentro de un fichero de cabecera separado.

El entorno de Arduino soporta múltiples placas con diferentes MCUs (no solo AVR), todo los datos de la placa están definidos en el fichero boards.txt en C:\Program Files (x86)\Arduino\hardware\arduino\avr. A la hora de compilar las opciones se toman del fichero boards.txt en la opción de build.

Cuando se compila desde el IDE de Arduino cono avr-gcc, las rutas donde se busca cuando se hace un include son:

  • El directorio del core de la placa <ARDUINO>/hardware/core/<CORE>/
  • El directorio de avr include <ARDUINO>/hardware/tools/avr/avr/include/
  • Cualquiera de los directorios de librerías <ARDUINO>/hardware/libraries/ o C:\Users\<USER>\Documents\Arduino\libraries

Cuando un sketch se verifica, se genera el fichero compilador en un directorio temporal del SO. Todos los ficheros .c y .cpp son compilados y la salida es un fichero .o (object file), tanto el del sktech final como otros que haya en el directorio del sketch o los de las librerías incluidas. Los fichero .o son enlazados todos juntos en una librería estática y el fichero del sketch principal. Solo las partes de las librerías necesitadas para el sketch son incluidas en el fichero final .hex, reduciendo el tamaño de la mayoría de los sketches.

El fichero .hex es la salida final de la compilación y es cargado en la memoria flash de Arduino. Este .hex es cargado en Arduino mediante avrdude y este proceso es controlado por las variables de las placas en el fichero de boards.txt dentro de las opciones “upload”

Para que me guarde los hex en una carpeta concreta: http://forum.arduino.cc/index.php?topic=142706.0

Durante el proceso de compilación se genera un fichero intermedio con extensión .elf que contiene el código máquina y otra información relacionado con información de la memoria y debug. El fichero elf (Executable and Linking Format), es el formato estándar para ficheros ejecutables, objetos de código y librerías compartidas. Elf es como una dll y consiste en unos enlaces simbólicos y tablas que pueden cargarse en una zona de memoria. Los fichero elf tienen varias secciones como datos, textos, etc.. Este fichero se suele usar para debug con emuladores.

Durante el proceso de compilación y carga, el IDE de Arduino muestra mucha información (hay que activarlo antes, ver https://aprendiendoarduino.wordpress.com/2016/06/26/instalacion-software-y-configuracion/) que puede resultar de ayuda para saber lo que está pasando durante el proceso. La herramienta que convierte el fichero .elf a .hex es avr-objcopy.

Más información:

Cómo compilar un sketch de Arduino con makefile:

Opciones de compilación:

Esta imagen muestra todo el proceso:

Información sobre las binutils: http://www.atmel.com/webdoc/AVRLibcReferenceManual/overview_1overview_binutils.html

Arduino también nos ofrece la posibilidad de una herramienta de línea de comandos para compilar los sketches de Arduino llamada Arduino Builder: https://github.com/arduino/arduino-builder, siendo uan herramienta muy intersante para hacer integración continua https://github.com/arduino/arduino-builder/wiki/Doing-continuous-integration-with-arduino-builder o testeo de nuestro softwatre (TDD).

También hay interfaces gráficos para GCC:

Probar/Ejercicio: El compilador de avr-gcc cuando detecta lineas iguales de Serial.print() no aumenta la memoria del static data porque lo reconoce y la guarda una sola vez en RAM y la repite al llamarla. Tampoco aumenta el tamaño del hex.

Ensamblador

Ensamblador es un lenguaje de bajo nivel. Consiste en una lista de instrucciones que no son de ninguna manera comparable a cualquier otro lenguaje como C, Basic o Pascal. El AVR tiene alrededor de 130 instrucciones, dependiendo del tipo, periféricos y tamaño del microcontrolador. En este link está el manual de las instrucciones con su respectiva explicación, y en este otro link se encuentra la guia de usuario de AVR Assembler. Se puede encontrar más información en http://www.avrbeginners.net/ y en http://www.avrfreaks.net/

Como ya hemos visto Arduino podemos programarlo en C puro, pero también podríamos programarlo en ensamblador, aunque no es una opción recomendable. Sin embargo como veremos podremos obtener el código en ensamblador de nuestro sketch.

Sin embargo, podría ser útil en determinadas situaciones insertar código en ensamblador dentro de nuestro sketch de Arduino. El compilador GNU C para procesadores Atmel AVR RISC procesadores permite incrustar el código de lenguaje ensamblador en programas C. Esta característica se puede utilizar para optimizar manualmente las partes críticas de tiempo del software o utilizar una instrucción de procesador específico, que no están disponibles en el lenguaje C.

Las instrucciones de cada procesador se pueden ver en los datasheet correspondientes y para el caso de los AVR de 8 bit en:

AVR Assembler User Guide: http://www.atmel.com/Images/doc1022.pdf

Para insertar instrucciones de ensamblador en C ver: Inline assembler cookbook: http://www.nongnu.org/avr-libc/user-manual/inline_asm.html, simplemente poner en el código C la instrucción asm volatile(); pasando como parámetro el código entre comillas.

Más información:

Proceso de Carga del Sketch

Una vez compilado y obtenido el fichero binario, hay que cargarlo en la memoria flash de Arduino, para ello el IDE de Arduino usa una herramienta llamada avr-dude.

Cuando tenemos problemas al cargar un sketch de Arduino, el problema suele venir de avrdude y en este enlace habla de los problemas y sus soluciones http://elcajondeardu.blogspot.com.es/2016/03/avrdude-nos-lo-explica-rufian-rufian-en.html

Página del proyecto avr-dude:

Manual del AVRdude:

Hay versiones con GUI para avr-dude que nos permiten cargar el sketch con la opciones que queramos.

Usar avr-dude con atmel studio y Arduino: http://wittyrobo.com/mcu328p/c04_p01.html

Uso de Arduino uploader:

Tutorial para usar AVRdude:

Adicionalmente se disponen de otras herramientas para la MCUs de AVR:

Soporte a tarjetas no oficiales con IDE Arduino

Al igual que hemos visto las herramientas necesarias para programar los microcontroladores AVR de Atmel y que necesitamos de una librería avr-libc, un compilador avr-gcc y un programa para cargar el .hex al microcontrolador, lo que compone el soporte en el IDE de Arduino para estos microcontroladores, en caso de querer programar otras MCUs, necesitaremos instalar las correspondientes herramientas para estos microcontroladores.

El proceso de dar soporte a otras tarjetas/microcontroladores en el IDE de Arduino se ha facilitado mucho, disponiendo de unos ficheros json que se descarga el IDE de Arduino y con ellos el IDE se descarga las herramientas necesarias y configura el IDE de Arduino para esas MCUs.

Instalar soporte IDE a otras placas:

IMPORTANTE: especificaciones de formato a usar en el IDE de Arduino para soporte de nuevas placas: https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5-3rd-party-Hardware-specification

Arduino ARM

Por ejemplo las herramienta usadas por Arduino para las placas basadas en microcontroladores ARM como el Arduino MKR1000, son las GNU ARM Embedded Toolchain https://launchpad.net/gcc-arm-embedded que es mantenido por los empleados de ARM. Usa gcc y newlib: https://sourceware.org/newlib/ y accesible el codigo fuente vía git en https://sourceware.org/git/gitweb.cgi?p=newlib-htdocs.git y git://sourceware.org/git/newlib-cygwin.git

Para programar la flash en los microcontroladores Atmel ARM de la familia SAM usar BOSSA: http://www.shumatech.com/web/products/bossa y su código fuente está en https://sourceforge.net/p/b-o-s-s-a/code/ref/master~/

También se usa otra herramienta llamada OpenOCD (Open On-Chip Debugger) http://openocd.org/ y ofrece soporte a la programación y debug, soportando muchos hardware debuggers.

Documentación: http://openocd.org/documentation/

Código fuente: https://sourceforge.net/p/openocd/code/ci/master/tree/

Librerías vs Funciones

Como se ha visto anteriormente, las librerías son trozos de código hechas por terceros que usamos en nuestro sketch. Esto nos facilita mucho la programación y hace que nuestro programa sea más sencillo de hacer y luego de entender. En el curso avanzado veremos como hacer una librería.

Una librería a diferencia de las funciones debe estar al menos en un fichero diferente con extensión .h y opcionalmente en otro .cpp y además debe ser llamada con #include desde el sketch de arduino y estar en una ruta accesible desde el IDE de Arduino, ya sea el mismo directorio del sketch o en algunas de las rutas configuradas para librerías.

La ventaja de usar librerías frente a las funciones es que no es necesario incluir el código cada vez que se va a reutilizar sino que con tener la librería instalada en el IDE y llamarla mediante #include ya la puedo usar en mi código.

Al llamar a una librería desde un sketch, la librería completa es cargada a la placa de Arduino incrementando el tamaño del espacio usado en el microcontrolador, tanto en la memoria flash como en la RAM.

NOTA: Como instalar una librería de github: http://scidle.com/install-github-libraries-on-arduino-ide/ habla de quitar caracteres no ASCII, pero comprueba también que no funcione porque haya subdirectorios.

Las librerías que usamos para los sketches tienen una versión, que se suelen actualizar con frecuencia. También tenemos un control de versiones en el nuevo IDE a partir de 1.6.4 que nos facilita la gestión de la versión de las librerías usadas. Este aspecto es importante porque un sketch que funciona con una versión de una librería es posible que al compilarlo con otra versión en otro IDE no funcione. Por ello es importante documentar con que versión de librería está hecho o distribuir el sketch con la librería con la que se ha creado. Generalmente las librerías tienen compatibilidad hacia atrás.

Al cambiar el IDE también nos podemos encontrar que nuestro sketch no es compatible con la versión de la librería que estemos usando, que es diferente con la que se diseñó originalmente el sketch.

Este tutorial explica como crear una librería, aunque se verá con mayor profundidad en el curso avanzado: http://arduino.cc/en/Hacking/LibraryTutorial

Guia de estilo para escribir librerías, que también se verá con profundidad en el avanzado: http://arduino.cc/en/Reference/APIStyleGuide

Listado de librerías: http://playground.arduino.cc/Main/LibraryList

Ver ejemplos simples de librerías:

Librería Time y Timezone

Como ejemplo para aprender a usar una librería, veamos Time y Timezone.

Librería Time: https://github.com/PaulStoffregen/Time

Librería Timezone: https://github.com/JChristensen/Timezone

Para aprender a manejarlas, simplemente leer el fichero readme que viene en el repositorio de github y luego los ejemplos.

Algunas funciones librería Time:

  • hour();            // the hour now  (0-23)
  • minute();          // the minute now (0-59)
  • second();          // the second now (0-59)
  • day();             // the day now (1-31)
  • weekday();         // day of the week, Sunday is day 0
  • month();           // the month now (1-12)
  • year();            // the full four digit year: (2009, 2010 etc)
  • hourFormat12();    // the hour now in 12 hour format
  • isAM();            // returns true if time now is AM
  • isPM();            // returns true if time now is PM
  • now();             // returns the current time as seconds since Jan 1 1970
  • setSyncProvider(getTimeFunction);  // set the external time provider
  • setSyncInterval(interval);         // set the number of seconds between re-sync

Algunas funciones librería Timezone:

  • time_t toLocal(time_t utc); Converts the given UTC time to local time, standard or daylight as appropriate.
  • TimeChangeRule myRule = {abbrev, week, dow, month, hour, offset};
    • abbrev is a character string abbreviation for the time zone; it must be no longer than five characters.
    • week is the week of the month that the rule starts.
    • dow is the day of the week that the rule starts.
    • hour is the hour in local time that the rule starts (0-23).
    • offset is the UTC offset in minutes for the time zone being defined.

Ejemplos:

  • TimeChangeRule usEDT = {“EDT”, Second, Sun, Mar, 2, -240};  //UTC – 4 hours
  • TimeChangeRule usEST = {“EST”, First, Sun, Nov, 2, -300};   //UTC – 5 hours

Uso de librerías vs funciones

Ya hemos visto lo que son las librerías y las funciones definidas por usuario, el uso de unas u otras dependerá de varios factores como la posibilidad de reutilización del código o la experiencia del programador con el uso de librerías.

Ejemplo de programa blink con funciones definidas por usuario: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio02-Funciones

Ejemplo de programa blink con librería: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio03-Librerias