Fundamentos · 17 min de lectura

EEPROM Arduino: guardar datos permanentes, leer y escribir

EEPROM en Arduino: 1024 bytes en el Uno, read, write, update, put y get, límite de 100.000 escrituras, guardar contador y contraseña, Preferences en ESP32.

La EEPROM de Arduino es una pequeña memoria permanente que conserva su contenido aunque la placa se quede sin alimentación. El ATmega328P del Arduino Uno, Nano y Pro Mini tiene 1024 bytes de EEPROM, y accedes a ella con los comandos EEPROM.read(), EEPROM.write(), EEPROM.update(), EEPROM.put() y EEPROM.get() de la librería EEPROM que viene con el IDE de Arduino. La RAM donde viven tus variables se borra en cada reinicio, pero un ajuste, un contador o una contraseña que escribas en la EEPROM sigue ahí cuando la placa vuelve a encenderse.

En esta guía verás cómo funciona la EEPROM, cuánta tiene cada placa, en qué se diferencian los comandos y la pregunta que todo el mundo se hace: qué significa en la práctica su vida útil de unas 100.000 escrituras. Después montaremos paso a paso un contador que recuerda su valor tras un corte de luz, una estructura de ajustes que guarda contraseña y opciones de una sola vez y un búfer circular que multiplica por 16 la vida útil. En las últimas secciones aprenderás a hacer lo mismo en el ESP32, que no tiene EEPROM real, con la librería Preferences, y en la Raspberry Pi Pico con el sistema de archivos de MicroPython.

Arduino Uno: el procesador ATmega328P contiene 1 KB de EEPROM
La EEPROM del Arduino Uno no es un chip aparte; está dentro del ATmega328P y guarda 1024 bytes de datos permanentes en las direcciones 0–1023.

¿Qué es la EEPROM y cómo funciona?

EEPROM son las siglas de Electrically Erasable Programmable Read-Only Memory, es decir, memoria de solo lectura programable y borrable eléctricamente. Cada celda guarda la información como una carga eléctrica atrapada en una puerta aislada, y esa carga no se pierde al cortar la alimentación. Imagina la EEPROM del Uno como un mueble con 1024 cajones: cada cajón tiene una dirección de 0 a 1023 y cabe exactamente 1 byte, un número entre 0 y 255. Una celda que nunca se ha escrito se lee como 255 (0xFF). Leer es casi instantáneo e ilimitado; escribir tarda unos 3,3 ms por byte y desgasta un poco la celda. La hoja de datos del ATmega328P especifica al menos 100.000 ciclos de escritura y borrado por celda de EEPROM y 100 años de retención de datos a 25 °C.

CaracterísticaFlash (memoria de programa)SRAMEEPROM
Tamaño en el Uno32 KB (0,5 KB para el bootloader)2 KB1 KB (1024 bytes)
¿Qué guarda?El sketch que subisteVariables, arraysAjustes, contadores, récords
Al cortar la alimentaciónSe conservaSe borraSe conserva
¿Se puede escribir mientras corre el sketch?Normalmente no (con PROGMEM solo se lee)Sí, sin límiteSí, con la librería EEPROM
Ciclos de escrituraUnos 10.000IlimitadosUnos 100.000 por celda
Velocidad de escrituraSolo al subir el códigoUn ciclo de relojUnos 3,3 ms por byte

En resumen, la SRAM es una pizarra y la EEPROM es un cuaderno. En la pizarra puedes escribir y borrar todo lo que quieras, pero al apagar la luz todo desaparece; lo que escribes en el cuaderno se queda, aunque no puedes borrar y reescribir la misma página eternamente. Por eso la EEPROM es ideal para datos pequeños que cambian poco: valores de calibración, ajustes del usuario, brillo o volumen, el récord de un juego, el código de una puerta, tarjetas RFID registradas o el último estado de una lámpara. Para datos grandes que se escriben a menudo, como un registro de temperatura con una medida por segundo, un módulo de tarjeta SD es mejor opción. Si te preguntas por qué la RAM se llena tan rápido, lee Arduino sin memoria suficiente.

¿Cuánta EEPROM tiene cada placa?

PlacaProcesadorEEPROMNota
Arduino Uno R3, Nano, Pro MiniATmega328P1024 bytesEEPROM real
Arduino Mega 2560ATmega25604096 bytesEEPROM real
Arduino Leonardo, MicroATmega32U41024 bytesEEPROM real
Arduino Nano EveryATmega4809256 bytesEEPROM real, pero pequeña
Arduino Uno R4 Minima, WiFiRenesas RA4M18 KBFlash de datos; la librería EEPROM funciona con los mismos comandos
ESP32ESP32 (Xtensa)No tienePartición NVS en la flash; se recomienda Preferences
ESP8266 (NodeMCU, Wemos D1)ESP8266No tieneEmulada en la flash; necesita EEPROM.begin() y commit()
Raspberry Pi PicoRP2040No tieneUn archivo en MicroPython, EEPROM emulada en la flash con el core de Arduino

Si tu código va a pasar de una placa a otra, no escribas el tamaño de la EEPROM a mano; EEPROM.length() devuelve el tamaño real de la placa (1024 en el Uno, 4096 en el Mega), así el mismo código de borrado o lectura funciona en todas. Para el resto de detalles de memoria y pines del Uno consulta Pines del Arduino Uno, y para elegir placa lee ¿Qué Arduino comprar?.

Comandos de la EEPROM: read, write, update, put y get

