Como eu faço para salvar dados no ESP32 sem perder ao desligar? usando a memória NVS, que é a memória Flash. Hoje veremos todos os detalhes sobre este tipo de armazenamento, como funciona e como usar em seus projetos.
Primeiramente devemos entender o que precisamos e quais opções temos no microcontrolador ESP32. Quando precisamentos salvar algum dados que não pode se perder ao desligar a placa, temos três opções. Elas são: EEPROM emulada (que na verdade é memória Flash), sistema de arquivos LittleFS, que também usa blocos da memória Flash e finalmente a NVS, que é o objetivo deste artigo.
NVS significa “non volatile storage”, e permite armazenar pequenas quantidades de dados individuais (variáveis) na memória Flash de qualquer ESP32. Isto significa que você não perde aquela informação, pelo menos não por até 100.000 ciclos de escrita/leitura. Você pode desligar e religar o ESP32 quantas vezes quiser, a informação vai estar salva lá naquela posição de memória.
Para este experimento eu vou utilizar o ESP32-C6 Super Mini da imagem abaixo, cujo part-number é ESP32-C6FH4. Segundo o seu datasheet, na página 13, ele tem 4Mb de memória Flash no chip. Para nosso experimento é mais do que suficiente, vamos salvar e manipular apenas uma variável. Porém existem versões do ESP32-C6 com até o dobro (8Mb) de memória Flash interna.

Ainda é possível expandir a quantidade de memória Flash de praticamente todos os ESP32 via porta SPI. A página 50 do datasheet do ESP32-C6 traz tanto a SPI0 como a SPI1 como possíveis conexões para memória Flash externa. Este tipo de expansão é usado nos casos onde se necessita velocidade e confiabilidade no armazenamento de pequenas quantidades de dados.
Isto porque hoje em dia com o advento da ioT (internet das coisas), muitos dados são enviados diretamente para a nuvem, nem ficam mais no dispositivo. E para casos onde eles devem ficar, e a quantidade de dados é muito grande, pode-se usar cartões de memória SD/microSD.
Então como armazenar dados na memória NVS? a documentação oficial fala em pares de “keys and values”, ou chaves e valores. Essencialmente você vai dar um nome (uma string) de até 15 caracteres para sua posição de memória. Por exemplo “minhaVariavel” ou “dado001”.
Os tipos de dados que podem ser armazenados são: uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t, Strings terminazadas em zero, blobs, float e double. A documentação oficial comenta que a NVS trabalha melhor com dados “menores/pequenos”, então blobs e strings não são tão recomendadas. Nestes casos seria melhor usar a o LittleFS.
Tanto que o limite para tamanho de Strings é de 4000 bytes, enquanto para blobs é de 508,000 bytes (97,6% do tamanho da partição). Uma questão importante é que as chaves (keys) devem ser únicas, escrever diferentes valores em uma mesma chave vai sobrescrever o dado antigo. Um dado imporante para levar em conta: a inicialiação da memória NVS leva tempo. A documentação oficial fala em 500 ms (milisegundos) a cada 1000 chaves (keys).
Um último detalhe imporante de se apresentar: a memória NVS tem “wear leveling” por padrão. Isto significa que ela é “inteligente” o suficiente para não escrever sempre no mesmo lugar. Ela vai alternando entre os espaços de memória para não desgastar apenas um bloco/parte da memória.
Hardware/montagem
Chegou a hora de fazermos nosso experimento do dia. O que vamos fazer é ter um botão (push button) e um LED, ambos conectados na placa ESP32-C6 Super Mini. O LED vai ficar apenas piscando, sem mais nenhuma função. A montagem será feita em uma pequena protoboard de 400 pontos, mais do que suficiente.
Utilizarei alguns (4 apenas) jumpers macho-macho, para conectar principamente o push button e o LED ao ESP32. O push button vai no pino 7 e o LED no pino 14 do ESP32-C6. Abaixo é visível o diagrama esquemático e também a foto da montagem.


