Arduino EEPROM: Daten dauerhaft speichern, lesen und schreiben
Arduino EEPROM nutzen: 1024 Byte beim Uno, read, write, update, put und get, 100.000 Schreibzyklen, Zähler und Passwort speichern, Preferences beim ESP32.
Das Arduino EEPROM ist ein kleiner dauerhafter Speicher, der seinen Inhalt auch dann behält, wenn das Board keinen Strom mehr hat. Der ATmega328P auf dem Arduino Uno, Nano und Pro Mini besitzt 1024 Byte EEPROM, und du greifst mit den Befehlen EEPROM.read(), EEPROM.write(), EEPROM.update(), EEPROM.put() und EEPROM.get() der EEPROM-Bibliothek darauf zu, die mit der Arduino IDE mitgeliefert wird. Der RAM mit deinen Variablen wird bei jedem Neustart gelöscht, eine Einstellung, ein Zähler oder ein Passwort im EEPROM ist dagegen nach dem Einschalten noch genau so da.
In dieser Anleitung erfährst du, wie das EEPROM funktioniert, wie viel davon jedes Board hat, worin sich die Befehle unterscheiden und was die viel zitierte Lebensdauer von rund 100.000 Schreibzyklen in der Praxis bedeutet. Danach bauen wir Schritt für Schritt einen Zähler, der Stromausfälle übersteht, eine Einstellungsstruktur, die Passwort und Optionen in einem Rutsch speichert, und einen Ringpuffer, der die Lebensdauer versechzehnfacht. In den letzten Abschnitten lernst du, wie du dasselbe auf dem ESP32 ohne echtes EEPROM mit der Preferences-Bibliothek und auf dem Raspberry Pi Pico mit dem MicroPython-Dateisystem erledigst.