La librería EEPROM viene con el paquete de la placa, así que no hay nada que instalar; basta con añadir la línea #include <EEPROM.h> al principio del sketch. Las direcciones empiezan en 0 y en el Uno terminan en 1023. La tabla resume qué hace cada comando y si desgasta las celdas:

ComandoQué haceTamañoDesgaste
EEPROM.read(dirección)Lee un byte (0–255)1 byteNinguno, leer es gratis
EEPROM.write(dirección, valor)Reescribe el byte en cada llamada1 byteGasta un ciclo en cada llamada
EEPROM.update(dirección, valor)Lee primero y escribe solo si el valor es distinto1 byteSolo cuando cambia el valor
EEPROM.put(dirección, dato)Escribe un int, float, array o struct byte a byte; usa update por dentrosizeof(dato)Solo los bytes que cambian
EEPROM.get(dirección, variable)Lee lo que escribió put, con el mismo tiposizeof(variable)Ninguno
EEPROM[dirección]Acceso como un array; leer es gratis, asignar con = se comporta como write1 byteEn cada asignación
EEPROM.length()Devuelve el tamaño total de la EEPROM-Ninguno
EEPROM.write() y EEPROM.read() solo manejan un byte. Si escribes un número mayor que 255, como 300, el sketch compila sin errores, pero solo se guarda el byte bajo: 300 − 256 = 44. Para datos de varios bytes como int, long, float y structs usa siempre EEPROM.put() y EEPROM.get(). Del mismo modo, la asignación EEPROM[5] = x se comporta como write, no como update.

Primer sketch: escribir y leer datos en la EEPROM

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3void setup() {
4  Serial.begin(9600);
5  Serial.print(F("EEPROM boyutu / size: "));
6  Serial.println(EEPROM.length());            // Uno'da 1024 / 1024 on the Uno
7
8  byte first = EEPROM.read(0);                // 0. adresi oku / read address 0
9  Serial.print(F("Adres 0 / address 0: "));
10  Serial.println(first);                      // ilk seferde 255 / 255 the first time
11
12  EEPROM.update(0, 42);                       // farklıysa yazar / writes only if different
13  EEPROM.write(1, 7);                         // her seferinde yazar / writes every time
14
15  int number = 300;
16  EEPROM.put(10, number);                     // int = 2 bayt: 10 ve 11 / int = 2 bytes: 10 and 11
17  float temperature = 23.75;
18  EEPROM.put(12, temperature);                // float = 4 bayt: 12-15 / float = 4 bytes: 12-15
19
20  int readNumber;
21  float readTemperature;
22  EEPROM.get(10, readNumber);                 // put ile aynı tür / same type as put
23  EEPROM.get(12, readTemperature);
24  Serial.println(readNumber);                 // 300
25  Serial.println(readTemperature);            // 23.75
26
27  EEPROM.update(20, 300);                     // HATA: yalnız alt bayt (44) kalır / BUG: only the low byte (44) is kept
28  Serial.println(EEPROM.read(20));            // 44
29}
30
31void loop() {}

Sube el sketch y abre el Monitor Serie a 9600 baudios. En la primera ejecución la dirección 0 devuelve 255, porque esa celda nunca se ha escrito. Ahora pulsa el botón de reset de la placa o desconecta y vuelve a conectar el cable USB: esta vez aparece 42. Esa es la prueba de que la EEPROM conserva los datos. En cambio, la línea write() de la dirección 1 reescribe la celda en cada arranque aunque el valor sea siempre 7; con update() solo se habría escrito la primera vez. El valor 300 de la última línea muestra la trampa del aviso anterior, y en pantalla sale 44. Si la opción Advertencias del compilador de Preferencias no está en Ninguno, el compilador marca esta línea con large integer implicitly truncated to unsigned type.

Cuando usas put() y get() tienes que saber cuántos bytes ocupa cada dato; si no, escribirás dos variables una encima de otra y estropearás una. En el ejemplo el int ocupa las direcciones 10 y 11, por eso el float empieza en la 12. Estos son los tamaños de los tipos de datos en el Uno:

Tipo de datoTamaño en el UnoRangoEjemplo de uso
byte, bool, char1 byte0–255, true/false o un carácterEstado encendido/apagado, modo
int2 bytes−32.768 a 32.767Desfase de calibración
unsigned int, uint16_t2 bytes0–65.535Récord de un juego
long, unsigned long4 bytesUnos ±2.100 millones o 0–4.290 millonesContador de larga duración
float4 bytes6–7 cifras de precisiónFactor de calibración de un sensor
array char[10]10 bytes9 caracteres más el terminadorContraseña, nombre del dispositivo
Crea un mapa de direcciones al principio del sketch, por ejemplo const int ADDR_MODE = 0; const int ADDR_SCORE = 1; const int ADDR_CAL = 3;. Calcula siempre la siguiente dirección como la dirección anterior + sizeof(dato anterior) y deja algunos bytes libres entre medias para poder añadir campos más adelante.

Vida útil de la EEPROM: ¿qué significa el límite de 100.000 escrituras?

100.000 escrituras parecen muchas, pero un sketch mal planteado puede agotarlas en minutos. La fórmula de la vida útil es sencilla: vida útil = 100.000 ÷ frecuencia de escritura en la misma celda. Lo importante es que el límite se aplica a cada celda por separado, no a toda la EEPROM; si sigues escribiendo en la misma dirección, solo se desgasta esa celda. Leer no afecta en absoluto a la vida útil. La tabla muestra cuánto dura una celda cuando se escribe la misma dirección con distinta frecuencia:

