EEPROM Arduino: salvare dati permanenti, leggere e scrivere
EEPROM Arduino: 1024 byte sull'Uno, read, write, update, put e get, limite di 100.000 scritture, salvare contatore e password, Preferences sull'ESP32.
La EEPROM di Arduino è una piccola memoria permanente che conserva il suo contenuto anche quando la scheda resta senza alimentazione. L'ATmega328P di Arduino Uno, Nano e Pro Mini ha 1024 byte di EEPROM, a cui accedi con i comandi EEPROM.read(), EEPROM.write(), EEPROM.update(), EEPROM.put() ed EEPROM.get() della libreria EEPROM inclusa nell'IDE di Arduino. La RAM che contiene le tue variabili si cancella a ogni riavvio, mentre un'impostazione, un contatore o una password scritti nella EEPROM sono ancora lì quando la scheda si riaccende.
In questa guida vedrai come funziona la EEPROM, quanta ne ha ogni scheda, in cosa differiscono i comandi e la domanda che tutti si fanno: che cosa significa in pratica la sua durata di circa 100.000 scritture. Poi costruiremo passo passo un contatore che ricorda il suo valore dopo un blackout, una struttura di impostazioni che salva password e opzioni in un colpo solo e un buffer circolare che moltiplica per 16 la durata. Nelle ultime sezioni imparerai a fare lo stesso sull'ESP32, che non ha una vera EEPROM, con la libreria Preferences, e sul Raspberry Pi Pico con il file system di MicroPython.

