Loja física na cidade do Montijo na Rua Cidade de Faro 111
A loja não tem serviço de reparação de equipamentos
Clientes registados, com conta criada, por cada 500€ acumulados de compras, receberão um voucher de 25€ para gastar na loja (Válido para compras a partir de 01-01-2025)
Trustpilot

Memória EEPROM: O Guia Definitivo para Guardar Dados Persistentes no Arduino e ESP (Mesmo Após Desligar)

Escrito em 2 de Abril de 2025

Memória EEPROM: O Guia Definitivo para Guardar Dados Persistentes no Arduino e ESP (Mesmo Após Desligar)

Memória EEPROM: O Guia Definitivo para Guardar Dados Persistentes no Arduino e ESP (Mesmo Após Desligar)

No fascinante mundo da eletrónica e da programação de microcontroladores como o Arduino e a família ESP (ESP8266, ESP32), um dos desafios recorrentes é a necessidade de guardar informações importantes que não se percam quando o dispositivo é desligado. Imagine configurar um termóstato, calibrar um sensor, ou simplesmente querer que o seu projeto se lembre do último estado em que estava – tudo isto seria impossível se os dados desaparecessem a cada ciclo de energia. É aqui que entra em jogo a heroína silenciosa da persistência de dados: a Memória EEPROM.

Se já se deparou com a frustração de perder configurações ou dados cruciais após um simples desligar e ligar do seu Arduino ou ESP, este artigo é para si. Vamos mergulhar profundamente no conceito da EEPROM, explorar como funciona, porquê é tão vital para inúmeros projetos, e, mais importante, como a pode utilizar eficazmente nas suas placas Arduino e ESP. Abordaremos desde os fundamentos básicos até exemplos práticos e considerações avançadas, garantindo que, no final, terá o conhecimento necessário para implementar armazenamento de dados persistente nos seus próprios projetos.

Prepare-se para desvendar os segredos da EEPROM e elevar os seus projetos de microcontroladores a um novo patamar de funcionalidade e robustez.

1. O Que é Exatamente a Memória EEPROM? Desmistificando o Acrónimo


EEPROM é o acrónimo de Electrically Erasable Programmable Read-Only Memory, que em português se traduz para Memória Programável e Eletricamente Apagável Apenas de Leitura. Vamos dissecar este nome para entender as suas características fundamentais:

Electrically Erasable (Eletricamente Apagável): Ao contrário de tecnologias mais antigas como a EPROM (sem o segundo 'E'), que necessitavam de luz ultravioleta para serem apagadas, a EEPROM pode ter os seus dados apagados (ou reescritos) utilizando apenas sinais elétricos. Isto torna-a muito mais prática para integração em circuitos e para atualizações de dados no próprio local onde o dispositivo opera.
Programmable (Programável): Significa que podemos escrever (programar) dados nesta memória. Podemos definir que valores queremos armazenar em localizações específicas.
Read-Only Memory (Memória Apenas de Leitura): Este termo pode ser um pouco enganador no contexto moderno. Historicamente, as memórias "ROM" eram verdadeiramente apenas de leitura, programadas na fábrica. No entanto, a EEPROM, apesar de manter "ROM" no nome, permite a escrita (programação) e a reescrita (apagamento elétrico). A parte "Read-Only" refere-se mais à sua característica principal: a não-volatilidade. Ou seja, uma vez que os dados são escritos, eles permanecem lá mesmo quando a alimentação elétrica é removida, tornando-a ideal para leitura frequente de dados que raramente mudam.
Como se Compara com Outros Tipos de Memória?

É crucial distinguir a EEPROM de outros tipos de memória comuns em microcontroladores:

RAM (Random Access Memory) ou SRAM (Static RAM): Esta é a memória de trabalho do microcontrolador. É extremamente rápida para leitura e escrita, sendo usada para armazenar variáveis temporárias, o estado do programa e a pilha de execução. A sua grande desvantagem é ser volátil: todos os dados na RAM desaparecem instantaneamente quando a energia é cortada.
Memória Flash: Esta é tipicamente onde o código do seu programa (o sketch do Arduino/ESP) é armazenado. É também não-volátil, o que é ótimo para guardar o programa. A escrita na memória Flash é possível, mas geralmente é feita em blocos maiores e tem um número de ciclos de escrita mais limitado do que a EEPROM para escritas byte a byte (embora as tecnologias modernas de Flash tenham melhorado significativamente). Em placas como o ESP8266 e ESP32, como veremos, a EEPROM é frequentemente emulada usando uma pequena porção da memória Flash.
EEPROM: Situa-se algures entre a RAM e a Flash em termos de velocidade e propósito. É não-volátil como a Flash, mas geralmente permite a escrita de bytes individuais, tornando-a mais flexível para pequenas quantidades de dados que precisam de ser atualizados ocasionalmente. A sua velocidade de escrita é consideravelmente mais lenta que a RAM, e tem um limite de ciclos de escrita por célula de memória (tipicamente na ordem das 100.000 a 1.000.000 de escritas por endereço).

2. Porquê a Persistência de Dados é Crucial? Casos de Uso da EEPROM


A necessidade de guardar dados que sobrevivam a um ciclo de energia é omnipresente em projetos de eletrónica e IoT. Sem memória persistente como a EEPROM, muitos dispositivos seriam impraticáveis ou extremamente inconvenientes. Vejamos alguns cenários comuns onde a EEPROM brilha:

Guardar Configurações do Utilizador:Termóstato Inteligente: Lembrar a temperatura alvo definida pelo utilizador, modos de funcionamento (aquecimento/arrefecimento), horários programados.
Sistema de Iluminação: Memorizar o nível de brilho preferido, a cor (em LEDs RGB), ou cenas de iluminação personalizadas.
Controlador de Irrigação: Armazenar os horários de rega, a duração para cada zona, e as configurações dos sensores de humidade.
Armazenar Dados de Calibração:Sensores: Muitos sensores (temperatura, pressão, gás, etc.) requerem calibração para fornecer leituras precisas. Os valores de offset ou fatores de escala resultantes da calibração podem ser guardados na EEPROM para serem usados sempre que o dispositivo arranca.
Servomotores: Guardar as posições mínimas e máximas calibradas para um braço robótico ou outro mecanismo.
Manter Contadores e Estados:Contador de Eventos: Um dispositivo que conta quantas vezes uma porta foi aberta, um botão foi pressionado, ou um ciclo de produção foi completado. A contagem deve persistir mesmo que falte a energia.
Rastreio do Último Estado: Num sistema de controlo, pode ser útil saber qual era o último estado ativo (ex: relé ligado/desligado) para retomar a operação corretamente após uma quebra de energia.
Horas de Funcionamento: Registar o tempo total de funcionamento de um motor ou equipamento para manutenção preventiva.
Identificação Única do Dispositivo:Armazenar um número de série único ou um identificador de dispositivo que não muda, mesmo que o firmware seja atualizado.
Pequenos Registos (Logging):Embora limitada em tamanho, a EEPROM pode guardar pequenos registos de erros ou eventos críticos recentes para diagnóstico posterior. Para registos extensos, outras soluções (como cartões SD ou memória Flash com sistema de ficheiros) são mais adequadas.
Credenciais de Rede (Com Cuidado):Em projetos IoT (especialmente com ESP8266/ESP32), pode ser tentador guardar credenciais Wi-Fi (SSID e palavra-passe) na EEPROM para reconexão automática. Embora possível, é preciso ter muito cuidado com a segurança, pois os dados na EEPROM geralmente não são encriptados por defeito.
Basicamente, sempre que precisar que o seu microcontrolador se "lembre" de algo importante entre sessões de funcionamento, a EEPROM é uma forte candidata a ser a solução. A sua simplicidade de uso (especialmente em Arduino) e a sua natureza não-volátil tornam-na uma ferramenta indispensável na caixa de ferramentas de qualquer maker ou engenheiro embebido.

3. EEPROM no Arduino: Utilização Clássica (UNO, Nano, Mega, etc.)


As placas Arduino mais tradicionais, baseadas em microcontroladores AVR da Atmel/Microchip (como o ATmega328P do UNO/Nano ou o ATmega2560 do Mega), vêm equipadas com uma EEPROM de hardware dedicada. O tamanho desta memória varia conforme o microcontrolador:

Arduino UNO, Nano, Mini (ATmega328P): 1024 bytes (1 KB)
Arduino Mega (ATmega2560): 4096 bytes (4 KB)
Arduino Leonardo (ATmega32U4): 1024 bytes (1 KB)
Outras placas: Consulte a folha de dados (datasheet) do microcontrolador específico.
O Arduino IDE fornece uma biblioteca padrão, EEPROM.h, que simplifica enormemente a interação com esta memória.

Incluir a Biblioteca:

Para usar as funções da EEPROM, a primeira coisa a fazer no seu sketch é incluir a biblioteca:

C++ 
#include <EEPROM.h>

Funções Essenciais da Biblioteca EEPROM.h (AVR Arduino):

EEPROM.read(address):

Função: Lê um único byte (valor entre 0 e 255) da EEPROM no endereço especificado.
Parâmetro: address (inteiro) - A posição na memória EEPROM de onde ler (começa em 0). O endereço máximo depende do tamanho da EEPROM da sua placa (ex: 0 a 1023 para o UNO).
Retorno: O byte (tipo byte ou uint8_t) lido desse endereço.
Exemplo: byte valorLido = EEPROM.read(10); // Lê o byte no endereço 10
EEPROM.write(address, value):

Função: Escreve um único byte na EEPROM no endereço especificado.
Parâmetros:address (inteiro) - A posição na memória EEPROM onde escrever (0 a N-1).
value (byte ou uint8_t) - O valor (0 a 255) a ser escrito.
Importante: Uma escrita na EEPROM leva algum tempo (tipicamente 3-4 milissegundos). O microcontrolador pausa durante esta operação. Além disso, cada escrita desgasta a célula de memória. Evite escritas desnecessárias!
Exemplo: EEPROM.write(10, 55); // Escreve o valor 55 no endereço 10
EEPROM.update(address, value):

Função: Uma versão otimizada da escrita. Primeiro, lê o valor atual no endereço especificado. Só escreve o novo valor se ele for diferente do valor já existente.
Parâmetros: Iguais ao EEPROM.write.
Vantagem Principal: Ajuda a minimizar o desgaste da EEPROM (wear leveling), pois evita escritas redundantes do mesmo valor. É altamente recomendável usar update() em vez de write() sempre que possível.
Exemplo: EEPROM.update(10, 55); // Só escreve 55 no endereço 10 se o valor lá não for já 55
EEPROM.put(address, data):