Frecuencia de escritura en la misma direcciónVida útil aproximada
write() en loop() sin pausa (unas 300 veces por segundo)5–6 minutos
Cada segundoUnas 28 horas
Cada minutoUnos 69 días
Cada 10 minutosUnos 1,9 años
Una vez por horaUnos 11 años
10 veces al díaUnos 27 años

100.000 es el mínimo que garantiza el fabricante; muchos chips aguantan bastante más, pero no diseñes tu proyecto contando con ello. Una celda desgastada no muere de golpe: empieza a no retener el valor escrito y algunos bits cambian solos. Si pasa, puedes mover los datos a otra dirección, porque las demás celdas no se ven afectadas. Formas de alargar la vida útil:

  • Usa update() y put(): Los dos leen la celda antes de escribir y no escriben nada si el valor es el mismo.
  • Escribe solo cuando haya cambios: Guarda un ajuste cuando el usuario lo cambie de verdad, no en cada vuelta de loop().
  • Retrasa el guardado: Si un valor cambia muchas veces seguidas, escríbelo una sola vez unos segundos después del último cambio; con millis() es muy fácil.
  • Escribe en eventos concretos: Guarda el récord al terminar la partida y los ajustes del menú al salir de él.
  • Reparte las escrituras: Escribe un contador que cambia a menudo en varias direcciones por turnos en lugar de en una (ver el ejemplo del búfer circular).
  • Usa memoria externa si hace falta: Para guardar muy a menudo elige una EEPROM externa de 1 millón de ciclos, una FRAM o una tarjeta SD.

Ejemplo 1: un contador que sobrevive a los cortes de luz

En el primer ejemplo haremos un contador que sube cada vez que se pulsa un pulsador y que sigue donde se quedó después de apagar y encender la placa. Conecta una pata del pulsador a D2 y la otra a GND; usamos la resistencia pull-up interna, así que no hace falta ninguna resistencia extra (detalles en Cómo conectar un pulsador a Arduino, y la posición de los pines en la herramienta Pinout). El truco es que el sketch no guarda el contador en cada pulsación, sino 5 segundos después de la última. Así, aunque alguien pulse 20 veces seguidas, la EEPROM se escribe una sola vez.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3const byte BUTTON_PIN = 2;                     // buton D2 ile GND arasında / button between D2 and GND
4const int  COUNT_ADDR = 0;                     // sayaç 0-3. baytlarda / counter in bytes 0-3
5const unsigned long SAVE_DELAY = 5000;         // son basıştan 5 sn sonra yaz / write 5 s after the last press
6
7unsigned long count = 0;
8bool dirty = false;                            // kaydedilmemiş değişiklik / unsaved change
9unsigned long lastPress = 0;
10unsigned long lastChange = 0;
11bool lastReading = HIGH;
12bool stableState = HIGH;
13
14void setup() {
15  Serial.begin(9600);
16  pinMode(BUTTON_PIN, INPUT_PULLUP);
17  EEPROM.get(COUNT_ADDR, count);
18  if (count == 0xFFFFFFFF) count = 0;          // boş EEPROM / blank EEPROM
19  Serial.print(F("Kayitli sayac / stored count: "));
20  Serial.println(count);
21}
22
23void loop() {
24  bool reading = digitalRead(BUTTON_PIN);
25  if (reading != lastReading) {                // pin değişti, sayacı başlat / pin changed, restart timer
26    lastChange = millis();
27    lastReading = reading;
28  }
29  if (millis() - lastChange > 30 && reading != stableState) {   // 30 ms sabit kaldı / stable for 30 ms
30    stableState = reading;
31    if (stableState == LOW) {                  // yeni basış / new press
32      count++;
33      dirty = true;
34      lastPress = millis();
35      Serial.println(count);
36    }
37  }
38
39  if (dirty && millis() - lastPress > SAVE_DELAY) {   // sessiz dönemden sonra yaz / write after a quiet period
40    EEPROM.put(COUNT_ADDR, count);
41    dirty = false;
42    Serial.println(F("EEPROM'a yazildi / saved to EEPROM"));
43  }
44}

La variable dirty indica que hay un cambio en la RAM que aún no se ha guardado. El contador es un unsigned long, así que ocupa 4 bytes (direcciones 0–3) y puede contar más de 4.000 millones de pulsaciones. En una EEPROM vacía esos cuatro bytes valen 0xFF, de modo que en el primer arranque se lee 4.294.967.295; al ver ese valor, el código empieza el contador desde cero. Como put() solo escribe los bytes que cambian, al subir el contador casi siempre se desgasta solo el byte más bajo. Si se corta la luz en los 5 segundos previos al guardado, se pierden las últimas pulsaciones; es un precio pequeño por cuidar la EEPROM.

Ejemplo 2: guardar ajustes y contraseña en un struct

