Armazenamento de dados na memória flash do ESP32

Armazenamento de dados na memória flash do ESP32 1

O ESP32 tornou-se referência em hardware para projetos em Internet das Coisas e para sistemas embarcados em geral. Com ótimo custo/benefício e largamente disponível no mercado, o ESP32 muitas vezes é uma escolha imbatível para um projeto neste escopo.

Seja qual for o projeto, muito provavelmente será necessário armazenar dados de forma permanente, tais como: nomes de redes e senhas wifi, contadores, configurações e mais uma infinidade de dados comumente gerados nos sistemas embarcados. Nesse quesito, o ESP32 também oferece bons recursos, possuindo uma área de sua memória Flash que pode ser dedicada justamente a esta finalidade.

Este post mostrará como armazenar dados permanentes na memória flash do ESP32, assim como fazer a leitura dos mesmos.

Como é dividida a memória Flash do ESP32?

De acordo com o site do fabricante do ESP32, a memória Flash do ESP32 possui, por padrão, o particionamento mostrado na figura 1.

Armazenamento de dados na memória flash do ESP32

Isso significa dizer que a memória Flash total do ESP32 é dividida em blocos ( = partições) com funcionalidades bem distintas, oferecendo uma ótima organização dos dados nela gravados. Dessas partições, destacam-se a partição NVS (acrônimo para Non-Volatile Storage) e a partição Factory. A Factory é a partição na qual o software que desenvolvemos para o ESP32 é gravado, cujo tamanho da partição é de 1MB. Já a NVS é a partição da memória Flash do ESP32 responsável por armazenar dados permanentes (tamanho total da partição: 24kB), partição que é nosso alvo neste post.

A partição NVS armazena dados permanentes do tipo chave-valor. Ou seja, os dados armazenados (valor) possuem uma chave única associada a eles. Dessa forma, para ler ou escrever um valor, fazemos referência a chave do mesmo. A chave é uma string / nome definida pelo programador, logo fica simples de se referenciar a um par chave-valor específico.

O que fazer antes de gravar e ler da partição NVS?

Antes de fazer a leitura ou escrita de dados na partição NVS do ESP32, é preciso fazer as seguintes operações:

  1. Inicializar a partição: a primeira coisa a ser feita é inicializar a partição a ser manipulada (leitura ou escrita). Isso é feito com a função nvs_flash_init_partition().
  2. Abrir a partição: uma vez inicializada, deve-se abrir a partição como leitura, escrita ou ambos. Para isso, utiliza-se a função nvs_open_from_partition().

Ainda, no caso da escrita de dados na partição NVS, é preciso confirmar a gravação dos dados com a função nvs_commit(). Se isso não for feito, os dados não serão efetivamente salvos na NVS.

Gravação de dados na partição NVS

Para gravar dados na partição NVS, a Espressif (fabricante do ESP32) disponibiliza uma série de funções, uma para cada tipo de dado a ser gravado (conforme pode ser visto aqui). Veja abaixo a relação das principais funções de gravação de dados:

  • nvs_set_i8(): gravação de um dado do tipo inteiro de 8 bits com sinal.
  • nvs_set_u8(): gravação de um dado do tipo inteiro de 8 bits sem sinal.
  • nvs_set_i16(): gravação de um dado do tipo inteiro de 16 bits com sinal.
  • nvs_set_u16(): gravação de um dado do tipo inteiro de 16 bits sem sinal.
  • nvs_set_i32(): gravação de um dado do tipo inteiro de 32 bits com sinal.
  • nvs_set_u32(): gravação de um dado do tipo inteiro de 32 bits sem sinal.
  • nvs_set_i64(): gravação de um dado do tipo inteiro de 64 bits com sinal.
  • nvs_set_u64(): gravação de um dado do tipo inteiro de 64 bits sem sinal.
  • nvs_set_str(): gravação de uma string.

Leitura de dados na partição NVS

Para ler dados da partição NVS, a Espressif disponibiliza uma série de funções, uma para cada tipo de dado a ser gravado (conforme pode ser visto aqui). Veja abaixo a relação das principais funções de leitura de dados:

  • nvs_get_i8(): leitura de um dado do tipo inteiro de 8 bits com sinal.
  • nvs_get_u8(): leitura de um dado do tipo inteiro de 8 bits sem sinal.
  • nvs_get_i16(): leitura de um dado do tipo inteiro de 16 bits com sinal.
  • nvs_get_u16(): leitura de um dado do tipo inteiro de 16 bits sem sinal.
  • nvs_get_i32(): leitura de um dado do tipo inteiro de 32 bits com sinal.
  • nvs_get_u32(): leitura de um dado do tipo inteiro de 32 bits sem sinal.
  • nvs_get_i64(): leitura de um dado do tipo inteiro de 64 bits com sinal.
  • nvs_get_u64(): leitura de um dado do tipo inteiro de 64 bits sem sinal.
  • nvs_get_str(): leitura de uma string.

E o que fazer após a gravação ou leitura de dados da partição NVS?

Após a manipulação de dados da NVS (leitura ou escrita), é preciso fechar a partição. Isso é feito com a função nvs_close().

Cuidados no uso da partição NVS

Para o uso correto da partição NVS, é preciso tomar alguns cuidados:

  • Faça o menor número de gravações possível: toda memória Flash existente é dividida em setores, e cada um deles possui um número máximo de vezes que pode ser reescrito. Após atingido este limite, o setor é inutilizado fisicamente (este setor da memória Flash deixa de funcionar, sendo impossível sua recuperação). Este número varia muito, indo na prática de 100.000 até 1.000.000 de reescritas. Dessa forma, para maximizar a vida útil da memória Flash do seu ESP32, faça o menor número de gravações (reescritas) possível.
  • Guarde somente o necessário: apesar de possuir uma quantidade bem significativa de espaço (24kB), a partição NVS é finita e, dependendo de como é utilizada, pode ser insuficiente para um projeto. Sendo assim, pense bem no que seu projeto precisa salvar nesta partição, de forma a guardar nela somente o necessário. Fazendo isso, você estará utilizando a partição NVS de forma eficiente.