Função: Uma função muito mais poderosa e flexível. Permite escrever qualquer tipo de dado (não apenas bytes) na EEPROM, começando no endereço especificado. Funciona para int, float, double, struct, arrays, etc. A função calcula automaticamente quantos bytes o tipo de dado ocupa e escreve-os sequencialmente.
Parâmetros:address (inteiro) - O endereço inicial onde começar a escrever os dados.
data (qualquer tipo) - A variável ou constante cujo valor deseja guardar.
Exemplo:C++ 
int contador = 12345;
float temperatura = 25.7;
struct Configuracao {
int id;
bool ativo;
};
Configuracao minhaConf = { 1, true };

EEPROM.put(0, contador); // Escreve o int (ocupa 2 bytes no UNO) a partir do endereço 0
EEPROM.put(10, temperatura); // Escreve o float (ocupa 4 bytes) a partir do endereço 10
EEPROM.put(20, minhaConf); // Escreve a struct (ocupa 2+1=3 bytes no UNO) a partir do endereço 20

Cuidado: Certifique-se de que há espaço suficiente na EEPROM a partir do address para guardar todo o dado. sizeof(data) indica quantos bytes serão escritos.
EEPROM.get(address, data):

Função: A contraparte do put(). Lê da EEPROM, a partir do endereço especificado, o número de bytes correspondente ao tipo de dado da variável fornecida e armazena o valor lido nessa variável.
Parâmetros:address (inteiro) - O endereço inicial de onde começar a ler os dados.
data (qualquer tipo, passado por referência) - A variável onde o valor lido será armazenado.
Retorno: A própria variável data (útil para encadeamento, mas geralmente não necessário).
Exemplo:C++ 
int contadorLido;
float temperaturaLida;
struct Configuracao {
int id;
bool ativo;
};
Configuracao confLida;

EEPROM.get(0, contadorLido);
EEPROM.get(10, temperaturaLida);
EEPROM.get(20, confLida);

Serial.println(contadorLido); // Deverá imprimir 12345 (se o put anterior foi executado)
Serial.println(temperaturaLida); // Deverá imprimir 25.70
Serial.println(confLida.id); // Deverá imprimir 1
Serial.println(confLida.ativo); // Deverá imprimir 1 (true)

EEPROM.length():

Função: Retorna o tamanho total da memória EEPROM disponível na placa (em bytes).
Retorno: Um valor inteiro (ex: 1024 para o UNO, 4096 para o Mega).
Exemplo: int tamanhoEEPROM = EEPROM.length(); Serial.println(tamanhoEEPROM);
Exemplo Prático 1: Guardar e Ler um Contador de Arranques (Arduino UNO)

Este exemplo simples conta quantas vezes o Arduino foi ligado ou reiniciado.

C++ 
#include <EEPROM.h>

// Endereço na EEPROM onde vamos guardar o contador (usaremos 2 bytes para um int)
int enderecoContador = 0;

void setup() {
Serial.begin(9600);
while (!Serial) {
; // Espera a porta serial conectar (necessário para Leonardo, Micro, etc.)
}
Serial.println("Programa de Contagem de Arranques");

int contadorArranques;

// Ler o valor anterior do contador da EEPROM
// Usamos EEPROM.get() pois o contador é um int (não um simples byte)
EEPROM.get(enderecoContador, contadorArranques);

Serial.print("Valor lido da EEPROM: ");
Serial.println(contadorArranques);

// Incrementar o contador
contadorArranques++;

Serial.print("Novo valor do contador: ");
Serial.println(contadorArranques);

// Guardar o novo valor na EEPROM
// Usamos EEPROM.put() para guardar o int inteiro
EEPROM.put(enderecoContador, contadorArranques);

Serial.println("Novo valor guardado na EEPROM.");
Serial.println("Reinicie o Arduino para ver a contagem aumentar.");
Serial.println("------------------------------------");
}

void loop() {
// Não precisamos fazer nada no loop para este exemplo
delay(5000); // Apenas para evitar spam se algo fosse colocado aqui
}

Explicação:

Incluímos EEPROM.h.
Definimos uma variável enderecoContador para saber onde guardar o nosso dado (boa prática).
No setup(), declaramos uma variável contadorArranques (tipo int).
Usamos EEPROM.get(enderecoContador, contadorArranques) para ler o valor que estava previamente guardado na EEPROM e colocá-lo na nossa variável. Na primeira vez que o sketch corre, o valor lido pode ser lixo (tipicamente -1 ou 65535, que corresponde a 0xFFFF), pois a EEPROM pode não ter sido inicializada. Nas vezes seguintes, lerá o valor guardado anteriormente.
Incrementamos o contador (contadorArranques++).
Usamos EEPROM.put(enderecoContador, contadorArranques) para escrever o novo valor de volta na EEPROM, no mesmo endereço. Isto garante que da próxima vez que o Arduino arrancar, EEPROM.get() lerá este valor incrementado.
O loop() está vazio porque a lógica principal (ler, incrementar, guardar) só precisa de ocorrer uma vez no arranque.
Exemplo Prático 2: Guardar Configurações Usando uma struct (Arduino UNO)

Vamos guardar algumas configurações simuladas para um dispositivo, como um ID e um estado ativo.

C++ 
#include <EEPROM.h>

// Definir uma estrutura para as nossas configurações
struct Configuracao {
int deviceID;
bool modoAtivo;
float limiarTemperatura;
};

// Endereço inicial na EEPROM para guardar a estrutura
int enderecoConfig = 10; // Começar no 10 para não colidir com o exemplo anterior

Configuracao configAtual; // Variável global para guardar a configuração em RAM

