Temeller · 13 dk okuma

Arduino EEPROM Kullanımı: Kalıcı Hafızaya Veri Yazma ve Okuma

Arduino EEPROM nasıl kullanılır? Uno'da 1024 bayt kalıcı hafıza, read, write, update, put, get, 100.000 yazma ömrü, sayaç ve şifre kaydı, ESP32 Preferences.

Arduino EEPROM, kartın elektriği kesildiğinde bile içindeki bilgiyi saklayan küçük bir kalıcı hafızadır. Arduino Uno, Nano ve Pro Mini'deki ATmega328P işlemcide 1024 baytlık EEPROM bulunur ve ona Arduino IDE ile hazır gelen EEPROM kütüphanesinin EEPROM.read(), EEPROM.write(), EEPROM.update(), EEPROM.put() ve EEPROM.get() komutlarıyla erişirsin. Değişkenlerin tutulduğu RAM her yeniden başlatmada sıfırlanırken EEPROM'a yazdığın bir ayar, sayaç ya da şifre kart yeniden açıldığında aynen yerinde durur.

Bu rehberde EEPROM'un nasıl çalıştığını, hangi kartta ne kadar yer olduğunu, komutların farkını ve en çok merak edilen konuyu, yani yaklaşık 100.000 yazmalık ömrün pratikte ne anlama geldiğini göreceksin. Ardından elektrik kesilse de unutmayan bir sayaç, şifreyi ve ayarları tek seferde saklayan bir ayar yapısı ve yazma ömrünü 16 katına çıkaran bir halka tampon örneğini adım adım kuracağız. Son bölümlerde gerçek EEPROM'u olmayan ESP32'de Preferences kütüphanesiyle, Raspberry Pi Pico'da ise MicroPython dosya sistemiyle aynı işi nasıl yapacağını öğreneceksin.

Arduino Uno: ATmega328P işlemcinin içinde 1 KB EEPROM bulunur
Arduino Uno'nun EEPROM'u ayrı bir yonga değildir; ATmega328P işlemcinin içindedir ve 0–1023 adreslerinde toplam 1024 bayt kalıcı veri saklar.

EEPROM nedir, nasıl çalışır?

EEPROM, İngilizce Electrically Erasable Programmable Read-Only Memory (elektrikle silinebilir, programlanabilir salt okunur bellek) ifadesinin kısaltmasıdır. Her bellek hücresi bilgiyi, yalıtılmış bir kapının içine hapsedilen elektrik yüküyle saklar; bu yük güç kesildiğinde de kaybolmaz. Uno'nun EEPROM'unu 1024 çekmeceli bir dolap gibi düşünebilirsin: her çekmecenin 0'dan 1023'e kadar bir adresi vardır ve her çekmeceye tam olarak 1 bayt, yani 0 ile 255 arasında bir sayı sığar. Hiç yazılmamış bir hücre 255 (0xFF) okunur. Okuma neredeyse anlıktır ve sınırsız yapılabilir; yazma ise bayt başına yaklaşık 3,3 ms sürer ve hücreyi biraz yıpratır. ATmega328P'nin veri sayfası her EEPROM hücresi için en az 100.000 yazma-silme döngüsü ve 25 °C'de 100 yıl veri saklama süresi verir.

ÖzellikFlash (program belleği)SRAMEEPROM
Uno'daki boyut32 KB (0,5 KB'ı önyükleyici)2 KB1 KB (1024 bayt)
Ne saklanır?Yüklediğin programDeğişkenler, dizilerAyarlar, sayaçlar, rekorlar
Elektrik kesilinceKorunurSilinirKorunur
Program çalışırken yazılabilir mi?Normalde hayır (PROGMEM ile yalnızca okunur)Evet, sınırsızEvet, EEPROM kütüphanesiyle
Yazma ömrüYaklaşık 10.000SınırsızHücre başına yaklaşık 100.000
Yazma hızıYalnızca kod yüklerkenTek saat döngüsüBayt başına yaklaşık 3,3 ms

Kısacası SRAM bir karalama tahtası, EEPROM ise bir defterdir. Tahtaya istediğin kadar yazıp silebilirsin ama ışıklar sönünce her şey gider; deftere yazdığın kalır ama aynı sayfayı sonsuza kadar silip yazamazsın. Bu yüzden EEPROM; kalibrasyon değerleri, kullanıcı ayarları, parlaklık ya da ses seviyesi, oyun rekoru, kapı şifresi, kayıtlı RFID kartları ve lambanın son durumu gibi az yer kaplayan ve seyrek değişen bilgiler için idealdir. Saniyede bir ölçüm kaydeden bir sıcaklık günlüğü gibi büyük ve sık yazılan veriler için ise SD kart modülü daha doğru seçimdir. RAM'in neden çabuk dolduğunu merak ediyorsan Arduino bellek yetersiz yazısına göz at.