Was ist ein EEPROM und wie funktioniert es?
EEPROM steht für Electrically Erasable Programmable Read-Only Memory, also elektrisch löschbarer, programmierbarer Festwertspeicher. Jede Speicherzelle speichert ihre Information als elektrische Ladung in einem isolierten Gate, und diese Ladung bleibt auch ohne Strom erhalten. Stell dir das EEPROM des Uno als Schrank mit 1024 Schubladen vor: Jede Schublade hat eine Adresse von 0 bis 1023 und fasst genau 1 Byte, also eine Zahl zwischen 0 und 255. Eine nie beschriebene Zelle liest 255 (0xFF). Lesen geht fast sofort und beliebig oft; Schreiben dauert etwa 3,3 ms pro Byte und nutzt die Zelle ein wenig ab. Das Datenblatt des ATmega328P garantiert pro EEPROM-Zelle mindestens 100.000 Schreib-/Löschzyklen und 100 Jahre Datenerhalt bei 25 °C.
| Eigenschaft | Flash (Programmspeicher) | SRAM | EEPROM |
|---|---|---|---|
| Größe beim Uno | 32 KB (0,5 KB für den Bootloader) | 2 KB | 1 KB (1024 Byte) |
| Was wird gespeichert? | Der hochgeladene Sketch | Variablen, Arrays | Einstellungen, Zähler, Highscores |
| Bei Stromausfall | Bleibt erhalten | Wird gelöscht | Bleibt erhalten |
| Beschreibbar, während der Sketch läuft? | Normalerweise nein (per PROGMEM nur lesbar) | Ja, unbegrenzt | Ja, mit der EEPROM-Bibliothek |
| Schreibzyklen | Etwa 10.000 | Unbegrenzt | Etwa 100.000 pro Zelle |
| Schreibgeschwindigkeit | Nur beim Hochladen | Ein Taktzyklus | Etwa 3,3 ms pro Byte |
Kurz gesagt: Der SRAM ist eine Tafel, das EEPROM ein Notizbuch. Auf die Tafel kannst du beliebig oft schreiben und wischen, aber wenn das Licht ausgeht, ist alles weg; was im Notizbuch steht, bleibt, nur kannst du dieselbe Seite nicht endlos radieren und neu beschreiben. Deshalb eignet sich das EEPROM ideal für Daten, die wenig Platz brauchen und sich selten ändern: Kalibrierwerte, Benutzereinstellungen, Helligkeit oder Lautstärke, den Highscore eines Spiels, einen Türcode, angelernte RFID-Karten oder den letzten Zustand einer Lampe. Für große, häufig geschriebene Daten wie ein Temperaturprotokoll mit einem Messwert pro Sekunde ist ein SD-Kartenmodul die bessere Wahl. Wenn du wissen willst, warum der RAM so schnell voll ist, lies Arduino Speicher voll.
Wie viel EEPROM hat welches Board?
| Board | Prozessor | EEPROM | Hinweis |
|---|---|---|---|
| Arduino Uno R3, Nano, Pro Mini | ATmega328P | 1024 Byte | Echtes EEPROM |
| Arduino Mega 2560 | ATmega2560 | 4096 Byte | Echtes EEPROM |
| Arduino Leonardo, Micro | ATmega32U4 | 1024 Byte | Echtes EEPROM |
| Arduino Nano Every | ATmega4809 | 256 Byte | Echtes EEPROM, aber klein |
| Arduino Uno R4 Minima, WiFi | Renesas RA4M1 | 8 KB | Daten-Flash; die EEPROM-Bibliothek funktioniert mit denselben Befehlen |
| ESP32 | ESP32 (Xtensa) | Keins | NVS-Partition im Flash; Preferences empfohlen |
| ESP8266 (NodeMCU, Wemos D1) | ESP8266 | Keins | Im Flash emuliert; braucht EEPROM.begin() und commit() |
| Raspberry Pi Pico | RP2040 | Keins | In MicroPython eine Datei, im Arduino-Core ein emuliertes EEPROM im Flash |
Wenn dein Code auf verschiedenen Boards laufen soll, schreib die EEPROM-Größe nicht fest hinein; EEPROM.length() liefert die tatsächliche Größe des Boards (1024 beim Uno, 4096 beim Mega), sodass derselbe Lösch- oder Auslesecode überall funktioniert. Weitere Speicher- und Pin-Details des Uno findest du unter Arduino Uno Pinbelegung, Hilfe bei der Boardwahl unter Welchen Arduino soll ich kaufen?.
EEPROM-Befehle: read, write, update, put und get
Die EEPROM-Bibliothek gehört zum Board-Paket, du musst also nichts installieren; schreib einfach die Zeile #include <EEPROM.h> an den Anfang deines Sketches. Die Adressen beginnen bei 0 und enden beim Uno bei 1023. Die Tabelle fasst zusammen, was jeder Befehl macht und ob er die Zellen abnutzt:
| Befehl | Was er macht | Größe | Abnutzung |
|---|---|---|---|
| EEPROM.read(Adresse) | Liest ein Byte (0–255) | 1 Byte | Keine, Lesen ist kostenlos |
| EEPROM.write(Adresse, Wert) | Schreibt das Byte bei jedem Aufruf neu | 1 Byte | Kostet bei jedem Aufruf einen Zyklus |
| EEPROM.update(Adresse, Wert) | Liest zuerst, schreibt nur bei abweichendem Wert | 1 Byte | Nur wenn sich der Wert ändert |
| EEPROM.put(Adresse, Daten) | Schreibt int, float, Array oder struct Byte für Byte; nutzt intern update | sizeof(Daten) | Nur geänderte Bytes |
| EEPROM.get(Adresse, Variable) | Liest das mit put Geschriebene im gleichen Typ zurück | sizeof(Variable) | Keine |
| EEPROM[Adresse] | Zugriff wie auf ein Array; Lesen kostenlos, Zuweisung mit = wirkt wie write | 1 Byte | Bei jeder Zuweisung |
| EEPROM.length() | Gibt die Gesamtgröße des EEPROM zurück | - | Keine |
Erster Sketch: Daten ins EEPROM schreiben und lesen
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() {}Lade den Sketch hoch und öffne den Seriellen Monitor mit 9600 Baud. Beim ersten Start liest Adresse 0 den Wert 255, weil diese Zelle noch nie beschrieben wurde. Drück jetzt den Reset-Taster des Boards oder zieh das USB-Kabel ab und steck es wieder ein: Diesmal erscheint 42. Das ist der Beweis, dass das EEPROM seine Daten behält. Die write()-Zeile für Adresse 1 dagegen schreibt die Zelle bei jedem Start neu, obwohl der Wert immer 7 ist; mit update() wäre nur beim ersten Mal geschrieben worden. Der Wert 300 in der letzten Zeile zeigt die Falle aus dem Hinweis oben, und auf dem Bildschirm steht 44. Steht die Option Compiler-Meldungen in den Einstellungen nicht auf Keine, meldet der Compiler für diese Zeile large integer implicitly truncated to unsigned type.
Bei put() und get() musst du wissen, wie viele Bytes jeder Wert belegt; sonst schreibst du zwei Variablen übereinander und zerstörst eine davon. Im Beispiel belegt der int die Adressen 10 und 11, deshalb beginnt der float bei Adresse 12. So groß sind die Datentypen auf dem Uno:
| Datentyp | Größe beim Uno | Wertebereich | Beispiel |
|---|---|---|---|
| byte, bool, char | 1 Byte | 0–255, true/false oder ein Zeichen | Ein/Aus-Zustand, Moduswahl |
| int | 2 Byte | −32.768 bis 32.767 | Kalibrier-Offset |
| unsigned int, uint16_t | 2 Byte | 0–65.535 | Highscore eines Spiels |
| long, unsigned long | 4 Byte | Etwa ±2,1 Milliarden oder 0–4,29 Milliarden | Langzeitzähler |
| float | 4 Byte | 6–7 Stellen Genauigkeit | Kalibrierfaktor eines Sensors |
| char-Array[10] | 10 Byte | 9 Zeichen plus Endezeichen | Passwort, Gerätename |
EEPROM-Lebensdauer: Was bedeutet die Grenze von 100.000 Zyklen?
100.000 Schreibvorgänge klingen nach viel, doch ein ungünstig aufgebauter Sketch verbraucht sie in wenigen Minuten. Die Formel für die Lebensdauer ist einfach: Lebensdauer = 100.000 ÷ Schreibhäufigkeit auf dieselbe Zelle. Entscheidend ist, dass die Grenze für jede Zelle einzeln gilt und nicht für das gesamte EEPROM; schreibst du immer an dieselbe Adresse, nutzt sich nur diese Zelle ab. Lesen hat überhaupt keinen Einfluss auf die Lebensdauer. Die Tabelle zeigt, wie lange eine Zelle hält, wenn dieselbe Adresse unterschiedlich oft beschrieben wird:
| Wie oft dieselbe Adresse beschrieben wird | Ungefähre Lebensdauer |
|---|---|
| write() in loop() ohne Pause (etwa 300-mal pro Sekunde) | 5–6 Minuten |
| Jede Sekunde | Etwa 28 Stunden |
| Jede Minute | Etwa 69 Tage |
| Alle 10 Minuten | Etwa 1,9 Jahre |
| Einmal pro Stunde | Etwa 11 Jahre |
| 10-mal am Tag | Etwa 27 Jahre |
100.000 ist der Mindestwert, den der Hersteller garantiert; viele Chips halten deutlich länger, aber plane dein Projekt nicht darauf. Eine abgenutzte Zelle fällt nicht plötzlich aus; sie kann den geschriebenen Wert zunehmend nicht mehr halten, und einzelne Bits kippen von selbst. Dann kannst du die Daten an eine andere Adresse verlegen, denn die übrigen Zellen sind davon nicht betroffen. So verlängerst du die Lebensdauer:
- update() und put() verwenden: Beide lesen die Zelle vor dem Schreiben und schreiben gar nicht, wenn der Wert gleich ist.
- Nur bei Änderung schreiben: Speichere eine Einstellung, wenn der Benutzer sie wirklich ändert, nicht bei jedem Durchlauf von loop().
- Speichern verzögern: Ändert sich ein Wert schnell hintereinander, schreib ihn einmal ein paar Sekunden nach der letzten Änderung; mit millis() geht das ganz einfach.
- Bei Ereignissen schreiben: Speichere den Highscore am Spielende und Menüeinstellungen beim Verlassen des Menüs.
- Schreibvorgänge verteilen: Schreib einen sich oft ändernden Zähler reihum an mehrere Adressen statt an eine (siehe Ringpuffer-Beispiel unten).
- Bei Bedarf externen Speicher nutzen: Für sehr häufiges Speichern nimm ein externes EEPROM mit 1 Million Zyklen, FRAM oder eine SD-Karte.
Beispiel 1: Ein Zähler, der Stromausfälle übersteht
Im ersten Beispiel bauen wir einen Zähler, der bei jedem Tastendruck hochzählt und nach dem Aus- und Einschalten dort weitermacht, wo er aufgehört hat. Schließ einen Anschluss des Tasters an D2 und den anderen an GND an; wir nutzen den internen Pull-up-Widerstand, ein zusätzlicher Widerstand ist also nicht nötig (Details in Taster am Arduino anschließen, die Pinlage im Werkzeug Pinout). Der Trick: Der Sketch speichert den Zähler nicht bei jedem Druck, sondern 5 Sekunden nach dem letzten Druck. Selbst wenn jemand den Taster 20-mal hintereinander drückt, wird das EEPROM nur einmal beschrieben.
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}Die Variable dirty zeigt an, dass es im RAM eine noch nicht gespeicherte Änderung gibt. Der Zähler ist ein unsigned long, belegt also 4 Byte (Adressen 0–3) und kann mehr als 4 Milliarden Tastendrücke zählen. In einem leeren EEPROM sind diese vier Bytes 0xFF, beim ersten Start wird also 4.294.967.295 gelesen; erkennt der Code diesen Wert, beginnt der Zähler bei null. Da put() nur geänderte Bytes schreibt, nutzt sich beim Hochzählen meist nur das unterste Byte ab. Fällt der Strom innerhalb der 5 Sekunden vor dem Speichern aus, gehen die letzten Tastendrücke verloren; ein kleiner Preis für ein langlebiges EEPROM.
Beispiel 2: Einstellungen und Passwort in einer struct speichern
Statt mehrere Einstellungen einzeln an Adressen zu schreiben, ist es ordentlicher und sicherer, sie in einer struct zu bündeln und mit einem einzigen put() zu speichern. In diesem Beispiel speichern wir die LED-Helligkeit und ein Passwort mit 4–6 Zeichen. Die ersten beiden Felder der Struktur sind entscheidend: magic (eine magische Zahl) verrät, ob die Einstellungen dieses Sketches wirklich im EEPROM stehen, und version verhindert, dass alte, unpassende Daten gelesen werden, wenn du der Struktur später ein Feld hinzufügst. Stimmt eines davon nicht, schreibt der Sketch die Standardeinstellungen.
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}Schließ die LED über einen LED-Vorwiderstand (220 Ohm reichen) an D9 an; D9 ist ein PWM-Pin. Öffne den Seriellen Monitor mit 9600 Baud, stell das Zeilenende auf Neue Zeile und probier diese Befehle aus: b200 setzt die Helligkeit auf 200, p1234,5678 ändert das Passwort von 1234 auf 5678, und ? zeigt die aktuelle Helligkeit. Schaltest du das Board aus und wieder ein, leuchtet die LED mit derselben Helligkeit und das neue Passwort gilt weiter. Die struct belegt auf dem Uno 11 Byte; da put() nur geänderte Bytes schreibt, nutzt eine Helligkeitsänderung nur eine einzige Zelle ab. Dieselbe Struktur kannst du für ein Codeschloss mit Keypad übernehmen oder mit dem RFID RC522 die 4-Byte-IDs berechtigter Karten in einem Array ablegen. Wenn serielle Befehle neu für dich sind, lies Arduino Serieller Monitor.
Lebensdauer vervielfachen: Ringpuffer (Wear Leveling)
Einen oft aktualisierten Zähler immer an dieselbe Adresse zu schreiben, verschleißt diese eine Zelle schnell. Beim Ringpuffer schreibst du den Wert reihum in verschiedene Speicherplätze: Ein Speichervorgang landet in einem Platz, der nächste im folgenden, und nach dem letzten Platz geht es wieder von vorn los. Jeder Platz wird nur bei jedem 16. Speichern beschrieben, die Lebensdauer steigt also auf das 16-Fache, rund 1,6 Millionen Speichervorgänge. Beim Start werden alle Plätze gelesen, um den aktuellen Wert zu finden; da der Zähler nur steigt, ist der größte Wert der neueste.
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() {}Das Beispiel erhöht den Zähler bei jedem Start; du kannst das durch einen Taster, ein Sensorereignis oder einen Betriebsstundenzähler ersetzen. Mehr Plätze bedeuten entsprechend mehr Lebensdauer: 64 Plätze belegen 256 Byte und halten etwa 6,4 Millionen Speichervorgänge. Wurde das Board schon mit anderen Sketches benutzt, können im Ringbereich alte Daten stehen; lösche das EEPROM deshalb vor der ersten Nutzung mit dem Code weiter unten. In aufwendigeren Projekten kannst du außerdem eine CRC-Prüfsumme wie im Arduino-Beispiel eeprom_crc ergänzen, um beschädigte Daten zu erkennen.
EEPROM löschen: Wird es beim Hochladen gelöscht?
Ein normaler Upload über das USB-Kabel beschreibt nur den Programmspeicher; die Daten im EEPROM bleiben erhalten. Lädst du ein neues Projekt hoch, stehen also noch die Einstellungen des vorherigen Projekts im EEPROM, und der neue Code liest womöglich Werte, mit denen er nicht rechnet. Beim Uno wird das EEPROM nur gelöscht, wenn du mit einem ISP-Programmer Sketch > Mit Programmer hochladen oder Werkzeuge > Bootloader brennen verwendest; diese Vorgänge löschen den ganzen Chip, und das EESAVE-Bit, das das EEPROM schützt, ist in den Fuse-Einstellungen des Uno nicht gesetzt. Um das EEPROM selbst zu löschen, lade diesen Sketch einmal hoch:
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() {}Das Beispiel Datei > Beispiele > EEPROM > eeprom_clear der Arduino IDE schreibt in jede Zelle eine 0; wir setzen die Zellen stattdessen auf ihren Auslieferungszustand 255 zurück. So erkennt Code, der eine magische Zahl oder 0xFF prüft, das EEPROM wieder als leer. Da wir update() verwenden, werden Zellen, die schon 255 sind, gar nicht beschrieben. Alle 1024 Zellen des Uno zu beschreiben dauert höchstens etwa 3,4 Sekunden; leuchtet die eingebaute LED, ist es fertig. Lade danach deinen eigentlichen Sketch hoch.
EEPROM beim ESP32: die Preferences-Bibliothek
Der ESP32 hat kein echtes EEPROM. Dauerhafte Daten liegen in der NVS-Partition (Non-Volatile Storage) des externen Flash-Speichers; in der Standard-Partitionstabelle ist dieser Bereich 20 KB groß. Espressif empfiehlt dafür die Preferences-Bibliothek, die zum ESP32-Board-Paket gehört. Preferences speichert Daten nicht über Adressen, sondern über Namespace und Schlüssel (Key); du musst dir also nie merken, an welcher Adresse der Zähler lag. NVS schreibt jeden neuen Wert in den nächsten freien Eintrag der Seite und markiert den alten als gelöscht, dadurch erfolgt das Wear Leveling automatisch. Namespace- und Schlüsselnamen dürfen höchstens 15 Zeichen lang sein und unterscheiden Groß- und Kleinschreibung. Wenn der ESP32 neu für dich ist, fang mit Was ist der ESP32 an.
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() {}| Befehl | Was er macht |
|---|---|
| prefs.begin("name", false) | Öffnet den Namespace; false heißt lesen und schreiben, true nur lesen |
| putUInt, putInt, putFloat, putBool, putString, putBytes | Speichert den Wert unter einem Schlüsselnamen |
| getUInt("key", Standardwert) | Liest den Wert; gibt den Standardwert zurück, wenn der Schlüssel fehlt |
| isKey("key") | Prüft, ob der Schlüssel existiert |
| remove("key") | Löscht einen einzelnen Schlüssel |
| clear() | Löscht alle Schlüssel im geöffneten Namespace |
| freeEntries() | Gibt die Zahl der freien Einträge im NVS zurück |
| end() | Schließt den Namespace |
Einen Wert musst du mit demselben Typ lesen, mit dem du ihn geschrieben hast: Schreibst du mit putUInt und liest mit getInt, bekommst du den Standardwert. Dass wir vor getString() isKey() aufrufen, hat ebenfalls einen Grund: Steht Core Debug Level im Menü Werkzeuge auf Error oder höher, schreibt das Lesen eines nicht vorhandenen Text-Schlüssels nvs_get_str len fail: ssid NOT_FOUND in den Seriellen Monitor; das ist harmlos, verwirrt aber. Ein erneuter Upload per USB löscht den NVS nicht, steht aber Erase All Flash Before Sketch Upload auf Enabled, ist nach jedem Upload alles weg. WLAN-Zugangsdaten mit Preferences zu speichern ist üblich; willst du Netzwerkname und Passwort lieber am Handy eingeben, hilft ESP32 WiFiManager, und der ESP32 legt seine WLAN-Einstellungen im selben NVS-Bereich ab. Für kurzlebige Werte, die nur das Aufwachen aus dem Tiefschlaf überstehen müssen, ist der RTC-Speicher besser, weil er den Flash gar nicht abnutzt; mehr dazu in ESP32 Deep Sleep.
Wenn du alten Arduino-Code auf den ESP32 portierst, funktioniert auch die EEPROM-Bibliothek, allerdings mit zwei Unterschieden: Vor der Nutzung musst du mit EEPROM.begin(Größe) angeben, wie viele Bytes du brauchst, und nach Änderungen EEPROM.commit() aufrufen. put() und write() ändern nur die Kopie im RAM; ohne commit() landet nichts im Flash. Auf dem ESP32 läuft diese Bibliothek als einzelner Datenblock im NVS und gilt bei Espressif als veraltet (deprecated); alle Bytes eines neu angelegten Bereichs sind 0xFF. Derselbe Code kompiliert auch für den ESP8266; dort sind höchstens 4096 Byte möglich, und jedes commit() löscht und beschreibt einen 4-KB-Flash-Sektor neu, ruf commit also sparsam auf.
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() {}Dauerhafte Daten auf dem Raspberry Pi Pico: das MicroPython-Dateisystem
Auch der RP2040-Chip des Raspberry Pi Pico hat kein EEPROM; auf dem Board sitzen 2 MB Flash-Speicher. MicroPython reserviert davon etwa 1,4 MB für ein LittleFS-Dateisystem (beim Pico W wegen der WLAN-Firmware weniger). Das Gegenstück zum EEPROM ist auf dem Pico also eine kleine Datei mit deinen Einstellungen. LittleFS verteilt Schreibvorgänge auf verschiedene Flash-Blöcke, übernimmt also selbst das Wear Leveling, und das Dateisystem wird auch bei einem Stromausfall während des Schreibens nicht beschädigt. Der folgende Code speichert die Einstellungen als JSON, zählt jeden Start und sichert den Zähler bei jedem Druck auf einen Taster zwischen GP15 und 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)Statt die Datei direkt zu überschreiben, schreiben wir zuerst in eine temporäre .tmp-Datei und ersetzen dann mit os.rename() die alte; so bleibt die alte Einstellungsdatei intakt, selbst wenn ein Schreibvorgang abbricht. Fehlt die Datei oder ist ihr Inhalt beschädigt, fällt load() auf die Standardwerte zurück, und wenn du dem Programm später eine neue Einstellung hinzufügst, ergänzt setdefault() den fehlenden Schlüssel automatisch. Programmierst du den Pico mit der Arduino IDE, kannst du auch die EEPROM-Bibliothek des Arduino-Pico-Cores von Earle Philhower nutzen: EEPROM.begin(256) öffnet einen Bereich von 256–4096 Byte, und EEPROM.commit() speichert deine Änderungen. Dieses emulierte EEPROM liegt im letzten 4-KB-Sektor des Flash, und jedes commit() löscht und beschreibt diesen Sektor neu; der Flash verträgt etwa 100.000 Löschzyklen pro Sektor, ruf commit also sparsam auf. Wenn du gerade mit dem Pico anfängst, sind Was ist der Raspberry Pi Pico und MicroPython Grundlagen ein guter Einstieg.
Mehr dauerhafter Speicher: externes EEPROM, FRAM und SD-Karte
Reicht 1 KB nicht oder musst du sehr oft speichern, kannst du externen Speicher ergänzen. I2C-EEPROM-Chips wie der AT24C256 bieten 32 KB und etwa 1 Million Schreibzyklen und werden nur über SDA und SCL angeschlossen (Was ist I2C). Ein wenig bekanntes Detail: Viele DS3231-RTC-Uhrmodule im Handel tragen bereits ein 4-KB-EEPROM vom Typ AT24C32, das meist unter der I2C-Adresse 0x57 erreichbar ist. Für Aufgaben, die viele Male pro Sekunde speichern, eignen sich FRAM-Module mit rund einer Billion (10^12) Schreibzyklen besser, für große Datenprotokolle ein SD-Kartenmodul, das die Daten als Dateien ablegt.
Projektideen mit dem EEPROM
- Lampe, die sich ihren letzten Zustand merkt: Kehrt der Strom zurück, versetzt du die Lampe am Relais wieder in den Zustand vor dem Ausfall.
- Tresor mit änderbarem Code: Schreib den am Keypad eingegebenen neuen Code ins EEPROM, damit er sich ohne neuen Upload ändern lässt.
- Türschloss mit Karte: Speichere die IDs berechtigter Karten, die mit dem RFID RC522 gelesen werden, und lerne neue Karten mit einer Masterkarte an.
- Dauerhafter Highscore: Merk dir die beste Punktzahl bei Simon, T-Rex oder Snake; Beispielcode findest du in Arduino Spiele-Projekte.
- Roboterarm, der Bewegungen aufzeichnet: Speichere Servowinkel Pose für Pose und spiel sie wieder ab; der Code in Arduino Roboterarm macht das mit EEPROM.update().
- Waage, die ihre Kalibrierung behält: Speichere den mit der HX711-Wägezelle ermittelten Kalibrierfaktor als float mit put().
- Reset-Zähler: Zähl, wie oft das Board neu startet, um Probleme mit der Stromversorgung zu finden; die Methode steht in Arduino startet ständig neu.
Häufige Fehler und ihre Lösungen
| Symptom | Ursache | Lösung |
|---|---|---|
| Beim ersten Start wird 255, -1, 65535 oder nan gelesen | Das EEPROM wurde nie beschrieben; leere Zellen sind 0xFF | Standardwerte schreiben, wenn die Prüfung auf magische Zahl oder 0xFF fehlschlägt |
| 300 geschrieben, 44 gelesen | write() und read() übertragen nur 1 Byte | Für Daten mit mehreren Bytes put() und get() nutzen |
| Zwei Werte überschreiben sich | Die Adressen überlappen; ein int belegt 2, ein float 4 Byte | Adresstabelle anlegen, Adressen mit sizeof berechnen |
| Text wird nach einem Neustart zu Zeichensalat | Ein String-Objekt wurde mit put() geschrieben | Text als char-Array speichern |
| Neuer Code liest die Einstellungen des alten Projekts | Ein normaler Upload löscht das EEPROM nicht | Magische Zahl und Versionsfeld nutzen oder das EEPROM löschen |
| Einstellungen nach dem Upload weg | Upload mit Programmer beim Uno, Option Erase All Flash beim ESP32 | Normal per USB hochladen, Option beim ESP32 auf Disabled stellen |
| Werte werden auf dem ESP32 nicht gespeichert | EEPROM.begin() oder EEPROM.commit() fehlt | Beides ergänzen oder zu Preferences wechseln |
| Preferences liefert immer den Standardwert | Lesen mit anderem Typ, Name länger als 15 Zeichen oder anderer Namespace | Typen von put und get angleichen, Namen kurz halten |
| Fehler nvs_open failed: NOT_FOUND | Ein nie angelegter Namespace wurde schreibgeschützt (true) geöffnet | Namespace zuerst mit false öffnen und einen Wert schreiben |
| Werte sind nach einiger Zeit beschädigt | Ständiges Schreiben in loop() oder Spannungseinbruch beim Schreiben | update() und verzögertes Speichern nutzen, Ringpuffer und CRC ergänzen |
Das EEPROM ist der einfachste Weg, deinen Arduino-Projekten ein Gedächtnis zu geben: Mit ein paar Zeilen Code schützt du Einstellungen, Zähler und Passwörter vor Stromausfällen. Achten musst du nur auf die Lebensdauer; solange du update() und put() verwendest und nicht unnötig schreibst, arbeitet das EEPROM des Uno jahrelang zuverlässig. In der BlueGrays-App findest du Projekte, die du mit dem EEPROM erweitern kannst, etwa die Passwort-Anwendung mit 4x4-Keypad, den smarten elektronischen Tresor, die RFID-Authentifizierung sowie die Spiele Simon und T-Rex, jeweils mit Schaltplan und Schritt-für-Schritt-Code.
Häufige Fragen
Wie oft kann man das Arduino EEPROM beschreiben?
Das Datenblatt des ATmega328P garantiert für jede EEPROM-Zelle mindestens 100.000 Schreib-/Löschzyklen. Die Grenze gilt pro Zelle, und Lesen hat darauf keinen Einfluss. Wer dieselbe Adresse jede Sekunde beschreibt, verbraucht die Zelle in etwa 28 Stunden; mit update() und Schreiben nur bei Änderungen hält das EEPROM jahrelang.
Wird das Arduino EEPROM beim Hochladen eines neuen Sketches gelöscht?
Nein. Ein normaler Upload per USB beschreibt nur den Programmspeicher, die Daten im EEPROM bleiben erhalten. Beim Uno wird das EEPROM nur gelöscht, wenn du mit einem ISP-Programmer hochlädst oder den Bootloader brennst. Prüf eine magische Zahl, damit ein neues Projekt keine alten Einstellungen übernimmt.
Was ist der Unterschied zwischen EEPROM.write und EEPROM.update?
write() schreibt den Wert bei jedem Aufruf neu in die Zelle und verbraucht jedes Mal einen Schreibzyklus. update() liest die Zelle zuerst und schreibt gar nicht, wenn der Wert gleich ist. Das Ergebnis ist identisch, aber update() schont die Lebensdauer; put() arbeitet intern genauso.
Wie lösche ich das Arduino EEPROM?
Schreib mit einer for-Schleife bis EEPROM.length() in jede Adresse 255 (oder 0); das Beispiel eeprom_clear der Arduino IDE schreibt bereits Nullen. Alle 1024 Byte des Uno zu löschen dauert etwa 3,4 Sekunden. Lass den Lösch-Sketch danach nicht auf dem Board, sondern lade dein eigentliches Programm hoch.
Hat der ESP32 ein EEPROM?
Der ESP32 hat kein echtes EEPROM; dauerhafte Daten liegen in der NVS-Partition des Flash-Speichers. Für neue Projekte wird die Preferences-Bibliothek empfohlen. Die EEPROM-Bibliothek funktioniert für alten Code weiterhin, du musst aber zuerst EEPROM.begin(Größe) und nach jeder Änderung EEPROM.commit() aufrufen.
Wie speichere ich int, float oder Text im EEPROM?
Daten mit mehreren Bytes wie int, long, float und structs schreibst du mit EEPROM.put(Adresse, Variable) und liest sie mit EEPROM.get(Adresse, Variable). Ein int belegt 2 Byte, ein float 4, danach richtet sich die nächste Adresse. Text speicherst du als char-Array fester Größe, nicht als String.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
LED mit Taster ein- und ausschalten
Einmal drücken und das Licht geht an, noch einmal drücken und es geht aus, genau wie der Lichtschalter in deinem Zimmer. Der Arduino bemerkt jeden Druck und schaltet die LED in den jeweils anderen Zus

