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.

¿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ística | Flash (memoria de programa) | SRAM | EEPROM |
|---|---|---|---|
| Tamaño en el Uno | 32 KB (0,5 KB para el bootloader) | 2 KB | 1 KB (1024 bytes) |
| ¿Qué guarda? | El sketch que subiste | Variables, arrays | Ajustes, contadores, récords |
| Al cortar la alimentación | Se conserva | Se borra | Se conserva |
| ¿Se puede escribir mientras corre el sketch? | Normalmente no (con PROGMEM solo se lee) | Sí, sin límite | Sí, con la librería EEPROM |
| Ciclos de escritura | Unos 10.000 | Ilimitados | Unos 100.000 por celda |
| Velocidad de escritura | Solo al subir el código | Un ciclo de reloj | Unos 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?
| Placa | Procesador | EEPROM | Nota |
|---|---|---|---|
| Arduino Uno R3, Nano, Pro Mini | ATmega328P | 1024 bytes | EEPROM real |
| Arduino Mega 2560 | ATmega2560 | 4096 bytes | EEPROM real |
| Arduino Leonardo, Micro | ATmega32U4 | 1024 bytes | EEPROM real |
| Arduino Nano Every | ATmega4809 | 256 bytes | EEPROM real, pero pequeña |
| Arduino Uno R4 Minima, WiFi | Renesas RA4M1 | 8 KB | Flash de datos; la librería EEPROM funciona con los mismos comandos |
| ESP32 | ESP32 (Xtensa) | No tiene | Partición NVS en la flash; se recomienda Preferences |
| ESP8266 (NodeMCU, Wemos D1) | ESP8266 | No tiene | Emulada en la flash; necesita EEPROM.begin() y commit() |
| Raspberry Pi Pico | RP2040 | No tiene | Un 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:
| Comando | Qué hace | Tamaño | Desgaste |
|---|---|---|---|
| EEPROM.read(dirección) | Lee un byte (0–255) | 1 byte | Ninguno, leer es gratis |
| EEPROM.write(dirección, valor) | Reescribe el byte en cada llamada | 1 byte | Gasta un ciclo en cada llamada |
| EEPROM.update(dirección, valor) | Lee primero y escribe solo si el valor es distinto | 1 byte | Solo cuando cambia el valor |
| EEPROM.put(dirección, dato) | Escribe un int, float, array o struct byte a byte; usa update por dentro | sizeof(dato) | Solo los bytes que cambian |
| EEPROM.get(dirección, variable) | Lee lo que escribió put, con el mismo tipo | sizeof(variable) | Ninguno |
| EEPROM[dirección] | Acceso como un array; leer es gratis, asignar con = se comporta como write | 1 byte | En cada asignación |
| EEPROM.length() | Devuelve el tamaño total de la EEPROM | - | Ninguno |
Primer sketch: escribir y leer datos en la EEPROM
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 dato | Tamaño en el Uno | Rango | Ejemplo de uso |
|---|---|---|---|
| byte, bool, char | 1 byte | 0–255, true/false o un carácter | Estado encendido/apagado, modo |
| int | 2 bytes | −32.768 a 32.767 | Desfase de calibración |
| unsigned int, uint16_t | 2 bytes | 0–65.535 | Récord de un juego |
| long, unsigned long | 4 bytes | Unos ±2.100 millones o 0–4.290 millones | Contador de larga duración |
| float | 4 bytes | 6–7 cifras de precisión | Factor de calibración de un sensor |
| array char[10] | 10 bytes | 9 caracteres más el terminador | Contraseña, nombre del dispositivo |
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ón | Vida útil aproximada |
|---|---|
| write() en loop() sin pausa (unas 300 veces por segundo) | 5–6 minutos |
| Cada segundo | Unas 28 horas |
| Cada minuto | Unos 69 días |
| Cada 10 minutos | Unos 1,9 años |
| Una vez por hora | Unos 11 años |
| 10 veces al día | Unos 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.
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.
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.
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.
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:
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.
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() {}| Comando | Qué hace |
|---|---|
| prefs.begin("nombre", false) | Abre el namespace; false es lectura y escritura, true solo lectura |
| putUInt, putInt, putFloat, putBool, putString, putBytes | Guarda 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.
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.
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íntoma | Causa | Solución |
|---|---|---|
| En el primer arranque se lee 255, -1, 65535 o nan | La EEPROM nunca se ha escrito; las celdas vacías valen 0xFF | Escribe valores por defecto si falla la comprobación del número mágico o de 0xFF |
| Escribí 300 y leo 44 | write() y read() solo llevan 1 byte | Usa 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 4 | Haz un mapa de direcciones y calcúlalas con sizeof |
| El texto sale con caracteres raros tras reiniciar | Se guardó un objeto String con put() | Guarda el texto como array de char |
| El código nuevo lee los ajustes del proyecto anterior | Una subida normal no borra la EEPROM | Usa número mágico y campo de versión, o borra la EEPROM |
| Los ajustes se borraron al subir el código | Subida con programador en el Uno, opción Erase All Flash en el ESP32 | Sube normalmente por USB y pon la opción del ESP32 en Disabled |
| En el ESP32 no se guardan los valores | Falta EEPROM.begin() o EEPROM.commit() | Añade los dos o pásate a Preferences |
| Preferences siempre devuelve el valor por defecto | Lectura con otro tipo, nombre de más de 15 caracteres u otro namespace | Iguala los tipos de put y get y usa nombres cortos |
| Error nvs_open failed: NOT_FOUND | Se 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 tiempo | Escritura continua en loop() o caída de tensión durante una escritura | Usa update() y guardado diferido, añade búfer circular y CRC |
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.
Proyectos para hacer después de leer esto
Ver todos los proyectos
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

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

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

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

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

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

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ó

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
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

