EEPROM Arduino : sauvegarder des données, lire et écrire
EEPROM Arduino : 1024 octets sur l'Uno, read, write, update, put et get, limite de 100 000 écritures, compteur et mot de passe sauvegardés, ESP32 Preferences.
L'EEPROM Arduino est une petite mémoire permanente qui garde son contenu même quand la carte n'est plus alimentée. L'ATmega328P de l'Arduino Uno, du Nano et du Pro Mini possède 1024 octets d'EEPROM, et tu y accèdes avec les commandes EEPROM.read(), EEPROM.write(), EEPROM.update(), EEPROM.put() et EEPROM.get() de la bibliothèque EEPROM fournie avec l'IDE Arduino. La RAM qui contient tes variables est effacée à chaque redémarrage, mais un réglage, un compteur ou un mot de passe écrit dans l'EEPROM est toujours là quand la carte se rallume.
Dans ce guide, tu verras comment fonctionne l'EEPROM, combien en possède chaque carte, ce qui distingue les commandes et la question que tout le monde se pose : ce que signifie en pratique sa durée de vie d'environ 100 000 écritures. Ensuite, nous construirons pas à pas un compteur qui se souvient de sa valeur après une coupure de courant, une structure de réglages qui enregistre mot de passe et options d'un seul coup, et un tampon circulaire qui multiplie la durée de vie par 16. Dans les dernières parties, tu apprendras à faire la même chose sur l'ESP32, qui n'a pas de vraie EEPROM, avec la bibliothèque Preferences, et sur le Raspberry Pi Pico avec le système de fichiers de MicroPython.