void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println("Programa de Configuração com Struct");

// Tentar carregar a configuração da EEPROM
EEPROM.get(enderecoConfig, configAtual);

Serial.println("Configuração lida da EEPROM:");
imprimirConfig(configAtual);

// --- Lógica para modificar a configuração (exemplo) ---
// Isto poderia vir de input do utilizador, botões, serial, etc.
// Aqui, vamos apenas modificar se o ID for 0 (valor inicial típico se EEPROM não foi usada)
if (configAtual.deviceID == 0 || configAtual.deviceID == -1) { // Checar se parece não inicializada
Serial.println("Parece ser a primeira execução ou EEPROM apagada. Definindo valores padrão.");
configAtual.deviceID = 123;
configAtual.modoAtivo = true;
configAtual.limiarTemperatura = 22.5;

// Guardar a nova configuração padrão na EEPROM
EEPROM.put(enderecoConfig, configAtual);
Serial.println("Nova configuração padrão guardada.");
} else {
// Simular uma pequena alteração se já existia config
configAtual.limiarTemperatura += 0.1;
EEPROM.put(enderecoConfig, configAtual); // Guarda a alteração
Serial.println("Limiar de temperatura incrementado e guardado.");
}

Serial.println("Configuração atual em RAM (após possível modificação):");
imprimirConfig(configAtual);
Serial.println("------------------------------------");

}

void loop() {
// O programa pode agora usar configAtual.deviceID, configAtual.modoAtivo, etc.
// Exemplo:
// if (configAtual.modoAtivo) {
// // Fazer algo se o modo ativo estiver ligado
// }
delay(1000);
}

// Função auxiliar para imprimir a configuração
void imprimirConfig(const Configuracao& config) {
Serial.print(" Device ID: "); Serial.println(config.deviceID);
Serial.print(" Modo Ativo: "); Serial.println(config.modoAtivo ? "Sim" : "Não");
Serial.print(" Limiar Temp.: "); Serial.println(config.limiarTemperatura);
}

Explicação:

Definimos uma struct Configuracao para agrupar os nossos dados.
Criamos uma instância global configAtual dessa struct para manter a configuração carregada na RAM.
No setup(), usamos EEPROM.get(enderecoConfig, configAtual) para carregar a struct inteira da EEPROM.
Incluímos uma lógica simples para verificar se a configuração parece ser a inicial (ex: ID = 0 ou -1). Se for, definimos valores padrão e guardamo-los com EEPROM.put(enderecoConfig, configAtual).
Se já existia uma configuração, simulamos uma pequena alteração e guardamo-la novamente.
A função imprimirConfig ajuda a visualizar os dados.
No loop(), o programa pode agora aceder aos membros da struct configAtual para tomar decisões.
Estes exemplos demonstram a facilidade de usar a EEPROM incorporada nas placas Arduino clássicas para persistência de dados, desde simples bytes até estruturas de dados complexas.

4. "EEPROM" nos ESP8266 e ESP32: Emulação via Memória Flash


Aqui as coisas mudam um pouco. Os populares microcontroladores ESP8266 e ESP32, conhecidos pelas suas capacidades Wi-Fi e Bluetooth (no caso do ESP32), não possuem uma memória EEPROM de hardware dedicada como os AVRs do Arduino.

No entanto, a comunidade e os desenvolvedores dos cores Arduino para ESP (as bibliotecas que permitem programar ESPs usando o Arduino IDE) implementaram uma solução inteligente: emular a funcionalidade da EEPROM utilizando uma pequena porção da memória Flash onboard.

A Biblioteca EEPROM.h para ESP:

Felizmente, para manter a compatibilidade e facilitar a transição de projetos, os cores ESP também incluem uma biblioteca chamada EEPROM.h. Ela oferece uma interface muito semelhante à do Arduino AVR, com funções como read(), write(), update(), put(), e get().

Diferenças Cruciais:

Apesar da semelhança na interface, existem duas diferenças fundamentais no funcionamento que precisa absolutamente de conhecer:

EEPROM.begin(size):

Necessidade: Como a EEPROM é emulada na Flash, você precisa explicitamente dizer ao sistema quanta memória Flash deseja reservar para esta emulação. Isto é feito chamando EEPROM.begin() no seu setup().
Parâmetro: size (inteiro) - O número de bytes que deseja alocar para a sua "EEPROM virtual". O tamanho máximo pode variar, mas geralmente está na ordem dos 4KB (4096 bytes), embora possa depender da configuração do seu filesystem Flash (SPIFFS ou LittleFS). Consulte a documentação do core ESP que está a usar para limites exatos.
Funcionamento Interno: Ao chamar EEPROM.begin(size), o sistema aloca um buffer na RAM com o tamanho size. Todas as operações de leitura (read, get) acedem a este buffer de RAM. As operações de escrita (write, update, put) modificam este buffer de RAM. Os dados ainda não são escritos na Flash!
Onde Chamar: Obrigatório chamar EEPROM.begin(size) no setup() antes de qualquer outra operação da EEPROM (read, write, get, put, commit).
Exemplo: EEPROM.begin(512); // Reserva 512 bytes da Flash para emulação EEPROM
EEPROM.commit():