Hangi kartta ne kadar EEPROM var?

KartİşlemciEEPROMNot
Arduino Uno R3, Nano, Pro MiniATmega328P1024 baytGerçek EEPROM
Arduino Mega 2560ATmega25604096 baytGerçek EEPROM
Arduino Leonardo, MicroATmega32U41024 baytGerçek EEPROM
Arduino Nano EveryATmega4809256 baytGerçek EEPROM, ama az yer
Arduino Uno R4 Minima, WiFiRenesas RA4M18 KBVeri flaşı; EEPROM kütüphanesi aynı komutlarla çalışır
ESP32ESP32 (Xtensa)YokFlaştaki NVS bölümü; Preferences önerilir
ESP8266 (NodeMCU, Wemos D1)ESP8266YokFlaşta taklit; EEPROM.begin() ve commit() gerekir
Raspberry Pi PicoRP2040YokMicroPython'da dosya, Arduino çekirdeğinde flaşta taklit EEPROM

Kodunu farklı kartlara taşıyacaksan EEPROM boyutunu sabit bir sayı olarak yazma; EEPROM.length() fonksiyonu kartın gerçek EEPROM boyutunu döndürür (Uno'da 1024, Mega'da 4096). Böylece aynı silme ya da tarama kodu her kartta doğru çalışır. Uno'nun diğer bellek ve pin özellikleri için Arduino Uno pinleri, kart seçimi için Hangi Arduino'yu almalıyım? yazılarına bakabilirsin.

EEPROM komutları: read, write, update, put ve get

EEPROM kütüphanesi kart paketiyle birlikte gelir; ayrıca kurman gerekmez, kodun en üstüne #include <EEPROM.h> satırını yazman yeterlidir. Adresler 0'dan başlar ve Uno'da 1023'te biter. Aşağıdaki tablo her komutun ne yaptığını ve hücreleri yıpratıp yıpratmadığını özetliyor:

KomutNe yapar?BoyutYıpranma
EEPROM.read(adres)Bir baytı okur (0–255)1 baytYok, okuma serbesttir
EEPROM.write(adres, değer)Baytı her çağrıda yeniden yazar1 baytHer çağrıda bir döngü harcar
EEPROM.update(adres, değer)Önce okur, değer farklıysa yazar1 baytYalnızca değer değişince
EEPROM.put(adres, veri)int, float, dizi ya da struct'ı bayt bayt yazar; içeride update kullanırsizeof(veri)Yalnızca değişen baytlar
EEPROM.get(adres, değişken)put ile yazılanı aynı türde geri okursizeof(değişken)Yok
EEPROM[adres]Dizi gibi erişim; okumak serbest, = ile atama write gibi çalışır1 baytHer atamada harcar
EEPROM.length()Toplam EEPROM boyutunu verir-Yok
EEPROM.write() ve EEPROM.read() yalnızca tek bir baytla çalışır. 300 gibi 255'ten büyük bir sayıyı write ile yazarsan kod hata vermeden derlenir ama hücreye yalnızca alt bayt, yani 300 − 256 = 44 yazılır. int, long, float ve struct gibi çok baytlı veriler için her zaman EEPROM.put() ve EEPROM.get() kullan. Aynı şekilde EEPROM[5] = x ataması da update değil write gibi davranır.

İlk program: EEPROM'a veri yazma ve okuma

C++ (Arduino IDE)
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() {}

Kodu yükle ve Seri Monitör'ü 9600 baud hızında aç. İlk çalıştırmada 0. adres 255 okunur, çünkü o hücreye daha önce hiç yazılmamıştır. Şimdi kartın reset düğmesine bas ya da USB kablosunu çıkarıp yeniden tak: bu kez 42 okunur. EEPROM'un kalıcılığının kanıtı budur. 1. adrese yazan write() satırı ise değer hep 7 olduğu hâlde her açılışta hücreyi yeniden yazar; update() kullansaydık yalnızca ilk seferde yazacaktı. Son satırdaki 300 değeri uyarıdaki tuzağı gösterir ve ekranda 44 görünür. Tercihler'de Derleyici uyarıları ayarı Hiçbiri dışında bir seviyedeyse derleyici bu satır için large integer implicitly truncated to unsigned type uyarısını verir.

put() ve get() kullanırken her verinin kaç bayt kapladığını bilmen gerekir; yoksa iki değişkeni üst üste yazıp birini bozarsın. Örnekte int 10 ve 11. adresleri kapladığı için float'u 12. adresten başlattık. Uno'daki veri türlerinin boyutları şöyledir:

Veri türüUno'da boyutDeğer aralığıÖrnek kullanım
byte, bool, char1 bayt0–255, true/false ya da tek karakterAçık-kapalı durumu, mod seçimi
int2 bayt−32.768 ile 32.767Kalibrasyon farkı
unsigned int, uint16_t2 bayt0–65.535Oyun rekoru
long, unsigned long4 baytYaklaşık ±2,1 milyar ya da 0–4,29 milyarUzun süreli sayaç
float4 bayt6–7 basamak hassasiyetSensör kalibrasyon katsayısı
char dizi[10]10 bayt9 karakter ve sonlandırıcıŞifre, cihaz adı
Kodunun başında bir adres haritası oluştur: const int ADDR_MODE = 0; const int ADDR_SCORE = 1; const int ADDR_CAL = 3; gibi. Bir sonraki adresi her zaman önceki adres + sizeof(önceki veri) olarak hesapla ve ileride yeni alanlar ekleyebilmek için aralarda birkaç bayt boş bırak.

EEPROM yazma ömrü: 100.000 sınırı ne demek?

100.000 yazma çok gibi görünür ama yanlış kurulmuş bir kod bu sınırı birkaç dakikada tüketebilir. Ömrü hesaplamanın formülü basittir: ömür = 100.000 ÷ aynı hücreye yapılan yazma sıklığı. Önemli nokta, sınırın tüm EEPROM için değil her hücre için ayrı geçerli olmasıdır; aynı adrese yazmaya devam ettikçe yalnızca o hücre yıpranır. Okumanın ise ömre hiçbir etkisi yoktur. Aşağıdaki tablo aynı adrese farklı sıklıklarla yazıldığında hücrenin ne kadar dayanacağını gösteriyor:

Aynı adrese yazma sıklığıYaklaşık ömür
loop() içinde beklemesiz write() (saniyede yaklaşık 300 kez)5–6 dakika
Her saniyeYaklaşık 28 saat
Her dakikaYaklaşık 69 gün
10 dakikada birYaklaşık 1,9 yıl
Saatte birYaklaşık 11 yıl
Günde 10 kezYaklaşık 27 yıl

100.000, üreticinin garanti ettiği en düşük değerdir; birçok yonga çok daha fazlasına dayanır ama projeni buna güvenerek tasarlama. Yıpranan bir hücre birden ölmez; önce yazdığın değeri tutamamaya, bazı bitleri kendiliğinden değiştirmeye başlar. Böyle bir durumda veriyi başka bir adrese taşıyabilirsin, çünkü diğer hücreler bundan etkilenmez. Ömrü uzatmanın yolları şunlardır:

  • update() ve put() kullan: İkisi de yazmadan önce hücreyi okur ve değer aynıysa hiç yazmaz.
  • Yalnızca değişince yaz: Ayarı her loop() turunda değil, kullanıcı gerçekten değiştirdiğinde kaydet.
  • Kaydı geciktir: Değer art arda değişiyorsa son değişiklikten birkaç saniye sonra tek bir kez yaz; bunu millis() ile kolayca yaparsın.
  • Olay anında yaz: Oyun rekorunu oyun bittiğinde, menü ayarını menüden çıkarken kaydet.
  • Yazmaları dağıt: Sık değişen bir sayacı tek adres yerine birkaç adrese sırayla yaz (aşağıdaki halka tampon örneği).
  • Gerekirse harici bellek kullan: Çok sık kayıt gerekiyorsa 1 milyon döngülü harici EEPROM, FRAM ya da SD kart seç.

Örnek 1: Elektrik kesilse de unutmayan sayaç

İlk örnekte bir butona her basıldığında artan ve kart kapanıp açıldığında kaldığı yerden devam eden bir sayaç yapacağız. Butonun bir bacağını D2'ye, diğerini GND'ye bağla; dahili pull-up direnci kullandığımız için ek dirence gerek yok (ayrıntılar Arduino'ya buton bağlama yazısında, pin konumları Pinout aracında). Kodun püf noktası, sayacı her basışta değil son basıştan 5 saniye sonra kaydetmesidir. Böylece birisi butona arka arkaya 20 kez bassa bile EEPROM'a yalnızca bir kez yazılır.

C++ (Arduino IDE)
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}