En lugar de escribir varios ajustes en direcciones sueltas, es más ordenado y seguro reunirlos en un struct y guardarlos con un solo put(). En este ejemplo guardamos el brillo de un LED y una contraseña de 4–6 caracteres. Los dos primeros campos de la estructura son clave: magic (un número mágico) nos dice si los ajustes de este sketch se han escrito de verdad en la EEPROM, y version evita que se lean datos antiguos e incompatibles cuando más adelante añadas un campo a la estructura. Si alguno no coincide, el sketch escribe los ajustes por defecto.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3const uint16_t MAGIC = 0xB6A1;                 // "ayar yazıldı" işareti / "settings written" marker
4const byte VERSION = 1;                        // yapı değişince artır / bump when the struct changes
5const int  SETTINGS_ADDR = 0;
6const byte LED_PIN = 9;                        // PWM pini / PWM pin
7
8struct Settings {
9  uint16_t magic;
10  byte version;
11  char password[7];                            // en çok 6 karakter + '\0' / up to 6 chars + '\0'
12  byte brightness;                             // LED parlaklığı 0-255 / LED brightness 0-255
13};
14
15Settings cfg;
16
17void writeDefaults() {
18  cfg.magic = MAGIC;
19  cfg.version = VERSION;
20  strcpy(cfg.password, "1234");
21  cfg.brightness = 128;
22  EEPROM.put(SETTINGS_ADDR, cfg);
23}
24
25void setup() {
26  Serial.begin(9600);
27  pinMode(LED_PIN, OUTPUT);
28  EEPROM.get(SETTINGS_ADDR, cfg);
29  if (cfg.magic != MAGIC || cfg.version != VERSION) {   // ilk açılış ya da eski yapı / first boot or old layout
30    writeDefaults();
31    Serial.println(F("Varsayilan ayarlar yazildi / defaults written"));
32  }
33  analogWrite(LED_PIN, cfg.brightness);
34  Serial.print(F("Ayar boyutu / settings size: "));
35  Serial.println(sizeof(Settings));            // Uno'da 11 bayt / 11 bytes on the Uno
36  Serial.println(F("Komutlar / commands: b0-255   p<eski>,<yeni>   ?"));
37}
38
39void loop() {
40  if (!Serial.available()) return;
41  String line = Serial.readStringUntil('\n');
42  line.trim();
43
44  if (line.startsWith("b")) {                  // örnek / example: b200
45    cfg.brightness = constrain(line.substring(1).toInt(), 0, 255);
46    analogWrite(LED_PIN, cfg.brightness);
47    EEPROM.put(SETTINGS_ADDR, cfg);            // yalnız değişen bayt yazılır / only the changed byte is written
48    Serial.println(F("Parlaklik kaydedildi / brightness saved"));
49  } else if (line.startsWith("p")) {           // örnek / example: p1234,5678
50    int comma = line.indexOf(',');
51    if (comma < 0) {
52      Serial.println(F("Bicim / format: p1234,5678"));
53      return;
54    }
55    String oldPw = line.substring(1, comma);
56    String newPw = line.substring(comma + 1);
57    if (oldPw != cfg.password) {
58      Serial.println(F("Eski sifre yanlis / wrong old password"));
59    } else if (newPw.length() < 4 || newPw.length() > 6) {
60      Serial.println(F("Yeni sifre 4-6 karakter olmali / new password must be 4-6 chars"));
61    } else {
62      newPw.toCharArray(cfg.password, sizeof(cfg.password));
63      EEPROM.put(SETTINGS_ADDR, cfg);
64      Serial.println(F("Sifre degisti / password changed"));
65    }
66  } else if (line == "?") {
67    Serial.print(F("Parlaklik / brightness: "));
68    Serial.println(cfg.brightness);
69  }
70}

Conecta el LED a D9 a través de una resistencia para LED (con 220 ohmios basta); D9 es un pin PWM. Abre el Monitor Serie a 9600 baudios, pon el ajuste de fin de línea en Nueva línea y prueba estos comandos: b200 pone el brillo a 200, p1234,5678 cambia la contraseña de 1234 a 5678 y ? muestra el brillo actual. Cuando apagues y enciendas la placa, el LED vuelve con el mismo brillo y la nueva contraseña sigue valiendo. El struct ocupa 11 bytes en el Uno; como put() solo escribe los bytes que cambian, modificar el brillo desgasta una sola celda. Puedes adaptar la misma estructura a una cerradura con teclado o guardar en un array los ID de 4 bytes de las tarjetas autorizadas con el RFID RC522. Si los comandos por serie son nuevos para ti, lee Monitor Serie de Arduino.

Nunca metas un objeto String en la EEPROM. Un String no contiene el texto en sí, sino su dirección y su longitud en la RAM; al hacer EEPROM.put(0, nombre) se guarda en la EEPROM un puntero de pocos bytes en lugar del texto, y tras reiniciar lees caracteres sin sentido. Guarda siempre el texto como un array de char de tamaño fijo, como en el ejemplo. Además, la EEPROM no está cifrada: cualquiera que conecte un programador a la placa puede leerla. Sirve para cerraduras caseras, no para algo que necesite seguridad real.

Multiplicar la vida útil: búfer circular (wear leveling)