Qu'est-ce que l'EEPROM et comment fonctionne-t-elle ?
EEPROM est l'abréviation de Electrically Erasable Programmable Read-Only Memory, c'est-à-dire mémoire morte programmable et effaçable électriquement. Chaque cellule stocke l'information sous forme d'une charge électrique piégée dans une grille isolée, et cette charge reste en place quand l'alimentation est coupée. Imagine l'EEPROM de l'Uno comme un meuble à 1024 tiroirs : chaque tiroir a une adresse de 0 à 1023 et contient exactement 1 octet, soit un nombre entre 0 et 255. Une cellule jamais écrite se lit 255 (0xFF). La lecture est quasi instantanée et illimitée ; l'écriture prend environ 3,3 ms par octet et use un peu la cellule. La fiche technique de l'ATmega328P garantit au moins 100 000 cycles d'écriture et d'effacement par cellule d'EEPROM et 100 ans de conservation des données à 25 °C.
| Caractéristique | Flash (mémoire programme) | SRAM | EEPROM |
|---|---|---|---|
| Taille sur l'Uno | 32 Ko (0,5 Ko pour le bootloader) | 2 Ko | 1 Ko (1024 octets) |
| Que contient-elle ? | Le croquis téléversé | Variables, tableaux | Réglages, compteurs, records |
| À la coupure de courant | Conservée | Effacée | Conservée |
| Inscriptible pendant l'exécution ? | Normalement non (lecture seule via PROGMEM) | Oui, sans limite | Oui, avec la bibliothèque EEPROM |
| Cycles d'écriture | Environ 10 000 | Illimités | Environ 100 000 par cellule |
| Vitesse d'écriture | Seulement au téléversement | Un cycle d'horloge | Environ 3,3 ms par octet |
En résumé, la SRAM est un tableau blanc et l'EEPROM un carnet. Tu peux écrire et effacer le tableau autant que tu veux, mais tout disparaît quand la lumière s'éteint ; ce que tu écris dans le carnet reste, mais tu ne peux pas gommer et réécrire la même page à l'infini. L'EEPROM est donc idéale pour les informations peu volumineuses qui changent rarement : valeurs d'étalonnage, réglages utilisateur, luminosité ou volume, record d'un jeu, code d'une porte, badges RFID enregistrés ou dernier état d'une lampe. Pour des données volumineuses et écrites souvent, comme un relevé de température à chaque seconde, un module carte SD est un meilleur choix. Si tu te demandes pourquoi la RAM se remplit si vite, lis Arduino mémoire insuffisante.
Combien d'EEPROM sur chaque carte ?
| Carte | Processeur | EEPROM | Remarque |
|---|---|---|---|
| Arduino Uno R3, Nano, Pro Mini | ATmega328P | 1024 octets | Vraie EEPROM |
| Arduino Mega 2560 | ATmega2560 | 4096 octets | Vraie EEPROM |
| Arduino Leonardo, Micro | ATmega32U4 | 1024 octets | Vraie EEPROM |
| Arduino Nano Every | ATmega4809 | 256 octets | Vraie EEPROM, mais petite |
| Arduino Uno R4 Minima, WiFi | Renesas RA4M1 | 8 Ko | Flash de données ; la bibliothèque EEPROM fonctionne avec les mêmes commandes |
| ESP32 | ESP32 (Xtensa) | Aucune | Partition NVS dans la flash ; Preferences recommandée |
| ESP8266 (NodeMCU, Wemos D1) | ESP8266 | Aucune | Émulée dans la flash ; nécessite EEPROM.begin() et commit() |
| Raspberry Pi Pico | RP2040 | Aucune | Un fichier en MicroPython, EEPROM émulée dans la flash avec le core Arduino |
Si ton code doit passer d'une carte à l'autre, n'écris pas la taille de l'EEPROM en dur ; EEPROM.length() renvoie la taille réelle de la carte (1024 sur l'Uno, 4096 sur la Mega), si bien que le même code d'effacement ou de lecture fonctionne partout. Pour les autres détails de mémoire et de broches de l'Uno, consulte Brochage de l'Arduino Uno, et pour choisir une carte, lis Quel Arduino acheter ?.
Les commandes EEPROM : read, write, update, put et get
La bibliothèque EEPROM est livrée avec le paquet de la carte, il n'y a donc rien à installer : ajoute simplement la ligne #include <EEPROM.h> en haut de ton croquis. Les adresses commencent à 0 et se terminent à 1023 sur l'Uno. Le tableau résume ce que fait chaque commande et si elle use les cellules :
| Commande | Ce qu'elle fait | Taille | Usure |
|---|---|---|---|
| EEPROM.read(adresse) | Lit un octet (0–255) | 1 octet | Aucune, la lecture est gratuite |
| EEPROM.write(adresse, valeur) | Réécrit l'octet à chaque appel | 1 octet | Consomme un cycle à chaque appel |
| EEPROM.update(adresse, valeur) | Lit d'abord, n'écrit que si la valeur diffère | 1 octet | Seulement si la valeur change |
| EEPROM.put(adresse, donnée) | Écrit un int, un float, un tableau ou une struct octet par octet ; utilise update en interne | sizeof(donnée) | Seulement les octets modifiés |
| EEPROM.get(adresse, variable) | Relit ce que put a écrit, avec le même type | sizeof(variable) | Aucune |
| EEPROM[adresse] | Accès comme un tableau ; lire est gratuit, l'affectation avec = agit comme write | 1 octet | À chaque affectation |
| EEPROM.length() | Renvoie la taille totale de l'EEPROM | - | Aucune |
Premier croquis : écrire et lire des données dans l'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() {}Téléverse le croquis et ouvre le Moniteur série à 9600 bauds. Au premier lancement, l'adresse 0 renvoie 255, car cette cellule n'a jamais été écrite. Appuie maintenant sur le bouton reset de la carte, ou débranche et rebranche le câble USB : cette fois, tu lis 42. C'est la preuve que l'EEPROM conserve ses données. La ligne write() de l'adresse 1, elle, réécrit la cellule à chaque démarrage alors que la valeur est toujours 7 ; avec update(), l'écriture n'aurait eu lieu que la première fois. La valeur 300 de la dernière ligne illustre le piège décrit plus haut, et 44 s'affiche. Si l'option Avertissements du compilateur des Préférences n'est pas sur Aucun, le compilateur signale cette ligne avec large integer implicitly truncated to unsigned type.
Avec put() et get(), tu dois savoir combien d'octets occupe chaque donnée ; sinon tu écriras deux variables l'une sur l'autre et tu en abîmeras une. Dans l'exemple, l'int occupe les adresses 10 et 11, c'est pourquoi le float commence à l'adresse 12. Voici la taille des types de données sur l'Uno :
| Type de donnée | Taille sur l'Uno | Plage | Exemple d'usage |
|---|---|---|---|
| byte, bool, char | 1 octet | 0–255, true/false ou un caractère | État marche/arrêt, choix du mode |
| int | 2 octets | −32 768 à 32 767 | Décalage d'étalonnage |
| unsigned int, uint16_t | 2 octets | 0–65 535 | Record d'un jeu |
| long, unsigned long | 4 octets | Environ ±2,1 milliards ou 0–4,29 milliards | Compteur de longue durée |
| float | 4 octets | 6 à 7 chiffres de précision | Coefficient d'étalonnage d'un capteur |
| tableau char[10] | 10 octets | 9 caractères plus le terminateur | Mot de passe, nom de l'appareil |
Durée de vie de l'EEPROM : que signifie la limite de 100 000 écritures ?
100 000 écritures, cela paraît beaucoup, mais un croquis mal conçu peut les épuiser en quelques minutes. La formule de la durée de vie est simple : durée de vie = 100 000 ÷ fréquence d'écriture dans la même cellule. Le point essentiel est que la limite s'applique à chaque cellule séparément, pas à l'EEPROM entière ; si tu écris toujours à la même adresse, seule cette cellule s'use. La lecture n'a aucun effet sur la durée de vie. Le tableau montre combien de temps tient une cellule quand la même adresse est écrite à différentes fréquences :
| Fréquence d'écriture à la même adresse | Durée de vie approximative |
|---|---|
| write() dans loop() sans pause (environ 300 fois par seconde) | 5 à 6 minutes |
| Chaque seconde | Environ 28 heures |
| Chaque minute | Environ 69 jours |
| Toutes les 10 minutes | Environ 1,9 an |
| Une fois par heure | Environ 11 ans |
| 10 fois par jour | Environ 27 ans |
100 000 est le minimum garanti par le fabricant ; beaucoup de puces tiennent bien plus longtemps, mais ne conçois pas ton projet en comptant dessus. Une cellule usée ne meurt pas d'un coup : elle commence à ne plus retenir la valeur écrite et certains bits changent tout seuls. Dans ce cas, tu peux déplacer les données vers une autre adresse, car les autres cellules ne sont pas touchées. Voici comment prolonger la durée de vie :
- Utilise update() et put() : Les deux lisent la cellule avant d'écrire et n'écrivent rien si la valeur est identique.
- N'écris qu'en cas de changement : Enregistre un réglage quand l'utilisateur le modifie vraiment, pas à chaque tour de loop().
- Retarde l'enregistrement : Si une valeur change plusieurs fois de suite, écris-la une seule fois quelques secondes après le dernier changement ; millis() rend cela très simple.
- Écris lors d'événements : Enregistre le record à la fin de la partie et les réglages du menu quand on le quitte.
- Répartis les écritures : Écris un compteur qui change souvent à plusieurs adresses à tour de rôle au lieu d'une seule (voir l'exemple du tampon circulaire).
- Utilise une mémoire externe si besoin : Pour des enregistrements très fréquents, choisis une EEPROM externe à 1 million de cycles, de la FRAM ou une carte SD.
Exemple 1 : un compteur qui résiste aux coupures de courant
Dans le premier exemple, nous allons créer un compteur qui augmente à chaque appui sur un bouton-poussoir et reprend là où il s'était arrêté après avoir éteint puis rallumé la carte. Branche une patte du bouton sur D2 et l'autre sur GND ; nous utilisons la résistance de pull-up interne, donc aucune résistance supplémentaire n'est nécessaire (détails dans Brancher un bouton sur Arduino, position des broches dans l'outil Pinout). L'astuce : le croquis n'enregistre pas le compteur à chaque appui, mais 5 secondes après le dernier appui. Ainsi, même si quelqu'un appuie 20 fois d'affilée, l'EEPROM n'est écrite qu'une fois.
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 signale qu'il existe dans la RAM une modification pas encore enregistrée. Le compteur est un unsigned long : il occupe 4 octets (adresses 0 à 3) et peut compter plus de 4 milliards d'appuis. Dans une EEPROM vierge, ces quatre octets valent 0xFF, donc le premier démarrage lit 4 294 967 295 ; en voyant cette valeur, le code repart de zéro. Comme put() n'écrit que les octets modifiés, c'est le plus souvent seulement l'octet de poids faible qui s'use quand le compteur augmente. Si le courant est coupé dans les 5 secondes qui précèdent l'enregistrement, les derniers appuis sont perdus ; c'est un petit prix à payer pour préserver l'EEPROM.
Exemple 2 : sauvegarder réglages et mot de passe dans une struct
Plutôt que d'écrire plusieurs réglages à des adresses séparées, il est plus propre et plus sûr de les regrouper dans une struct et de les sauvegarder avec un seul put(). Dans cet exemple, nous enregistrons la luminosité d'une LED et un mot de passe de 4 à 6 caractères. Les deux premiers champs de la structure sont essentiels : magic (un nombre magique) indique si les réglages de ce croquis ont vraiment été écrits dans l'EEPROM, et version empêche de lire d'anciennes données incompatibles lorsque tu ajoutes plus tard un champ à la structure. Si l'un des deux ne correspond pas, le croquis écrit les réglages par défaut.
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}Branche la LED sur D9 à travers une résistance pour LED (220 ohms suffisent) ; D9 est une broche PWM. Ouvre le Moniteur série à 9600 bauds, règle la fin de ligne sur Nouvelle ligne et essaie ces commandes : b200 règle la luminosité à 200, p1234,5678 change le mot de passe de 1234 en 5678 et ? affiche la luminosité actuelle. Quand tu éteins puis rallumes la carte, la LED revient avec la même luminosité et le nouveau mot de passe reste valable. La struct occupe 11 octets sur l'Uno ; comme put() n'écrit que les octets modifiés, changer la luminosité n'use qu'une seule cellule. Tu peux adapter la même structure à une serrure à code avec clavier ou stocker dans un tableau les identifiants de 4 octets des badges autorisés avec le RFID RC522. Si les commandes série sont nouvelles pour toi, lis Moniteur série Arduino.
Multiplier la durée de vie : le tampon circulaire (wear leveling)
Écrire un compteur souvent mis à jour toujours à la même adresse use très vite cette cellule. Avec un tampon circulaire (ring buffer), tu écris la valeur à tour de rôle dans différents emplacements : un enregistrement va dans un emplacement, le suivant dans l'emplacement d'après, et après le dernier, on revient au début. Chaque emplacement n'est écrit qu'une fois tous les 16 enregistrements, la durée de vie est donc multipliée par 16, soit environ 1,6 million d'enregistrements. Au démarrage, tous les emplacements sont lus pour trouver la valeur la plus récente ; comme le compteur ne fait qu'augmenter, la plus grande valeur est la plus récente.
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'exemple incrémente le compteur à chaque démarrage ; tu peux le remplacer par un bouton, un événement de capteur ou un compteur d'heures de fonctionnement. Plus d'emplacements donnent proportionnellement plus de durée de vie : 64 emplacements occupent 256 octets et tiennent environ 6,4 millions d'enregistrements. Si la carte a déjà servi avec d'autres croquis, d'anciennes données peuvent rester dans la zone du tampon ; efface donc l'EEPROM avec le code ci-dessous avant la première utilisation. Dans des projets plus avancés, tu peux aussi ajouter une somme de contrôle CRC, comme dans l'exemple eeprom_crc d'Arduino, pour détecter les données corrompues.
Comment effacer l'EEPROM ? Est-elle effacée au téléversement ?
Un téléversement normal par le câble USB n'écrit que la mémoire programme ; les données de l'EEPROM sont conservées. Ainsi, quand tu téléverses un nouveau projet, les réglages du précédent restent dans l'EEPROM et le nouveau code peut lire des valeurs auxquelles il ne s'attend pas. Sur l'Uno, l'EEPROM n'est effacée que si tu utilises un programmateur ISP avec Croquis > Téléverser en utilisant un programmateur ou Outils > Graver la séquence d'initialisation (Burn Bootloader) ; ces opérations effacent toute la puce, et le bit EESAVE qui protège l'EEPROM est désactivé dans les fusibles de l'Uno. Pour effacer l'EEPROM toi-même, téléverse une fois ce croquis :
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'exemple Fichier > Exemples > EEPROM > eeprom_clear de l'IDE Arduino écrit 0 dans toutes les cellules ; nous les remettons plutôt à leur état d'usine, 255. Ainsi, un code qui vérifie un nombre magique ou 0xFF reconnaît de nouveau l'EEPROM comme vierge. Comme nous utilisons update(), les cellules qui valent déjà 255 ne sont pas écrites. Écrire les 1024 cellules de l'Uno prend au maximum environ 3,4 secondes ; quand la LED intégrée s'allume, c'est terminé. Téléverse ensuite ton vrai croquis.
L'EEPROM sur l'ESP32 : la bibliothèque Preferences
L'ESP32 n'a pas de vraie EEPROM. Les données permanentes sont stockées dans la partition NVS (Non-Volatile Storage) de la mémoire flash externe ; dans la table de partitions par défaut, cette zone fait 20 Ko. Espressif recommande la bibliothèque Preferences, fournie avec le paquet de cartes ESP32. Preferences enregistre les données par espace de noms (namespace) et par clé (key) plutôt que par adresse : tu n'as jamais à retenir à quelle adresse se trouvait le compteur. NVS écrit chaque nouvelle valeur dans l'entrée libre suivante de la page et marque l'ancienne comme effacée, si bien que la répartition de l'usure (wear leveling) se fait automatiquement. Les noms d'espace de noms et de clé font au maximum 15 caractères et respectent la casse. Si l'ESP32 est nouveau pour toi, commence par Qu'est-ce que 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() {}| Commande | Ce qu'elle fait |
|---|---|
| prefs.begin("nom", false) | Ouvre l'espace de noms ; false pour lecture et écriture, true pour lecture seule |
| putUInt, putInt, putFloat, putBool, putString, putBytes | Enregistre la valeur sous un nom de clé |
| getUInt("cle", defaut) | Lit la valeur ; renvoie la valeur par défaut si la clé n'existe pas |
| isKey("cle") | Vérifie si la clé existe |
| remove("cle") | Supprime une seule clé |
| clear() | Supprime toutes les clés de l'espace de noms ouvert |
| freeEntries() | Renvoie le nombre d'entrées libres restant dans la NVS |
| end() | Ferme l'espace de noms |
Tu dois relire une valeur avec le même type que celui utilisé pour l'écrire : si tu écris avec putUInt et lis avec getInt, tu obtiens la valeur par défaut. Appeler isKey() avant getString() a aussi une raison : si Core Debug Level dans le menu Outils est sur Error ou plus, lire une clé texte inexistante affiche nvs_get_str len fail: ssid NOT_FOUND dans le Moniteur série ; c'est sans danger, mais déroutant. Téléverser à nouveau par USB n'efface pas la NVS, mais si Erase All Flash Before Sketch Upload est sur Enabled, chaque téléversement supprime toutes les entrées. Enregistrer les identifiants Wi-Fi avec Preferences est courant ; si tu préfères saisir le nom du réseau et le mot de passe depuis ton téléphone, ESP32 WiFiManager convient aussi, et l'ESP32 conserve ses réglages Wi-Fi dans cette même zone NVS. Pour des valeurs éphémères qui doivent seulement survivre au réveil d'un sommeil profond, la mémoire RTC est plus adaptée, car elle n'use pas du tout la flash ; voir ESP32 deep sleep.
Quand tu portes un ancien code Arduino sur l'ESP32, la bibliothèque EEPROM fonctionne aussi, avec deux différences : avant de l'utiliser, tu dois indiquer combien d'octets il te faut avec EEPROM.begin(taille), et après tes modifications, appeler EEPROM.commit(). put() et write() ne modifient que la copie en RAM ; sans commit(), rien n'arrive dans la flash. Sur l'ESP32, cette bibliothèque fonctionne comme un unique bloc de données dans la NVS et Espressif la considère comme obsolète (deprecated) ; tous les octets d'une zone nouvellement créée valent 0xFF. Le même code compile aussi pour l'ESP8266, où la taille est limitée à 4096 octets et où chaque commit() efface et réécrit un secteur de flash de 4 Ko : appelle donc commit avec parcimonie.
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() {}Données permanentes sur le Raspberry Pi Pico : le système de fichiers MicroPython
La puce RP2040 du Raspberry Pi Pico n'a pas non plus d'EEPROM ; la carte embarque 2 Mo de mémoire flash. MicroPython réserve environ 1,4 Mo de cette flash à un système de fichiers LittleFS (moins sur le Pico W à cause du firmware Wi-Fi). Sur le Pico, l'équivalent de l'EEPROM consiste donc à écrire tes réglages dans un petit fichier. LittleFS répartit les écritures sur différents blocs de la flash, il gère donc lui-même l'usure, et le système de fichiers ne se corrompt pas même si le courant est coupé pendant une écriture. Le code suivant enregistre les réglages au format JSON, compte chaque démarrage et sauvegarde le compteur à chaque appui sur un bouton relié entre GP15 et 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)Au lieu d'écraser directement le fichier, nous écrivons d'abord dans un fichier temporaire .tmp, puis nous utilisons os.rename() pour le mettre à la place de l'ancien ; ainsi, l'ancien fichier de réglages reste intact même si l'écriture est interrompue. Si le fichier est absent ou son contenu corrompu, load() revient aux valeurs par défaut, et quand tu ajoutes plus tard un nouveau réglage au programme, setdefault() complète automatiquement la clé manquante. Si tu programmes le Pico avec l'IDE Arduino, tu peux aussi utiliser la bibliothèque EEPROM du core Arduino-Pico d'Earle Philhower : EEPROM.begin(256) ouvre une zone de 256 à 4096 octets et EEPROM.commit() enregistre tes modifications. Cette EEPROM émulée se trouve dans le dernier secteur de 4 Ko de la flash et chaque commit() efface et réécrit ce secteur ; la flash supporte environ 100 000 cycles d'effacement par secteur, appelle donc commit avec parcimonie. Si tu débutes avec le Pico, Qu'est-ce que le Raspberry Pi Pico et Les bases de MicroPython sont un bon point de départ.
Plus de mémoire permanente : EEPROM externe, FRAM et carte SD
Si 1 Ko ne suffit pas ou si tu dois enregistrer très souvent, tu peux ajouter une mémoire externe. Les puces EEPROM I2C comme l'AT24C256 offrent 32 Ko et environ 1 million de cycles d'écriture, et se branchent avec les seuls fils SDA et SCL (Qu'est-ce que l'I2C). Un détail peu connu : beaucoup de modules horloge RTC DS3231 du commerce intègrent déjà une EEPROM AT24C32 de 4 Ko, généralement accessible à l'adresse I2C 0x57. Pour les tâches qui enregistrent de nombreuses fois par seconde, les modules FRAM, qui supportent environ mille milliards (10^12) de cycles d'écriture, sont plus adaptés, et pour de gros journaux de données, un module carte SD qui stocke les données sous forme de fichiers.
Idées de projets avec l'EEPROM
- Lampe qui se souvient de son dernier état : Au retour du courant, remets la lampe branchée sur un relais dans l'état où elle était avant la coupure.
- Coffre-fort à code modifiable : Écris dans l'EEPROM le nouveau code saisi au clavier matriciel pour le changer sans téléverser à nouveau le croquis.
- Serrure à badge : Enregistre les identifiants des badges autorisés lus avec le RFID RC522 et ajoute de nouveaux badges avec un badge maître.
- Record permanent : Garde le meilleur score à Simon, T-Rex ou Snake ; des exemples de code sont dans Projets de jeux Arduino.
- Bras robotique qui enregistre ses mouvements : Enregistre les angles des servos pose par pose et rejoue-les ; le code de Bras robotique Arduino le fait avec EEPROM.update().
- Balance qui garde son étalonnage : Sauvegarde en float avec put() le coefficient d'étalonnage trouvé avec la cellule de charge HX711.
- Compteur de redémarrages : Compte combien de fois la carte redémarre pour diagnostiquer un problème d'alimentation ; la méthode est dans Arduino redémarre sans arrêt.
Erreurs fréquentes et solutions
| Symptôme | Cause | Solution |
|---|---|---|
| Au premier démarrage, on lit 255, -1, 65535 ou nan | L'EEPROM n'a jamais été écrite ; les cellules vierges valent 0xFF | Écrire les valeurs par défaut si le test du nombre magique ou de 0xFF échoue |
| J'ai écrit 300, je lis 44 | write() et read() ne transportent qu'un octet | Utiliser put() et get() pour les données sur plusieurs octets |
| Deux valeurs s'abîment mutuellement | Les adresses se chevauchent ; un int occupe 2 octets, un float 4 | Faire une carte des adresses, les calculer avec sizeof |
| Le texte devient illisible après un redémarrage | Un objet String a été écrit avec put() | Stocker le texte dans un tableau de char |
| Le nouveau code lit les réglages de l'ancien projet | Un téléversement normal n'efface pas l'EEPROM | Utiliser un nombre magique et un champ de version, ou effacer l'EEPROM |
| Réglages perdus après le téléversement | Téléversement avec programmateur sur l'Uno, option Erase All Flash sur l'ESP32 | Téléverser normalement par USB, mettre l'option de l'ESP32 sur Disabled |
| Les valeurs ne sont pas enregistrées sur l'ESP32 | EEPROM.begin() ou EEPROM.commit() manquant | Ajouter les deux, ou passer à Preferences |
| Preferences renvoie toujours la valeur par défaut | Lecture avec un autre type, nom de plus de 15 caractères ou autre espace de noms | Faire correspondre les types de put et get, garder des noms courts |
| Erreur nvs_open failed: NOT_FOUND | Un espace de noms jamais créé a été ouvert en lecture seule (true) | Ouvrir d'abord l'espace de noms avec false et écrire une valeur |
| Les valeurs se corrompent au bout d'un moment | Écritures continues dans loop() ou chute de tension pendant une écriture | Utiliser update() et l'enregistrement différé, ajouter tampon circulaire et CRC |
L'EEPROM est le moyen le plus simple de donner une mémoire à tes projets Arduino : en quelques lignes de code, tu protèges les réglages, compteurs et mots de passe de ton montage contre les coupures de courant. La seule chose à surveiller est l'endurance en écriture ; tant que tu utilises update() et put() et que tu n'écris pas inutilement, l'EEPROM de l'Uno fonctionnera sans souci pendant des années. Dans l'application BlueGrays, tu trouveras des projets que tu peux enrichir avec l'EEPROM, comme l'application de mot de passe avec clavier 4x4, le coffre-fort électronique intelligent, l'authentification RFID et les jeux Simon et T-Rex, chacun avec son schéma de câblage et son code pas à pas.
Questions fréquentes
Combien de fois peut-on écrire l'EEPROM d'un Arduino ?
La fiche technique de l'ATmega328P garantit au moins 100 000 cycles d'écriture et d'effacement par cellule d'EEPROM. La limite s'applique par cellule et la lecture ne l'affecte pas. Écrire la même adresse chaque seconde use la cellule en environ 28 heures ; avec update() et des écritures seulement quand la valeur change, l'EEPROM dure des années.
L'EEPROM de l'Arduino est-elle effacée quand on téléverse un nouveau croquis ?
Non. Un téléversement normal par USB n'écrit que la mémoire programme et les données de l'EEPROM sont conservées. Sur l'Uno, l'EEPROM n'est effacée qu'en téléversant avec un programmateur ISP ou en gravant le bootloader. Vérifie un nombre magique pour qu'un nouveau projet ne récupère pas d'anciens réglages.
Quelle est la différence entre EEPROM.write et EEPROM.update ?
write() réécrit la valeur dans la cellule à chaque appel et consomme un cycle d'écriture à chaque fois. update() lit d'abord la cellule et n'écrit rien si la valeur est identique. Le résultat est le même, mais update() préserve la durée de vie de l'EEPROM ; put() utilise la même méthode en interne.
Comment effacer l'EEPROM d'un Arduino ?
Écris 255 (ou 0) à toutes les adresses avec une boucle for qui va jusqu'à EEPROM.length() ; l'exemple eeprom_clear de l'IDE Arduino écrit déjà des zéros. Effacer les 1024 octets de l'Uno prend environ 3,4 secondes. Une fois terminé, ne laisse pas le croquis d'effacement sur la carte : téléverse ton vrai programme.
L'ESP32 a-t-il une EEPROM ?
L'ESP32 n'a pas de vraie EEPROM ; les données permanentes sont stockées dans la partition NVS de la mémoire flash. Pour les nouveaux projets, la bibliothèque Preferences est recommandée. La bibliothèque EEPROM fonctionne toujours pour l'ancien code, mais il faut appeler EEPROM.begin(taille) au départ et EEPROM.commit() après chaque modification.
Comment sauvegarder un int, un float ou du texte dans l'EEPROM ?
Les données sur plusieurs octets comme int, long, float et struct s'écrivent avec EEPROM.put(adresse, variable) et se lisent avec EEPROM.get(adresse, variable). Un int occupe 2 octets et un float 4, choisis donc l'adresse suivante en conséquence. Stocke le texte dans un tableau de char de taille fixe, pas dans une String.
Projets à réaliser après cet article
Voir tous les projets
Allumer et éteindre une LED avec un bouton
Un appui et la lumière s'allume, un deuxième et elle s'éteint, exactement comme l'interrupteur de ta chambre. L'Arduino remarque chaque appui et fait passer la LED à l'état inverse, en attendant un co