dirty değişkeni RAM'de henüz kaydedilmemiş bir değişiklik olduğunu gösterir. Sayaç unsigned long türünde olduğu için 4 bayt (0–3. adresler) kaplar ve 4 milyardan fazla basışı sayabilir. Hiç yazılmamış EEPROM'da bu dört bayt 0xFF olduğundan ilk açılışta 4.294.967.295 okunur; kod bu değeri görünce sayacı sıfırdan başlatır. put() yalnızca değişen baytları yazdığından sayaç artarken çoğu zaman yalnızca en alttaki bayt yıpranır. Kayıttan önceki 5 saniye içinde elektrik kesilirse son basışlar kaybolur; bu, EEPROM ömrünü korumak için ödenen küçük bir bedeldir.

Örnek 2: Ayarları ve şifreyi struct ile saklamak

Birden fazla ayarı tek tek adreslere yazmak yerine hepsini bir struct içinde toplayıp tek bir put() ile kaydetmek hem daha düzenli hem de daha güvenlidir. Bu örnekte LED parlaklığını ve 4–6 karakterli bir şifreyi saklıyoruz. Yapının başındaki iki alan çok önemlidir: magic (sihirli sayı) EEPROM'a bu programın ayarlarının gerçekten yazılıp yazılmadığını anlamamızı sağlar; version ise yapıya ileride yeni bir alan eklediğinde eski ve uyumsuz verinin okunmasını engeller. İkisinden biri tutmazsa kod varsayılan ayarları yazar.

C++ (Arduino IDE)
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}

LED'i bir LED direnci (220 ohm yeterli) üzerinden D9'a bağla; D9 bir PWM pinidir. Seri Monitör'ü 9600 baud hızında açıp satır sonu ayarını Yeni Satır yap ve şu komutları dene: b200 parlaklığı 200 yapar, p1234,5678 şifreyi 1234'ten 5678'e çevirir, ? mevcut parlaklığı gösterir. Kartı kapatıp açtığında LED aynı parlaklıkla yanar ve yeni şifre geçerli kalır. Struct Uno'da 11 bayt tutar; put() yalnızca değişen baytları yazdığı için parlaklığı değiştirmek tek bir hücreyi yıpratır. Aynı yapıyı keypad'li şifreli kilide uyarlayabilir ya da RFID RC522 ile yetkili kartların 4 baytlık kimliklerini bir dizi olarak saklayabilirsin. Seri komutlara yeniysen Arduino Serial Monitor yazısına bak.

EEPROM'a asla String nesnesi koyma. String metnin kendisini değil, RAM'deki adresini ve uzunluğunu tutar; EEPROM.put(0, isim) yazdığında EEPROM'a metin yerine birkaç baytlık bir işaretçi gider ve yeniden başlatınca anlamsız karakterler okursun. Metinleri her zaman örnekteki gibi sabit boyutlu bir char dizisi olarak sakla. Ayrıca EEPROM şifrelenmez; karta programlayıcı bağlayan biri içeriği okuyabilir. Bu yöntem hobi kilitleri için uygundur, gerçek güvenlik gerektiren işler için değil.

Yazma ömrünü katlamak: halka tampon (wear leveling)

Sık güncellenen bir sayacı hep aynı adrese yazmak o tek hücreyi hızla tüketir. Halka tampon (ring buffer) yönteminde değeri sırayla farklı yuvalara yazarsın: ilk kayıt bir yuvaya, sonraki kayıt bir sonrakine gider ve son yuvadan sonra başa dönülür. Her yuva 16 kayıtta bir yazıldığı için ömür 16 kat, yani yaklaşık 1,6 milyon kayda çıkar. Açılışta en güncel değeri bulmak için tüm yuvalar okunur; sayaç hep arttığı için en büyük değer en yenisidir.

C++ (Arduino IDE)
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() {}

Örnek her açılışta sayacı bir artırır; sen bunu bir buton, bir sensör olayı ya da çalışma saati sayacıyla değiştirebilirsin. Yuva sayısını artırdıkça ömür de aynı oranda artar: 64 yuva 256 bayt kaplar ve yaklaşık 6,4 milyon kayda dayanır. Kart daha önce başka programlarda kullanıldıysa halka bölgesinde eski veriler kalmış olabilir; ilk kullanımdan önce aşağıdaki silme koduyla EEPROM'u temizle. Daha gelişmiş projelerde Arduino'nun kendi eeprom_crc örneğindeki gibi bir CRC sağlama toplamı ekleyerek bozulmuş veriyi de yakalayabilirsin.

EEPROM nasıl sıfırlanır? Kod yükleyince silinir mi?