Mãos à obra!

O melhor jeito de aprender algo é botando o conhecimento adquirido em prática. Sendo assim, segue abaixo um exemplo prático de escrita e leitura de um dado do tipo inteiro 32-bits (sem sinal) na NVS. Neste exemplo, é considerada a chave “teste”, ou seja, haverá um registro de um valor de inteiro 32-bits sem sinal na NVS, atribuído à chave / identificação “teste”.

/* Header-file com as funções utilizadas para 
   manipulação da partição NVS */
#include "nvs_flash.h"   

/* Definição - baudrate da serial de debug */
#define BAUDRATE_SERIAL_DEBUG    115200

/* Chave atribuida ao valor a ser escrito e lido
   da partição NVS */
#define CHAVE_NVS  "teste"

/* Protótipos */
void grava_dado_nvs(uint32_t dado);
uint32_t le_dado_nvs(void);

/* Função: grava na NVS um dado do tipo interio 32-bits
 *         sem sinal, na chave definida em CHAVE_NVS
 * Parâmetros: dado a ser gravado
 * Retorno: nenhum
 */
void grava_dado_nvs(uint32_t dado)
{
    nvs_handle handler_particao_nvs;
    esp_err_t err;
   
    err = nvs_flash_init_partition("nvs");
    
    if (err != ESP_OK)
    {
        Serial.println("[ERRO] Falha ao iniciar partição NVS.");           
        return;
    }

    err = nvs_open_from_partition("nvs", "ns_nvs", NVS_READWRITE, &handler_particao_nvs);
    if (err != ESP_OK)
    {
        Serial.println("[ERRO] Falha ao abrir NVS como escrita/leitura"); 
        return;
    }

    /* Atualiza valor do horimetro total */
    err = nvs_set_u32(handler_particao_nvs, CHAVE_NVS, dado);

    if (err != ESP_OK)
    {
        Serial.println("[ERRO] Erro ao gravar horimetro");                   
        nvs_close(handler_particao_nvs);
        return;
    }
    else
    {
        Serial.println("Dado gravado com sucesso!");     
        nvs_commit(handler_particao_nvs);    
        nvs_close(handler_particao_nvs);      
    }
}

/* Função: le da NVS um dado do tipo interio 32-bits
 *         sem sinal, contido na chave definida em CHAVE_NVS
 * Parâmetros: nenhum
 * Retorno: dado lido
 */
uint32_t le_dado_nvs(void)
{
    nvs_handle handler_particao_nvs;
    esp_err_t err;
    uint32_t dado_lido;
    
    err = nvs_flash_init_partition("nvs");
    
    if (err != ESP_OK)
    {
        Serial.println("[ERRO] Falha ao iniciar partição NVS.");         
        return 0;
    }

    err = nvs_open_from_partition("nvs", "ns_nvs", NVS_READWRITE, &handler_particao_nvs);
    if (err != ESP_OK)
    {
        Serial.println("[ERRO] Falha ao abrir NVS como escrita/leitura");         
        return 0;
    }

    /* Faz a leitura do dado associado a chave definida em CHAVE_NVS */
    err = nvs_get_u32(handler_particao_nvs, CHAVE_NVS, &dado_lido);
    
    if (err != ESP_OK)
    {
        Serial.println("[ERRO] Falha ao fazer leitura do dado");         
        return 0;
    }
    else
    {
        Serial.println("Dado lido com sucesso!");  
        nvs_close(handler_particao_nvs);   
        return dado_lido;
    }
}

void setup() 
{
    uint32_t dado_a_ser_escrito = 123456;
    uint32_t dado_lido;
    
    /* Inicializa a serial de debug */
    Serial.begin(BAUDRATE_SERIAL_DEBUG);

    /* Escreve na NVS (na chave definida em CHAVE_NVS)
       o valor da variável "dado_a_ser_escrito" */
    grava_dado_nvs(dado_a_ser_escrito);

    /* Le da NVS (na chave definida em CHAVE_NVS)
       o valor escrito e compara com o que foi escrito */
    dado_lido = le_dado_nvs();

    if (dado_lido == dado_a_ser_escrito)
        Serial.println("Sucesso: dado lido eh igual ao escrito");
    else
        Serial.println("Erro: dado lido nao eh igual ao escrito");        
}

void loop() 
{
   
}

Execução do programa

Na figura 2, é possível ver o resultado da execução do programa no ESP32.

Armazenamento de dados na memória flash do ESP32

Conclusão

Neste post, você aprendeu que é possível utilizar NVS (partição da memória Flash do ESP32) para armazenar, de forma permanente, dados do tipo chave-valor. Isso permitirá que seus projetos fiquem muito mais robustos, de forma que seja possível guardar configurações, preferências e credenciais (como nome e senha de rede wifi, por exemplo) no ESP32 sem precisar de uma memória externa, como uma E2PROM.

Gostou deste post sobre como armazenar dados na memória Flash do ESP32? Deixe seu comentário logo abaixo.

Faça seu comentário

Acesse sua conta e participe

Um Comentário

  1. Obrigado Pedro Bertoleti por partilhar seus conhecimentos, foi de grande valia. Somou muito aos meus conhecimentos.