L’EEPROM (Electrically Erasable Programmable Read-Only Memory) est une mémoire non volatile intégrée au microcontrôleur, qui conserve son contenu après coupure d’alimentation. Sur Arduino Uno R3, elle fait 1 ko ; sur Mega 2560, 4 ko. C’est l’endroit où l’on stocke les paramètres utilisateur (calibration capteur, mode de fonctionnement, compteur d’événements) pour les retrouver au redémarrage. La bibliothèque EEPROM.h livrée avec l’IDE encapsule la lecture / écriture à l’octet près.

À quoi ça sert ?

Cas d’usage en projet école :

  • Compteur d’événements persistant — nombre de démarrages, nombre de cycles effectués, durée totale d’utilisation.
  • Paramètres de calibration — offset capteur, seuils utilisateur, mode de fonctionnement.
  • Mémorisation de la dernière configuration — dernier mode actif, dernière valeur de consigne, pour reprendre où on s’était arrêté.
  • Petits logs — quelques dernières valeurs d’un capteur conservées pour analyse post-démarrage.

Limites importantes : capacité réduite (1 ko = 1024 octets sur Uno R3), nombre de cycles d’écriture limité (~100 000 par cellule), pas de gestion de système de fichiers. C’est une mémoire brute adressée par numéro d’octet. Pour des volumes plus gros, voir la carte SD (voir SPI sur Arduino).

Procédure pas à pas

Quatre étapes : connaître la capacité de la carte, lire la valeur initiale, écrire avec économie, gérer les structures de données.

1. Connaître la capacité

CarteCapacité EEPROMType
Uno R3, Nano (ATmega328P)1024 octetsEEPROM dédiée
Mega 25604096 octetsEEPROM dédiée
Uno R48 ko émulés sur Data Flash (Renesas RA4M1)émulation — effacement par page
ESP32aucune EEPROM matérielle — émulée sur Flash (taille déclarée à l’init)émulation via EEPROM.h ou Preferences.h

Sur Uno R4, l’EEPROM n’existe pas physiquement. Le Renesas RA4M1 utilise sa Data Flash (8 ko) pour émuler le comportement. La bibliothèque EEPROM.h reste compatible, mais l’effacement se fait par page de 1024 octets : écrire un seul octet peut obliger à réécrire toute la page. Conséquence : l’usure grimpe bien plus vite qu’en EEPROM dédiée, et update() n’y apporte pas le même gain qu’en AVR. Ne jamais faire de data logging via EEPROM.h sur R4. Pour des écritures fréquentes, passer par une carte SD.

Sur ESP32, préférer Preferences.h (plus moderne) à EEPROM.h (héritage compatibilité).

2. Lecture / écriture basique

#include <EEPROM.h>
 
void setup() {
  Serial.begin(115200);
  delay(2000);
 
  // Écrire un octet à l'adresse 0
  EEPROM.write(0, 42);
 
  // Lire le même octet
  byte valeur = EEPROM.read(0);
  Serial.print("Lu : "); Serial.println(valeur);
}
 
void loop() {}

EEPROM.write(adresse, octet) écrit. EEPROM.read(adresse) lit. L’adresse est un entier entre 0 et la capacité - 1 (0-1023 sur Uno R3).

Pour les types multi-octets (int, float, structures), utiliser EEPROM.put() et EEPROM.get() qui gèrent automatiquement la sérialisation :

float seuil = 3.14;
EEPROM.put(0, seuil);  // écrit 4 octets
 
float seuilLu;
EEPROM.get(0, seuilLu);  // lit 4 octets et reconstitue le float

3. Économiser les cycles d’écriture

Chaque cellule EEPROM supporte ~100 000 cycles d’écriture (variable selon les conditions). À 1 écriture par seconde, ça dure ~28 heures avant usure. Discipline obligatoire : ne pas écrire en boucle, n’écrire que quand la valeur change.

void enregistrer(int adresse, byte nouvelleValeur) {
  byte valeurActuelle = EEPROM.read(adresse);
  if (valeurActuelle != nouvelleValeur) {
    EEPROM.write(adresse, nouvelleValeur);
  }
}

EEPROM.update(adresse, octet) fait cela automatiquement (lecture préalable, écriture si différent). À préférer à EEPROM.write() chaque fois qu’on n’est pas sûr que la valeur change vraiment.

4. Gérer des structures de données

Pour stocker plusieurs paramètres ensemble, définir une structure et l’écrire en bloc :

struct Config {
  int seuilCapteur;
  float coeffEtalonnage;
  byte modeActif;
  uint32_t nbDemarrages;
};
 
void sauverConfig(Config &c) {
  EEPROM.put(0, c);  // écrit l'intégralité de la structure à l'adresse 0
}
 
void chargerConfig(Config &c) {
  EEPROM.get(0, c);
}

Avantage : la structure documente le format des données et facilite l’évolution.

Carte mémoire de l'EEPROM : un octet de signature à l'adresse 0, puis la structure Config étalée (seuilCapteur sur 2 octets, coeffEtalonnage sur 4, modeActif sur 1, nbDemarrages sur 4), le reste libre à 0xFF. L'adresse de chaque champ se déduit de la taille du précédent.|640

Exemple — Compteur de démarrages persistant

Cas complet : compter le nombre de démarrages de l’Arduino et l’afficher au moniteur série.

#include <EEPROM.h>
 
const int ADRESSE_COMPTEUR = 0;  // les 4 premiers octets
 