USB kablosuyla normal yükleme yaptığında Arduino yalnızca program belleğini yazar; EEPROM'daki veriler korunur. Bu yüzden yeni bir proje yüklediğinde önceki projenin ayarları EEPROM'da kalır ve yeni kodun beklemediği değerleri okumasına yol açabilir. Uno'da EEPROM yalnızca bir ISP programlayıcıyla Eskiz > Programlayıcı Kullanarak Yükle ya da Araçlar > Bootloader'ı Yükle (Burn Bootloader) yapıldığında silinir; bu işlemler yongayı tamamen siler ve Uno'nun sigorta (fuse) ayarlarında EEPROM'u koruyan EESAVE biti kapalıdır. EEPROM'u kendin temizlemek için şu kodu bir kez yükle:

C++ (Arduino IDE)
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() {}

Arduino IDE'deki Dosya > Örnekler > EEPROM > eeprom_clear örneği tüm hücrelere 0 yazar; biz ise hücreleri fabrika durumu olan 255'e döndürüyoruz. Böylece magic sayı ve 0xFF kontrolü yapan kodlar EEPROM'u yeniden boş olarak tanır. update() kullandığımız için zaten 255 olan hücrelere hiç yazılmaz. Uno'da 1024 hücrenin hepsini yazmak en fazla yaklaşık 3,4 saniye sürer; dahili LED yandığında işlem bitmiştir. Ardından asıl programını yükle.

ESP32'de EEPROM: Preferences kütüphanesi

ESP32'de gerçek bir EEPROM yoktur. Kalıcı veriler, harici flaş belleğin NVS (Non-Volatile Storage) adlı bölümünde tutulur; varsayılan bölüm tablosunda bu alan 20 KB'tır. Espressif'in önerdiği yol, ESP32 kart paketiyle gelen Preferences kütüphanesidir. Preferences veriyi adres yerine ad alanı (namespace) ve anahtar (key) isimleriyle saklar; yani sayacın hangi adreste olduğunu düşünmezsin. NVS yeni değeri her seferinde sayfadaki bir sonraki boş kayda yazıp eskisini silinmiş olarak işaretlediği için aşınma dengelemesi kendiliğinden yapılır. Ad alanı ve anahtar isimleri en çok 15 karakter olabilir ve büyük-küçük harfe duyarlıdır. ESP32'ye yeniysen önce ESP32 nedir yazısına göz at.

C++ (Arduino IDE)
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() {}
KomutNe yapar?
prefs.begin("ad", false)Ad alanını açar; false okuma ve yazma, true yalnızca okuma
putUInt, putInt, putFloat, putBool, putString, putBytesDeğeri anahtar adıyla kaydeder
getUInt("anahtar", varsayılan)Değeri okur; anahtar yoksa varsayılanı döndürür
isKey("anahtar")Anahtarın var olup olmadığına bakar
remove("anahtar")Tek bir anahtarı siler
clear()Açık ad alanındaki tüm anahtarları siler
freeEntries()NVS'te kalan boş kayıt sayısını verir
end()Ad alanını kapatır

Bir değeri hangi türle yazdıysan o türle okumalısın: putUInt ile yazıp getInt ile okursan varsayılan değer döner. Kodda getString() öncesinde isKey() kullanmamızın da bir sebebi var: Araçlar menüsündeki Core Debug Level ayarı Error ya da üstündeyse, olmayan bir metin anahtarını okumak Seri Monitör'e nvs_get_str len fail: ssid NOT_FOUND satırını yazar; zararsızdır ama kafa karıştırır. Kodu USB'den yeniden yüklemek NVS'yi silmez, ancak Erase All Flash Before Sketch Upload seçeneği Enabled ise her yüklemede tüm kayıtlar gider. Wi-Fi bilgilerini Preferences ile saklamak yaygındır; ağ adını ve şifreyi telefondan girmek için ESP32 WiFiManager de ESP32'nin Wi-Fi ayarlarını aynı NVS alanında tutar. Derin uykudan uyanırken saklanması gereken kısa ömürlü değerler için flaşı hiç yıpratmayan RTC belleği daha uygundur; ayrıntılar ESP32 deep sleep yazısında.

Eski Arduino kodlarını ESP32'ye taşırken EEPROM kütüphanesi de çalışır, ama iki farkı vardır: kullanmadan önce EEPROM.begin(boyut) ile kaç bayt kullanacağını söylemen, değişiklikten sonra da EEPROM.commit() çağırman gerekir. put() ve write() yalnızca RAM'deki kopyayı değiştirir; commit() olmadan hiçbir şey flaşa yazılmaz. ESP32'de bu kütüphane NVS içinde tek bir veri bloğu olarak çalışır ve Espressif tarafından eskimiş (deprecated) kabul edilir; yeni oluşturulan alanın tüm baytları 0xFF'tir. Aynı kod ESP8266'da da derlenir; orada boyut en çok 4096 bayt olabilir ve her commit() 4 KB'lık bir flaş sektörünü silip yeniden yazar, bu yüzden commit'i seyrek çağır.