Escribir un contador que se actualiza a menudo siempre en la misma dirección agota esa celda muy rápido. Con un búfer circular (ring buffer) escribes el valor por turnos en distintas posiciones: un guardado va a una posición, el siguiente a la siguiente y, tras la última, vuelves al principio. Cada posición se escribe solo una vez cada 16 guardados, así que la vida útil se multiplica por 16, hasta 1,6 millones de guardados aproximadamente. Al arrancar se leen todas las posiciones para encontrar el valor más reciente; como el contador solo sube, el valor más grande es el más nuevo.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3const int  RING_START = 100;                   // halka 100. adresten başlar / ring starts at address 100
4const byte SLOTS = 16;                         // 16 yuva x 4 bayt = 64 bayt / 16 slots x 4 bytes = 64 bytes
5
6uint32_t counter = 0;
7byte slot = 0;
8
9void loadCounter() {
10  for (byte i = 0; i < SLOTS; i++) {
11    uint32_t v;
12    EEPROM.get(RING_START + i * 4, v);
13    if (v != 0xFFFFFFFF && v >= counter) {     // en büyük değer en yenisidir / the largest value is the newest
14      counter = v;
15      slot = i;
16    }
17  }
18}
19
20void saveCounter() {
21  slot = (slot + 1) % SLOTS;                   // her kayıtta sıradaki yuva / next slot on every save
22  EEPROM.put(RING_START + slot * 4, counter);
23}
24
25void setup() {
26  Serial.begin(9600);
27  loadCounter();
28  counter++;                                   // örnek: her açılışı say / example: count every boot
29  saveCounter();
30  Serial.print(F("Acilis sayisi / boot count: "));
31  Serial.println(counter);
32  Serial.print(F("Kullanilan yuva / slot used: "));
33  Serial.println(slot);
34}
35
36void loop() {}

El ejemplo incrementa el contador en cada arranque; puedes sustituirlo por un pulsador, un evento de un sensor o un contador de horas de funcionamiento. Más posiciones significan proporcionalmente más vida: 64 posiciones ocupan 256 bytes y aguantan unos 6,4 millones de guardados. Si la placa se usó antes con otros sketches, puede haber datos viejos en la zona del búfer, así que borra la EEPROM con el código de más abajo antes del primer uso. En proyectos más avanzados también puedes añadir una suma de comprobación CRC, como en el ejemplo eeprom_crc de Arduino, para detectar datos corruptos.

¿Cómo se borra la EEPROM? ¿Se borra al subir un sketch?

Una subida normal por el cable USB solo escribe la memoria de programa; los datos de la EEPROM se conservan. Por eso, al subir un proyecto nuevo, los ajustes del anterior siguen en la EEPROM y el código nuevo puede leer valores que no espera. En el Uno la EEPROM solo se borra cuando usas un programador ISP con Sketch > Cargar usando el programador o Herramientas > Grabar Bootloader; estas operaciones borran el chip entero, y el bit EESAVE que protege la EEPROM está desactivado en los fusibles del Uno. Para borrar la EEPROM tú mismo, sube este sketch una vez:

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3void setup() {
4  for (uint16_t i = 0; i < EEPROM.length(); i++) {
5    EEPROM.update(i, 255);                     // fabrika durumu 0xFF / factory state 0xFF
6  }
7  pinMode(LED_BUILTIN, OUTPUT);
8  digitalWrite(LED_BUILTIN, HIGH);             // LED yandı: bitti / LED on: done
9}
10
11void loop() {}

El ejemplo Archivo > Ejemplos > EEPROM > eeprom_clear del IDE de Arduino escribe 0 en todas las celdas; nosotros, en cambio, las devolvemos a su estado de fábrica, 255. Así, el código que comprueba un número mágico o 0xFF vuelve a reconocer la EEPROM como vacía. Como usamos update(), las celdas que ya valen 255 ni se tocan. Escribir las 1024 celdas del Uno tarda como mucho unos 3,4 segundos; cuando se enciende el LED integrado, ha terminado. Después sube tu sketch de verdad.

EEPROM en el ESP32: la librería Preferences

El ESP32 no tiene EEPROM real. Los datos permanentes se guardan en la partición NVS (Non-Volatile Storage) de la memoria flash externa; en la tabla de particiones por defecto esa zona mide 20 KB. Espressif recomienda la librería Preferences, que viene con el paquete de placas ESP32. Preferences guarda los datos por espacio de nombres (namespace) y clave (key) en lugar de por direcciones, así que nunca tienes que recordar en qué dirección estaba el contador. NVS escribe cada valor nuevo en la siguiente entrada libre de la página y marca la anterior como borrada, de modo que el reparto del desgaste (wear leveling) es automático. Los nombres de namespace y de clave pueden tener como máximo 15 caracteres y distinguen mayúsculas y minúsculas. Si eres nuevo con el ESP32, empieza por Qué es el ESP32.

C++ (Arduino IDE)
1#include <Preferences.h>
2
3Preferences prefs;
4
5void setup() {
6  Serial.begin(115200);
7  prefs.begin("bluegrays", false);             // ad alanı; false = okuma + yazma / namespace; false = read + write
8
9  uint32_t boots = prefs.getUInt("boots", 0);  // anahtar yoksa 0 / 0 if the key is missing
10  boots++;
11  prefs.putUInt("boots", boots);
12
13  if (!prefs.isKey("ssid")) {                  // ilk açılış / first boot
14    prefs.putString("ssid", "EvAgi");          // örnek değer / example value
15  }
16  String ssid = prefs.getString("ssid", "");   // kayıtlı Wi-Fi adı / stored Wi-Fi name
17  int brightness = prefs.getInt("bright", 128);   // yoksa 128 / 128 if missing
18
19  Serial.print("Acilis / boot: ");
20  Serial.println(boots);
21  Serial.print("SSID: ");
22  Serial.println(ssid);
23  Serial.print("Parlaklik / brightness: ");
24  Serial.println(brightness);
25  Serial.print("Bos kayit yeri / free entries: ");
26  Serial.println(prefs.freeEntries());
27  prefs.end();                                 // ad alanını kapat / close the namespace
28}
29
30void loop() {}
ComandoQué hace
prefs.begin("nombre", false)Abre el namespace; false es lectura y escritura, true solo lectura
putUInt, putInt, putFloat, putBool, putString, putBytesGuarda el valor con un nombre de clave
getUInt("clave", por_defecto)Lee el valor; si la clave no existe devuelve el valor por defecto
isKey("clave")Comprueba si la clave existe
remove("clave")Borra una sola clave
clear()Borra todas las claves del namespace abierto
freeEntries()Devuelve cuántas entradas libres quedan en NVS
end()Cierra el namespace

