Dumping da Memória EEPROM do Arduino

Sumário

Conceitos da EERPROM no Arduino

Uma característica dos microcontroladores hoje em dia é que eles contém uma gama de funcionalidade que você não vai encontrar em microprocessadores. Uma delas é a capacidade de armazenar dados em memória não volátil. Isso em geral é feito através da incorporação de uma memória EEPROM (Electrically-Erasable Programmable Read-Only Memory).

A maioria dos microcontroladores AVR usados nos kits Arduino dispõem desse tipo de memória no próprio chip, com capacidades entre 512 a 2048 bytes (tipicamente 1024 bytes, como no caso do Arduino UNO, Nano, pro micro, etc).

Portanto, quando há a necessidade de reter alguma informação que será usada por um sketch entre um reset e outro, pode-se utilizar dessa memória. No entanto é importante ficar atento para algumas limitações desse tipo de memória:

  • O tamanho da EEPROM é limitado.
  • Essa é uma memória de acesso um pouco mais lento. Algo como 4 ciclos de CPU para leitura de um byte (versus 2 ciclos para a memória RAM), e de 3,3ms para a escrita de um byte (tabela 7-2 no datasheet do ATMega328P).
  • O número de escritas em um mesmo endereço na memória EEPROM é limitado a aproximadamente 100.000 ciclos, isso inclui apagar a memória. E se você pensa que 100.000 vezes é muito, pense novamente. Este limite é atingido em menos de um ano com um sketch que salva para um mesmo endereço de memória EEPROM a cada cinco minutos [(60/5)*24*365=105.120]. Passado esse limite, aumenta muito a possibilidade de que a posição de memória não mais consiga armazenar dados de forma confiável. Há versões de microcontroladores AVR com limites de escrita maiores, como 1.000.000 de ciclos de escrita.
    Alguns sistemas onde escritas frequentes são necessárias, mantém um contador e um índice apontando para o segmento de memória sendo utilizada. Assim, é possível escrever em um outro segmento na EEPROM quando o contador se aproximar dos 100.000.
  • O acesso à EEPROM é feito através de uma interface, sendo necessário chamar uma função toda vez que ler ou escrever para ela.

Você pode utilizar a biblioteca padrão EEPROM para acessar este espaço de memória.

Como fazer um dump da EEPROM interna na porta Serial

A título de aprender e demonstrar como utilizar-se desta biblioteca eu criei um sketch para fazer o dump de todo o conteúdo da memória EEPROM no arduino. 

É necessário incluir o cabeçalho da biblioteca EEPROM (já inclusa com a instalação do IDE Arduino):

				
					#include <EEPROM.h>
				
			

Nas funções padrão inicializo a serial e faço uma chamada à função de dump (descarga). Fiz assim apenas para separar o código das estruturas padrão do Arduino. Isso também permite que a função dumpEEPROM() possa ser facilmente incorporada em outros programas se desejado:

				
					void setup() 
{
    Serial.begin(115200);
    while (!Serial) yield();    // Aguarda inicialização da porta serial
    dumpEEPROM(); 
}

void loop()
{
    // Nada para rodar
}
				
			

Como a EEPROM não faz parte do espaço regular de endereçamento de memória, é necessário manter algumas variáveis, especialmente um índice da posição atual de memória em que estamos trabalhando. Isso aqui é feito pela variável memIdx inicializada para o endereço 0x00. As outras variáveis estão relacionadas com a formatação dos dados para apresentação:

				
					void dumpEEPROM()
{
    int memIdx = 0;       // Indice na memória EEPROM
    int n = 0;            
    int tmp = 0;
    int precision = 1;    // Número de dígitos hex no endereço de memória
    int addrLength = 0;   // Total de bytes na memória EEPROM
    int rows = 0;         // Linha sendo processada nos resultados
    char address[128];    // Buffer contendo o intervalo de memória nos resultados
    char mask[16];        // Buffer com máscara de formatação do intervalo de memória
				
			

O método EEPROM.length() retorna o total de bytes na EEPROM, esse valor pode variar dependendo do chip executando o código. A variável rows define o total de linhas que serão apresentadas, onde cada linha conterá 16 bytes ou caracteres. Em seguida faço uso da função sprintf() para apresentar o endereço em hexadecimal com zeros à esquerda para deixar a apresentação uniforme. Esta função escreve uma string formatada (similar à mais conhecida função printf()) em um buffer que você define (variável mask). Depois basta imprimir este buffer (neste caso uma string) para a porta serial:

				
					    // Diferentes arquiteturas podem variar o tamanho da EEPROM. Obtenho o numero de bytes.
    addrLength = EEPROM.length();
    tmp = addrLength;
    rows = addrLength / 16;   // São 16 bytes mostrados por linha
    // Cálculo do número de digitos hex no endereço, usado para formatação da saída
    do {
        tmp = tmp >> 4;
        if (tmp > 0) precision++;
    } while (tmp > 0);
    sprintf(mask, "0x%%.%dX 0x%%.%dX  ", precision, precision);

    Serial.print("\nDescarregando conteúdo da EEPROM (");
    Serial.print(addrLength);
    Serial.print(" bytes):\n");
				
			

No código seguinte eu imprimo o conteúdo da memória EEPROM. No início da linha escrevo o intervalo de memória da linha, os respectivos bytes em hexadecimal e finalmente o valor correspondente em ASCII, observando que caracteres abaixo do valor 32 e superiores ao valor 128 não são impressos, pois nem sempre são representados corretamente na saída serial:

				
					
    // Apresenta o conteúdo da memória EEPROM
    for (int r = 0; r < rows; r++)
    {
        sprintf(address, mask, memIdx, memIdx + 0xF);
        Serial.print(address);
        for (n = 0; n < 16; n++)
        {
            Serial.print(EEPROM[memIdx + n], HEX);
            if (n != 7)Serial.print(" ");
            else Serial.print("-");
        }
        Serial.print("  ");
        for (int n = 0; n < 16; n++)
        {
            // Mostra apenas caracteres válidos (entre ASCII 32 e 128)
            if (EEPROM[memIdx + n] > 128 || EEPROM[memIdx + n] < 32) Serial.print(".");
            else Serial.write(EEPROM[memIdx + n]);
        }
        memIdx += n;
        Serial.print("\n");
    }
				
			

E finalmente imprimo uma mensagem indicando a finalização:

				
					    Serial.print("fim!");
}
				
			