C++ (Arduino IDE)
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() {}

Raspberry Pi Pico'da kalıcı veri: MicroPython dosya sistemi

Raspberry Pi Pico'nun RP2040 yongasında da EEPROM yoktur; kartın üzerinde 2 MB'lık bir flaş bellek bulunur. MicroPython bu flaşın yaklaşık 1,4 MB'lık kısmını LittleFS dosya sistemi olarak ayırır (Pico W'de Wi-Fi yazılımı yüzünden daha az). Yani Pico'da EEPROM'un karşılığı, ayarlarını küçük bir dosyaya yazmaktır. LittleFS yazmaları flaşın farklı bloklarına dağıttığı için aşınma dengelemesini kendisi yapar ve yazma sırasında elektrik kesilse bile dosya sistemi bozulmaz. Aşağıdaki kod ayarları JSON biçiminde saklar, her açılışı sayar ve GP15 ile GND arasına bağlı bir butona her basıldığında sayacı kaydeder.

MicroPython
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)

Dosyayı doğrudan üzerine yazmak yerine önce .tmp uzantılı geçici bir dosyaya yazıp sonra os.rename() ile eskisinin yerine koyuyoruz; böylece yazma yarıda kalsa bile eski ayar dosyası sağlam kalır. Dosya yoksa ya da içeriği bozuksa load() varsayılan değerlere döner; programa sonradan yeni bir ayar eklediğinde setdefault() eksik anahtarı kendiliğinden tamamlar. Pico'yu Arduino IDE ile programlıyorsan Earle Philhower'ın Arduino-Pico çekirdeğindeki EEPROM kütüphanesini de kullanabilirsin: EEPROM.begin(256) ile 256–4096 bayt arasında bir alan açar, değişiklikleri EEPROM.commit() ile kaydedersin. Bu taklit EEPROM flaşın sonundaki 4 KB'lık sektörde durur ve her commit() o sektörü silip yeniden yazar; flaş da sektör başına yaklaşık 100.000 silme döngüsüne dayandığı için commit'i seyrek çağır. Pico'ya yeni başlıyorsan Raspberry Pi Pico nedir ve MicroPython temelleri yazıları iyi bir başlangıçtır.

Daha fazla kalıcı hafıza: harici EEPROM, FRAM ve SD kart

1 KB yetmiyorsa ya da çok sık kayıt yapman gerekiyorsa harici bir bellek ekleyebilirsin. AT24C256 gibi I2C EEPROM yongaları 32 KB yer ve yaklaşık 1 milyon yazma döngüsü sunar; yalnızca SDA ve SCL kablolarıyla bağlanır (I2C nedir). Pek bilinmeyen bir ayrıntı: piyasadaki birçok DS3231 RTC saat modülünün üzerinde zaten 4 KB'lık bir AT24C32 EEPROM bulunur ve genellikle 0x57 I2C adresinden erişilir. Saniyede defalarca kayıt gerektiren işler için yaklaşık bir trilyon (10^12) yazma döngüsüne dayanan FRAM modülleri, büyük veri günlükleri için ise veriyi dosya olarak saklayan SD kart modülü daha uygundur.

EEPROM ile proje fikirleri

  • Son durumu hatırlayan lamba: Elektrik gidip geldiğinde röleye bağlı lambayı kesintiden önceki durumuna döndür.
  • Şifresi değiştirilebilen kasa: Keypad ile girilen yeni şifreyi EEPROM'a yaz; kod yeniden yüklenmeden şifre değişsin.
  • Kartlı kapı kilidi: RFID RC522 ile okutulan yetkili kartların kimliklerini sakla, yönetici kartıyla yeni kart ekle.
  • Kalıcı oyun rekoru: Simon, T-Rex ya da yılan oyununda en yüksek puanı sakla; örnek kodlar Arduino oyun projeleri yazısında.
  • Hareket kaydeden robot kol: Servo açılarını poz poz kaydedip tekrar oynat; Arduino robot kol yazısındaki kod bunu EEPROM.update() ile yapar.
  • Kalibrasyonu unutmayan tartı: HX711 yük hücresi ile bulduğun kalibrasyon katsayısını float olarak put() ile sakla.
  • Reset sayacı: Kartın kaç kez yeniden başladığını sayarak besleme sorunlarını teşhis et; yöntem Arduino sürekli resetleniyor yazısında.

Sık yapılan hatalar ve çözümleri

