Cómo salvar los valores de Arduino con EEPROM

¿Qué es la memoria EEPROM?

Dentro de tu ordenador, tienes uno o varios discos duros para almacenar todos tus datos. ¿Pero qué hay de un tablero de Arduino? ¿Cómo puede guardar los valores directamente en la placa Arduino sin un dispositivo de almacenamiento externo?

EEPROM son las siglas de «Electrically Erasable Programmable Read-Only Memory» (Memoria de sólo lectura programable y borrable eléctricamente). Los microcontroladores utilizados en la mayoría de las placas de Arduino tienen 512, 1024 o 4096 bytes de memoria EEPROM incorporada en el chip. Esta memoria no es volátil, lo que significa que los datos no se borran cuando la placa pierde energía.

Puedes ver la EEPROM de Arduino como una matriz donde cada elemento es un byte. Cuando lees y escribes en esta memoria, especificas una dirección que en el mundo de Arduino equivale a un índice de matriz.

La EEPROM tiene una vida útil total de ~100.000 ciclos de escritura. ¡Ten cuidado al escribir el código para no escribir en la EEPROM demasiado a menudo! Recuerda que borrar la memoria también es una operación de escritura.

La memoria EEPROM te permite mantener los valores dentro de tu placa Arduino, incluso si la apagas y la enciendes.

Pero es un tipo de memoria muy diferente a la que puedes encontrar en tu propio ordenador.

Aquí hay algunas características:

  • La EEPROM es muy limitada. Mientras que un disco duro puede almacenar hasta varios terabytes de datos, sólo puedes almacenar unos pocos bytes, a veces kilobytes en la EEPROM.
  • No todas las placas de Arduino tienen EEPROM. En Arduino Uno y Mega, tienes 1024 bytes, pero si tienes un Arduino Zero, no tienes EEPROM disponible.
  • Hay un límite en cuanto a la cantidad de veces que puedes escribir en una sola ubicación de la memoria EEPROM. Después de unas 100.000 operaciones de escritura, la ubicación de la memoria podría estar muerta. Por eso es necesario manipular esta memoria con precauciones.
  • Para almacenar números en múltiples bytes (int, long, double, …) necesitas saber cuántos bytes tomará cada valor, para poder espaciar los valores en consecuencia en la memoria.

Por lo tanto, no esperes almacenar una imagen de la cámara, o incluso un documento escrito en la memoria EEPROM. Esta memoria es realmente adecuada para valores pequeños, por ejemplo una configuración por defecto para aplicar en el arranque, o una preferencia de usuario.

Cómo guardar un valor en la EEPROM

Vamos a empezar a escribir algo de código:

#include <EEPROM.h>

void setup() {
  EEPROM.write(0, 7);
  EEPROM.write(3, 50); 
}

void loop() { }

Primero, tienes que incluir la biblioteca de EEPROM en la parte superior de tu archivo.

Escribimos aquí 2 valores en la memoria EEPROM:

  • Número 7 en la dirección número 0
  • Número 50 en la dirección número 3

Ahora, los valores se almacenan, e incluso si reinicias tu placa Arduino con un programa totalmente diferente, esos valores seguirán estando aquí, en las direcciones 0 y 3.

Algunos puntos importantes a tener en cuenta:

  • No escribas varios valores en la misma dirección, de lo contrario perderás el número previamente escrito (a menos que eso sea lo que quieras hacer)
  • Un lugar de memoria sólo puede almacenar un byte de datos. Por lo tanto, para los números entre 0 y 255, está bien, pero para otros números tendrás que dividir el número en varios bytes, y almacenar cada byte por separado. Por ejemplo, un valor doble en Arduino Uno toma 4 bytes. Además, eso significa que sólo puedes almacenar 1024/4 = 256 valores dobles en la memoria EEPROM.
  • No escribas un valor en la EEPROM dentro de un bucle infinito sin ningún tipo de retraso o comprueba la entrada del usuario. Recuerda que sólo tienes unos 100 000 ciclos de escritura disponibles por dirección. Si sólo escribes en la EEPROM en la función loop() sin ningún otro código, podrías destruir el almacenamiento de la EEPROM bastante rápido.