Compteur avec afficheur 7 segments et boutons
Un compteur tout simple à trois boutons : avec l'un, le chiffre affiché monte d'un cran, avec l'autre il descend d'un cran, et le troisième le remet à zéro. L'afficheur 7 segments à un seul chiffre co

Serrure à code avec clavier 4x4 et écran LCD
Une petite serrure électronique qui ne laisse entrer que ceux qui connaissent le code. Tu appuies sur la touche dièse et tapes le code sur le clavier ; s'il est bon, l'écran affiche accès autorisé, la

Coffre-fort électronique intelligent
Un petit coffre-fort pour tes objets précieux, qui s'ouvre avec un code. Tu tapes ton code à quatre chiffres sur le clavier ; s'il est bon, le servomoteur ouvre le verrou, la lumière verte s'allume et

Authentification par RFID
Comme le badge d'entrée d'un immeuble : tu approches ta carte du lecteur RFID, et l'Arduino compare son numéro avec celui qu'il a en mémoire. Avec la bonne carte, la LED verte s'allume trois secondes,

Jeu de mémoire avec Arduino (4 LED + 4 boutons + buzzer)
Tu as confiance en ta mémoire ? Quatre LED de couleur s'allument dans un certain ordre, chacune avec sa propre note, et tu dois refaire la même suite en appuyant sur les boutons. Chaque manche réussie