Necessidade: Como as escritas (write, update, put) apenas modificam o buffer na RAM, os seus dados não serão persistentes se o dispositivo for desligado nesse ponto. Para efetivamente guardar o conteúdo do buffer de RAM na memória Flash física, você precisa chamar EEPROM.commit().
Funcionamento: Esta função compara o buffer de RAM com o setor da Flash reservado. Se houver diferenças, escreve o conteúdo do buffer na Flash. Esta operação de escrita na Flash pode levar algum tempo (dezenas ou centenas de milissegundos, dependendo do tamanho e da tecnologia Flash) e é uma operação que contribui para o desgaste da Flash.
Quando Chamar: Chame EEPROM.commit() depois de ter feito todas as alterações desejadas nos dados (usando write, update, put). Não chame commit() após cada write() individual se estiver a fazer várias escritas – agrupe as suas escritas e chame commit() uma vez no final.
Retorno: Retorna true se a escrita na Flash foi bem-sucedida, false caso contrário. É boa prática verificar este retorno.
Exemplo:C++ 
EEPROM.put(0, valor1);
EEPROM.put(10, valor2);
if (EEPROM.commit()) {
Serial.println("Dados EEPROM guardados na Flash com sucesso.");
} else {
Serial.println("ERRO ao guardar dados EEPROM na Flash!");
}

EEPROM.end() (Menos Comum):

Função: Liberta o buffer de memória RAM que foi alocado por EEPROM.begin(). Após chamar end(), não pode mais usar as funções da EEPROM até chamar EEPROM.begin() novamente.
Utilidade: Pode ser útil em cenários com memória RAM muito limitada, onde precisa de libertar o buffer da EEPROM temporariamente para usar essa RAM para outras tarefas intensivas. Na maioria dos casos, não é necessário chamá-la.
Em Resumo - Fluxo de Trabalho Típico no ESP:

setup():Incluir #include <EEPROM.h>.
Chamar EEPROM.begin(tamanho_desejado);.
Usar EEPROM.get() ou EEPROM.read() para carregar dados existentes do buffer (que foi preenchido a partir da Flash durante o begin()).
Durante a Execução (loop() ou outras funções):Modificar dados conforme necessário usando EEPROM.write(), EEPROM.update(), ou EEPROM.put(). Estas operações são rápidas, pois apenas alteram a RAM.
Quando Precisar de Guardar Permanentemente:Chamar EEPROM.commit(). Esta operação é mais lenta e escreve na Flash. Chame-a apenas quando for realmente necessário persistir as alterações.
Exemplo Prático: Guardar Credenciais Wi-Fi Simples (ESP8266/ESP32)

Atenção: Guardar credenciais em texto simples na EEPROM/Flash não é seguro para produção. Use apenas para prototipagem ou redes não críticas. Soluções mais seguras envolvem encriptação ou gestores de configuração seguros.

C++ 
#include <EEPROM.h>
#ifdef ESP32
#include <WiFi.h>
#else // ESP8266
#include <ESP8266WiFi.h>
#endif

// Tamanho da EEPROM emulada a reservar (ex: 128 bytes)
#define EEPROM_SIZE 128

// Endereços na EEPROM (relativos ao início do bloco reservado)
#define ADDR_SSID 0
#define MAX_SSID_LEN 32 // Incluindo terminador nulo
#define ADDR_PASS (ADDR_SSID + MAX_SSID_LEN)
#define MAX_PASS_LEN 64 // Incluindo terminador nulo

void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("\nExemplo EEPROM ESP - Guardar Wi-Fi");

// 1. Inicializar a EEPROM emulada
if (!EEPROM.begin(EEPROM_SIZE)) {
Serial.println("Falha ao inicializar EEPROM!");
Serial.println("Reiniciando...");
delay(1000);
ESP.restart();
}
Serial.print("EEPROM inicializada com tamanho: ");
Serial.println(EEPROM_SIZE);

// 2. Ler credenciais guardadas (se existirem)
char savedSsid[MAX_SSID_LEN];
char savedPass[MAX_PASS_LEN];

// Usamos get para ler strings (arrays de char)
EEPROM.get(ADDR_SSID, savedSsid);
EEPROM.get(ADDR_PASS, savedPass);

Serial.print("SSID Lido da EEPROM: ["); Serial.print(savedSsid); Serial.println("]");
Serial.print("Pass Lido da EEPROM: ["); Serial.print(savedPass); Serial.println("]");

// --- Lógica para obter novas credenciais (ex: via Serial, WebServer, etc.) ---
// Simulação: Se o SSID lido estiver vazio, pede novas credenciais via Serial
if (strlen(savedSsid) == 0) {
Serial.println("Nenhum SSID guardado. Introduza novas credenciais:");
String newSsid = readStringFromSerial("SSID: ");
String newPass = readStringFromSerial("Password: ");

// Copiar para os arrays de char (com cuidado para não exceder o tamanho)
strncpy(savedSsid, newSsid.c_str(), MAX_SSID_LEN - 1);
savedSsid[MAX_SSID_LEN - 1] = '\0'; // Garantir terminação nula
strncpy(savedPass, newPass.c_str(), MAX_PASS_LEN - 1);
savedPass[MAX_PASS_LEN - 1] = '\0'; // Garantir terminação nula

// 3. Escrever as novas credenciais no buffer da EEPROM
Serial.println("A guardar novas credenciais...");
EEPROM.put(ADDR_SSID, savedSsid);
EEPROM.put(ADDR_PASS, savedPass);

// 4. Fazer COMMIT para guardar na Flash!
if (EEPROM.commit()) {
Serial.println("Credenciais guardadas na Flash com sucesso.");
} else {
Serial.println("ERRO ao guardar credenciais na Flash!");
}
} else {
Serial.println("Usando credenciais guardadas anteriormente.");
}