BelirtiSebepÇözüm
İlk açılışta 255, -1, 65535 ya da nan okunuyorEEPROM'a hiç yazılmamış; boş hücreler 0xFF'tirMagic sayı ya da 0xFF kontrolüyle varsayılan değerleri yaz
300 yazıldı, 44 okunuyorwrite() ve read() yalnızca 1 bayt taşırÇok baytlı veriler için put() ve get() kullan
İki değer birbirini bozuyorAdresler çakışıyor; int 2, float 4 bayt kaplarAdres haritası yap, adresleri sizeof ile hesapla
Yeniden başlatınca metin anlamsız karakterlere dönüşüyorString nesnesi put() ile yazılmışMetni char dizisi olarak sakla
Yeni kod eski projenin ayarlarını okuyorNormal yükleme EEPROM'u silmezMagic sayı ve sürüm alanı kullan ya da EEPROM'u temizle
Ayarlar kod yükleyince silindiUno'da programlayıcıyla yükleme, ESP32'de Erase All Flash seçeneğiUSB'den normal yükle, ESP32'de seçeneği Disabled yap
ESP32'de değerler kaydedilmiyorEEPROM.begin() ya da EEPROM.commit() eksikİkisini ekle ya da Preferences'a geç
Preferences hep varsayılan değeri döndürüyorFarklı türle okuma, 15 karakteri aşan isim ya da farklı ad alanıput ve get türlerini eşleştir, isimleri kısa tut
nvs_open failed: NOT_FOUND hatasıHiç oluşturulmamış bir ad alanı salt okunur (true) açılmışAd alanını önce false ile açıp bir değer yaz
Bir süre sonra değerler bozuluyorloop() içinde sürekli yazma ya da yazma sırasında besleme düşmesiupdate() ve gecikmeli kayıt kullan, halka tampon ve CRC ekle
EEPROM'da ne olduğunu görmek en hızlı teşhis yöntemidir. Bir for döngüsüyle ilk 64 adresi EEPROM.read() ile okuyup Seri Monitör'e yazdırırsan (ya da Arduino'nun hazır eeprom_read örneğini açarsan) boş hücreleri, yanlış adrese yazılmış değerleri ve eski projelerden kalan verileri hemen fark edersin.

EEPROM, Arduino projelerine hafıza kazandırmanın en kolay yoludur: birkaç satır kodla cihazının ayarlarını, sayaçlarını ve şifrelerini elektrik kesintilerine karşı korursun. Dikkat etmen gereken tek şey yazma ömrüdür; update() ve put() kullandığın ve gereksiz yere yazmadığın sürece Uno'nun EEPROM'u yıllarca sorunsuz çalışır. BlueGrays uygulamasında 4x4 keypad ile şifre uygulaması, akıllı elektronik kasa, RFID kimlik doğrulama, Simon ve T-Rex oyunları gibi EEPROM'la geliştirebileceğin projeleri devre şemaları ve adım adım kodlarıyla bulabilirsin.

Sıkça sorulan sorular

Arduino EEPROM kaç kez yazılabilir?

ATmega328P'nin veri sayfası her EEPROM hücresi için en az 100.000 yazma-silme döngüsü verir. Sınır hücre başınadır ve okuma ömrü hiç etkilemez. Aynı adrese saniyede bir yazmak hücreyi yaklaşık 28 saatte tüketir; update() kullanıp yalnızca değer değişince yazarsan EEPROM yıllarca dayanır.

Arduino'ya yeni kod yükleyince EEPROM silinir mi?

Hayır. USB üzerinden normal yükleme yalnızca program belleğini yazar ve EEPROM'daki veriler korunur. EEPROM, Uno'da yalnızca ISP programlayıcıyla yükleme yapıldığında ya da önyükleyici yazıldığında silinir. Yeni projede eski ayarlarla karşılaşmamak için magic sayı kontrolü kullan.

EEPROM.write ile EEPROM.update arasındaki fark nedir?

write() değeri her çağrıda hücreye yeniden yazar ve her seferinde bir yazma döngüsü harcar. update() önce hücreyi okur, değer aynıysa hiç yazmaz. Sonuç aynıdır ama update() EEPROM ömrünü korur; put() de içeride aynı yöntemi kullanır.

Arduino EEPROM nasıl temizlenir?

EEPROM.length() kadar dönen bir for döngüsüyle tüm adreslere 255 (ya da 0) yazarsın; Arduino IDE'deki eeprom_clear örneği hazır olarak 0 yazar. Uno'da 1024 baytı silmek yaklaşık 3,4 saniye sürer. İşlem bitince silme kodunu kartta bırakma, asıl programını yükle.

ESP32'de EEPROM var mı?

ESP32'de gerçek bir EEPROM yoktur; kalıcı veriler flaş bellekteki NVS bölümünde tutulur. Yeni projelerde Preferences kütüphanesi önerilir. Eski kodlar için EEPROM kütüphanesi de çalışır, ama önce EEPROM.begin(boyut) ve her değişiklikten sonra EEPROM.commit() çağırmak zorunludur.

EEPROM'a int, float ya da metin nasıl kaydedilir?

int, long, float ve struct gibi çok baytlı verileri EEPROM.put(adres, değişken) ile yazar, EEPROM.get(adres, değişken) ile okursun. int 2, float 4 bayt kapladığı için sonraki verinin adresini buna göre seç. Metni String olarak değil, sabit boyutlu bir char dizisi olarak sakla.

Tüm projeleri gör
Buton İle Led Yakma (On Off)
#10Başlangıç

Buton İle Led Yakma (On Off)

Bir butona bir kez basınca ışık yanar, bir daha basınca söner; tıpkı odandaki lamba düğmesi gibi. Arduino her basışı fark edip LED'in durumunu tersine çevirir, düğme içindeki minik titreşimler ışığı k

4.8(100)
167
7 Segment Display Ve Buton İle Sayaç
#43İleri
Premium

7 Segment Display Ve Buton İle Sayaç

Üç butonlu basit bir sayaç: birine bastıkça ekrandaki rakam bir artar, diğerine bastıkça bir azalır, üçüncüsü sıfıra döndürür. Tek haneli 7 segment ekran 0 ile 9 arasında sayar; sınıra dayandığında kö

4.6(35)
47
4x4 Keypad LCD Ekran Şifre Uygulaması
#64İleri
Premium

4x4 Keypad LCD Ekran Şifre Uygulaması

Doğru şifreyi bilmeyenin giremediği küçük bir elektronik kapı kilidi. Kare tuşuna basıp şifreyi tuş takımından girersin; şifre doğruysa ekranda giriş izni yazar, yeşil LED yanar ve servo motor kapıyı

4.8(59)
75
Akıllı Elektronik Kasa
#92İleri
Premium

Akıllı Elektronik Kasa

Değerli eşyalarını saklayabileceğin, şifreyle açılan küçük bir kasa. Tuş takımından dört haneli şifreni girersin; doğruysa servo motor kilidi açar, yeşil ışık yanar ve ekran kasanın durumunu yazar, he

4.7(42)
63
RFID Kimlik Doğrulama
#54Orta
Premium

RFID Kimlik Doğrulama

Apartman kapısındaki kartlı giriş gibi: RFID okuyucuya kartını yaklaştırırsın, Arduino kartın numarasını kayıtlı olanla karşılaştırır. Doğru kartta yeşil, yabancı kartta kırmızı LED üç saniye yanar ve

4.8(64)
83
Arduino Hafıza Oyunu (4 LED + 4 Buton + Buzzer)
#93İleri
Premium

Arduino Hafıza Oyunu (4 LED + 4 Buton + Buzzer)

Hafızana güveniyor musun? Dört renkli LED sırayla yanar ve her biri kendi notasını çalar; sen de aynı sırayı butonlara basarak tekrar edersin. Her doğru turda sıraya yeni bir renk eklenir, küçük bir z

4.8(46)
69
OLED T-Rex Run: Arduino Dinozor Oyunu
#87İleri
Premium

OLED T-Rex Run: Arduino Dinozor Oyunu

İnternet kesilince çıkan dinozor oyunu artık masanda: küçük OLED ekranda dinozor koşar, kaktüsler üstüne gelir ve sen tek butonla zıplarsın. Puanın yandaki 4 haneli ekranda canlı olarak artar, her 100

4.7(33)
53
ESP32 Deep Sleep — Pil Tasarrufu (OLED + RTC RAM Sayaç)
#135İleri
Premium

ESP32 Deep Sleep — Pil Tasarrufu (OLED + RTC RAM Sayaç)

ESP32'nin Deep Sleep modu, CPU ve çoğu çevre birimini kapatarak akım tüketimini ~10µA seviyesine düşürür ve pille çalışan IoT cihazları için kritik öneme sahiptir. Bu projede üç farklı uyandırma kaynağını kullanıyoruz: zamanlayıcı (timer, 15sn), harici pin (EXT0 — buton GPIO 33) ve touch (T0/GPIO 4). RTC_DATA_ATTR ile işaretlenen değişkenler deep sleep boyunca korunur — boot sayacı, timer wake sayacı ve buton wake sayacı kalıcı olarak izlenir. SSD1306 OLED ekranda her uyanışta wake reason, say

4.3(10)
16