Fundamentos · 17 min de leitura

EEPROM do Arduino: como usar read, write, update, put e get

EEPROM do Arduino explicada: 1024 bytes no Uno, read, write, update, put e get, o limite de 100.000 gravações, como salvar contadores e senhas e Preferences no ESP32.

A EEPROM do Arduino é uma pequena memória permanente que mantém seu conteúdo mesmo quando a placa fica sem alimentação. O ATmega328P do Arduino Uno, Nano e Pro Mini tem 1024 bytes de EEPROM, e você a acessa com os comandos EEPROM.read(), EEPROM.write(), EEPROM.update(), EEPROM.put() e EEPROM.get() da biblioteca EEPROM que já vem com a Arduino IDE. A RAM que guarda suas variáveis é apagada a cada reinicialização, mas uma configuração, um contador ou uma senha que você grava na EEPROM continua lá quando a placa é ligada de novo.

Neste guia você vai ver como a EEPROM funciona, quanto dela cada placa tem, como os comandos se diferenciam e a pergunta que todo mundo faz: o que a vida útil de cerca de 100.000 gravações significa na prática. Depois vamos montar, passo a passo, um contador que lembra seu valor mesmo com quedas de energia, uma estrutura de configurações que guarda uma senha e opções de uma só vez e um buffer circular que multiplica a vida útil de gravação por 16. Nas últimas seções você vai aprender a fazer o mesmo no ESP32, que não tem EEPROM de verdade, com a biblioteca Preferences, e no Raspberry Pi Pico com o sistema de arquivos do MicroPython.

Arduino Uno: o processador ATmega328P contém 1 KB de EEPROM
A EEPROM do Arduino Uno não é um chip separado; ela fica dentro do ATmega328P e guarda 1024 bytes de dados permanentes nos endereços 0–1023.

O que é EEPROM e como funciona?

EEPROM é a sigla de Electrically Erasable Programmable Read-Only Memory (memória somente de leitura programável e apagável eletricamente). Cada célula de memória guarda a informação como uma carga elétrica presa dentro de uma porta isolada, e essa carga permanece quando a energia acaba. Pense na EEPROM do Uno como um armário com 1024 gavetas: cada gaveta tem um endereço de 0 a 1023 e guarda exatamente 1 byte, um número entre 0 e 255. Uma célula que nunca foi gravada é lida como 255 (0xFF). A leitura é quase instantânea e ilimitada; a gravação leva cerca de 3,3 ms por byte e desgasta um pouco a célula. O datasheet do ATmega328P especifica pelo menos 100.000 ciclos de gravação/apagamento por célula de EEPROM e 100 anos de retenção de dados a 25 °C.

CaracterísticaFlash (memória de programa)SRAMEEPROM
Tamanho no Uno32 KB (0,5 KB para o bootloader)2 KB1 KB (1024 bytes)
O que é guardado?O sketch que você carregouVariáveis, arraysConfigurações, contadores, recordes
Quando falta energiaMantidoApagadoMantido
Gravável com o sketch em execução?Normalmente não (só leitura via PROGMEM)Sim, ilimitadoSim, com a biblioteca EEPROM
Vida útil de gravaçãoCerca de 10.000IlimitadaCerca de 100.000 por célula
Velocidade de gravaçãoSó ao carregar o códigoUm ciclo de clockCerca de 3,3 ms por byte

Resumindo, a SRAM é um quadro branco e a EEPROM é um caderno. Você pode escrever e apagar no quadro quantas vezes quiser, mas tudo some quando as luzes se apagam; o que você escreve no caderno fica, mas não dá para apagar e reescrever a mesma página para sempre. Por isso a EEPROM é ideal para informações pequenas e que mudam pouco: valores de calibração, configurações do usuário, brilho ou volume, o recorde de um jogo, a senha de uma porta, cartões RFID cadastrados ou o último estado de uma lâmpada. Para dados grandes e gravados com frequência, como um registro de temperatura que grava a cada segundo, um módulo de cartão SD é a melhor escolha. Se você quer entender por que a RAM enche tão rápido, leia Arduino sem memória.