// 5. Tentar conectar ao Wi-Fi com as credenciais (atuais ou lidas)
connectToWiFi(savedSsid, savedPass);

Serial.println("Setup concluído.");
// EEPROM.end(); // Opcional, geralmente não necessário
}

void loop() {
// O loop principal pode fazer outras coisas
delay(10000); // Ex: verificar conexão, etc.
}

// Função auxiliar para ler String da porta Serial
String readStringFromSerial(const char* prompt) {
String str = "";
Serial.print(prompt);
while (Serial.available() == 0) { delay(10); } // Espera por input
str = Serial.readStringUntil('\n');
str.trim(); // Remove espaços em branco e newlines
Serial.println(str); // Ecoa o que foi lido (exceto a password talvez)
return str;
}

// Função para conectar ao Wi-Fi
void connectToWiFi(const char* ssid, const char* password) {
Serial.print("A conectar a ");
Serial.println(ssid);
WiFi.begin(ssid, password);

int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) { // Tenta por ~10 segundos
delay(500);
Serial.print(".");
attempts++;
}

if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi conectado!");
Serial.print("Endereço IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nFalha ao conectar ao WiFi.");
}
}

Explicação das Partes Chave (ESP):

EEPROM.begin(EEPROM_SIZE): Essencial no setup() para alocar o espaço.
EEPROM.get(): Usado para ler as strings (arrays de char). Note que get funciona bem aqui.
EEPROM.put(): Usado para escrever as novas strings no buffer da RAM.
EEPROM.commit(): Fundamental após o put() para persistir os dados na Flash. Sem isto, as novas credenciais seriam perdidas no próximo reboot.
Verificação de Retorno: O if (EEPROM.commit()) verifica se a operação de escrita na Flash foi bem-sucedida.
O resto do código lida com a leitura da Serial e a conexão Wi-Fi.
Compreender a necessidade de begin() e commit() é a chave para usar a EEPROM emulada nos ESPs corretamente.

5. Limitações, Melhores Práticas e Considerações Avançadas


Apesar de ser extremamente útil, a EEPROM (seja de hardware ou emulada) tem limitações e requer algumas boas práticas para ser usada de forma eficaz e para prolongar a sua vida útil.

a) Ciclos de Escrita Limitados (Wear Leveling):

O Problema: Cada célula de memória EEPROM (ou Flash usada para emulação) só pode ser escrita um número finito de vezes antes de se degradar e deixar de reter dados de forma fiável. Este limite é tipicamente de 100.000 ciclos de escrita para EEPROM de hardware e pode variar para Flash (frequentemente na mesma ordem de grandeza ou superior para tecnologias modernas, mas a escrita na Flash é mais complexa).
Impacto: Se escrever continuamente no mesmo endereço da EEPROM (por exemplo, guardar um valor de sensor a cada segundo), rapidamente atingirá o limite de ciclos e essa parte da memória falhará.
Soluções (Wear Leveling - Nivelamento de Desgaste):Escrever Apenas Quando Necessário: A estratégia mais importante. Não escreva na EEPROM a cada ciclo do loop(). Escreva apenas quando o dado realmente mudou e precisa ser persistido.
Usar EEPROM.update() (Arduino AVR): Como mencionado, update() verifica antes de escrever, evitando escritas desnecessárias do mesmo valor. É a forma mais simples de wear leveling.
Usar EEPROM.put() / EEPROM.get(): Estas funções, especialmente nas implementações mais recentes, podem incluir otimizações internas que só escrevem os bytes que realmente mudaram dentro de uma estrutura de dados maior, ajudando a reduzir o desgaste.
Verificação Antes de Escrever (Manual): Antes de chamar EEPROM.put() ou EEPROM.write(), leia o valor atual da EEPROM com EEPROM.get() ou EEPROM.read() e compare-o com o novo valor que pretende escrever. Só execute a escrita se os valores forem diferentes.C++ 
int novoValor = lerSensor();
int valorGuardado;
EEPROM.get(enderecoValor, valorGuardado);

if (novoValor != valorGuardado) {
EEPROM.put(enderecoValor, novoValor);
// No ESP, não se esqueça do commit() eventual!
// EEPROM.commit(); // Chamar commit() aqui ou mais tarde, dependendo da lógica
}

Distribuir as Escritas (Manual Wear Leveling): Se tiver um dado que muda frequentemente (ex: um contador), em vez de escrever sempre no mesmo endereço, pode usar uma área maior da EEPROM. Por exemplo, pode usar 10 localizações para guardar o contador. Cada vez que precisar de atualizar, escreve na próxima localização (circularmente). Isto distribui o desgaste por 10 células em vez de concentrá-lo numa só. Requer lógica adicional para saber qual é a localização mais recente.
Bibliotecas de Wear Leveling: Existem bibliotecas mais avançadas (especialmente para ESP, onde a gestão da Flash é mais complexa) que implementam algoritmos de wear leveling mais sofisticados, muitas vezes associados a sistemas de ficheiros como LittleFS.
b) Velocidade de Escrita:

A escrita na EEPROM/Flash é significativamente mais lenta do que a escrita na RAM. Uma escrita de byte pode levar vários milissegundos. Uma operação EEPROM.commit() no ESP pode levar muito mais tempo, dependendo do tamanho do buffer e da atividade da Flash.
Implicação: Evite realizar escritas na EEPROM dentro de loops críticos de tempo ou interrupções. Planeie as escritas para momentos onde uma pequena pausa não seja problemática. Agrupe múltiplas escritas antes de fazer commit() (no ESP) para minimizar o número de operações lentas de escrita na Flash.
c) Tamanho Limitado:

A EEPROM disponível é relativamente pequena (1KB a 4KB é comum). A emulação no ESP também costuma usar uma quantidade limitada de Flash.
Implicação: Não pode guardar grandes quantidades de dados (como logs extensos, imagens ou ficheiros de áudio) na EEPROM. É destinada a pequenas configurações, estados ou contadores. Planeie cuidadosamente como vai usar o espaço disponível. Use sizeof() para verificar o tamanho dos seus dados antes de os guardar.
d) Organização dos Dados:

Como a EEPROM é basicamente um array de bytes, é sua responsabilidade organizar como os dados são armazenados.
Boas Práticas:Use structs para agrupar dados relacionados. Isto torna o código mais legível e a gestão dos endereços mais fácil (só precisa de saber o endereço inicial da struct).
Use #define ou const int para nomear os endereços ou os tamanhos, em vez de usar números "mágicos" diretamente no código (#define ADDR_CONTADOR 0 é melhor que EEPROM.get(0, ...)).
Mantenha um "mapa" (mesmo que seja apenas em comentários no código) de como a sua EEPROM está organizada:C++ 
// Mapa da EEPROM:
// 0-1: Contador de Arranques (int)
// 10-?: Struct Configuracao (ver sizeof(Configuracao))
// 50: Flag de Status (byte)

Considere usar uma versão ou um "número mágico" no início da sua área de dados na EEPROM. No arranque, leia este número. Se for diferente do esperado, significa que a estrutura dos dados na EEPROM pode ter mudado (devido a uma atualização de firmware) ou que a EEPROM nunca foi inicializada. Neste caso, pode inicializar a EEPROM com valores padrão.
e) Corrupção de Dados:

Se a energia for cortada exatamente durante uma operação de escrita na EEPROM ou Flash (commit()), existe uma pequena possibilidade de os dados ficarem corrompidos (nem o valor antigo nem o novo ficam completamente escritos).
Mitigação:Checksums: Calcule um checksum (como um CRC simples ou a soma de todos os bytes) dos seus dados antes de os guardar. Guarde o checksum juntamente com os dados. No arranque, leia os dados e o checksum guardado. Recalcule o checksum dos dados lidos e compare com o checksum guardado. Se não coincidirem, os dados estão provavelmente corrompidos, e deve usar valores padrão ou indicar um erro.
Escrita em Duas Fases (Transacional Simples): Para dados críticos, pode usar duas cópias na EEPROM. Para atualizar, escreva o novo valor na cópia "inativa", verifique se a escrita foi bem-sucedida (se possível), e depois atualize um "ponteiro" ou "flag" que indica qual das cópias é a ativa. Isto reduz a janela de tempo para corrupção da cópia ativa.
Sistemas de Ficheiros (ESP): Usar sistemas de ficheiros como LittleFS ou SPIFFS no ESP geralmente oferece maior robustez contra corrupção por falha de energia, pois implementam técnicas de jornalização ou copy-on-write.
f) Segurança:

Os dados na EEPROM (hardware ou emulada) não são encriptados por defeito. Qualquer pessoa com acesso físico à placa (e às ferramentas certas) ou, em alguns casos, através de software, pode potencialmente ler o conteúdo da EEPROM/Flash.
Implicação: Não guarde informações sensíveis (palavras-passe, chaves de API, dados privados) em texto simples na EEPROM. Se precisar de o fazer, explore bibliotecas de encriptação (como AES), mas esteja ciente de que a gestão de chaves de encriptação se torna um novo desafio. Para ESP32, considere usar as funcionalidades de Flash Encryption e Secure Boot, se a segurança for uma prioridade elevada.
Ao ter estas limitações e boas práticas em mente, poderá usar a EEPROM de forma muito mais robusta e fiável nos seus projetos Arduino e ESP.

6. Alternativas à EEPROM


Embora a EEPROM seja uma excelente solução para muitos casos, existem cenários onde outras tecnologias de armazenamento podem ser mais adequadas:

Memória Flash com Sistema de Ficheiros (SPIFFS, LittleFS - Principalmente ESP):

O quê: Os ESP8266 e ESP32 permitem formatar uma parte da sua memória Flash interna com um sistema de ficheiros (similar a um pequeno disco). LittleFS é geralmente preferido sobre SPIFFS por ser mais robusto e eficiente.
Vantagens: Permite organizar dados em ficheiros e diretórios, o que é muito mais flexível para dados maiores ou mais complexos. Os sistemas de ficheiros geralmente implementam wear leveling e oferecem melhor proteção contra corrupção de dados. Ideal para guardar páginas web, ficheiros de configuração JSON/XML, pequenos logs.
Desvantagens: Usa mais memória Flash e RAM. A API é de ficheiros (File f = LittleFS.open(...)), não a API simples da EEPROM. A performance pode variar.
Quando Usar: Quando precisa de armazenar mais do que alguns Kbytes de dados, ou quando uma estrutura de ficheiros é mais natural para a sua aplicação.
FRAM (Ferroelectric RAM):