Veja que você pode utilizar qualquer ESP32 para este experimento, basta alterar o nome/número do pino para o LED e o push button. Logo abaixo na parte de firmware/código vamos ver como fazer isso.
Uma questão importante: note que o push button está conectado entre o pino 7 e o GND, ou seja, não há 3,3V envolvido. Então como o pressionar do botão é detectado?. O pino 7 está configurado via código como PULL-UP, ou seja, tem um resistor dentro do ESP32 puxando este pino para 3,3V. Quando você pressina o botão, este pino vai para 0V, permitindo detectar variação do estado do pino.
Firmware/código
Conforme comentado acima, o LED neste circuito está ali apenas para piscar, não tem função além disso. Para fazer ele funcionar eu criei uma biblioteca, que você pode baixar neste link. Para baixar o .ZIP da bliblioteca, no link clique em “Code (botão verde) > download ZIP”. Para instala-la na IDE do Arduino, vá em “Sketch > Include library > add .ZIP library”.
O código completo para nosso experimento está neste link e também abaixo, completo. Já nos includes, no topo do código você vê algumas coisas interessantes: a biblioteca “Preferences.h” que é usada para acessar a memória NVS. Tem também a biblioteca “WiFi”, que é usada para gerar números aleatórios; nós não vamos usar WiFi além desta função neste artigo. E também a minha biblioteca “BlinkLED”, que faz apenas o LED piscar sem interromper o restante do códiog.
// reference: https://dronebotworkshop.com/esp32-non-volatile-storage/
#define button 7
#define LED 14
#include <Preferences.h>
#include <esp_system.h> // Required for esp_random()
#include <WiFi.h> // Necessary to generate TRNG
#include <BlinkLED.h>
Preferences prefs;
volatile unsigned long debounceTimer = 0;
volatile int pressCount = 0;
const int ledInterval = 300;
volatile int numberOfPresses = 0;
volatile int decideNow = 0;
volatile uint16_t randomVarPrint = 0;
volatile int randomNumber = 0;
volatile unsigned long pressTimer = 0;
// this struct "compacts" each variable below in a byte, single bit each,
// which takes less space than bool itself, which uses 1 byte per variable.
struct MyBitStruct {
bool buttonStatus : 1;
bool lastButton : 1;
bool memoryControl : 1;
};
MyBitStruct singlebit;
// Returns a random number in the range [0, bound)
// Avoids modulo bias using rejection sampling
uint32_t randomBounded(uint32_t bound) {
if (bound == 0) return 0; // avoid division by zero
uint32_t x;
uint32_t limit = UINT32_MAX - (UINT32_MAX % bound);
do {
x = esp_random();
} while (x >= limit); // Retry until unbiased
return x % bound;
}
// Returns a random number in the range [minVal, maxVal)
uint32_t randomRange(uint32_t minVal, uint32_t maxVal) {
if (maxVal <= minVal) return minVal;
return minVal + randomBounded(maxVal - minVal);
}
void setup(){
pinMode(button, INPUT_PULLUP);
pinMode(LED, OUTPUT);
prefs.begin("nv-memory", false);
Serial.begin(115200);
// Uses WiFi for random number generation only
WiFi.mode(WIFI_MODE_STA); // or WIFI_STA
WiFi.begin(); // starts WiFi driver (no need to connect)
// Optional: immediately stop scanning/connecting attempts
WiFi.disconnect(true);
singlebit.lastButton = digitalRead(button);
}
void loop() {
blinkLED(LED, ledInterval);
decideNow= recordRetrieve(); // Controls the whole button pushing structure
if(decideNow == 2){ // record information on memory
numberOfPresses= 0;
decideNow= 0;
randomNumber = getRandom();
prefs.putUShort("RandomVar", randomNumber);
Serial.print("Saved ");
Serial.println(randomNumber);
}else if(decideNow == 1){ // retrieve information from memory
numberOfPresses= 0;
decideNow= 0;
randomVarPrint = prefs.getUShort("RandomVar", 0);
Serial.print("Read ");
Serial.println(randomVarPrint);
}else{
}
}
// This function reads and debounces a push button, waiting for 1 or 2 presses to be read
int recordRetrieve(){
singlebit.buttonStatus = digitalRead(button);
if(singlebit.buttonStatus != singlebit.lastButton){
if(millis() - debounceTimer > 300){
debounceTimer = millis();
if(!singlebit.buttonStatus){
pressCount++;
if(pressCount == 1){
pressTimer = millis();
}
}
singlebit.lastButton = singlebit.buttonStatus;
}
}
if(pressCount > 0 && millis() - pressTimer > 2000){ // Gives two seconds before deciding what to do with button presses
numberOfPresses = pressCount;
pressCount = 0;
}
return numberOfPresses; // return number of button presses
}
int getRandom(){
// Example: number from 0 to 100 (including 100)
uint32_t r = randomRange(0, 101);
return r;
}
Este código tem algumas coisas interessantes: ao invés de usar bool para variáveis binárias, que usar 8 bit, eu usei um struct. Assim cada variável usa apenas um bit de um byte:
// this struct "compacts" each variable below in a byte, single bit each,
// which takes less space than bool itself, which uses 1 byte per variable.
struct MyBitStruct {
bool buttonStatus : 1;
bool lastButton : 1;
bool memoryControl : 1;
};
Eu também fiz o debounce do botão, com o código abaixo. Serve para detectar apenas uma pressão, ao invés de várias. Isto porque mecanicamente o botão “oscila” um pouco ao ser pressionado, é normal:
int recordRetrieve(){
singlebit.buttonStatus = digitalRead(button);
if(singlebit.buttonStatus != singlebit.lastButton){
if(millis() - debounceTimer > 300){
debounceTimer = millis();
if(!singlebit.buttonStatus){
pressCount++;
if(pressCount == 1){
pressTimer = millis();
}
}
singlebit.lastButton = singlebit.buttonStatus;
}
}
Eu também criei uma função (na verdade copiei de uma que eu já tinha feito) para gerar números aleatórios entre 1 e 100. Neste caso quando pressionarmos o botão duas vezes, o ESP32 gera um número aleatório entre 1 e 100 e salva este número na memória NVS.
// Returns a random number in the range [0, bound)
// Avoids modulo bias using rejection sampling
uint32_t randomBounded(uint32_t bound) {
if (bound == 0) return 0; // avoid division by zero
uint32_t x;
uint32_t limit = UINT32_MAX - (UINT32_MAX % bound);
do {
x = esp_random();
} while (x >= limit); // Retry until unbiased
return x % bound;
}
// Returns a random number in the range [minVal, maxVal)
uint32_t randomRange(uint32_t minVal, uint32_t maxVal) {
if (maxVal <= minVal) return minVal;
return minVal + randomBounded(maxVal - minVal);
}
A parte abaixo é o “suco” deste experimento: ao pressionar o botão uma vez, o número salvo na memória é lido e apresentado no monitor serial da IDE do Arduino. Ao pressionar o botão duas vezes ele tem o comporamento já citado acima, gera um número aleatório entre 1 e 100 e salva na memória.
if(decideNow == 2){ // record information on memory
numberOfPresses= 0;
decideNow= 0;
randomNumber = getRandom();
prefs.putUShort("RandomVar", randomNumber);
Serial.print("Saved ");
Serial.println(randomNumber);
}else if(decideNow == 1){ // retrieve information from memory
numberOfPresses= 0;
decideNow= 0;
randomVarPrint = prefs.getUShort("RandomVar", 0);
Serial.print("Read ");
Serial.println(randomVarPrint);
}else{
}
A função “prefs.putUShort(“RandomVar”, randomNumber);” vai guardar a variável “randomNumber” dentro da posição de memória “RandomVar” da NVS. Já a função “randomVarPrint = prefs.getUShort(“RandomVar”, 0);” lê a variável “RandomVar” e a salva em “randomVarPrint”.
E o que acontece se eu pressionar o botão uma vez (para ler o número salvo) e não há número salvo ainda?. Neste caso o ESP32 é mais esperto, retorna um zero (0). A partir do momento que você salvar um número (pressionar o botão duas vezes), ele ficará permanentemente salvo ali. Pelo menos até você pressionar o botão duas vezes novamente.
Salvar dados no ESP32
Copie o código completo acima, ou do GitHub apresentado e cole na sua IDE do Arduino. Instale a biblioteca BlinkLED, através do arquivo .ZIP também do link apresentado. Conecte seu ESP32-C6 ao computador atráves de um cabo USB.
Clique em Upload “->”, um botão verde no canto superior esquerdo da IDE do Arduino e aguarde alguns segundos. O no início deste artigo demonstar o funcionamento do sistema, mas vou te ensinar por aqui também:
- Abra o monitor serial da IDE do Arduino, indo em “Tools > Serial monitor”
- Pressione o botão push button duas vezes, não muito rápido mas dentro de 2 segundos
- Será impresso no monitor serial “Saved xx”, onde “xx” é um número entre 0 e 100
- Pressione o mesmo botão apenas uma vez. Dentro de 2 segundos outra mensagem será impressa:
- Você verá “Read xx”, onde “xx” é o mesmo número que acabou de ser salvo acima.
- Você pode pressionar o botão apenas uma vez, quantas vezes quiser, sempre o mesmo número será impresso
- Mesmo se você desligar e religar a placa, pressionando o botão apenas uma vez, o mesmo número “Read xx” será impresso.
- Para salvar um outro número, basta pressionar o botão 2x dentro de 2 segundos.
Se você quiser comprar a placa ESP32-C6 Super Mini que eu estou usando, clique neste link do Aliexpress e aproveite. Se tiver dúvidas e quiser comentar, use o formulário abaixo ou então nos comentários do Youtube. Nos vemos logo mais, pessoal.