Quanto de EEPROM cada placa tem?

PlacaProcessadorEEPROMObservação
Arduino Uno R3, Nano, Pro MiniATmega328P1024 bytesEEPROM real
Arduino Mega 2560ATmega25604096 bytesEEPROM real
Arduino Leonardo, MicroATmega32U41024 bytesEEPROM real
Arduino Nano EveryATmega4809256 bytesEEPROM real, mas pequena
Arduino Uno R4 Minima, WiFiRenesas RA4M18 KBFlash de dados; a biblioteca EEPROM funciona com os mesmos comandos
ESP32ESP32 (Xtensa)NenhumaPartição NVS na flash; recomenda-se Preferences
ESP8266 (NodeMCU, Wemos D1)ESP8266NenhumaEmulada na flash; requer EEPROM.begin() e commit()
Raspberry Pi PicoRP2040NenhumaUm arquivo no MicroPython; EEPROM emulada na flash com o core Arduino

Se o seu código vai passar de uma placa para outra, não fixe o tamanho da EEPROM no código; EEPROM.length() retorna o tamanho real da EEPROM da placa (1024 no Uno, 4096 no Mega), e assim o mesmo código de limpeza ou de leitura completa funciona em qualquer uma. Para outros detalhes de memória e pinos do Uno, veja Pinagem do Arduino Uno, e para escolher uma placa, leia Qual Arduino comprar?.

Comandos da EEPROM: read, write, update, put e get

A biblioteca EEPROM vem com o pacote da placa, então não há nada para instalar; basta adicionar a linha #include <EEPROM.h> no topo do sketch. Os endereços começam em 0 e terminam em 1023 no Uno. A tabela abaixo resume o que cada comando faz e se ele desgasta as células:

ComandoO que fazTamanhoDesgaste
EEPROM.read(endereço)Lê um byte (0–255)1 byteNenhum, a leitura é livre
EEPROM.write(endereço, valor)Regrava o byte a cada chamada1 byteGasta um ciclo a cada chamada
EEPROM.update(endereço, valor)Lê primeiro e só grava se o valor for diferente1 byteSó quando o valor muda
EEPROM.put(endereço, dados)Grava um int, float, array ou struct byte a byte; usa update internamentesizeof(dados)Só os bytes que mudaram
EEPROM.get(endereço, variável)Lê de volta o que put gravou, no mesmo tiposizeof(variável)Nenhum
EEPROM[endereço]Acesso como array; ler é livre, atribuir com = funciona como write1 byteA cada atribuição
EEPROM.length()Retorna o tamanho total da EEPROM-Nenhum
EEPROM.write() e EEPROM.read() só lidam com um único byte. Se você gravar um número maior que 255, como 300, o sketch compila sem erro, mas só o byte baixo é armazenado: 300 − 256 = 44. Para dados de vários bytes, como int, long, float e structs, use sempre EEPROM.put() e EEPROM.get(). Da mesma forma, a atribuição EEPROM[5] = x funciona como write, não como update.

Primeiro sketch: gravar e ler dados na EEPROM

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() {}

Carregue o sketch e abra o Monitor Serial a 9600 baud. Na primeira execução o endereço 0 é lido como 255, porque essa célula nunca foi gravada. Agora aperte o botão de reset da placa ou desconecte e reconecte o cabo USB: desta vez a leitura é 42. Essa é a prova de que a EEPROM guarda seus dados. Já a linha write() do endereço 1 regrava a célula a cada inicialização, mesmo que o valor seja sempre 7; com update() ela teria gravado só na primeira vez. O valor 300 na última linha mostra a armadilha do aviso acima, e 44 aparece na tela. Se a configuração Avisos do compilador em Preferências estiver em qualquer opção diferente de Nenhum, o compilador marca essa linha com large integer implicitly truncated to unsigned type.