Leyendo un valor de la EEPROM

Después de haber escrito algunos valores en la EEPROM, ahora puedes reiniciar tu Arduino o simplemente reiniciar tu programa. Los valores seguirán estando ahí, y leerlos es bastante fácil.

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);

  int value1 = EEPROM.read(0);
  Serial.println(value1);

  int value2 = EEPROM.read(3);
  Serial.println(value2);
}

void loop() { }

Ten en cuenta que la regla de los 100.000 es sólo para la escritura. Puedes leer de la EEPROM todo lo que quieras sin ningún problema.

Si has guardado un número que requiere más de un byte (por ejemplo, el doble), entonces tendrás que leer todas las direcciones de este número, y reconstruir el número de nuevo con todos los bytes.

Completar el código de la aplicación: Guardar un valor dado por un usuario para hacer parpadear un LED

Ahora veremos un ejemplo real usando una placa Arduino Uno y 4 LEDs conectados a pines digitales (con resistencias de 220 Ohm).

Esquema de conexiones:

conexión de arduino Uno a 4 leds y resistencias

Lo que queremos hacer:

  • Elegir qué LED se enciende dependiendo de la entrada del usuario (de la comunicación en serie)
  • Guardar la última elección del usuario
  • Cuando el tablero se reinicia, enciende el último LED elegido por el usuario

El código

#include <EEPROM.h>

#define LED_1_PIN 9
#define LED_2_PIN 10
#define LED_3_PIN 11
#define LED_4_PIN 12

#define ARRAY_SIZE 4
#define LAST_SELECTED_LED_EEPROM_ADDR 10

int ledPinArray[4] = { LED_1_PIN, LED_2_PIN, LED_3_PIN, LED_4_PIN };

int i;

void setLedPinModes() 
{
  for (i = 0 ; i < ARRAY_SIZE ; i++) {
    pinMode(ledPinArray[i], OUTPUT);
  }
}

void setInitialLedStates()
{
  for (i = 0 ; i < ARRAY_SIZE ; i++) {
    digitalWrite(ledPinArray[i], LOW);
  }
}

void powerOnLed(int ledIndex)
{
  for (i = 0; i < ARRAY_SIZE; i++) {
    if (i == ledIndex) {
      digitalWrite(ledPinArray[i], HIGH);
    }
    else {
      digitalWrite(ledPinArray[i], LOW);
    }
  }
}


void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  setLedPinModes();
  int lastSelectedLedIndex = EEPROM.read(LAST_SELECTED_LED_EEPROM_ADDR);
  powerOnLed(lastSelectedLedIndex);
}

void loop() {
  // put your main code here, to run repeatedly:

  if (Serial.available() > 0) {
    int ledIndex = Serial.parseInt();
    if (ledIndex >= 0 && ledIndex < ARRAY_SIZE) {
      powerOnLed(ledIndex);
      // write on the eeprom
      EEPROM.write(LAST_SELECTED_LED_EEPROM_ADDR, ledIndex);
    }
  }
}

Ahora vamos a desglosar el código paso a paso para que puedas entender cómo funciona el proyecto.

El código explicado paso a paso

#include <EEPROM.h>

#define LED_1_PIN 9
#define LED_2_PIN 10
#define LED_3_PIN 11
#define LED_4_PIN 12

#define ARRAY_SIZE 4
#define LAST_SELECTED_LED_EEPROM_ADDR 10

int ledPinArray[4] = { LED_1_PIN, LED_2_PIN, LED_3_PIN, LED_4_PIN };

Primero incluimos la biblioteca EEPROM y definimos algunos nombres para los pines usados para todos los componentes del hardware. Esta es una buena práctica que os animamos a seguir a partir de ahora (si no lo estáis haciendo ya).

Declaramos una matriz para los 4 LEDs para que podamos manejarlos fácilmente más tarde.

int i;

void setLedPinModes() 
{
  for (i = 0 ; i < ARRAY_SIZE ; i++) {
    pinMode(ledPinArray[i], OUTPUT);
  }
}

void setInitialLedStates()
{
  for (i = 0 ; i < ARRAY_SIZE ; i++) {
    digitalWrite(ledPinArray[i], LOW);
  }
}