Che cos'è la EEPROM e come funziona?
EEPROM è l'acronimo di Electrically Erasable Programmable Read-Only Memory, cioè memoria a sola lettura programmabile e cancellabile elettricamente. Ogni cella memorizza l'informazione come carica elettrica intrappolata in un gate isolato, e questa carica non si perde quando manca l'alimentazione. Immagina la EEPROM dell'Uno come un mobile con 1024 cassetti: ogni cassetto ha un indirizzo da 0 a 1023 e contiene esattamente 1 byte, cioè un numero tra 0 e 255. Una cella mai scritta si legge come 255 (0xFF). La lettura è quasi istantanea e illimitata; la scrittura richiede circa 3,3 ms per byte e consuma un po' la cella. Il datasheet dell'ATmega328P garantisce almeno 100.000 cicli di scrittura e cancellazione per cella di EEPROM e 100 anni di conservazione dei dati a 25 °C.
| Caratteristica | Flash (memoria di programma) | SRAM | EEPROM |
|---|---|---|---|
| Dimensione sull'Uno | 32 KB (0,5 KB per il bootloader) | 2 KB | 1 KB (1024 byte) |
| Cosa contiene? | Lo sketch caricato | Variabili, array | Impostazioni, contatori, record |
| Quando manca la corrente | Si conserva | Si cancella | Si conserva |
| Scrivibile mentre lo sketch gira? | Di norma no (con PROGMEM solo lettura) | Sì, senza limiti | Sì, con la libreria EEPROM |
| Cicli di scrittura | Circa 10.000 | Illimitati | Circa 100.000 per cella |
| Velocità di scrittura | Solo durante il caricamento | Un ciclo di clock | Circa 3,3 ms per byte |
In breve, la SRAM è una lavagna e la EEPROM un quaderno. Sulla lavagna puoi scrivere e cancellare quanto vuoi, ma quando si spegne la luce sparisce tutto; quello che scrivi sul quaderno resta, però non puoi cancellare e riscrivere la stessa pagina all'infinito. Per questo la EEPROM è ideale per informazioni piccole e che cambiano di rado: valori di calibrazione, impostazioni dell'utente, luminosità o volume, il record di un gioco, il codice di una porta, tessere RFID registrate o l'ultimo stato di una lampada. Per dati grandi e scritti spesso, come un registro di temperatura con una misura al secondo, un modulo scheda SD è la scelta migliore. Se ti chiedi perché la RAM si riempie così in fretta, leggi Arduino memoria insufficiente.
Quanta EEPROM ha ogni scheda?
| Scheda | Processore | EEPROM | Nota |
|---|---|---|---|
| Arduino Uno R3, Nano, Pro Mini | ATmega328P | 1024 byte | EEPROM vera |
| Arduino Mega 2560 | ATmega2560 | 4096 byte | EEPROM vera |
| Arduino Leonardo, Micro | ATmega32U4 | 1024 byte | EEPROM vera |
| Arduino Nano Every | ATmega4809 | 256 byte | EEPROM vera, ma piccola |
| Arduino Uno R4 Minima, WiFi | Renesas RA4M1 | 8 KB | Flash dati; la libreria EEPROM funziona con gli stessi comandi |
| ESP32 | ESP32 (Xtensa) | Assente | Partizione NVS nella flash; consigliata Preferences |
| ESP8266 (NodeMCU, Wemos D1) | ESP8266 | Assente | Emulata nella flash; servono EEPROM.begin() e commit() |
| Raspberry Pi Pico | RP2040 | Assente | Un file in MicroPython, EEPROM emulata nella flash con il core Arduino |
Se il tuo codice deve passare da una scheda all'altra, non scrivere a mano la dimensione della EEPROM: EEPROM.length() restituisce la dimensione reale della scheda (1024 sull'Uno, 4096 sul Mega), così lo stesso codice di cancellazione o lettura funziona ovunque. Per gli altri dettagli di memoria e pin dell'Uno guarda Pin di Arduino Uno, e per scegliere la scheda leggi Quale Arduino comprare?.
I comandi della EEPROM: read, write, update, put e get
La libreria EEPROM arriva con il pacchetto della scheda, quindi non c'è nulla da installare: basta aggiungere la riga #include <EEPROM.h> all'inizio dello sketch. Gli indirizzi partono da 0 e sull'Uno finiscono a 1023. La tabella riassume cosa fa ogni comando e se consuma le celle:
| Comando | Cosa fa | Dimensione | Usura |
|---|---|---|---|
| EEPROM.read(indirizzo) | Legge un byte (0–255) | 1 byte | Nessuna, leggere è gratis |
| EEPROM.write(indirizzo, valore) | Riscrive il byte a ogni chiamata | 1 byte | Consuma un ciclo a ogni chiamata |
| EEPROM.update(indirizzo, valore) | Legge prima e scrive solo se il valore è diverso | 1 byte | Solo quando il valore cambia |
| EEPROM.put(indirizzo, dato) | Scrive un int, float, array o struct byte per byte; usa update internamente | sizeof(dato) | Solo i byte cambiati |
| EEPROM.get(indirizzo, variabile) | Rilegge ciò che put ha scritto, con lo stesso tipo | sizeof(variabile) | Nessuna |
| EEPROM[indirizzo] | Accesso come un array; leggere è gratis, l'assegnazione con = si comporta come write | 1 byte | A ogni assegnazione |
| EEPROM.length() | Restituisce la dimensione totale della EEPROM | - | Nessuna |
Primo sketch: scrivere e leggere dati nella 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() {}Carica lo sketch e apri il Monitor seriale a 9600 baud. Alla prima esecuzione l'indirizzo 0 restituisce 255, perché quella cella non è mai stata scritta. Ora premi il pulsante di reset della scheda oppure scollega e ricollega il cavo USB: questa volta compare 42. È la prova che la EEPROM conserva i dati. La riga write() dell'indirizzo 1, invece, riscrive la cella a ogni avvio anche se il valore è sempre 7; con update() la scrittura sarebbe avvenuta solo la prima volta. Il valore 300 dell'ultima riga mostra la trappola dell'avviso precedente, e sullo schermo appare 44. Se l'opzione Warning del compilatore nelle Impostazioni non è su Nessuno, il compilatore segnala questa riga con large integer implicitly truncated to unsigned type.
Con put() e get() devi sapere quanti byte occupa ogni dato; altrimenti scriverai due variabili una sopra l'altra e ne rovinerai una. Nell'esempio l'int occupa gli indirizzi 10 e 11, per questo il float parte dall'indirizzo 12. Queste sono le dimensioni dei tipi di dato sull'Uno:
| Tipo di dato | Dimensione sull'Uno | Intervallo | Esempio d'uso |
|---|---|---|---|
| byte, bool, char | 1 byte | 0–255, true/false o un carattere | Stato acceso/spento, scelta della modalità |
| int | 2 byte | −32.768 a 32.767 | Offset di calibrazione |
| unsigned int, uint16_t | 2 byte | 0–65.535 | Record di un gioco |
| long, unsigned long | 4 byte | Circa ±2,1 miliardi o 0–4,29 miliardi | Contatore di lunga durata |
| float | 4 byte | 6–7 cifre di precisione | Fattore di calibrazione di un sensore |
| array char[10] | 10 byte | 9 caratteri più il terminatore | Password, nome del dispositivo |
Durata della EEPROM: cosa significa il limite di 100.000 scritture?
100.000 scritture sembrano tante, ma uno sketch impostato male può esaurirle in pochi minuti. La formula della durata è semplice: durata = 100.000 ÷ frequenza di scrittura sulla stessa cella. Il punto chiave è che il limite vale per ogni cella separatamente, non per l'intera EEPROM; se continui a scrivere sullo stesso indirizzo, si consuma solo quella cella. La lettura non ha alcun effetto sulla durata. La tabella mostra quanto dura una cella quando lo stesso indirizzo viene scritto con frequenze diverse:
| Frequenza di scrittura sullo stesso indirizzo | Durata approssimativa |
|---|---|
| write() in loop() senza pause (circa 300 volte al secondo) | 5–6 minuti |
| Ogni secondo | Circa 28 ore |
| Ogni minuto | Circa 69 giorni |
| Ogni 10 minuti | Circa 1,9 anni |
| Una volta all'ora | Circa 11 anni |
| 10 volte al giorno | Circa 27 anni |
100.000 è il minimo garantito dal produttore; molti chip durano molto di più, ma non progettare il tuo circuito contando su questo. Una cella consumata non muore all'improvviso: comincia a non trattenere il valore scritto e alcuni bit cambiano da soli. In quel caso puoi spostare i dati su un altro indirizzo, perché le altre celle non ne risentono. Ecco come allungare la durata:
- Usa update() e put(): Entrambi leggono la cella prima di scrivere e non scrivono nulla se il valore è uguale.
- Scrivi solo quando cambia qualcosa: Salva un'impostazione quando l'utente la modifica davvero, non a ogni giro di loop().
- Ritarda il salvataggio: Se un valore cambia molte volte di seguito, scrivilo una sola volta qualche secondo dopo l'ultima modifica; con millis() è facilissimo.
- Scrivi in occasione di eventi: Salva il record a fine partita e le impostazioni del menu quando lo si chiude.
- Distribuisci le scritture: Scrivi un contatore che cambia spesso su più indirizzi a turno invece che su uno solo (vedi l'esempio del buffer circolare).
- Usa una memoria esterna se serve: Per salvataggi molto frequenti scegli una EEPROM esterna da 1 milione di cicli, una FRAM o una scheda SD.
Esempio 1: un contatore che sopravvive ai blackout
Nel primo esempio realizziamo un contatore che aumenta a ogni pressione di un pulsante e riprende da dove si era fermato dopo aver spento e riacceso la scheda. Collega un piedino del pulsante a D2 e l'altro a GND; usiamo la resistenza di pull-up interna, quindi non serve una resistenza in più (dettagli in Come collegare un pulsante ad Arduino, posizione dei pin nello strumento Pinout). Il trucco è che lo sketch non salva il contatore a ogni pressione, ma 5 secondi dopo l'ultima. Così, anche se qualcuno preme il pulsante 20 volte di fila, la EEPROM viene scritta una sola volta.
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 variabile dirty indica che nella RAM c'è una modifica non ancora salvata. Il contatore è un unsigned long, quindi occupa 4 byte (indirizzi 0–3) e può contare più di 4 miliardi di pressioni. In una EEPROM vuota quei quattro byte valgono 0xFF, perciò al primo avvio si legge 4.294.967.295; vedendo questo valore il codice fa ripartire il contatore da zero. Poiché put() scrive solo i byte cambiati, mentre il contatore sale si consuma quasi sempre solo il byte più basso. Se la corrente manca nei 5 secondi prima del salvataggio, le ultime pressioni vanno perse; è un piccolo prezzo per proteggere la EEPROM.
Esempio 2: salvare impostazioni e password in una struct
Invece di scrivere più impostazioni su indirizzi separati, è più ordinato e più sicuro raccoglierle in una struct e salvarle con un solo put(). In questo esempio memorizziamo la luminosità di un LED e una password di 4–6 caratteri. I primi due campi della struttura sono fondamentali: magic (un numero magico) ci dice se le impostazioni di questo sketch sono state davvero scritte nella EEPROM, e version evita di leggere dati vecchi e incompatibili quando in futuro aggiungerai un campo alla struttura. Se uno dei due non corrisponde, lo sketch scrive le impostazioni predefinite.
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}Collega il LED a D9 tramite una resistenza per LED (bastano 220 ohm); D9 è un pin PWM. Apri il Monitor seriale a 9600 baud, imposta il fine riga su A capo (New Line) e prova questi comandi: b200 porta la luminosità a 200, p1234,5678 cambia la password da 1234 a 5678 e ? mostra la luminosità attuale. Quando spegni e riaccendi la scheda, il LED torna con la stessa luminosità e la nuova password resta valida. La struct occupa 11 byte sull'Uno; poiché put() scrive solo i byte cambiati, modificare la luminosità consuma una sola cella. Puoi adattare la stessa struttura a una serratura a combinazione con tastierino oppure salvare in un array gli ID da 4 byte delle tessere autorizzate con l'RFID RC522. Se i comandi seriali sono una novità per te, leggi Monitor seriale di Arduino.
Moltiplicare la durata: il buffer circolare (wear leveling)
Scrivere un contatore aggiornato spesso sempre sullo stesso indirizzo consuma in fretta quell'unica cella. Con un buffer circolare (ring buffer) scrivi il valore a turno in posizioni diverse: un salvataggio va in una posizione, il successivo in quella dopo e, dopo l'ultima, si ricomincia dall'inizio. Ogni posizione viene scritta solo una volta ogni 16 salvataggi, quindi la durata si moltiplica per 16, arrivando a circa 1,6 milioni di salvataggi. All'avvio si leggono tutte le posizioni per trovare il valore più recente; dato che il contatore cresce soltanto, il valore più grande è il più nuovo.
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() {}L'esempio incrementa il contatore a ogni avvio; puoi sostituirlo con un pulsante, un evento di un sensore o un contaore. Più posizioni significano proporzionalmente più durata: 64 posizioni occupano 256 byte e reggono circa 6,4 milioni di salvataggi. Se la scheda è già stata usata con altri sketch, nella zona del buffer potrebbero esserci vecchi dati; cancella quindi la EEPROM con il codice qui sotto prima del primo utilizzo. Nei progetti più avanzati puoi anche aggiungere un checksum CRC, come nell'esempio eeprom_crc di Arduino, per individuare i dati corrotti.
Come si cancella la EEPROM? Si cancella caricando uno sketch?
Un normale caricamento tramite cavo USB scrive solo la memoria di programma; i dati nella EEPROM restano. Per questo, quando carichi un nuovo progetto, le impostazioni del precedente rimangono nella EEPROM e il nuovo codice potrebbe leggere valori che non si aspetta. Sull'Uno la EEPROM si cancella solo quando usi un programmatore ISP con Sketch > Carica tramite programmatore (Upload Using Programmer) o Strumenti > Scrivi il bootloader (Burn Bootloader); queste operazioni cancellano l'intero chip, e il bit EESAVE che protegge la EEPROM è disattivato nei fuse dell'Uno. Per cancellare la EEPROM da solo, carica questo sketch una volta:
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() {}L'esempio File > Esempi > EEPROM > eeprom_clear dell'IDE di Arduino scrive 0 in tutte le celle; noi invece le riportiamo allo stato di fabbrica, 255. In questo modo il codice che controlla un numero magico o 0xFF riconosce di nuovo la EEPROM come vuota. Usando update(), le celle che valgono già 255 non vengono scritte. Scrivere tutte le 1024 celle dell'Uno richiede al massimo circa 3,4 secondi; quando il LED integrato si accende, il lavoro è finito. Poi carica il tuo sketch vero e proprio.
EEPROM sull'ESP32: la libreria Preferences
L'ESP32 non ha una vera EEPROM. I dati permanenti sono conservati nella partizione NVS (Non-Volatile Storage) della memoria flash esterna; nella tabella delle partizioni predefinita questa zona è di 20 KB. Espressif consiglia la libreria Preferences, inclusa nel pacchetto delle schede ESP32. Preferences salva i dati per namespace e chiave (key) invece che per indirizzo, quindi non devi mai ricordare a quale indirizzo si trovava il contatore. NVS scrive ogni nuovo valore nella successiva voce libera della pagina e segna la vecchia come cancellata, così il livellamento dell'usura (wear leveling) avviene in automatico. I nomi di namespace e chiave possono avere al massimo 15 caratteri e distinguono maiuscole e minuscole. Se l'ESP32 è nuovo per te, comincia da Cos'è l'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 | Cosa fa |
|---|---|
| prefs.begin("nome", false) | Apre il namespace; false per lettura e scrittura, true per sola lettura |
| putUInt, putInt, putFloat, putBool, putString, putBytes | Salva il valore con un nome di chiave |
| getUInt("chiave", predefinito) | Legge il valore; se la chiave non esiste restituisce il predefinito |
| isKey("chiave") | Controlla se la chiave esiste |
| remove("chiave") | Cancella una singola chiave |
| clear() | Cancella tutte le chiavi del namespace aperto |
| freeEntries() | Restituisce il numero di voci libere rimaste nella NVS |
| end() | Chiude il namespace |
Devi rileggere un valore con lo stesso tipo con cui l'hai scritto: se scrivi con putUInt e leggi con getInt, ottieni il valore predefinito. Anche chiamare isKey() prima di getString() ha un motivo: se Core Debug Level nel menu Strumenti è su Error o superiore, leggere una chiave di testo inesistente stampa nvs_get_str len fail: ssid NOT_FOUND nel Monitor seriale; è innocuo ma confonde. Ricaricare il codice via USB non cancella la NVS, ma se Erase All Flash Before Sketch Upload è su Enabled ogni caricamento elimina tutte le voci. Salvare i dati del Wi-Fi con Preferences è comune; se preferisci inserire nome della rete e password dal telefono, va bene anche ESP32 WiFiManager, e l'ESP32 conserva le sue impostazioni Wi-Fi nella stessa zona NVS. Per valori di breve durata che devono solo sopravvivere al risveglio dal deep sleep è più adatta la memoria RTC, che non consuma affatto la flash; dettagli in ESP32 deep sleep.
Quando porti vecchio codice Arduino sull'ESP32, funziona anche la libreria EEPROM, con due differenze: prima di usarla devi indicare quanti byte ti servono con EEPROM.begin(dimensione) e, dopo le modifiche, chiamare EEPROM.commit(). put() e write() cambiano solo la copia in RAM; senza commit() nella flash non arriva nulla. Sull'ESP32 questa libreria funziona come un unico blocco di dati dentro la NVS ed Espressif la considera deprecata (deprecated); tutti i byte di una zona appena creata valgono 0xFF. Lo stesso codice compila anche per l'ESP8266, dove la dimensione massima è 4096 byte e ogni commit() cancella e riscrive un settore di flash da 4 KB, quindi chiama commit con parsimonia.
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() {}Dati permanenti sul Raspberry Pi Pico: il file system di MicroPython
Nemmeno il chip RP2040 del Raspberry Pi Pico ha una EEPROM; la scheda monta 2 MB di memoria flash. MicroPython riserva circa 1,4 MB di questa flash a un file system LittleFS (meno sul Pico W per via del firmware Wi-Fi). Sul Pico, quindi, l'equivalente della EEPROM è scrivere le impostazioni in un piccolo file. LittleFS distribuisce le scritture su blocchi diversi della flash, quindi gestisce da solo l'usura, e il file system non si corrompe nemmeno se la corrente manca durante una scrittura. Il codice seguente salva le impostazioni in formato JSON, conta ogni avvio e salva il contatore a ogni pressione di un pulsante collegato tra GP15 e 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)Invece di sovrascrivere direttamente il file, scriviamo prima in un file temporaneo .tmp e poi usiamo os.rename() per metterlo al posto del vecchio; così il file di impostazioni precedente resta integro anche se la scrittura si interrompe. Se il file manca o il contenuto è danneggiato, load() torna ai valori predefiniti, e quando in futuro aggiungi una nuova impostazione al programma, setdefault() completa da solo la chiave mancante. Se programmi il Pico con l'IDE di Arduino, puoi usare anche la libreria EEPROM del core Arduino-Pico di Earle Philhower: EEPROM.begin(256) apre un'area di 256–4096 byte ed EEPROM.commit() salva le modifiche. Questa EEPROM emulata si trova nell'ultimo settore da 4 KB della flash e ogni commit() cancella e riscrive quel settore; la flash sopporta circa 100.000 cicli di cancellazione per settore, quindi fai commit con parsimonia. Se stai iniziando con il Pico, Cos'è il Raspberry Pi Pico e Le basi di MicroPython sono un buon punto di partenza.
Più memoria permanente: EEPROM esterna, FRAM e scheda SD
Se 1 KB non basta o devi salvare molto spesso, puoi aggiungere una memoria esterna. I chip EEPROM I2C come l'AT24C256 offrono 32 KB e circa 1 milione di cicli di scrittura, e si collegano solo con i fili SDA e SCL (Cos'è l'I2C). Un dettaglio poco noto: molti moduli orologio RTC DS3231 in commercio hanno già a bordo una EEPROM AT24C32 da 4 KB, di solito raggiungibile all'indirizzo I2C 0x57. Per lavori che salvano molte volte al secondo sono più adatti i moduli FRAM, che reggono circa mille miliardi (10^12) di cicli di scrittura, e per grandi registri di dati un modulo scheda SD che salva i dati come file.
Idee di progetti con la EEPROM
- Lampada che ricorda l'ultimo stato: Quando torna la corrente, riporta la lampada collegata a un relè allo stato che aveva prima del blackout.
- Cassaforte con codice modificabile: Scrivi nella EEPROM il nuovo codice inserito con il tastierino, così puoi cambiarlo senza ricaricare lo sketch.
- Serratura a tessera: Salva gli ID delle tessere autorizzate lette con l'RFID RC522 e aggiungi nuove tessere con una tessera master.
- Record permanente: Conserva il punteggio migliore in Simon, T-Rex o Snake; trovi codice di esempio in Progetti di giochi con Arduino.
- Braccio robotico che registra i movimenti: Registra gli angoli dei servo posa per posa e riproducili; il codice di Braccio robotico con Arduino lo fa con EEPROM.update().
- Bilancia che non dimentica la calibrazione: Salva come float con put() il fattore di calibrazione trovato con la cella di carico HX711.
- Contatore di riavvii: Conta quante volte la scheda si riavvia per diagnosticare problemi di alimentazione; il metodo è in Arduino si resetta continuamente.
Errori comuni e soluzioni
| Sintomo | Causa | Soluzione |
|---|---|---|
| Al primo avvio si legge 255, -1, 65535 o nan | La EEPROM non è mai stata scritta; le celle vuote valgono 0xFF | Scrivi i valori predefiniti se il controllo del numero magico o di 0xFF fallisce |
| Ho scritto 300 e leggo 44 | write() e read() trasportano un solo byte | Usa put() e get() per i dati su più byte |
| Due valori si rovinano a vicenda | Gli indirizzi si sovrappongono; un int occupa 2 byte, un float 4 | Crea una mappa degli indirizzi e calcolali con sizeof |
| Dopo un riavvio il testo diventa illeggibile | Un oggetto String è stato scritto con put() | Salva il testo come array di char |
| Il nuovo codice legge le impostazioni del vecchio progetto | Un normale caricamento non cancella la EEPROM | Usa numero magico e campo versione, oppure cancella la EEPROM |
| Impostazioni sparite dopo il caricamento | Caricamento con programmatore sull'Uno, opzione Erase All Flash sull'ESP32 | Carica normalmente via USB, imposta l'opzione dell'ESP32 su Disabled |
| Sull'ESP32 i valori non vengono salvati | Manca EEPROM.begin() o EEPROM.commit() | Aggiungi entrambi oppure passa a Preferences |
| Preferences restituisce sempre il predefinito | Lettura con tipo diverso, nome oltre 15 caratteri o namespace diverso | Fai coincidere i tipi di put e get, usa nomi brevi |
| Errore nvs_open failed: NOT_FOUND | Un namespace mai creato è stato aperto in sola lettura (true) | Apri prima il namespace con false e scrivi un valore |
| Dopo un po' i valori si corrompono | Scritture continue in loop() o calo di tensione durante una scrittura | Usa update() e salvataggio ritardato, aggiungi buffer circolare e CRC |
La EEPROM è il modo più semplice per dare memoria ai tuoi progetti Arduino: con poche righe di codice proteggi impostazioni, contatori e password del tuo dispositivo dalle interruzioni di corrente. L'unica cosa da tenere d'occhio è la durata in scrittura; finché usi update() e put() e non scrivi inutilmente, la EEPROM dell'Uno funzionerà senza problemi per anni. Nell'app BlueGrays trovi progetti che puoi arricchire con la EEPROM, come l'applicazione password con tastierino 4x4, la cassaforte elettronica intelligente, l'autenticazione RFID e i giochi Simon e T-Rex, ognuno con schema del circuito e codice passo passo.
Domande frequenti
Quante volte si può scrivere la EEPROM di Arduino?
Il datasheet dell'ATmega328P garantisce almeno 100.000 cicli di scrittura e cancellazione per ogni cella di EEPROM. Il limite vale per cella e la lettura non lo influenza. Scrivere lo stesso indirizzo una volta al secondo consuma la cella in circa 28 ore; usando update() e scrivendo solo quando il valore cambia, la EEPROM dura anni.
La EEPROM di Arduino si cancella caricando un nuovo sketch?
No. Un normale caricamento via USB scrive solo la memoria di programma e i dati nella EEPROM restano. Sull'Uno la EEPROM si cancella solo caricando con un programmatore ISP o scrivendo il bootloader. Usa un controllo con numero magico perché un nuovo progetto non erediti vecchie impostazioni.
Che differenza c'è tra EEPROM.write ed EEPROM.update?
write() riscrive il valore nella cella a ogni chiamata e consuma ogni volta un ciclo di scrittura. update() legge prima la cella e non scrive nulla se il valore è uguale. Il risultato è identico, ma update() protegge la durata della EEPROM; anche put() usa internamente lo stesso metodo.
Come si cancella la EEPROM di Arduino?
Scrivi 255 (o 0) in tutti gli indirizzi con un ciclo for che arriva fino a EEPROM.length(); l'esempio eeprom_clear dell'IDE di Arduino scrive già degli zeri. Cancellare i 1024 byte dell'Uno richiede circa 3,4 secondi. Alla fine non lasciare lo sketch di cancellazione sulla scheda: carica il tuo programma.
L'ESP32 ha la EEPROM?
L'ESP32 non ha una vera EEPROM; i dati permanenti sono conservati nella partizione NVS della memoria flash. Per i nuovi progetti è consigliata la libreria Preferences. La libreria EEPROM funziona ancora con il codice vecchio, ma bisogna chiamare prima EEPROM.begin(dimensione) ed EEPROM.commit() dopo ogni modifica.
Come salvare un int, un float o un testo nella EEPROM?
I dati su più byte come int, long, float e struct si scrivono con EEPROM.put(indirizzo, variabile) e si leggono con EEPROM.get(indirizzo, variabile). Un int occupa 2 byte e un float 4, quindi scegli di conseguenza l'indirizzo successivo. Salva il testo in un array di char di dimensione fissa, non in una String.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
Accendere e spegnere un LED con un pulsante
Premi il pulsante una volta e la luce si accende, premilo di nuovo e si spegne, proprio come l'interruttore della tua stanza. L'Arduino si accorge di ogni pressione e porta il LED nello stato opposto,