Ao usar put() e get(), você precisa saber quantos bytes cada dado ocupa; senão vai gravar duas variáveis uma em cima da outra e corromper uma delas. No exemplo, o int ocupa os endereços 10 e 11, então o float começa no endereço 12. Estes são os tamanhos dos tipos de dados no Uno:

Tipo de dadoTamanho no UnoFaixaExemplo de uso
byte, bool, char (tipos de 1 byte)1 byte0–255, true/false ou um caractereEstado ligado/desligado, seleção de modo
int2 bytes−32.768 a 32.767Offset de calibração
unsigned int, uint16_t2 bytes0–65.535Recorde de jogo
long, unsigned long (inteiros de 4 bytes)4 bytesCerca de ±2,1 bilhões ou 0–4,29 bilhõesContador de longa duração
float4 bytes6–7 dígitos de precisãoFator de calibração de sensor
char vetor[10]10 bytes9 caracteres mais o terminadorSenha, nome do dispositivo
Crie um mapa de endereços no topo do sketch, como const int ADDR_MODE = 0; const int ADDR_SCORE = 1; const int ADDR_CAL = 3;. Calcule sempre o próximo endereço como o endereço anterior + sizeof(dado anterior) e deixe alguns bytes livres entre eles para poder adicionar novos campos depois.

Vida útil da EEPROM: o que significa o limite de 100.000?

100.000 gravações parece muito, mas um sketch mal estruturado pode esgotar isso em minutos. A fórmula da vida útil é simples: vida útil = 100.000 ÷ frequência de gravação na mesma célula. O ponto principal é que o limite vale para cada célula separadamente, não para a EEPROM inteira; se você continuar gravando no mesmo endereço, só essa célula se desgasta. A leitura não tem nenhum efeito na vida útil. A tabela mostra quanto tempo uma célula dura quando o mesmo endereço é gravado em ritmos diferentes:

Frequência de gravação no mesmo endereçoVida útil aproximada
write() no loop() sem espera (cerca de 300 vezes por segundo)5–6 minutos
A cada segundoCerca de 28 horas
A cada minutoCerca de 69 dias
A cada 10 minutosCerca de 1,9 ano
Uma vez por horaCerca de 11 anos
10 vezes por diaCerca de 27 anos

100.000 é o mínimo garantido pelo fabricante; muitos chips duram bem mais, mas não planeje seu projeto contando com isso. Uma célula desgastada não morre de repente; ela começa a não conseguir manter o valor gravado e alguns bits mudam sozinhos. Se isso acontecer, você pode mover os dados para outro endereço, porque as outras células não são afetadas. Formas de prolongar a vida útil:

  • Use update() e put(): os dois leem a célula antes de gravar e não gravam nada se o valor for o mesmo.
  • Grave só quando mudar: salve uma configuração quando o usuário realmente a alterar, não a cada passagem pelo loop().
  • Atrase o salvamento: se um valor muda várias vezes seguidas, grave uma única vez alguns segundos após a última mudança; millis() facilita isso.
  • Grave em eventos: salve o recorde quando o jogo acabar e as configurações do menu quando o usuário sair do menu.
  • Espalhe as gravações: grave um contador que muda com frequência em vários endereços alternadamente, em vez de um só (veja o exemplo de buffer circular abaixo).
  • Use memória externa se precisar: para salvamentos muito frequentes, escolha uma EEPROM externa de 1 milhão de ciclos, FRAM ou um cartão SD.

Exemplo 1: contador que sobrevive a quedas de energia

No primeiro exemplo vamos montar um contador que aumenta a cada vez que um botão é pressionado e continua de onde parou depois que a placa é desligada e ligada. Ligue um terminal do botão ao D2 e o outro ao GND; usamos o resistor de pull-up interno, então não é preciso resistor extra (detalhes em Como ligar um botão no Arduino, posição dos pinos na ferramenta Pinagem). O truque é que o sketch não salva o contador a cada toque, e sim 5 segundos após o último toque. Assim, mesmo que alguém aperte o botão 20 vezes seguidas, a EEPROM é gravada uma única vez.

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}