Zähler mit 7-Segment-Anzeige und Tastern
Ein einfacher Zähler mit drei Knöpfen: Mit dem einen zählt die Ziffer auf der Anzeige um eins hoch, mit dem anderen um eins runter, und der dritte setzt sie auf null zurück. Die einstellige 7-Segment-

Passwortschloss mit 4x4-Tastenfeld und LCD
Ein kleines elektronisches Türschloss, das niemanden ohne den richtigen Code hereinlässt. Du drückst die Rautetaste und tippst den Code auf dem Tastenfeld ein; stimmt er, meldet das Display Zutritt er

Intelligenter elektronischer Safe
Ein kleiner Tresor für deine Wertsachen, der sich mit einem Code öffnet. Du tippst deinen vierstelligen Code auf dem Tastenfeld ein; stimmt er, öffnet der Servomotor das Schloss, das grüne Licht geht

RFID-Authentifizierung
Wie der Kartenzugang an der Haustür: Du hältst deine Karte an den RFID-Leser, und der Arduino vergleicht ihre Nummer mit der gespeicherten. Bei der richtigen Karte leuchtet drei Sekunden lang die grün

Arduino Gedächtnisspiel (4 LEDs + 4 Taster + Summer)
Vertraust du deinem Gedächtnis? Vier bunte LEDs leuchten nacheinander auf, jede mit ihrem eigenen Ton, und du wiederholst die Reihenfolge mit den Tastern. Jede richtige Runde fügt der Folge eine neue