void powerOnLed(int ledIndex)
{
  for (i = 0; i < ARRAY_SIZE; i++) {
    if (i == ledIndex) {
      digitalWrite(ledPinArray[i], HIGH);
    }
    else {
      digitalWrite(ledPinArray[i], LOW);
    }
  }
}

 

Escribimos algunas funciones para que el código sea más claro.

  • La función setLedPinModes() se usará para establecer el modo (salida para los LEDs) en la función setup().
  • La función setInitialLedStates() apagará todos los LEDs.
  • La función powerOnLed() toma un parámetro: el índice de LED en el array que declaramos previamente. Encenderá el LED elegido y apagará todos los demás LED.
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  setLedPinModes();
  int lastSelectedLedIndex = EEPROM.read(LAST_SELECTED_LED_EEPROM_ADDR);
  powerOnLed(lastSelectedLedIndex);
}

Aquí iniciamos la comunicación en serie y ponemos todos los pines de los LEDs en salida.

Luego, leemos de la memoria EEPROM para encontrar qué LED fue elegido por última vez por el usuario. Una vez que sabemos qué LED era, podemos encenderlo.

void loop() {
  // put your main code here, to run repeatedly:

  if (Serial.available() > 0) {
    int ledIndex = Serial.parseInt();
    if (ledIndex >= 0 && ledIndex < ARRAY_SIZE) {
      powerOnLed(ledIndex);
      // write on the eeprom
      EEPROM.write(LAST_SELECTED_LED_EEPROM_ADDR, ledIndex);
    }
  }
}

Sólo hacemos una cosa en la función loop(): esperamos una entrada de usuario.

Cuando el usuario envía un número, encendemos el LED que corresponde al índice dado, y guardamos este índice en la memoria EEPROM. De esta manera, podemos recuperar este valor en el próximo arranque, y eso es precisamente lo que hacemos dentro de la función setup().

Cómo añadir más seguridad a la memoria EEPROM

Nota importante: antes dijimos que no escribieras a la EEPROM dentro de un bucle infinito. La función loop() es infinita, así que ¿por qué estamos haciendo eso?

Bueno, estamos esperando una entrada del usuario, y el bloque de código donde usamos EEPROM.write() sólo será llamado cuando el usuario envíe algo. Así que podemos considerarlo mucho más seguro para la memoria.

Es muy improbable que el usuario envíe 100 000 valores en muy poco tiempo. Pero no siempre se puede confiar en lo que el usuario hará. Una mejora aquí podría ser añadir un intervalo mínimo de tiempo entre 2 operaciones de escritura, por ejemplo medio segundo. De esta manera, incluso si el usuario envía miles de valores, la memoria EEPROM se conservará.

También se puede utilizar la función EEPROM.update() en lugar de EEPROM.write(). Esta leerá primero el valor almacenado actual y comprobará si es diferente de lo que quiere escribir. Si el valor es diferente, se escribirá. Si no, entonces no se escribe nada y sólo se guarda un ciclo de escritura. Ten en cuenta que esto lleva más tiempo, ya que hay más cálculos involucrados, por lo que no siempre es una buena idea.

Conclusión

Con Arduino, la EEPROM incorporada es una forma práctica de almacenar datos permanentemente. El lenguaje Arduino lo ha hecho súper fácil de usar, como se demuestra en el ejemplo anterior. Sin embargo, ten mucho cuidado de no escribir muy a menudo en la EEPROM ya que tiene una vida útil limitada.

El uso de la memoria EEPROM con Arduino te permitirá construir aplicaciones más complejas. Puedes guardar algunos ajustes predeterminados o preferencias de usuario para empezar cuando reinicies tu Arduino.

Esto también podría ser una posición, por ejemplo si estás construyendo un robot. La posición podría ser las últimas coordenadas (x,y) antes de que el robot se apague. Entonces, cuando el robot arranque, volverá a esas coordenadas y continuará trabajando desde allí.

Hay miles de casos de uso en los que la memoria EEPROM es útil.

Sin ir más lejos, puedes empezar a buscar cómo almacenar números más grandes con un tipo de datos diferentes, como el int largo, el doble, las cadenas, etcétera.

Pin It on Pinterest

Shares