A variável dirty indica que existe uma alteração na RAM que ainda não foi salva. O contador é um unsigned long, então ocupa 4 bytes (endereços 0–3) e pode contar mais de 4 bilhões de toques. Numa EEPROM em branco esses quatro bytes são 0xFF, então a primeira inicialização lê 4.294.967.295; quando o código vê esse valor, começa o contador do zero. Como put() só grava os bytes que mudaram, normalmente só o byte mais baixo se desgasta enquanto o contador sobe. Se a energia cair nos 5 segundos antes de um salvamento, os últimos toques se perdem; é um preço pequeno para proteger a EEPROM.

Exemplo 2: guardar configurações e uma senha em uma struct

Em vez de gravar várias configurações em endereços separados, é mais organizado e seguro reuni-las em uma struct e salvá-las com um único put(). Neste exemplo guardamos o brilho do LED e uma senha de 4–6 caracteres. Os dois primeiros campos da estrutura são essenciais: magic (um número mágico) diz se as configurações deste sketch foram mesmo gravadas na EEPROM, e version impede que dados antigos e incompatíveis sejam lidos depois que você adicionar um novo campo à estrutura. Se algum dos dois não bater, o sketch grava as configurações padrão.

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}

Ligue o LED ao D9 através de um resistor para LED (220 ohms serve); o D9 é um pino PWM. Abra o Monitor Serial a 9600 baud, defina o final de linha como Nova linha e teste estes comandos: b200 define o brilho em 200, p1234,5678 muda a senha de 1234 para 5678 e ? mostra o brilho atual. Ao desligar e ligar a placa de novo, o LED volta com o mesmo brilho e a nova senha continua valendo. A struct ocupa 11 bytes no Uno; como put() só grava os bytes que mudaram, alterar o brilho desgasta uma única célula. Você pode adaptar a mesma estrutura para uma fechadura com senha e teclado matricial ou guardar os IDs de 4 bytes dos cartões autorizados em um array com o RFID RC522. Se comandos seriais são novidade para você, leia Monitor Serial do Arduino.

Nunca coloque um objeto String na EEPROM. Uma String guarda o endereço e o tamanho do texto na RAM, não o texto em si; quando você chama EEPROM.put(0, name), alguns bytes de ponteiro vão para a EEPROM em vez do texto, e depois de reiniciar você lê lixo. Guarde sempre o texto como um array de char de tamanho fixo, como no exemplo. Além disso, a EEPROM não é criptografada: qualquer pessoa que conecte um programador à placa pode lê-la. Essa abordagem serve para fechaduras de hobby, não para algo que exija segurança de verdade.

Multiplicando a vida útil: buffer circular (wear leveling)

Gravar um contador atualizado com frequência sempre no mesmo endereço desgasta rapidamente essa única célula. Com um buffer circular (ring buffer), você grava o valor em posições diferentes alternadamente: um salvamento vai para uma posição, o próximo para a seguinte, e depois da última posição você volta ao início. Cada posição é gravada só uma vez a cada 16 salvamentos, então a vida útil cresce 16 vezes, para cerca de 1,6 milhão de salvamentos. Na inicialização, todas as posições são lidas para encontrar o valor mais recente; como o contador só aumenta, o maior valor é o mais novo.

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() {}

O exemplo incrementa o contador a cada inicialização; você pode trocar isso por um botão, um evento de sensor ou um horímetro. Mais posições significam proporcionalmente mais vida útil: 64 posições ocupam 256 bytes e duram cerca de 6,4 milhões de salvamentos. Se a placa já foi usada com outros sketches, podem ter sobrado dados antigos na área do buffer, então limpe a EEPROM com o código abaixo antes do primeiro uso. Em projetos mais avançados, você também pode adicionar um checksum CRC, como no exemplo eeprom_crc do próprio Arduino, para detectar dados corrompidos.

Como zerar a EEPROM? Carregar um código apaga a memória?