void setup() {
  Serial.begin(115200);
  delay(2000);
 
  // Lire le compteur actuel
  uint32_t compteur;
  EEPROM.get(ADRESSE_COMPTEUR, compteur);
 
  // EEPROM neuve : tous les octets sont à 0xFF — résultat 0xFFFFFFFF = 4294967295
  // Détecter ce cas et initialiser à 0
  if (compteur == 0xFFFFFFFF) {
    compteur = 0;
    Serial.println("EEPROM neuve, initialisation");
  }
 
  // Incrémenter
  compteur++;
 
  // Sauvegarder
  EEPROM.put(ADRESSE_COMPTEUR, compteur);
 
  Serial.print("Demarrage n°");
  Serial.println(compteur);
}
 
void loop() {}

Comment lire ce code

EEPROM.get(ADRESSE_COMPTEUR, compteur) relit les 4 octets d’un uint32_t à l’adresse 0. Sur une carte neuve, ces octets valent tous 0xFF, donc compteur vaut 0xFFFFFFFF (soit 4294967295) : on teste ce cas pour repartir de 0 plutôt que d’un nombre aberrant. On incrémente, puis EEPROM.put(...) réécrit les 4 octets. get/put gèrent seuls la (dé)sérialisation du type, pas besoin de découper en octets à la main.

Téléverser, observer la valeur (par exemple : Demarrage n°1). Débrancher l’Arduino, rebrancher : la valeur s’incrémente (n°2, n°3…). Elle persiste même après une déconnexion totale.

Pièges

Détection de l’EEPROM neuve. Une EEPROM vierge a tous ses octets à 0xFF (255). Si on lit un int à l’adresse 0 sans avoir jamais écrit, on lit 0xFFFF (soit -1 en signé, 65535 en non signé). Toujours détecter ce cas et initialiser à une valeur sensée. Sinon, le programme part avec des paramètres aberrants. Variante : écrire une signature (octet de valeur connue à une adresse fixe) qu’on vérifie au démarrage.

Oublier EEPROM.commit() sur ESP32. Sur ESP32, l’EEPROM.h émulée nécessite un appel explicite à EEPROM.commit() après chaque modification pour que les données soient effectivement écrites en Flash. Sur Uno R3, ce n’est pas nécessaire. Un code écrit pour Uno qui passe sur ESP32 et perd ses données, c’est l’oubli du commit().

Écriture en boucle. Code comme EEPROM.write(0, capteur); dans loop() : chaque tour de loop() écrit, on dépasse les 100 000 cycles en quelques heures. Cellule grillée, valeurs erratiques. Utiliser EEPROM.update() ou n’écrire que sur événement (changement de mode, bouton).

Adresses qui se chevauchent. Stocker un int (2 octets) à l’adresse 0, puis un float (4 octets) à l’adresse 1 : le float écrase les octets 1, 2, 3, 4, donc le second octet de l’int est corrompu. Toujours calculer les adresses : ADR_FLOAT = ADR_INT + sizeof(int);.

Casse de structure entre versions. Modifier le struct Config (ajout d’un champ, changement d’un type) brise la compatibilité avec l’EEPROM existante. Au démarrage, on lit des octets dans la mauvaise structure. Bonne pratique : numéroter la version dans la structure et migrer si la version diffère.

Corruption d’EEPROM par reset pendant l’écriture. Une coupure d’alimentation pendant EEPROM.write() peut corrompre l’octet en cours d’écriture (~3 ms sur Uno R3). Pour des données critiques, utiliser un double-buffer (deux copies à adresses différentes, basculer entre les deux) ou un checksum.

Mauvaise interprétation des types. Relire en float (via EEPROM.get(adr, monFloat)) ce qui avait été écrit en int (via EEPROM.put(adr, monInt)) donne des valeurs aberrantes — pas d’erreur de compilation, juste de mauvais bits réinterprétés. get/put déduisent le type de la variable passée : la cohérence type-à-type entre l’écriture et la lecture est de la responsabilité du programmeur.

Confondre Flash et EEPROM. La mémoire Flash d’un Arduino contient le sketch : on ne peut pas l’écrire en cours d’exécution depuis le code utilisateur. La EEPROM est une zone séparée, dédiée aux données utilisateur. Sur Uno R4 et ESP32, la frontière s’estompe (émulation), mais le concept reste valide (voir les mémoires d’un microcontrôleur).

Cas particulier — Stockage sur ESP32

Sur ESP32, l’EEPROM.h est seulement émulée (rétrocompatibilité Arduino, avec commit() obligatoire). L’API native recommandée est Preferences.h : des paires clé-valeur stockées en Flash (NVS), avec gestion automatique de l’espace et de l’usure, nettement plus robuste pour des paramètres persistants. Le détail de cette API relève du parcours ESP32. Ici, retenir surtout que le code EEPROM d’un Arduino ne se transpose pas tel quel sur ESP32 (au minimum, ajouter commit()).

Raccrochage projet

  • Étape 3 de la phase de preuve de concept — au premier paramètre utilisateur réglable (seuil, mode), envisager la sauvegarde en EEPROM dès la PoC pour ne pas avoir à recoder le paramétrage à chaque cycle.
  • Étape 4 de la phase de dossier technique — la persistance des paramètres et compteurs fait partie des choix d’architecture du firmware.
  • Étape 3 de la phase d’intégration et tests — la calibration en condition réelle se sauvegarde en EEPROM pour ne pas être perdue à la coupure.

L’EEPROM est un petit outil simple mais souvent négligé en projet débutant : l’introduire au moment où apparaît le premier paramètre persistant transforme un sketch de démo en système qui se souvient, étape clé pour un démonstrateur crédible.

Voir aussi