Tienes que leer un valor con el mismo tipo con el que lo escribiste: si escribes con putUInt y lees con getInt, obtienes el valor por defecto. Llamar a isKey() antes de getString() también tiene su motivo: si Core Debug Level en el menú Herramientas está en Error o superior, leer una clave de texto que no existe muestra nvs_get_str len fail: ssid NOT_FOUND en el Monitor Serie; no hace daño, pero confunde. Volver a subir el código por USB no borra NVS, pero si Erase All Flash Before Sketch Upload está en Enabled, cada subida elimina todas las entradas. Guardar los datos de la Wi-Fi con Preferences es habitual; si prefieres introducir el nombre de la red y la contraseña desde el móvil, también sirve ESP32 WiFiManager, y el ESP32 guarda su configuración Wi-Fi en esa misma zona NVS. Para valores de vida corta que solo deben sobrevivir al despertar del sueño profundo es mejor la memoria RTC, que no desgasta la flash; más detalles en ESP32 deep sleep.

Si pasas código antiguo de Arduino al ESP32, la librería EEPROM también funciona, con dos diferencias: antes de usarla debes indicar cuántos bytes necesitas con EEPROM.begin(tamaño) y, después de los cambios, llamar a EEPROM.commit(). put() y write() solo cambian la copia en RAM; sin commit() no llega nada a la flash. En el ESP32 esta librería funciona como un único bloque de datos dentro de NVS y Espressif la considera obsoleta (deprecated); todos los bytes de una zona recién creada valen 0xFF. El mismo código compila también para el ESP8266, donde el tamaño máximo es de 4096 bytes y cada commit() borra y reescribe un sector de flash de 4 KB, así que llama a commit con moderación.

C++ (Arduino IDE)
1#include <EEPROM.h>
2
3void setup() {
4  Serial.begin(115200);
5  EEPROM.begin(64);                            // kullanılacak bayt sayısı / number of bytes to use
6  uint32_t boots;
7  EEPROM.get(0, boots);
8  if (boots == 0xFFFFFFFF) boots = 0;          // yeni alan 0xFF dolu / a fresh area is 0xFF
9  boots++;
10  EEPROM.put(0, boots);                        // yalnız RAM'deki kopyayı değiştirir / changes only the RAM copy
11  EEPROM.commit();                             // flaşa şimdi yazılır / written to flash now
12  Serial.println(boots);
13  EEPROM.end();
14}
15
16void loop() {}

Datos permanentes en la Raspberry Pi Pico: el sistema de archivos de MicroPython

El chip RP2040 de la Raspberry Pi Pico tampoco tiene EEPROM; la placa lleva 2 MB de memoria flash. MicroPython reserva unos 1,4 MB de esa flash para un sistema de archivos LittleFS (menos en la Pico W por el firmware de Wi-Fi). Así que en la Pico, el equivalente de la EEPROM es escribir los ajustes en un pequeño archivo. LittleFS reparte las escrituras entre distintos bloques de la flash, por lo que gestiona él mismo el desgaste, y el sistema de archivos no se corrompe aunque se vaya la luz durante una escritura. El siguiente código guarda los ajustes en formato JSON, cuenta cada arranque y guarda el contador cada vez que se pulsa un pulsador conectado entre GP15 y GND.

MicroPython
1import json, os, time
2from machine import Pin
3
4FILE = "settings.json"
5DEFAULTS = {"boots": 0, "count": 0, "password": "1234"}
6
7def load():
8    try:
9        with open(FILE) as f:
10            data = json.load(f)
11    except (OSError, ValueError):            # dosya yok ya da bozuk / file missing or corrupt
12        data = {}
13    for key, value in DEFAULTS.items():      # eksik anahtarları tamamla / fill in missing keys
14        data.setdefault(key, value)
15    return data
16
17def save(data):
18    with open(FILE + ".tmp", "w") as f:      # önce geçici dosyaya yaz / write a temp file first
19        json.dump(data, f)
20    os.rename(FILE + ".tmp", FILE)           # sonra eskisinin yerine koy / then replace the old one
21
22settings = load()
23settings["boots"] += 1
24save(settings)
25print("Acilis / boot:", settings["boots"], "Sayac / count:", settings["count"])
26
27button = Pin(15, Pin.IN, Pin.PULL_UP)        # buton GP15 ile GND arasında / button between GP15 and GND
28
29while True:
30    if button.value() == 0:                  # basıldı / pressed
31        time.sleep_ms(30)                    # sıçramayı bekle / let the bounce settle
32        if button.value() == 0:
33            settings["count"] += 1
34            save(settings)                   # seyrek olay, her basışta yaz / rare event, save on each press
35            print("Sayac / count:", settings["count"])
36            while button.value() == 0:       # bırakılmasını bekle / wait for release
37                time.sleep_ms(10)
38            time.sleep_ms(30)                # bırakma sıçraması / release bounce
39    time.sleep_ms(10)