Um carregamento normal pelo cabo USB grava só a memória de programa; os dados da EEPROM são mantidos. Por isso, quando você carrega um projeto novo, as configurações do projeto anterior continuam na EEPROM e o novo código pode ler valores que não espera. No Uno, a EEPROM só é apagada quando você usa um programador ISP com Sketch > Carregar usando programador ou Ferramentas > Gravar bootloader; essas operações apagam o chip inteiro, e o bit EESAVE, que protege a EEPROM, fica desativado nos fuses do Uno. Para limpar a EEPROM você mesmo, carregue este sketch uma vez:

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() {}

O exemplo Arquivo > Exemplos > EEPROM > eeprom_clear da Arduino IDE grava 0 em todas as células; nós, em vez disso, devolvemos as células ao estado de fábrica de 255. Assim, códigos que verificam um número mágico ou 0xFF reconhecem a EEPROM como vazia de novo. Como usamos update(), células que já estão em 255 nem são gravadas. Gravar todas as 1024 células do Uno leva no máximo cerca de 3,4 segundos; quando o LED integrado acender, terminou. Depois, carregue seu sketch de verdade.

EEPROM no ESP32: a biblioteca Preferences

O ESP32 não tem EEPROM de verdade. Os dados permanentes ficam na partição NVS (Non-Volatile Storage) da memória flash externa; na tabela de partições padrão essa área tem 20 KB. O caminho recomendado pela Espressif é a biblioteca Preferences, que vem com o pacote de placas do ESP32. A Preferences guarda os dados por nomes de namespace e de chave (key) em vez de endereços, então você nunca precisa lembrar em que endereço estava o contador. O NVS grava cada valor novo na próxima entrada livre da página e marca o antigo como apagado, então o nivelamento de desgaste acontece automaticamente. Os nomes de namespace e de chave podem ter no máximo 15 caracteres e diferenciam maiúsculas de minúsculas. Se você é novo no ESP32, comece por O que é o ESP32.

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() {}
ComandoO que faz
prefs.begin("nome", false)Abre o namespace; false significa leitura e gravação, true significa somente leitura
putUInt, putInt, putFloat, putBool, putString, putBytesSalva o valor com um nome de chave
getUInt("chave", padrão)Lê o valor; retorna o padrão se a chave não existir
isKey("chave")Verifica se a chave existe
remove("chave")Apaga uma única chave
clear()Apaga todas as chaves do namespace aberto
freeEntries()Retorna o número de entradas livres restantes no NVS
end()Fecha o namespace

Você precisa ler um valor com o mesmo tipo com que o gravou: se gravar com putUInt e ler com getInt, recebe o valor padrão. Também há um motivo para chamarmos isKey() antes de getString(): se o Core Debug Level no menu Ferramentas estiver em Error ou acima, ler uma chave de texto que não existe imprime nvs_get_str len fail: ssid NOT_FOUND no Monitor Serial; é inofensivo, mas confunde. Carregar o código de novo pelo USB não apaga o NVS, mas se Erase All Flash Before Sketch Upload estiver em Enabled, cada carregamento apaga todas as entradas. Guardar os dados de Wi-Fi com Preferences é comum; se você prefere digitar o nome da rede e a senha pelo celular, o ESP32 WiFiManager também funciona, e o ESP32 guarda as configurações de Wi-Fi na mesma área do NVS. Para valores de curta duração que precisam sobreviver ao despertar do deep sleep, a memória RTC é mais adequada porque não desgasta a flash; veja deep sleep no ESP32.

Ao portar código antigo do Arduino para o ESP32, a biblioteca EEPROM também funciona, com duas diferenças: antes de usá-la você precisa informar quantos bytes vai usar com EEPROM.begin(tamanho) e, depois das alterações, chamar EEPROM.commit(). put() e write() só mudam a cópia na RAM; sem commit() nada chega à flash. No ESP32 essa biblioteca funciona como um único bloco de dados dentro do NVS e a Espressif a considera obsoleta (deprecated); todos os bytes de uma área recém-criada são 0xFF. O mesmo código também compila para o ESP8266, onde o tamanho pode ser de no máximo 4096 bytes e cada commit() apaga e regrava um setor de 4 KB da flash, então chame commit com moderação.

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() {}