Resultados

A figura abaixo mostra os resultados na porta serial após executar este código em um Arduino UNO. Os valores impressos vão variar dependendo dos dados já armazenados em memória. 

Observe que alguns gravadores de programas irão reinicializar os dados na EEPROM quando fazendo o upload de um sketch. Normalmente a memória é preenchida com o valor 0xFF, mas já vi casos onde a memória é preenchida com 0x00. Neste exemplo a memória contém a string ARDUANDO.

Saida porta serial com dump de memória
Saída na porta serial do sketch DumpEERPOM.ino

Simulação

Use o serviço de emulação de kits de microcontroladores provido pelo site wokwi.com para rodar este sketch. Clique na imagem abaixo para acessar um simulador do Arduino Nano executando este sketch. No site, basta selecionar o botão de play () para executar o sketch.

Código fonte completo

Incluo o código completo a seguir para que você possa testar no seu Arduino (também disponível no Github).

				
					/**
 * Copyright (c) 2022 - James Owens <jjo(at)arduando.com.br>
 *
 * File:	DumpEeprom.ino
 * Created:	14/11/2022 15:33
 * Version:
 * Source:  https://github.com/jjjowens/Arduando/blob/master/DumpEeprom/DumpEeprom.ino
 * Website: https://arduando.com.br
 *
 * Description: Dumps the content of internal eeprom in microcontroller. 
 * Tested on Arduino Uno, Nano and Leonardo.
 *
 * DISCLAIMER:
 * The author is in no way responsible for any problems or damage caused by
 * using this code. Use at your own risk.
 *
 * LICENSE:
 * This code is distributed under the GNU Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * More details can be found at http://www.gnu.org/licenses/gpl.txt
 */


#include <EEPROM.h>

void setup() 
{
    Serial.begin(115200);
    while (!Serial) yield();    // Aguarda inicialização da porta serial
    dumpEEPROM(); 
}

void loop()
{
    // Nada para rodar
}

/**
 * Finalidade: ler e imprimir o conteúdo da memória EEPROM de
 * kits tipo Arduino.
 *
 * Para executar, compile e chame esta função a partir do 
 * seu código de loop, e faça o upload para o seu kit Arduino.
 *
 * Testado em:  ATMega32U4 (BSFrance LoRa32u4 II), Arduino UNO,
 * Arduino Pro Micro (Leonardo), Arduino NANO
 */
void dumpEEPROM()
{
    int memIdx = 0;       // Indice na memória EEPROM
    int n = 0;            
    int tmp = 0;
    int precision = 1;    // Número de dígitos hex no endereço de memória
    int addrLength = 0;   // Total de bytes na memória EEPROM
    int rows = 0;         // Linha sendo processada nos resultados
    char address[128];    // Buffer contendo o intervalo de memória nos resultados
    char mask[16];        // Buffer com máscara de formatação do intervalo de memória

    // Diferentes arquiteturas podem variar o tamanho da EEPROM. Obtenho o numero de bytes.
    addrLength = EEPROM.length();
    tmp = addrLength;
    rows = addrLength / 16;   // São 16 bytes mostrados por linha
    // Cálculo do número de digitos hex no endereço, usado para formatação da saída
    do {
        tmp = tmp >> 4;
        if (tmp > 0) precision++;
    } while (tmp > 0);
    sprintf(mask, "0x%%.%dX 0x%%.%dX  ", precision, precision);

    Serial.print("\nDescarregando conteúdo da EEPROM (");
    Serial.print(addrLength);
    Serial.print(" bytes):\n");

    // Apresenta o conteúdo da memória EEPROM
    for (int r = 0; r < rows; r++)
    {
        sprintf(address, mask, memIdx, memIdx + 0xF);
        Serial.print(address);
        for (n = 0; n < 16; n++)
        {
            Serial.print(EEPROM[memIdx + n], HEX);
            if (n != 7)Serial.print(" ");
            else Serial.print("-");
        }
        Serial.print("  ");
        for (int n = 0; n < 16; n++)
        {
            // Mostra apenas caracteres válidos (entre ASCII 32 e 128)
            if (EEPROM[memIdx + n] > 128 || EEPROM[memIdx + n] < 32) Serial.print(".");
            else Serial.write(EEPROM[memIdx + n]);
        }
        memIdx += n;
        Serial.print("\n");
    }
    Serial.print("fim!");
}