En lugar de sobrescribir el archivo directamente, primero escribimos en un archivo temporal .tmp y luego usamos os.rename() para ponerlo en el lugar del antiguo; así el archivo de ajustes anterior queda intacto aunque la escritura se interrumpa. Si el archivo no existe o su contenido está dañado, load() vuelve a los valores por defecto, y cuando más adelante añadas un ajuste nuevo al programa, setdefault() completa la clave que falte automáticamente. Si programas la Pico con el IDE de Arduino, también puedes usar la librería EEPROM del core Arduino-Pico de Earle Philhower: EEPROM.begin(256) abre una zona de 256–4096 bytes y EEPROM.commit() guarda los cambios. Esta EEPROM emulada está en el último sector de 4 KB de la flash y cada commit() borra y reescribe ese sector; la flash aguanta unos 100.000 ciclos de borrado por sector, así que haz commit con moderación. Si estás empezando con la Pico, Qué es la Raspberry Pi Pico y Fundamentos de MicroPython son un buen punto de partida.

Más memoria permanente: EEPROM externa, FRAM y tarjeta SD

Si 1 KB no te basta o necesitas guardar muy a menudo, puedes añadir memoria externa. Los chips EEPROM I2C como el AT24C256 ofrecen 32 KB y alrededor de 1 millón de ciclos de escritura, y se conectan solo con los cables SDA y SCL (Qué es I2C). Un detalle poco conocido: muchos módulos de reloj RTC DS3231 del mercado ya llevan una EEPROM AT24C32 de 4 KB, normalmente accesible en la dirección I2C 0x57. Para tareas que guardan muchas veces por segundo encajan mejor los módulos FRAM, que aguantan alrededor de un billón (10^12) de ciclos de escritura, y para registros de datos grandes, un módulo de tarjeta SD que guarda los datos en archivos.

Ideas de proyectos con EEPROM

  • Lámpara que recuerda su último estado: Cuando vuelva la luz, devuelve la lámpara conectada a un relé al estado que tenía antes del corte.
  • Caja fuerte con código modificable: Escribe en la EEPROM el nuevo código introducido con un teclado matricial para cambiarlo sin volver a subir el sketch.
  • Cerradura con tarjeta: Guarda los ID de las tarjetas autorizadas leídas con el RFID RC522 y añade tarjetas nuevas con una tarjeta maestra.
  • Récord permanente: Conserva la mejor puntuación en Simon, T-Rex o Snake; tienes código de ejemplo en Proyectos de juegos con Arduino.
  • Brazo robótico que graba movimientos: Graba los ángulos de los servos pose a pose y reprodúcelos; el código de Brazo robótico con Arduino lo hace con EEPROM.update().
  • Báscula que no olvida su calibración: Guarda como float con put() el factor de calibración obtenido con la celda de carga HX711.
  • Contador de reinicios: Cuenta cuántas veces se reinicia la placa para diagnosticar problemas de alimentación; el método está en Arduino se reinicia solo.

Errores frecuentes y cómo solucionarlos

SíntomaCausaSolución
En el primer arranque se lee 255, -1, 65535 o nanLa EEPROM nunca se ha escrito; las celdas vacías valen 0xFFEscribe valores por defecto si falla la comprobación del número mágico o de 0xFF
Escribí 300 y leo 44write() y read() solo llevan 1 byteUsa put() y get() para datos de varios bytes
Dos valores se estropean entre síLas direcciones se solapan; un int ocupa 2 bytes y un float 4Haz un mapa de direcciones y calcúlalas con sizeof
El texto sale con caracteres raros tras reiniciarSe guardó un objeto String con put()Guarda el texto como array de char
El código nuevo lee los ajustes del proyecto anteriorUna subida normal no borra la EEPROMUsa número mágico y campo de versión, o borra la EEPROM
Los ajustes se borraron al subir el códigoSubida con programador en el Uno, opción Erase All Flash en el ESP32Sube normalmente por USB y pon la opción del ESP32 en Disabled
En el ESP32 no se guardan los valoresFalta EEPROM.begin() o EEPROM.commit()Añade los dos o pásate a Preferences
Preferences siempre devuelve el valor por defectoLectura con otro tipo, nombre de más de 15 caracteres u otro namespaceIguala los tipos de put y get y usa nombres cortos
Error nvs_open failed: NOT_FOUNDSe abrió en solo lectura (true) un namespace que nunca se creóAbre primero el namespace con false y escribe un valor
Los valores se corrompen con el tiempoEscritura continua en loop() o caída de tensión durante una escrituraUsa update() y guardado diferido, añade búfer circular y CRC
Ver qué hay en la EEPROM es la forma más rápida de depurar. Lee las primeras 64 direcciones con EEPROM.read() en un bucle for y muéstralas en el Monitor Serie (o abre el ejemplo eeprom_read que trae Arduino) y verás al instante las celdas vacías, los valores escritos en direcciones equivocadas y los restos de proyectos antiguos.

La EEPROM es la forma más sencilla de dar memoria a tus proyectos con Arduino: con unas pocas líneas proteges los ajustes, contadores y contraseñas de tu dispositivo frente a los cortes de luz. Lo único que debes vigilar es la vida útil de escritura; mientras uses update() y put() y no escribas sin necesidad, la EEPROM del Uno funcionará sin problemas durante años. En la app BlueGrays encontrarás proyectos que puedes ampliar con EEPROM, como la aplicación de contraseña con teclado 4x4, la caja fuerte electrónica inteligente, la autenticación RFID y los juegos Simon y T-Rex, cada uno con su esquema de circuito y su código paso a paso.

Preguntas frecuentes

¿Cuántas veces se puede escribir la EEPROM de Arduino?