Dados permanentes no Raspberry Pi Pico: o sistema de arquivos do MicroPython

O chip RP2040 do Raspberry Pi Pico também não tem EEPROM; a placa traz 2 MB de memória flash. O MicroPython reserva cerca de 1,4 MB dessa flash para um sistema de arquivos LittleFS (menos no Pico W, por causa do firmware de Wi-Fi). Então, no Pico, o equivalente à EEPROM é gravar suas configurações em um pequeno arquivo. O LittleFS espalha as gravações por blocos diferentes da flash, então faz seu próprio nivelamento de desgaste, e o sistema de arquivos não se corrompe mesmo que a energia caia durante uma gravação. O código abaixo guarda as configurações em JSON, conta cada inicialização e salva o contador cada vez que um botão ligado entre o GP15 e o GND é pressionado.

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)

Em vez de sobrescrever o arquivo diretamente, primeiro gravamos em um arquivo temporário .tmp e depois usamos os.rename() para colocá-lo no lugar do antigo; assim o arquivo de configurações antigo continua intacto mesmo que uma gravação seja interrompida. Se o arquivo não existir ou o conteúdo estiver corrompido, load() volta aos valores padrão, e quando você adicionar uma nova configuração ao programa depois, setdefault() preenche automaticamente a chave que falta. Se você programa o Pico com a Arduino IDE, também pode usar a biblioteca EEPROM do core Arduino-Pico de Earle Philhower: EEPROM.begin(256) abre uma área de 256–4096 bytes e EEPROM.commit() salva suas alterações. Essa EEPROM emulada fica no último setor de 4 KB da flash e cada commit() apaga e regrava esse setor; a flash suporta cerca de 100.000 ciclos de apagamento por setor, então use commit com moderação. Se você está começando com o Pico, O que é o Raspberry Pi Pico e Fundamentos de MicroPython são bons pontos de partida.

Mais memória permanente: EEPROM externa, FRAM e cartões SD

Se 1 KB não for suficiente ou se você precisar salvar com muita frequência, pode adicionar uma memória externa. Chips de EEPROM I2C como o AT24C256 oferecem 32 KB e cerca de 1 milhão de ciclos de gravação, e se conectam só com os fios SDA e SCL (O que é I2C). Um detalhe pouco conhecido: muitos módulos de relógio RTC DS3231 do mercado já trazem uma EEPROM AT24C32 de 4 KB, normalmente acessível no endereço I2C 0x57. Para tarefas que salvam muitas vezes por segundo, módulos FRAM com cerca de um trilhão (10^12) de ciclos de gravação são mais adequados, e para grandes registros de dados, um módulo de cartão SD, que guarda os dados como arquivos.

Ideias de projetos com EEPROM

  • Lâmpada que lembra o último estado: quando a energia voltar, devolva a lâmpada ligada a um relé ao estado em que estava antes da queda.
  • Cofre com senha alterável: grave na EEPROM a nova senha digitada em um teclado matricial, para que a senha mude sem carregar o sketch de novo.
  • Fechadura de porta com cartão: guarde os IDs dos cartões autorizados lidos com o RFID RC522 e adicione novos cartões com um cartão mestre.
  • Recorde permanente: guarde a melhor pontuação no Simon, no T-Rex ou no jogo da cobrinha; há códigos de exemplo em Jogos com Arduino.
  • Braço robótico que grava movimentos: grave os ângulos dos servos pose a pose e reproduza-os; o código de Braço robótico com Arduino faz isso com EEPROM.update().
  • Balança que lembra a calibração: salve como float, com put(), o fator de calibração encontrado com a célula de carga HX711.
  • Contador de resets: conte quantas vezes a placa reinicia para diagnosticar problemas de alimentação; o método está em Arduino reiniciando sozinho.

Erros comuns e como corrigi-los