O quê: Um tipo de memória não-volátil que combina a velocidade da SRAM com a não-volatilidade da EEPROM/Flash. Geralmente disponível como chips externos (ligados via I2C ou SPI).
Vantagens: Extremamente rápida para escrita (semelhante à RAM), virtualmente ilimitados ciclos de escrita (10^12 ou mais), baixo consumo de energia. Leitura e escrita byte a byte.
Desvantagens: Mais cara que a EEPROM. Requer hardware externo e pinos de ligação. Capacidades geralmente na ordem dos Kbytes a alguns Mbytes.
Quando Usar: Aplicações que requerem registo de dados (data logging) muito frequente e não-volátil, onde o limite de escrita da EEPROM/Flash é um problema.
Cartões SD/MicroSD:

O quê: Usar um módulo leitor de cartões SD (ligado via SPI) para armazenar dados num cartão SD.
Vantagens: Capacidade de armazenamento massiva (Gigabytes), sistema de ficheiros padrão (FAT16/FAT32) facilmente legível em computadores, velocidade razoável.
Desvantagens: Requer hardware externo (módulo SD), consome mais energia, fisicamente maior, mais complexo de implementar (biblioteca SD). Os cartões SD também têm desgaste (wear leveling é gerido internamente pelo cartão, com qualidade variável).
Quando Usar: Projetos que necessitam de armazenar grandes volumes de dados (logs extensos, ficheiros multimédia, bases de dados).
Armazenamento na Nuvem (Cloud Storage):

O quê: Para dispositivos IoT (especialmente ESP com Wi-Fi), enviar os dados para serem armazenados num servidor na Internet (ex: Google Cloud, AWS IoT, Firebase, Thingspeak, um servidor próprio).
Vantagens: Armazenamento virtualmente ilimitado, acessível de qualquer lugar, permite análise e visualização de dados avançada.
Desvantagens: Requer conectividade à Internet constante ou intermitente. Introduz latência. Pode ter custos associados. Dependência de serviços de terceiros (ou manutenção de servidor próprio). Questões de segurança e privacidade.
Quando Usar: Projetos IoT onde os dados precisam de ser acedidos remotamente, agregados de múltiplos dispositivos, ou analisados centralmente.
EEPROM Externa:

O quê: Chips de memória EEPROM adicionais (ligados via I2C ou SPI).
Vantagens: Permite adicionar mais memória EEPROM a uma placa que tenha pouca ou nenhuma (ou contornar o limite de tamanho da emulação no ESP). Pode escolher chips com maior capacidade.
Desvantagens: Requer hardware externo e pinos. A interface de programação será diferente da biblioteca EEPROM.h padrão (precisará de bibliotecas específicas para o chip ou comunicar diretamente via I2C/SPI). Mantém as limitações de velocidade e ciclos de escrita da tecnologia EEPROM.
Quando Usar: Quando precisa de um pouco mais de armazenamento não-volátil do que a placa oferece, mas não precisa da capacidade massiva de um cartão SD ou da complexidade de um sistema de ficheiros Flash.
A escolha da tecnologia de armazenamento depende fortemente dos requisitos específicos do seu projeto: quantidade de dados, frequência de escrita, necessidade de acesso a ficheiros, orçamento, consumo de energia e complexidade de implementação.

7. Conclusão: Dominando a Persistência de Dados


A memória EEPROM, seja ela o hardware dedicado nas placas Arduino clássicas ou a sua versão emulada na Flash dos ESP8266/ESP32, é uma ferramenta fundamental e poderosa para qualquer entusiasta ou profissional que trabalhe com microcontroladores. Ela liberta os seus projetos das amarras da volatilidade da RAM, permitindo que configurações, estados, calibrações e contagens importantes persistam mesmo quando a energia falha.

Neste guia extenso, explorámos:

O que é a EEPROM e como se diferencia da RAM e da Flash.
Porquê a persistência de dados é tão crucial em inúmeras aplicações.
Como utilizar a biblioteca EEPROM.h padrão nas placas Arduino AVR, com funções como read, write, update, put, e get.
As diferenças chave ao usar a EEPROM emulada nos ESP8266/ESP32, destacando a necessidade vital de EEPROM.begin() e EEPROM.commit().
As limitações inerentes, especialmente os ciclos de escrita finitos, e as melhores práticas para mitigar o desgaste (wear leveling), gerir o espaço e evitar a corrupção de dados.
Alternativas como sistemas de ficheiros Flash, FRAM, cartões SD e armazenamento na nuvem, para cenários onde a EEPROM pode não ser a solução ideal.
Armado com este conhecimento, está agora muito mais preparado para implementar soluções de armazenamento de dados robustas e fiáveis nos seus projetos. Lembre-se sempre de considerar as limitações, especialmente o desgaste por escrita, e de aplicar as boas práticas discutidas. Use update() ou verifique antes de escrever, planeie a organização dos seus dados e, nos ESPs, nunca se esqueça de begin() e commit().

A capacidade de fazer um dispositivo "lembrar-se" de informações importantes é o que muitas vezes separa um protótipo simples de um produto final funcional e conveniente. A EEPROM oferece uma maneira acessível e relativamente simples de alcançar essa persistência.

Agora, vá em frente e experimente! Comece com exemplos simples, como o contador de arranques, e avance gradualmente para guardar estruturas de configuração mais complexas. Explore como a EEPROM pode tornar os seus próprios projetos Arduino e ESP mais inteligentes, mais resilientes e mais úteis. A persistência de dados está ao seu alcance!


Calculadora online de Resistência (4 Faixas)

Valor: --

Tolerância: --

Gama: --