En este tutorial vamos a aprender como guardar estructuras de datos complejas dentro de Arduino mediante la librería EEPROM.

La EEPROM es el contenido de memoría no volátil de nuestra placa y nuestra placa Arduino UNO dispone de 1KB que es más que suficiente para nuestro interés.

En muchas ocasiones podemos ver en la necesidad de querer guardar un dato, de manera que cuando apaguemos y encendamos la placa podamos acceder a un registro de memoría.

Por ejemplo, cuando requerimos de parámetros de calibración que dependen del modelo mecánico o constructivo es conveniente guardar en este apartado de memoria los parámetros que dependen de una puesta a punto inicial.

En mi caso lo voy a utilizar para guardar datos de calibración de un sensor de color TCS3200. Este sensor obtiene los valores RGB de los colores, pero para obtener estos valores se requiere de una calibración con respecto al blanco y negro, pero estos difieren de un modelo a otro en función de la distancia al valor leido.

Es por ello, que voy a realizar una revisión de esta librería para poder meterlos en una estructura de la siguiente forma.

#define RGB_SIZE 3
typedef struct{
		uint8_t color [RGB_SIZE]; // the evaluated colour data (RGB value 0-255 or other)
} colorData;

Para empezar vamos a familiarizarnos con el modo de funcionamiento y los métodos disponibles dentro de esta librería.

En todos los métodos de acceso, búsqueda, lectura y escritura de datos, se encuentran los siguientes métodos.

Estos métodos requieren de una dirección de memoria y el valor que debe ser de un byte, es decir solo valores de 0 a 255. Podría crear sucesiones de números R, G y B por separado gestionando las direcciones de memoria, pero eso llevaría mucho más trabajo que orientarlo por otros métodos mejores.

Es por ello, que vamos a pasar de lo anterior para llegar a las funciones:

El primer programa que vamos a realizar es un repaso del contenido de nuestra EEPROM, para saber qué hay contenido. Es posible que si existen datos guardados o corruptos, estos puedan contribuir en una extensión de errores, en el acceso a esos espacios de memoria. Hay que fijarse, que de una forma fácil podemos acceder a la memoría EEPROM como si se tratara de un vector por medio de un subíndice.


#include <EEPROM.h>

void setup() {

  //Start serial
  Serial.begin(9600);

  //Print length of data to run CRC on.
  Serial.print("EEPROM length: ");
  Serial.println(EEPROM.length());
  Serial.println();
  
  for (int i = 0 ; i < EEPROM.length()  ; ++i) {
    Serial.print("EEPROM Data: ");
    Serial.print(i);
    Serial.print(" Value: ");
    
    Serial.println(EEPROM[i]);
  }
}

void loop() {

}

Como se puede observar, los primeros accesos contienen información y a partir de ahí, todos tienen el valor 255, que es el valor que dispone por defecto.

Si queremos inicializar la tabla, desde este ejemplo clear, se pueden poner todos los valores a 0. Yo prefiero inicializarlos a 255.

Ahora vamos a guardar una estructura en la EEPROM con el formato RGB definido anteriormente y accederemos a él para comprobar el guardado de su contenido.

#include <EEPROM.h>

#define RGB_SIZE 3

typedef struct{
    uint8_t color[RGB_SIZE]; // the evaluated colour data (RGB value 0-255 or other)
  } colorData;

void setup() {

    Serial.begin(9600);

    int address = 0;   //Location we want the data to be put.
    colorData rgb = { 255,128,0};

    //One simple call, with the address first and the object second.
    EEPROM.put(address, rgb);

    Serial.println("Written custom data type! ");

    colorData rgbstored;
    EEPROM.get( address, rgbstored );
    Serial.println( "Read custom object from EEPROM: " );
    Serial.println( rgbstored.color[0] );
    Serial.println( rgbstored.color[1] );
    Serial.println( rgbstored.color[2] );
}

void loop() {

}

El resultado de esto en la EEPROM aparece de la siguiente manera en la que los únicos datos modificados son los 3 primeros números asociados a los valores RGB. Es decir, el proceso es exactamente el mismo, que si lo hicieramos de manera independiente.

Ahora vamos a crear un vector de datos de este tipo de estructura, y definir una tabla de colores.

 


#include <EEPROM.h>

#define RGB_SIZE 3

typedef struct{
    uint8_t color[RGB_SIZE]; // the evaluated colour data (RGB value 0-255 or other)
  } colorData;

colorData white = { 255,255,255};
colorData black = { 0,0,0};
colorData red = { 255,0,0};
colorData green = { 0,255,0};
colorData blue = { 0,0,255};
colorData yellow = { 255,255,0};
colorData brown = { 140,60,20};
  
colorData rgb[7] ={
    white,
    black,
    red,
    green,
    blue,
    yellow,
    brown
};

void setup() {

    Serial.begin(9600);

    int address = 0;   //Location we want the data to be put.

    //One simple call, with the address first and the object second.
    EEPROM.put(address, rgb);

    Serial.println("Written custom data type! ");

    /*colorData rgbstored;
    EEPROM.get( address, rgbstored );
    Serial.println( "Read custom object from EEPROM: " );
    Serial.println( rgbstored.color[0] );
    Serial.println( rgbstored.color[1] );
    Serial.println( rgbstored.color[2] );*/

    for (int i = 0 ; i < EEPROM.length()  ; ++i) {
      Serial.print("EEPROM Data: ");
      Serial.print(i);
      Serial.print(" Value: ");
    
      Serial.println(EEPROM[i]);
    }
}