SintomaCausaSolução
A primeira inicialização lê 255, -1, 65535 ou nanA EEPROM nunca foi gravada; células vazias são 0xFFGrave os valores padrão quando a verificação do número mágico ou de 0xFF falhar
Gravou 300, leu 44write() e read() carregam só 1 byteUse put() e get() para dados de vários bytes
Dois valores se corrompemOs endereços se sobrepõem; um int ocupa 2 bytes, um float 4Faça um mapa de endereços e calcule os endereços com sizeof
O texto vira lixo depois de reiniciarUm objeto String foi gravado com put()Guarde o texto como array de char
O código novo lê as configurações do projeto antigoUm carregamento normal não apaga a EEPROMUse um número mágico e um campo de versão, ou limpe a EEPROM
As configurações sumiram depois do carregamentoCarregamento com programador no Uno, opção Erase All Flash no ESP32Carregue normalmente pelo USB e coloque a opção do ESP32 em Disabled
Os valores não são salvos no ESP32Falta EEPROM.begin() ou EEPROM.commit()Adicione os dois ou passe para Preferences
Preferences sempre retorna o padrãoLeitura com tipo diferente, nome com mais de 15 caracteres ou namespace diferenteUse os mesmos tipos no put e no get e mantenha os nomes curtos
Erro nvs_open failed: NOT_FOUNDUm namespace que nunca foi criado foi aberto como somente leitura (true)Abra o namespace primeiro com false e grave um valor
Os valores se corrompem depois de um tempoGravações constantes no loop() ou queda de alimentação durante uma gravaçãoUse update() e salvamento atrasado, adicione buffer circular e CRC
Ver o que há na EEPROM é a forma mais rápida de depurar. Leia os primeiros 64 endereços com EEPROM.read() em um laço for e imprima no Monitor Serial (ou abra o exemplo pronto eeprom_read do Arduino), e você vai identificar na hora células vazias, valores gravados no endereço errado e dados que sobraram de projetos antigos.

A EEPROM é o jeito mais fácil de dar memória aos seus projetos com Arduino: com algumas linhas de código, você protege as configurações, os contadores e as senhas do seu dispositivo contra quedas de energia. A única coisa a observar é a vida útil de gravação; desde que você use update() e put() e não grave sem necessidade, a EEPROM do Uno vai funcionar sem problemas por anos. No app BlueGrays você encontra projetos que pode ampliar com EEPROM, como o app de senha com teclado 4x4, o cofre eletrônico inteligente, a autenticação RFID e os jogos Simon e T-Rex, todos com esquema do circuito e código passo a passo.

Perguntas frequentes

Quantas vezes dá para gravar na EEPROM do Arduino?

O datasheet do ATmega328P especifica pelo menos 100.000 ciclos de gravação/apagamento para cada célula da EEPROM. O limite é por célula, e a leitura não o afeta. Gravar no mesmo endereço uma vez por segundo desgasta a célula em cerca de 28 horas; se você usar update() e gravar só quando o valor mudar, a EEPROM dura anos.

Carregar um novo sketch apaga a EEPROM do Arduino?

Não. Um carregamento normal pelo USB grava só a memória de programa, e os dados da EEPROM são mantidos. No Uno, a EEPROM só é apagada quando você carrega com um programador ISP ou grava o bootloader. Use uma verificação de número mágico para que um projeto novo não pegue configurações antigas.

Qual é a diferença entre EEPROM.write e EEPROM.update?

write() regrava o valor na célula a cada chamada e gasta um ciclo de gravação toda vez. update() lê a célula primeiro e não grava nada se o valor for o mesmo. O resultado é idêntico, mas update() preserva a vida útil da EEPROM; put() usa o mesmo método internamente.

Como limpar a EEPROM do Arduino?

Grave 255 (ou 0) em todos os endereços com um laço for que vai até EEPROM.length(); o exemplo eeprom_clear da Arduino IDE já grava 0. Limpar os 1024 bytes do Uno leva cerca de 3,4 segundos. Quando terminar, não deixe o sketch de limpeza na placa; carregue seu programa de verdade.

