Cuando declaras una variable en un programa de Arduino su vida dura lo que dura la alimentación de Arduino a no ser que utilices la memoria EEPROM. Lo que quiero decir es que si reinicias o apagas Arduino, los datos almacenados desaparecen.
No se mantienen guardados como si fuera un disco duro.
Esto es un verdadero problema si, por ejemplo, quiere almacenar una fecha o una hora concreta en la que quieres empezar a regar tus plantas.
Para solventar este problema y poder mantener los datos almacenados para uso futuro, debemos usar un tipo de memoria que se llama EEPROM (siglas en inglés Electrically Erasable Programmable Read-Only Memory). Esta memoria nos permite guardar datos incluso cuando el Arduino se reinicia o se apaga.
De todo esto hablaremos en este artículo.
Indice de contenidos
¿Qué es la memoria EEPROM?
El microcontrolador de la placa de Arduino (en el caso de un Arduino UNO y Arduino Nano es el ATMEGA328 y en Arduino MEGA es el ATMEGA2560) tiene una memoria EEPROM, una memoria que permite almacenar variables de forma permanente.
Incluso cuando se reinicia o se apaga Arduino, mantendrá sus valores. Es como si fuera el disco duro de tu ordenador pero con una capacidad mucho más pequeña como veremos más adelante.
Puedes escribir, leer y borrar los datos de la EEPROM fácilmente gracias a la librería para Arduino que se llama EEPROM.
¿Cuántos bytes podemos almacenar?
Las memorias se dividen en posiciones o huecos y cada una puede almacenar un byte lo que quiere decir que cada posición puede almacenar un número de 8-bit (un número de 0 a 255).
La capacidad de la memoria EEPROM depende del microcontrolador incluso hay microcontroladores que no disponen de memoria EEPROM como le sucede al ESP8266.
En la siguiente tabla puedes ver las memorias EEPROM de diferentes microcontroladores que se utilizan en las placas de Arduino.
Microcontrolador | EEPROM |
---|---|
ATmega328 (Arduino UNO, Nano, Mini) | 1024 bytes |
ATmega168 (Arduino Nano) | 512 bytes |
ATmega2560 (Arduino Mega) | 4096 bytes |
La memoria EEPROM tiene una vida limitada. En Arduino su vida es de 100.000 ciclos de escritura por cada posición de memoria, es decir, la posición 1 solo podrás escribir o actualizar un valor 100.000 veces.
Aunque este límite te parezca poco en realidad es más que suficiente para todos nuestros proyectos.
Cómo se almacenan los datos en una memoria EEPROM
La memoria EEPROM está dividida en huecos. Cada hueco representa un byte. Es la unidad mínima que puede almacenar cada hueco.
Recuerda que 1 byte es un número entre 0 y 255.
En el caso de Arduino UNO, el microcontrolador ATmega328 tiene 1024 bytes, es decir, 1024 posiciones de memoria.
Para guardar un dato tenemos que indicar la posición de la memoria y el dato. Por ejemplo, si quieres almacenar el número 25 en la posición 0 tendrás que decir:
guardar(posición:0, dato:25)
El número 25 ocuparía una posición de memoria pero ¿qué pasa si quieres almacenar un número mayor que un byte o una cadena de texto?
Ocupará más de una posición de memoria. Un tipo de dato int ocupa 2 bytes y por lo tanto ocupará dos huecos.
Un tipo float ocupa 4 bytes.
Y una cadena de texto depende de los caracteres. Cada letra ocupa un byte por lo tanto Arduino ocupará 7 bytes.
Qué pasa si queremos guardar primero el número 25 y luego la palabra Arduino. En estos casos hay que indicar desde dónde empieza a guardar la información
Y luego indicamos que guarde Arduino a partir de la posición 1
guardar(posición:1, dato:Arduino)
Lo que quiero decir con todo esto es que la gestión de la memoria debemos hacerla nosotros mismos. Si por ejemplo escribes un 12 en la posición 6, sobreescribirá la letra n.
Toda esta gestión depende de nosotros pero gracias a la librería EEPROM y a un poco de sentido común, nos resultará muy fácil.
Aplicaciones donde es útil almacenar información en la memoria EEPROM
Imagina que utilizas una aplicación como Blynk para enviar información a Arduino y viceversa o que necesitas almacenar cualquier tipo de configuración. En tipo de aplicaciones puedes comprobar cómo esos datos no se mantienen ni en la aplicación ni en Arduino.
Cuando la aplicación se reinicia se pierden.
Cuando Arduino se resetea o pierde alimentación se pierden.
La memoria EEPROM es un buen aliado en estos casos.
Por ejemplo, creas una aplicación para controlar tiras de LEDs en la que puedes configurar el modo de animación de las luces.
En concreto puedes elegir entre 3 modos:
- Modo Navidad
- Modo fundido
- Modo loco
Cuando seleccionas un modo se envía a Arduino donde se almacena en una variable global.
De repente, alguien de tu familia desconecta de la alimentación Arduino. Al iniciarse de nuevo no sabe qué modo ejecutar ya que esa información se ha perdido y selecciona el modo por defecto, el asignado a la variable en el momento de su declaración.
Este es un caso típico donde la memoria EEPROM nos puede ser muy útil.
Librería EEPROM para Arduino
Puedes leer, escribir o actualizar un valor de la memoria EEPROM utilizando la librería de Arduino EEPROM como veremos a continuación.
Para poder utilizarla en cualquier programa lo primero es incluirla.
1 |
#include <EEPROM.h> |
Esta librería está preparada para leer byte a byte o tipos de datos más complejos como int o float. A continuación tienes las funciones más importantes.
Escribir
Para escribir un byte en la memoria EEPROM se utiliza la función EEPROM.write().
No hace falta declarar un objeto de la clase ya que se trata de una clase estática como la clase Serial. Con solo poner el nombre seguido de un punto puedes acceder a sus funciones.
Esta función admite dos parámetros.
1 |
EEPROM.write(direccion, valor); |
Donde
- direccion: es la posición de la memoria EEPROM empezando desde 0.
- valor: es el dato que se quiere guardar. Debe ser del tipo byte.
Si lo que quieres es guardar una variable mayor que un byte, debes utilizar la función EEPROM.put().
Esta función admite dos parámetros.
1 |
EEPROM.put(direccion, valor); |
Donde:
- direccion: es la posición de la memoria EEPROM donde se quiere empezar a escribir empezando por cero.
- valor: es el valor que se quiere guardar. Puede ser cualquier tipo (por ejemplo float, int, byte, …).
Leer
Para leer un byte de la memoria EEPROM utiliza la función EEPROM.read().
Esta función admite un único parámetro.
1 |
EEPROM.read(direccion); |
Donde:
- direccion: es la dirección de memoria donde hay que leer el byte
Devuelve un valor del tipo byte.
Para leer cualquier tipo de dato mayor o igual que un byte utiliza la función EEPROM.get().
Esta función admite dos parámetros.
1 |
EEPROM.get(direccion, valor); |
Donde:
- direccion: es la dirección de memoria donde hay que leer el dato.
- valor: es la variable del tipo de dato que se va a leer. En ella se almacenará el valor. Debe coincidir con el tipo de dato guardado.
Actualizar un valor
Para actualizar un byte de la memoria EEPROM utiliza la función EEPROM.update(). Sólo actualiza el valor si es diferente al que se pasa en la función, en caso contrario no escribirá en la memoria.
Muy útil para evitar no gastar ciclos de escritura. Recuerda que la vida útil de la memoria EEPROM son 100.000 ciclos de escritura.
Sólo sirve para guardar datos de byte en byte no pudiendo ser utilizada con otros tipos de datos.
Esta función admite dos parámetros.
1 |
EEPROM.update(direccion, valor); |
Donde:
- direccion: es la dirección de memoria que se pretende actualizar.
- valor: es el valor que se quiere actualizar. Si es el mismo que el que está guardado, no escribirá en la memoria.
Ejemplo práctico librería memoria EEPROM con Arduino
A continuación vamos a hacer un ejemplo práctico bastante sencillo donde almacenaremos un estado que nos indique qué LED debe activarse al encender de nuevo nuestra placa de Arduino.
La selección se hace a través de un pulsador con Arduino.
Esquema eléctrico ejemplo memoria EEPROM con Arduino
El esquema eléctrico del ejemplo práctico sería el siguiente.
El circuito es bastante sencillo solo requiere del siguiente material:
- Arduino UNO o compatible.
- 3 LEDs de diferentes colores aunque pueden ser del mismo color
- 3 resistencias de 220 ohmios
- 1 pulsador
- Cables
- Protoboard
Las resistencias no importa en qué orden se conecten. El pulsador no necesita una resistencia pull-up o pull-down ya que la configuraremos por software.
Puedes utilizar los pines que quieras pero acuérdate luego en el código cambiarlos.
La idea es que cuando doy al pulsador cambie el LED encendido pero que a la vez guarde la información en la memoria EEPROM para que si se apaga, se encienda exactamente el mismo LED que se había seleccionado.
Para conseguirlo voy a utilizar la librería EEPROM. Esta librería no hay que instalarla ya que viene instalada por defecto cuando instalas el IDE de Arduino.
Vamos a ver directamente el código.
Código ejemplo memoria EEPROM con Arduino
El código completo sería el siguiente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
#include <EEPROM.h> // LEDS const byte ledRojo = 3; const byte ledNaranja = 4; const byte ledVerde = 5; // Pulsador const byte pulsador = 2; // Variabel de modo animación // Si vale 1 → LED rojo (por defecto) // Si vale 2 → LED naranja // Si vale 3 → LED verde byte modoAnimacion = 0; // Posición de memoria const byte posicionMemoria = 0; void setup() { // Serial Serial.begin(9600); // Modo pines pinMode(ledRojo, OUTPUT); pinMode(ledNaranja, OUTPUT); pinMode(ledVerde, OUTPUT); pinMode(pulsador, INPUT_PULLUP); // Lectura modo animación modoAnimacion = EEPROM.read(posicionMemoria); Serial.print("Modo Animacion cargado EEPROM: "); Serial.println(modoAnimacion); // En el caso de no ser ningún valor entre 1 y 3, hay que poner un modo por defecto if (modoAnimacion < 1 || modoAnimacion > 3) { // Guardar el valor por defecto EEPROM.write(posicionMemoria, 1); // Valor por defecto animación y contador modoAnimacion = 1; } } void loop() { // Leer pulsador boolean pulsado = digitalRead(pulsador); delay(200); // Si se ha pulsado sumar una al contador if (!pulsado) { modoAnimacion++; // Comprobar que no se salga de rango if (modoAnimacion > 3) { modoAnimacion = 1; } Serial.print("Modo Animacion: "); Serial.println(modoAnimacion); } // Empezar con todos los LEDs apagados digitalWrite(ledRojo, LOW); digitalWrite(ledNaranja, LOW); digitalWrite(ledVerde, LOW); // Encender el LED correspondiente if (modoAnimacion == 1) { // LED rojo digitalWrite(ledRojo, HIGH); } else if (modoAnimacion == 2) { // LED naranja digitalWrite(ledNaranja, HIGH); } else if (modoAnimacion == 3) { // LED verde digitalWrite(ledVerde, HIGH); } // Comprobar si ha cambiado el valor // actualizar memoria EEPROM if (!pulsado) { Serial.println("Cambio modo animación EEPROM"); EEPROM.write(posicionMemoria, modoAnimacion); } } |
Voy a analizarlo por bloques comenzando por la librería y variables.
Librería y variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <EEPROM.h> // LEDS const byte ledRojo = 3; const byte ledNaranja = 4; const byte ledVerde = 5; // Pulsador const byte pulsador = 2; // Variabel de modo animación // Si vale 1 → LED rojo (por defecto) // Si vale 2 → LED naranja // Si vale 3 → LED verde byte modoAnimacion = 0; // Posición de memoria const byte posicionMemoria = 0; |
Para utilizar la memoria EEPROM en el código lo primero y antes de todo hay que añadir la librería EEPROM.h. Esta librería se instala por defecto cuando instalas el IDE de Arduino. No tienes que hacer nada más.
Luego he añadido 4 variables constantes del tipo byte para almacenar los pines digitales donde están conectados los 3 LEDs y el pulsador.
La siguiente variable, modoAnimacion, sirve para almacenar el LED que está encendido según es su valor y según el siguiente criterio:
- modoAnimacion = 1 → LED rojo encendido (valor por defecto para el primer arranque)
- modoAnimacion = 2 → LED naranja encendido
- modoAnimacion = 3 → LED verde encendido
Por último necesitamos una variable constante del tipo byte para almacenar la posición de la memoria.
En este caso solo vamos a utilizar una posición de memoria que ocupa un byte. Si necesitas almacenar otro byte tendrías que establecer la posición de la memoria de ese segundo byte a 1 y así sucesivamente.
Veamos ahora la función setup().
Función setup()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void setup() { // Serial Serial.begin(9600); // Modo pines pinMode(ledRojo, OUTPUT); pinMode(ledNaranja, OUTPUT); pinMode(ledVerde, OUTPUT); pinMode(pulsador, INPUT_PULLUP); // Lectura modo animación modoAnimacion = EEPROM.read(posicionMemoria); Serial.print("Modo Animacion cargado EEPROM: "); Serial.println(modoAnimacion); // En el caso de no ser ningún valor entre 1 y 3, hay que poner un modo por defecto if (modoAnimacion < 1 || modoAnimacion > 3) { // Guardar el valor por defecto EEPROM.write(posicionMemoria, 1); // Valor por defecto animación y contador modoAnimacion = 1; } } |
Comenzamos la función setup iniciando el monitor serie con Serial.begin(9600).
Luego ponemos el modo de los pines donde está conectado el LED en modo OUTPUT.
El pin donde está conectado el pulsador lo ponemos en modo INPUT_PULLUP. Es un modo especial de entrada donde se activa una resistencia pull-up interna y así nos evitamos tener que poner físicamente. Si no haces esto el pin del pulsador se quedará al aire y tendrás valores aleatorios.
Lo siguiente es leer la posición de memoria de la EEPROM para obtener el dato guardado. El resultado lo almacenamos en la variable modoAnimacion.
La siguiente condicional if nos permite determinar si está dentro de los valores posibles. Buscamos un valor entre 1 y 3 de no ser así es que el dato o está corrupto o es la primera vez que se lee.
También puede ser que algún código anterior haya modificado esa posición de memoria.
Sea como sea hay que comprobarlo para partir de un modo conocido, entre 1 y 3. De no ser así asignamos el modo 1 tanto a la variable modoAnimacion como en la posición de memoria EEPROM correspondiente.
Ahora vamos a la función loop().
Función loop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
void loop() { // Leer pulsador boolean pulsado = digitalRead(pulsador); delay(200); // Si se ha pulsado sumar una al contador if (!pulsado) { modoAnimacion++; // Comprobar que no se salga de rango if (modoAnimacion > 3) { modoAnimacion = 1; } Serial.print("Modo Animacion: "); Serial.println(modoAnimacion); } // Empezar con todos los LEDs apagados digitalWrite(ledRojo, LOW); digitalWrite(ledNaranja, LOW); digitalWrite(ledVerde, LOW); // Encender el LED correspondiente if (modoAnimacion == 1) { // LED rojo digitalWrite(ledRojo, HIGH); } else if (modoAnimacion == 2) { // LED rojo digitalWrite(ledNaranja, HIGH); } else if (modoAnimacion == 3) { // LED rojo digitalWrite(ledVerde, HIGH); } // Comprobar si ha cambiado el valor // actualizar memoria EEPROM if (!pulsado) { Serial.println("Cambio modo animación EEPROM"); EEPROM.write(posicionMemoria, modoAnimacion); } } |
En la función loop() es donde está toda la lógica.
Nada más empezar leemos el pulsador. El tiempo de espera que se sitúa justo a continuación sirve para evitar el fatídico efecto rebote de los pulsadores. Lo suyo sería utilizar una interrupción de Arduino pero para no complicar en exceso el código he decidido hacerlo así.
Una vez leído, detectamos si ha cambiado de estado lo que quiere decir que se ha pulsado.
Te preguntarás, ¿por qué utiliza Luis la condición !pulsado?
Muy buena pregunta :)
Como he dicho en la función setup(), hemos configurado el pin del pulsador con la resistencia pull-up interna. Esto quiere decir que cuando no esté pulsado tendremos siempre a la entrada de ese pin 5V, es decir, un estado HIGH.
Debido a que el pulsador está cableado para que cuando se pulse tengamos 0V (estado LOW), la variable boolean pulsado siempre valdrá HIGH salvo cuando se pulse el pulsador.
De ahí que la condición sea !pulsado porque cuando sea false (pulsado) al utilizar el operador de negación (!) cambiará a true y entrará en el if.
Quizás parezca complejo pero es muy sencillo, este operador invierte el valor de la condición. Si es true lo convierte en false y viceversa.
Dentro de la condicional añadimos uno al modo y comprobamos que no se salga de rango para que si lo hace vuelva a empezar asignando a modoAnimacion un valor de 1.
A continuación apagamos todos los LEDs para empezar con un estado conocido.
Luego las siguiente 3 sentencias condicionales comprueban el modo y dependiendo del valor, se enciende un LED u otro.
Por último no hay que olvidar de guardar el estado en la memoria EEPROM. Eso sí, hay que hacerlo siempre y cuando haya un cambio de modo.
Y eso ocurre solo cuando se ha pulsado el pulsador. Por eso comprobamos la variable pulsado igual que hemos hecho arriba. Se podría utilizar la misma sentencia condicional que hay al principio del bucle loop() ya que es la misma condición pero he preferido dejarlo así para que quede más claro.
Para guardar en la posición de memoria utilizamos la función EEPROM.write().
Y ya está, así de sencillo es guardar la información en la memoria no volátil EEPROM. Vamos a probarlo.
Prueba ejemplo práctico memoria EEPROM con Arduino
Carga el código y abre el monitor serie. Aparecerá un mensaje indicando que el modo de animación es el 1 y se iluminará el LED rojo.
Ahora, por ejemplo, pulsa dos veces para que el modo sea el 3 y se ilumine el LED verde. Aparecerá algo parecido a esto en el monitor serie.
Si das al botón reset de la placa en principio la variable modoAnimacion perderá los datos pero cómo los hemos almacenado en la memoria EEPROM, al reiniciar y pasar de nuevo por la función setup() obtendrá el último valor utilizado.
Lo puedes comprobar si miras el monitor serie donde indica que el modo de animación cargado de la EEPROM ese el 3.
También puedes probar a quitar la alimentación si no te fías. Tendría que empezar iluminándose el LED verde.
Y con esto hemos visto cómo almacenar datos permanentes en Arduino utilizando la memoria EEPROM. Cualquier duda o sugerencia en los comentarios.
Que pases un feliz día y nos leemos pronto.
Lectura complementaria
Gracias a Depositphotos por la cesión de las imágenes.