Contatore con display a 7 segmenti e pulsanti
Un contatore semplice con tre pulsanti: con uno la cifra sul display sale di uno, con un altro scende di uno, e il terzo la riporta a zero. Il display a 7 segmenti a una sola cifra conta da 0 a 9, e q

Serratura con password, tastierino 4x4 e LCD
Una piccola serratura elettronica che non fa entrare chi non conosce il codice. Premi il tasto cancelletto e digiti il codice sul tastierino; se è giusto, lo schermo indica accesso consentito, si acce

Cassaforte elettronica intelligente
Una piccola cassaforte per le tue cose di valore che si apre con un codice. Digiti il tuo codice a quattro cifre sul tastierino; se è giusto, il servomotore apre la serratura, si accende la luce verde

Autenticazione RFID
Come l'ingresso con tessera di un condominio: avvicini la tua tessera al lettore RFID e l'Arduino confronta il suo numero con quello salvato. Con la tessera giusta il LED verde resta acceso tre second

Gioco di memoria con Arduino (4 LED + 4 pulsanti + buzzer)
Ti fidi della tua memoria? Quattro LED colorati si accendono in sequenza, ognuno con la sua nota, e tu ripeti lo stesso ordine premendo i pulsanti. Ogni turno giusto aggiunge un nuovo colore alla sequ