O ESP32 tem EEPROM?

O ESP32 não tem EEPROM de verdade; os dados permanentes ficam na partição NVS da memória flash. Para projetos novos, recomenda-se a biblioteca Preferences. A biblioteca EEPROM ainda funciona para códigos antigos, mas você precisa chamar EEPROM.begin(tamanho) primeiro e EEPROM.commit() após cada alteração.

Como salvar um int, um float ou um texto na EEPROM?

Grave dados de vários bytes, como int, long, float e structs, com EEPROM.put(endereço, variável) e leia com EEPROM.get(endereço, variável). Um int ocupa 2 bytes e um float 4, então escolha o próximo endereço de acordo. Guarde o texto como um array de char de tamanho fixo, não como String.

Ver todos os projetos
Ligar e desligar um LED com botão
#10Iniciante

Ligar e desligar um LED com botão

Pressione o botão uma vez para acender a luz e outra vez para apagar, como o interruptor do seu quarto. O Arduino detecta cada toque e inverte o estado do LED, aguardando um breve instante para que as

4.8(100)
167
Contador com display de 7 segmentos e botões
#43Avançado
Premium

Contador com display de 7 segmentos e botões

Um contador simples com três botões: ao pressionar um deles, o algarismo no display aumenta em um; com outro, diminui em um; e o terceiro zera a contagem. O display de 7 segmentos de um dígito conta d

4.6(35)
47
Fechadura com senha, teclado 4x4 e LCD
#64Avançado
Premium

Fechadura com senha, teclado 4x4 e LCD

Uma pequena fechadura eletrônica que impede a entrada de quem não sabe a senha. Você pressiona a cerquilha e digita a senha no teclado. Se estiver correta, o display indica que o acesso foi permitido,

4.8(59)
75
Cofre eletrônico inteligente
#92Avançado
Premium

Cofre eletrônico inteligente

Um pequeno cofre para guardar seus objetos de valor, que abre com uma senha. Você digita sua senha de quatro dígitos no teclado; se estiver correta, o servo motor abre a trava, a luz verde acende e o

4.7(42)
63
Autenticação por RFID
#54Intermediário
Premium

Autenticação por RFID

Como na entrada com cartão de um prédio: você aproxima o cartão do leitor RFID e o Arduino compara o número dele com o número salvo. Com o cartão correto, o LED verde acende por três segundos; com um

4.8(64)
83
Jogo da memória com Arduino (4 LEDs + 4 botões + buzzer)
#93Avançado
Premium

Jogo da memória com Arduino (4 LEDs + 4 botões + buzzer)

Você confia na sua memória? Quatro LEDs coloridos acendem em sequência, cada um tocando sua própria nota, e você repete a mesma ordem apertando os botões. A cada rodada correta, uma nova cor é adicion

4.8(46)
69
Corrida do T-Rex no OLED: jogo do dinossauro com Arduino
#87Avançado
Premium

Corrida do T-Rex no OLED: jogo do dinossauro com Arduino

O jogo do dinossauro que aparece quando a internet cai, agora na sua mesa: o dinossauro corre pelo pequeno display OLED, os cactos vêm na sua direção e você pula com um único botão. Sua pontuação aume

4.7(33)
53
ESP32 em sono profundo — Economia de bateria (OLED + contador na RAM RTC)
#135Avançado
Premium

ESP32 em sono profundo — Economia de bateria (OLED + contador na RAM RTC)

O modo de sono profundo do ESP32 reduz o consumo de corrente para ~10 µA ao desligar a CPU e a maioria dos periféricos, sendo essencial para dispositivos IoT alimentados por bateria. Neste projeto, usamos três fontes de despertar: um temporizador (15 segundos), um pino externo (EXT0 — botão no GPIO 33) e toque (T0/GPIO 4). As variáveis marcadas com RTC_DATA_ATTR são preservadas durante o sono profundo, mantendo os contadores de inicializações e de despertares por temporizador e botão.

4.3(10)
16