La hoja de datos del ATmega328P especifica al menos 100.000 ciclos de escritura y borrado por celda de EEPROM. El límite es por celda y leer no le afecta. Escribir la misma dirección una vez por segundo agota la celda en unas 28 horas; si usas update() y solo escribes cuando cambia el valor, la EEPROM dura años.

¿Se borra la EEPROM de Arduino al subir un sketch nuevo?

No. Una subida normal por USB solo escribe la memoria de programa y los datos de la EEPROM se conservan. En el Uno la EEPROM solo se borra al subir con un programador ISP o al quemar el bootloader. Usa una comprobación con número mágico para que un proyecto nuevo no herede ajustes viejos.

¿Qué diferencia hay entre EEPROM.write y EEPROM.update?

write() reescribe el valor en la celda en cada llamada y gasta un ciclo de escritura cada vez. update() lee primero la celda y no escribe nada si el valor es el mismo. El resultado es idéntico, pero update() protege la vida útil de la EEPROM; put() usa el mismo método por dentro.

¿Cómo borrar la EEPROM de Arduino?

Escribe 255 (o 0) en todas las direcciones con un bucle for que llegue hasta EEPROM.length(); el ejemplo eeprom_clear del IDE de Arduino ya escribe ceros. Borrar los 1024 bytes del Uno tarda unos 3,4 segundos. Al terminar, no dejes el sketch de borrado en la placa: sube tu programa.

¿El ESP32 tiene EEPROM?

El ESP32 no tiene EEPROM real; los datos permanentes se guardan en la partición NVS de la memoria flash. Para proyectos nuevos se recomienda la librería Preferences. La librería EEPROM sigue funcionando con código antiguo, pero hay que llamar primero a EEPROM.begin(tamaño) y a EEPROM.commit() después de cada cambio.

¿Cómo guardar un int, un float o un texto en la EEPROM?

Los datos de varios bytes como int, long, float y structs se escriben con EEPROM.put(dirección, variable) y se leen con EEPROM.get(dirección, variable). Un int ocupa 2 bytes y un float 4, así que elige la siguiente dirección en consecuencia. Guarda el texto como un array de char de tamaño fijo, no como String.

Ver todos los proyectos
Encender y apagar un LED con un botón
#10Principiante

Encender y apagar un LED con un botón

Pulsas el botón una vez y la luz se enciende, lo pulsas otra vez y se apaga, igual que el interruptor de tu habitación. El Arduino nota cada pulsación y cambia el LED al estado contrario, esperando un

4.8(100)
167
Contador con display de 7 segmentos y botones
#43Avanzado
Premium

Contador con display de 7 segmentos y botones

Un contador sencillo con tres botones: con uno el número de la pantalla sube de uno en uno, con otro baja, y el tercero lo devuelve a cero. La pantalla de 7 segmentos de un solo dígito cuenta del 0 al

4.6(35)
47
Cerradura con contraseña, teclado 4x4 y LCD
#64Avanzado
Premium

Cerradura con contraseña, teclado 4x4 y LCD

Una pequeña cerradura electrónica que no deja pasar a quien no sepa la clave. Pulsas la tecla de almohadilla y escribes la clave en el teclado; si es correcta, la pantalla indica acceso permitido, se

4.8(59)
75
Caja fuerte electrónica inteligente
#92Avanzado
Premium

Caja fuerte electrónica inteligente

Una pequeña caja fuerte para tus cosas de valor que se abre con un código. Escribes tu código de cuatro cifras en el teclado; si es correcto, el servomotor abre el cierre, se enciende la luz verde y l

4.7(42)
63
Autenticación RFID
#54Intermedio
Premium

Autenticación RFID

Como el acceso con tarjeta del portal de un edificio: acercas tu tarjeta al lector RFID y el Arduino compara su número con el que tiene guardado. Con la tarjeta correcta se enciende el LED verde duran

4.8(64)
83
Juego de memoria con Arduino (4 LED + 4 pulsadores + zumbador)
#93Avanzado
Premium

Juego de memoria con Arduino (4 LED + 4 pulsadores + zumbador)

¿Te fías de tu memoria? Cuatro LED de colores se encienden en secuencia, cada uno con su propia nota, y tú repites el mismo orden pulsando los botones. Cada ronda acertada añade un color nuevo a la se

4.8(46)
69
OLED T-Rex Run: Juego del dinosaurio con Arduino
#87Avanzado
Premium

OLED T-Rex Run: Juego del dinosaurio con Arduino

El juego del dinosaurio que aparece cuando se va internet, ahora en tu mesa: el dinosaurio corre por la pequeña pantalla OLED, los cactus se le echan encima y tú saltas con un solo botón. Tu puntuació

4.7(33)
53
Modo de suspensión profunda del ESP32 — Ahorro de batería (OLED + contador en RTC RAM)
#135Avanzado
Premium

Modo de suspensión profunda del ESP32 — Ahorro de batería (OLED + contador en RTC RAM)

El modo «Deep Sleep» del ESP32 reduce el consumo de corriente a unos 10 µA al desactivar la CPU y la mayoría de los periféricos, lo cual es de vital importancia para los dispositivos IoT que funcionan con pilas. En este proyecto utilizamos tres fuentes de activación diferentes: el temporizador (15 s), un pin externo (EXT0 — botón GPIO 33) y el sensor táctil (T0/GPIO 4). Las variables marcadas con RTC_DATA_ATTR se conservan durante el modo Deep Sleep: el contador de arranque, el contador de act

4.3(10)
16