OLED T-Rex Run: Gioco del dinosauro con Arduino
Il gioco del dinosauro che compare quando salta internet, ora sulla tua scrivania: il dinosauro corre sul piccolo display OLED, i cactus gli vengono incontro e tu salti con un solo pulsante. Il punteg

ESP32 Deep Sleep — Risparmio della batteria (OLED + contatore in RTC RAM)
La modalità Deep Sleep dell'ESP32 riduce il consumo di corrente a circa 10 µA spegnendo la CPU e la maggior parte dei dispositivi periferici ed è di fondamentale importanza per i dispositivi IoT alimentati a batteria. In questo progetto utilizziamo tre diverse fonti di risveglio: il timer (15 secondi), un pin esterno (EXT0 — pulsante GPIO 33) e il sensore tattile (T0/GPIO 4). Le variabili contrassegnate con RTC_DATA_ATTR vengono preservate durante la modalità Deep Sleep: il contatore di avvio,
Altri articoli
App BlueGrays
Costruisci questo circuito passo passo sul telefono
Schema, codice ed elenco componenti in un’unica schermata. Oltre 200 progetti Arduino, ESP32 e Pico da realizzare.
Inquadra con il telefono
Punta la fotocamera sul codice: si apre lo store del tuo telefono.
oppure apri lo store