OLED T-Rex Run: Arduino Dinosaurier Spiel
Das Dinosaurierspiel, das erscheint, wenn das Internet weg ist, jetzt auf deinem Schreibtisch: Der Dino rennt über das kleine OLED-Display, Kakteen kommen auf ihn zu, und du springst mit einem einzige

ESP32 Deep Sleep – Batteriesparmodus (OLED + RTC-RAM-Zähler)
Der Deep-Sleep-Modus des ESP32 senkt den Stromverbrauch auf etwa 10 µA, indem er die CPU und die meisten Peripheriegeräte abschaltet, und ist für batteriebetriebene IoT-Geräte von entscheidender Bedeutung. In diesem Projekt verwenden wir drei verschiedene Weckquellen: den Timer (15 s), einen externen Pin (EXT0 – Taste GPIO 33) und die Berührungserkennung (T0/GPIO 4). Mit RTC_DATA_ATTR markierte Variablen bleiben während des Deep-Sleep-Modus erhalten – der Boot-Zähler, der Timer-Wake-Zähler und
Weitere Artikel
BlueGrays-App
Diese Schaltung Schritt für Schritt auf dem Handy nachbauen
Schaltplan, Code und Teileliste auf einem Bildschirm. Über 200 Arduino-, ESP32- und Pico-Projekte zum Nachbauen.
Mit dem Handy scannen
Richte die Kamera auf den Code, dann öffnet sich der App-Store deines Handys.
oder im Store öffnen