OLED T-Rex Run : Jeu de dinosaures Arduino
Le jeu du dinosaure qui apparaît quand internet est coupé, maintenant sur ton bureau : le dinosaure court sur le petit écran OLED, les cactus foncent sur lui et tu sautes avec un seul bouton. Ton scor

Mode veille profonde de l'ESP32 — Économie de batterie (OLED + compteur RAM RTC)
Le mode « Deep Sleep » de l'ESP32 réduit la consommation de courant à environ 10 µA en désactivant le processeur et la plupart des périphériques, ce qui revêt une importance cruciale pour les appareils IoT fonctionnant sur batterie. Dans ce projet, nous utilisons trois sources de réveil différentes : une minuterie (timer, 15 s), une broche externe (EXT0 — bouton GPIO 33) et un capteur tactile (T0/GPIO 4). Les variables marquées avec RTC_DATA_ATTR sont conservées pendant le mode Deep Sleep — le
Autres articles
Application BlueGrays
Réalise ce montage pas à pas sur ton téléphone
Schéma, code et liste des composants sur un seul écran. Plus de 200 projets Arduino, ESP32 et Pico à réaliser.
Scanne avec ton téléphone
Pointe l’appareil photo vers le code : la boutique d’applications de ton téléphone s’ouvre.
ou ouvre la boutique

