Armazenando dados de forma não volátil no Arduino – EEPROM
A necessidade de se armazenar informações é algo bastante recorrente no desenvolvimento de projetos embarcados. Sejam elas um conjunto de variáveis para configurar o seu sistema, ou até mesmos dados que você queira visualizar após um intervalo de tempo, o armazenamento de informações é muito importante em praticamente todos os tipos de projetos. Porém nem sempre dispomos de um cartão SD, seja por limitações de custo ou até mesmo pinos de I/O, impossibilitando assim que a utilização de um cartão SD resolvesse o problema. Sabendo disso, neste tutorial, você aprenderá como utilizar a memória EEPROM existente nos microcontroladores Atmega das placas Arduino para armazenar dados permanentemente.
[toc]
Memória EEPROM
A memória EEPROM ou Electrically-Erasable Programmable Read-Only Memory, consiste em um modelo de memória onde diferente da memória RAM, podemos manter dados armazenados após desligarmos o nosso equipamento. Desta forma, é possível salvar informações que podem ser necessárias para o funcionamento do sistema após o seu desligamento, como por exemplo:
- Configurações
- Dados de difícil obtenção
- Dados estáticos
A utilização de memórias EEPROM, possibilitam com que o desenvolvedor seja capaz de armazenar diversas informações do sistema para uso posterior. Porém, este tipo de memória possui um ciclo muito pequeno de escritas, o que torna o seu uso viável apenas para o armazenamento de informações que serão pouco modificadas.
Memória eeprom i2c com encapsulamento dip.
O processo de leitura e escrita neste tipo de memória pode ser feito de duas formas:
- Paralelo: Cada byte é escrito de forma paralela na memória, sendo este escrito com base no endereço definido no barramento. Mais rápida, porém gasta uma maior quantidade de pinos para I/O
- Serial: Cada byte é escrito de forma serializada (normalmente utilizando o protocolo i2c) na memória eeprom. Mais lenta, porém gasta uma menor quantidade de pinos para I/O
Memória EEPROM no Arduino
Os microcontroladores ATmega possuem em sua arquitetura uma pequena memória eeprom, que pode ser utilizada como uma unidade de armazenamento. Cada microcontrolador possui uma de tamanho específico, segmentada em 1 byte por endereço. A lista abaixo ilustra a quantidade de memória disponível para a microcontroladores utilizados em plataformas como Arduino Nano, Arduino Uno e Arduino Mega:
- ATmega8,ATmega168 – 512 Bytes
- ATmega326 – 1024 Bytes
- ATmega1280, ATmega2560 – 4096 Bytes
Mãos à obra – Armazenando dados na memória EEPROM do Arduino
Neste projeto iremos apenas aprender a como manipular e realizar leituras e escritas na memória EEPROM do Arduino, desta forma iremos precisar apenas de um Arduino com algum dos microcontroladores citados acima.
Componentes Utilizados
Neste projeto iremos utilizar um Arduino Nano, porém praticamente todos os microcontroladores ATmega dispõem de uma memória eeprom nativa.
Programando
– Bibliotecas
Como iremos apenas aprender como armazenar e ler dados da memória EEPROM do Arduíno, iremos utilizar apenas a biblioteca EEPROM.h que já é nativa da ide que pode ser importada ao seu código da seguinte forma:
#include <EEPROM.h>
– Código Utilizado
Agora que temos o nosso sistema montado, e as bibliotecas adicionadas ao projeto, podemos partir para o código. Observem o código a seguir que utilizaremos como base para o nosso manipulador de memória.
#include <EEPROM.h> // Biblioteca para acesso e manipulação da EEPROM void setup() { Serial.begin(115200); // Inicialização da comunicação serial Serial.print("Espaco Disponivel em Bytes: "); // Mostra a quantidade de memória da EEPROM do seu microcontrolador Serial.println(EEPROM.length()); // Mostra a quantidade de memória da EEPROM do seu microcontrolador escreveByte(0,254); // Escreve o valor 255 na posição 0 da memória byte valor = leByte(0); // Lê o endereço 0 da memória ( onde escrevemos 255 ) Serial.print("Byte Armazenado: "); // Mostra o Byte Armazenado Serial.println(valor); // Mostra o Byte Armazenado escreveInt(1,2,1200); // Escreve o valor 1200 na EEPROM ( por ser um int de 2 bytes precisamos utilizar 2 endereços para armazenar) Serial.print ("Inteiro Armazenado: "); // Mostra o inteiro Armazenado Serial.println(lerInt(1,2)); // Mostra o inteiro Armazenado escreveString(3,"Vida de Silício"); // Escreve a String Vida de Silício na EEPROM, começando no endereço 3 Serial.println("String Armazenada: "+leString(3)); // Mostra a String armazenada } void loop(){ } byte leByte (int endereco1){ return EEPROM.read(endereco1); // Realizamosa leitura de 1 byte e retornamos } void escreveByte (int endereco1, byte valor){ // Escreve um byte na EEPROM no endereço especificado byte valorAtual = leByte(endereco1); // Lemos o byte que desejamos escrever if (valorAtual == valor){ // Se os valores forem iguais não precisamos escrever ( economia de ciclos de escrita ) return; } else { // Senão escrevemos o byte no endereço especificado na função EEPROM.write(endereco1,valor); // Escreve o byte no endereço especificado na função } } void escreveInt(int endereco1, int endereco2, int valor){ // Escreve um inteiro de 2 bytes na EEPROM int valorAtual = lerInt(endereco1,endereco2); // Lemos o valor inteiro da memória if (valorAtual == valor){ // Se o valor lido for igual ao que queremos escrever não é necessário escrever novamente return; } else{ // Caso contrário "quebramos nosso inteiro em 2 bytes e escrevemos cada byte em uma posição da memória byte primeiroByte = valor&0xff; //Executamos a operação AND de 255 com todo o valor, o que mantém apenas o primeiro byte byte segundoByte = (valor >> 8) &0xff; // Realizamos um deslocamento de 8 bits para a direita e novamente executamos um AND com o valor 255, o que retorna apenas o byte desejado EEPROM.write(endereco1,primeiroByte); // Copiamos o primeiro byte para o endereço 1 EEPROM.write(endereco2,segundoByte); // Copiamos o segundo byte para o endereço 2 } } int lerInt(int endereco1, int endereco2){ // Le o int armazenado em dois endereços de memória int valor = 0; // Inicializamos nosso retorno byte primeiroByte = EEPROM.read(endereco1); // Leitura do primeiro byte armazenado no endereço 1 byte segundoByte = EEPROM.read(endereco2); // Leitura do segundo byte armazenado no endereço 2 valor = (segundoByte << 8) + primeiroByte; // Deslocamos o segundo byte 8 vezes para a esquerda ( formando o byte mais significativo ) e realizamos a soma com o primeiro byte ( menos significativo ) return valor; // Retornamos o valor da leitura } void escreveString(int enderecoBase, String mensagem){ // Salva a string nos endereços de forma sequencial if (mensagem.length()>EEPROM.length() || (enderecoBase+mensagem.length()) >EEPROM.length() ){ // verificamos se a string cabe na memória a partir do endereço desejado Serial.println ("A sua String não cabe na EEPROM"); // Caso não caiba mensagem de erro é mostrada } else{ // Caso seja possível armazenar for (int i = 0; i<mensagem.length(); i++){ EEPROM.write(enderecoBase,mensagem[i]); // Escrevemos cada byte da string de forma sequencial na memória enderecoBase++; // Deslocamos endereço base em uma posição a cada byte salvo } EEPROM.write(enderecoBase,'\0'); // Salvamos marcador de fim da string } } String leString(int enderecoBase){ String mensagem=""; if (enderecoBase>EEPROM.length()){ // Se o endereço base for maior que o espaço de endereçamento da EEPROM retornamos uma string vazia return mensagem; } else { // Caso contrário, lemos byte a byte de cada endereço e montamos uma nova String char pos; do{ pos = EEPROM.read(enderecoBase); // Leitura do byte com base na posição atual enderecoBase++; // A cada leitura incrementamos a posição a ser lida mensagem = mensagem + pos; // Montamos string de saídaa } while (pos != '\0'); // Fazemos isso até encontrar o marcador de fim de string } return mensagem; // Retorno da mensagem }
Colocando pra funcionar
Se nada tiver sido modificado no código e sua EEPROM ainda possuir ciclos de escrita, a seguinte mensagem será apresentada no monitor serial:
Entendendo a Fundo
Software
– Incluindo bibliotecas necessárias
Para este sistema, iremos precisar apenas da biblioteca EEPROM.h que já é nativa do Arduíno e será utilizada como interface entre a EEPROM e o nosso código, desta forma ela pode ser adicionada da seguinte forma:
#include <EEPROM.h>
– Função Setup
Em nossa função setup, iremos basicamente escrever e ler as variáveis que desejamos armazenar na EEPROM, foram definidos um conjunto de 6 funções, sendo três para escrita e três para leitura de tipos de dados normalmente utilizados, sendo eles:
- Int
- Byte
- String
Sendo assim, no setup iremos apenas utilizar estas funções, da seguinte forma:
– Função escreve byte
Escreve um valor em byte no endereço especificado, neste exemplo estamos escrevendo o valor 254 no endereço 0 da memória.
escreveByte(0,254); // Escreve o valor 255 na posição 0 da memória
– Função lê byte
Realiza a leitura de um único byte no endereço especificado pelo usuário, neste exemplo estamos lendo o endereço 0.
byte valor = leByte(0);
– Função escreve int
Escreve um valor inteiro composto por 2 bytes, em duas posições quaisquer da eeprom, neste exemplo estamos escrevendo o valor 1200 utilizando os endereços 1 e 2.
escreveInt(1,2,1200);
– Função lê int
Realiza a leitura de dois endereços especificados pelo usuário, reconstruindo o valor inteiro que elas representam, neste exemplo estamos lendo os endereços 1 e 2 da memória.
Serial.println(lerInt(1,2));
– Função escreve string
Escreve uma determinada string de forma sequencial na EEPROM, neste exemplo estamos utilizando um total de 15 endereços (começando do endereço 3) para escrever a string “Vida de Silício” em nossa memória.
escreveString(3,"Vida de Silício");
– Função lê string
Dado um endereço base fornecido, esta função lê sequencialmente toda a string armazenada.
leString(3);
Entendendo cada função implementada
Cada função implementada possui um grau complexidade devido a realização de operações lógicas para quebra de bytes e armazenamento sequencial, desta forma iremos mostrar a forma como cada função é capaz de salvar e recuperar seus respectivos tipos de dados. Porém antes vamos entender um pouco a forma como a nossa memoria funciona.
Imagine a sua memória como um um conjunto de blocos onde podemos armazenar valores em um intervalo de 0 até 255, e cada um desses intervalos está associado a um endereço, sendo assim, podemos imaginar a nossa memória da seguinte forma: Com essa estrutura de memória podemos salvar qualquer tipo de informação, desde que o espaço seja suficientemente grande para armazenar todos os valores.
– Função escreveByte
A função escreveByte recebe como argumentos o endereço e o valor a ser escrito naquela posição, verificando apenas se o byte que desejamos escrever já não está lá, desta forma economizamos uma escrita que seria feita desnecessariamente, prologando assim a vida útil de nossa memória.
void escreveByte (int endereco1, byte valor){ byte valorAtual = leByte(endereco1); if (valorAtual == valor){ return; } else { EEPROM.write(endereco1,valor); } }
Estruturalmente, podemos dizer que esta função executa a seguinte operação:
– Função leByte
Já a função leByte, basicamente realiza a leitura do endereço especificado em seu argumento, retornando assim o valor que está armazenado naquele endereço.
byte leByte (int endereco1){ return EEPROM.read(endereco1); }
Utilizando o nosso modelo de memória, podemos dizer que estamos realizando a seguinte operação:
– Função escreveInt
Nos microcontroladores ATmega, uma variável do tipo int, é representada por um total de 2 bytes, sendo estes divididos entre mais significativos (HB) e menos significativos (LB), como a nossa memória permite apenas o armazenamento de 1 byte por endereço. Sabendo disso, é necessário que a nossa função seja capaz de “quebrar” o nosso valor inteiro de 2 bytes, em duas variáveis de 1 byte cada, para que assim seja possível armazenar esta informação na memória. Para que isso seja feito, iremos utilizar dois tipos de operações lógicas existente em praticamente todas as arquiteturas de computadores, que são as operações rigth shift e and bitwise.
void escreveInt(int endereco1, int endereco2, int valor){ int valorAtual = lerInt(endereco1,endereco2); if (valorAtual == valor){ return; } else{ byte primeiroByte = valor&0xff; byte segundoByte = (valor >> 8) &0xff; EEPROM.write(endereco1,primeiroByte); EEPROM.write(endereco2,segundoByte); } }
Sabendo disso o algoritmo funciona da seguinte forma:
- Inicialmente verificamos se o valor que desejamos inserir é igual ao que está na memória ( se for economizamos duas escritas na memória )
- Caso não seja executamos a seguinte operação:
- Dado o nosso valor de exemplo 1001, serão executadas as seguintes operações:
- Aplicamos uma operação AND bit a bit sob o valor que desejamos quebrar com o número 255, desta forma iremos preservar apenas os bits da primeira cadeia de byte que desejamos armazenar. A figura abaixo ilustra como o processo é feito e o resultado obtido através da operação realizada.
- Para o segundo byte, realizaremos o mesmo processo do passo 2, porém ao invés apenas aplicar a operação lógica AND, iremos antes deslocar os bits que estão na camada superior. Para que dessa forma, cheguem a posição dos bits menos significativos para a aplicação da máscara.
- Por fim, com os bytes separados, a função salva cada um deles no endereço especificado como mostra a figura abaixo:
– Função leInt
O processo de leitura segue praticamente a lógica inversa do processo de escrita, onde iremos ler cada byte e montar o nosso valor da seguinte forma:
- Relizamos uma leitura do endereço de valor mais significativo, este valor é deslocado 8 bits a esquerda para que volte a sua posição de bit mais significativo.
- Realizamos uma leitura do endereço de valor menos significativo, este valor é somado ao valor deslocado para que volte a posição de bit menos significativo.
- Temos o nosso valor montado novamente.
int lerInt(int endereco1, int endereco2){ int valor = 0; byte primeiroByte = EEPROM.read(endereco1); byte segundoByte = EEPROM.read(endereco2); valor = (segundoByte << 8) + primeiroByte; return valor; }
– Função escreveString
A função escreve string por sua vez, não utiliza de recursos de deslocamento de bits, devido ao fato de que cada caractere, é um único byte. Ou seja, uma string nada mais é do que um vetor de bytes alocados sequencialmente. Sabendo disso, o nosso algoritmo de salvar uma string na memória segue a seguinte lógica:
- Dado um endereço base, e o tamanho da string verificamos se é possível o armazenamento ou não.
- Caso não seja possível, o valor não é salvo.
- Caso seja possível, a string é alocada sequencialmente na memória começando com o endereço base e terminando sempre com um ”\0”
void escreveString(int enderecoBase, String mensagem){ if (mensagem.length()>EEPROM.length() || (enderecoBase+mensagem.length()) >EEPROM.length() ){ Serial.println ("A sua String não cabe na EEPROM"); } else{ for (int i = 0; i<mensagem.length(); i++){ EEPROM.write(enderecoBase,mensagem[i]); enderecoBase++; } EEPROM.write(enderecoBase,'\0'); } }
– Função leString
A função leString por sua vez, realiza praticamente o mesmo processo que a leitura, porém ao invés de armazenar os bytes como na função escreveString, ela utiliza a função read, para ler cada byte armazenado sequencialmente. Desta forma podemos dizer que o algoritmo funciona da seguinte forma:
- Com base no endereço inicial, a função lê cada byte lido, até que o caractere de fim de texto ‘\0’, seja encontrado. Quando encontrado uma string montada é retornada pela função.
String leString(int enderecoBase){ String mensagem=""; if (enderecoBase>EEPROM.length()){ return mensagem; } else { char pos; do{ pos = EEPROM.read(enderecoBase); enderecoBase++; mensagem = mensagem + pos; } while (pos != '\0'); } return mensagem; }
Desafio
Agora que sabemos como manipular a memória EEPROM do nosso Arduino, tente adicionar esta funcionalidade a algum projeto que seja necessária a configuração de uma variável em tempo real. Um exemplo de sistema desse tipo são os dataloggers, onde precisamos configurar informações como tempo de leitura e configuração de sensores.
Considerações finais
Este tutorial, teve como objetivo mostrar como manipular a memória EEPROM, interna do seu Arduino, funcionando como uma pequena unidade de armazenamento para informações importantes. Espero que tenham gostado do conteúdo apresentado, sinta-se à vontade para nos dar sugestões, críticas ou elogios. Lembre-se de deixar suas dúvidas nos comentários abaixo.
Formado em Ciência da computação pela UFV-CAF em 2017 e atualmente cursando pós-graduação Stricto Sensu em Ciência da computação pela Universidade Federal de Viçosa, na área de arquitetura de computadores. É um entusiasta na área de sistemas embarcados e robótica.
16 Comments
Deixe uma pergunta, sugestão ou elogio! Estamos ansiosos para ter ouvir!Cancelar resposta
Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.
Olá a rotina leString inclui um ‘\o’ no fim da string desnecessário, que pode dar problemas.
Não poderia ser assim:
{ char pos;
do
{ pos = EEPROM.read(EnderecoBase); // Leitura do byte com base na posição atual
EnderecoBase++; // A cada leitura incrementamos a posição a ser lida
//***Alterado*********************
if (pos != ‘\0’)
{ mensagem = mensagem + pos; // Montamos string de saídaa
}
}
while (pos != ‘\0’ && EnderecoBaseInicial+40>EnderecoBase ); // Fazemos isso até encontrar o marcador de fim de string
}
return mensagem; // Retorno da mensagem
Boa noite amigo,
Tem como recuperar dados perdidos de uma e2prom 24C08 caso os dados estejam corrompidos.
E não tenha outro arquivo para regravar a memória
Este código não está gravando as linhas na Eprom corretamente, alguém tem alguma idéia ?
#include
int memoria, conteudo, comando;
String cpID = “”, cpIDEPROM = “”, cpADM = “”;
void setup(){
Serial.begin(9600); // Inicialização da comunicação serial
for (int i = 0 ; i 0){
cpID = “AAAAAAAAAAAAAAAAAAAAAAA”; // Valor capturado por leitor de RFIP …substring(0,10);
int comando = Serial.parseInt();
String cpIDEPROM = leString(comando);
Serial.println(comando);
Serial.println(cpID);
Serial.println( cpID );
if( cpID != cpIDEPROM ){
Serial.println(” OK “);
}else{
Serial.println(” Acesso permitido “);
}
}
}
//————————————————————————
//————————————————————————
void escreveString(int enderecoBase, String mensagem){ // Salva a string nos endereços de forma sequencial
if (mensagem.length()>EEPROM.length() || (enderecoBase+mensagem.length())>EEPROM.length() ){ // verificamos se a string cabe na memória a partir do endereço desejado
Serial.println (“A String não cabe na EEPROM”); // Caso não caiba mensagem de erro é mostrada
} else { // Caso seja possível armazenar
for (int i = 0; iEEPROM.length()){ // Se o endereço base for maior que o espaço de endereçamento da EEPROM retornamos uma string vazia
return mensagem;
} else { // Caso contrário, lemos byte a byte de cada endereço e montamos uma nova String
char pos;
do{
pos = EEPROM.read(enderecoBase);// Leitura do byte com base na posição atual
enderecoBase++; // A cada leitura incrementamos a posição a ser lida
mensagem = mensagem + pos; // Montamos string de saídaa
}
while (pos != ‘\0’); // Fazemos isso até encontrar o marcador de fim de string
}
return mensagem; // Retorno da mensagem
}
//————————————————————————
//————————————————————————
//————————————————————————
//————————————————————————
Esqueci alguma coisa , nãoRecupero dados da EEPROM, alguém pode me ajudar ? Obrigado
#include
int memoria, conteudo, comando;
String cpID = “”, cpIDEPROM = “”, cpADM = “”;
void setup(){
Serial.begin(9600); // Inicialização da comunicação serial
for (int i = 0 ; i 0){
cpID = “AAAAAAAAAAAAAAAAAAAAAAA”; // Valor capturado por leitor de RFIP …substring(0,10);
int comando = Serial.parseInt();
String cpIDEPROM = leString(comando);
Serial.println(comando);
Serial.println(cpID);
Serial.println( cpID );
if( cpID != cpIDEPROM ){
Serial.println(” OK “);
}else{
Serial.println(” Acesso permitido “);
}
}
}
//————————————————————————
//————————————————————————
void escreveString(int enderecoBase, String mensagem){ // Salva a string nos endereços de forma sequencial
if (mensagem.length()>EEPROM.length() || (enderecoBase+mensagem.length())>EEPROM.length() ){ // verificamos se a string cabe na memória a partir do endereço desejado
Serial.println (“A String não cabe na EEPROM”); // Caso não caiba mensagem de erro é mostrada
} else { // Caso seja possível armazenar
for (int i = 0; iEEPROM.length()){ // Se o endereço base for maior que o espaço de endereçamento da EEPROM retornamos uma string vazia
return mensagem;
} else { // Caso contrário, lemos byte a byte de cada endereço e montamos uma nova String
char pos;
do{
pos = EEPROM.read(enderecoBase);// Leitura do byte com base na posição atual
enderecoBase++; // A cada leitura incrementamos a posição a ser lida
mensagem = mensagem + pos; // Montamos string de saídaa
}
while (pos != ‘\0’); // Fazemos isso até encontrar o marcador de fim de string
}
return mensagem; // Retorno da mensagem
}
//————————————————————————
//————————————————————————
//————————————————————————
//————————————————————————
Apliquei suas funções num Aplicativo que estou trabalhando. mais só que coloquei a função lestring void loop, aplicando uma parametro conforme digino um numero no monitor serial, não esta capturando as posições na memoria . Obrigado
pq quando eu clico em reset, as vezes ele começa a contar de um numero aleatorio?
até quando eu desligo ele, as vezes ele continua de onde parou, as vezes continuar de um numero nada a ver
Gostei muito da explicação, mais tenho uma duvida, em um Arduino Uno ou Mega o BootLoader fica gravado em que area do MicroControlador.
Como faço pra ele gravar um movimento que faço no servo motor e depois o eeprom repita o movimento que eu fiz apertando um botão. Esperando ajuda
Gostei da explicação, vai ajudar muito…
Parabens muito boa a explicacao e com aplicabilidade vou precisar salvar valores de um sensor e estava a procura de um artigo como seu o que e muito dificil de encontrar.
Parabéns pela abordagem, e didática perfeita. O mundo e renovado com pessoas como você! Parabéns!
Valeu Wanderson. Fico feliz que tenha conseguido agregar conhecimento com esse post :)
Muito bom o tutorial, estava tentando escrever e ler na EEPROM mas, não estava 100%, seguindo os exemplos desse tutorial consegui implementar no meu projeto, onde estou criando um Timer com Temporizador digital onde preciso guardar 6 programações diferentes, que acionarão 6 relés que irão ligar válvulas solenóides para irrigação do meu pomar, nos horários pré-programados que estarão gravados na EEPROM.
Meus parabéns!
Fico feliz que este tutorial tenha te ajudo em seu projeto Claudecir. :D
Olá amigo vc sabe qual atmega de 28 pinos tem mais memoria que o atmega 328
Preciso de um com a mesma pinagem porém maior capacidade