void loop() {

}

 

El resultado en la EEPROM será el siguiente.

Todo correcto, pero no tenemos forma de indexar el vector de la tabla de colores si no lo hemos definido la longitud de la tabla anteriormente. Nos aparece como un conjunto ordenado de valores sucesivos de números enteros hasta 255. Por lo que el error se extiende si pasamos de los límites de la tabla, al ser todos los demás valores 255, se asociará al color blanco. De la misma manera que si hubieramos limpiado la EEPROM con 0, se asociaría al color negro.

Todo esto es muy bonito para datos de byte en un array, pero en el caso de querer extender un objeto más complejo, con valores numéricos float o con cadenas de texto como el siguiente,tendremos varios problemas asociados.

#define SIZENAME 10
#define RGB_SIZE 3
typedef struct	{
		char    name[SIZENAME];  // color name 8+nul
		uint8_t color[RGB_SIZE];    // RGB value
	} colorData;

En este caso añadimos un nombre para conocer cuál es el nombre asociado a este color.

#include <EEPROM.h>

#define SIZENAME 10
#define RGB_SIZE 3

typedef struct  {
    char    name[SIZENAME];  // color name 8+nul
    uint8_t color[RGB_SIZE];    // RGB value
} colorData;

colorData white = { "WHITE", {255,255,255}};
colorData black = { "BLACK", {0,0,0}};
colorData red = {"RED", { 255,0,0}};
colorData green = {"GREEN", { 0,255,0}};
colorData blue = {"BLUE", { 0,0,255}};
colorData yellow = {"YELLOW", { 255,255,0}};
colorData brown = {"BROWN", { 140,60,20}};
  
colorData rgb[7] ={
    white,
    black,
    red,
    green,
    blue,
    yellow,
    brown
};

void setup() {

    Serial.begin(9600);

    int address = 0;   //Location we want the data to be put.

    //One simple call, with the address first and the object second.
    EEPROM.put(address, rgb);

    Serial.println("ColorData List RGB ");

    for (int i = 0 ; i < EEPROM.length()  ; ++i) {
      Serial.print("EEPROM Data: ");
      Serial.print(i);
      Serial.print(" Value: ");
    
      Serial.println(EEPROM[i]);
    }
}

void loop() {

}

En este caso ocurre que los espacios de memoria ocupados en cada objeto son 10 espacios para el nombre y 3 espacios para definir el color.

¿Cuál puede ser la solución a nuestro problema?

La solución es la siguiente. Acceder siempre con el método get de la EEPROM. y desplazar la dirección de acceso tantos espacios como ocupe el objeto. Esto se puede conseguir con la función sizeOf que determina cuantos espacios de bytes ocupa nuestro objeto; en nuestro caso ocupa 13 espacios de memoria en bytes. De esta manera proporcionaremos fácilmente los datos desde el propio objeto e ir repasando la memoria EEPROM hasta un número límitado de veces que es la longitud del vector.

#include <EEPROM.h>

#define SIZECOLORS 7
#define SIZENAME 10
#define RGB_SIZE 3

typedef struct {
    char name[SIZENAME]; // color name 8+nul
    uint8_t color[RGB_SIZE]; // RGB value
} colorData;

colorData white = { "WHITE", {255,255,255}};
colorData black = { "BLACK", {0,0,0}};
colorData red = {"RED", { 255,0,0}};
colorData green = {"GREEN", { 0,255,0}};
colorData blue = {"BLUE", { 0,0,255}};
colorData yellow = {"YELLOW", { 255,255,0}};
colorData brown = {"BROWN", { 140,60,20}};

colorData rgb[SIZECOLORS] ={
    white,
    black,
    red,
    green,
    blue,
    yellow,
    brown
};

void setup() {

    Serial.begin(9600);

    int address = 0; //Location we want the data to be put.
    Serial.print("Size of DataColor: ");
    Serial.println(sizeof(colorData));

    //One simple call, with the address first and the object second.
    EEPROM.put(address, rgb);

    Serial.println(" ColorData List RGB ");

    for (int i = 0 ; i < SIZECOLORS ; ++i) {
        colorData c;
        EEPROM.get(address, c);
        Serial.print("Color Name: ");
        Serial.print(c.name);
        Serial.print(" R: ");
        Serial.print(c.color[0]);
        Serial.print(" G: ");
        Serial.print(c.color[1]);
        Serial.print(" B: ");
        Serial.println(c.color[2]);

        address += sizeof(colorData);
    }

}

void loop() {

}

Una cosa que he de añadir es que si queremos meter varias estructuras diferentes o tablas con una longitud variable, esto supondrá un problema a la hora de almacenar datos en la EEPROM, ya que si los datos cambian de tamaño de un programa a otro nunca sabremos donde se encuentran los datos. Hay que reservar tipos de estructura y tablas pensando a priori qué espacio de la EEPROM, desde dónde empieza la lectura hasta dónde acaba para luego acceder  ellas.

EEPROM es realmente una librería muy útil que casi nadie utiliza y que dispone de muchas ventajas.

De esta forma termino con este tutorial, en el que podemos crear funciones para guardar datos de calibración dentro de la EEPROM con una estructura definida, para luego consultarlos sin que se desaparezcan estos valores al apagar la placa.

En caso de querer profundizar en otros métodos extendidos para almacenar estructuras complejas, está disponible la siguiente librería para gestión de memoría EEPROMArduino-EEPROMEx

https://github.com/thijse/Arduino-EEPROMEx