Uma utilidade interessante presente no Arduino é gravar dados na memória para que eles não sejam perdidos no desligamento. Dessa forma, podemos gravar informações para serem passadas para um computador, por exemplo. Portanto, vamos aprender a utilizar as funções da memória EEPROM, que é a responsável por essa utilidade.
Na aula anterior, vimos como utilizar o modo sleep do Arduino.
Informações importantes
Conceito
A memória EEPROM é uma estrutura, para armazenar dados, que mantém seu estado mesmo não estando energizada, logo ela é classificada como não-volátil. Este tipo de memória é de leitura e escrita. Isto é, os bytes da memória podem ser lidos e alterados individualmente. A imagem abaixo mostra um CI de memória EEPROM com capacidade de armazenar 2 Kibytes (2048 bytes).
Teoricamente, a leitura do byte pode ser feita um número ilimitado de vezes. Entretanto, na prática, existe um limite para cada byte, principalmente no caso da escrita. O valor médio para a EEPROM do Atmega328p é cerca de 100.000 escritas/leituras (de acordo com o datasheet).
Como o Arduino é capaz de fazer processamentos rapidamente, devemos ter cuidado para não exagerar no uso da EEPROM para não alcançar este limite. Por exemplo, considere o caso de gravar um dado no mesmo byte da EEPROM a cada 10 milissegundos. Em um segundo, teriam sido feitas 100 gravações, e, em pouco mais de 16 minutos, o limite de gravações teria sido alcançado.
Organização da memória
No caso do Atmega328p (Arduino UNO), a memória EEPROM tem um limite de 1Kibyte (1024 bytes). Sendo assim, temos 1024 espaços para armazenar nossos dados. E, lembrando que, cada byte possui 8 bits, o que restringe um valor armazenado à faixa de 0 a 255 em decimal (de 00000000 a 11111111). No tópico seguinte veremos uma forma de contornar isso. Veja a imagem abaixo para entender melhor:
Quando formos gravar ou ler dados, iremos acessar os bytes com endereços de 0 a 1023. Logo, é importante conhecer a estrutura da memória para saber a melhor forma de organizar seus dados, sejam eles medições de algum sensor ou configurações.
Programação
Para ler ou escrever dados na EEPROM do Arduino, existe uma biblioteca com funções bem simples e fáceis de usar e entender. E ela deve ser inicializada no início do programa para que as funções possam ser utilizadas:
1 | #include <EEPROM.h> |
Antes de sair utilizando os comandos abaixo, é importante ter em mente como os seus dados ficarão organizados na memória.
Por exemplo, imagine o caso onde é preciso gravar as medições de um sensor. Uma ideia interessante seria deixar os 2 primeiros bytes armazenando o valor da posição de memória que o programa deverá salvar os dados em seguida. Pois, caso o Arduino seja desligado, esses dois primeiros bytes informarão exatamente a partir de qual ponto o programa deve continuar a gravação.
Escrita
Para fazer a gravação de valores na EEPROM, podemos contar com três funções distintas, que são:
- EEPROM.write(endereço, valor_byte)
A função acima recebe como parâmetro um endereço (0 a 1023) e o valor do byte (0 a 255) a ser gravado na memória. Este comando é bem direto, basta executá-lo que ele irá gravar diretamente o valor do byte no endereço da EEPROM especificado. Este procedimento gasta cerca de 3.4ms para ser feito (pág 42 do datasheet).
- EEPROM.update(endereço, valor_byte)
Esta função é praticamente igual a anterior, com a diferença que o dado só é gravado no endereço se o valor do byte for diferente do valor que está previamente gravado na memória. Ou seja, utilizando esta função, você possivelmente aumenta a vida útil da EEPROM, já que evitará gravações desnecessárias de valores repetidos. Novamente o valor do byte é um número de 0 a 255.
- EEPROM.put(endereço, dado)
A função put tem o mesmo comportamento da função update, a grande mudança aqui é na forma como o dado é gravado. Neste caso, a função aceita variáveis de tipos diferentes de uma variável de valor de 0 a 255 (ex: int, float) e também aceita struct. Portanto, esta função utiliza mais de 1 byte para armazenar o valor da variável.
Então, é importante conhecer sobre os tamanhos de cada variável, para saber quantos endereços cada uma está ocupando e, assim, evitar erros de sobreposição no seu programa.
Leitura
Tópico corrigido dia 07/06/23 graças a apontamento de Rodrigo.
No caso da leitura, também há mais de uma função para realizar a extração dos dados armazenados:
- EEPROM.read(endereço)
A função acima basicamente retorna o valor armazenado no endereço da memória EEPROM especificado.
- EEPROM.get(endereço, tipo_dado)
Esta função também retorna o valor armazenado na memória. Neste caso, o valor retornado é por referência no segundo parâmetro da função. Isto significa que o valor será armazenado na variável “tipo_dado” diretamente. E o valor retornado depende do tipo do dado que você deseja extrair (ex: int, float, char). Portanto, ela não irá retornar apenas o valor do endereço especificado, mas, dependendo, os valores dos endereços seguintes que formam o tipo da variável especificado.
Para explicar melhor, imagine que, no endereço 0, eu gravei um valor de float com o comando put. Como a variável float ocupa 32 bits (4 bytes), os endereços 0, 1, 2 e 3 ficarão todos ocupados para armazenar a float. Sendo assim, o comando get irá ler o valor dos endereços 0, 1, 2 e 3. Veja o exemplo:
1 2 3 4 5 6 7 | ... float teste = 0; EEPROM.get(0, teste); ... |
No código acima, criei uma variável float chamada “teste” e armazenei a leitura da função get, que recebeu o endereço 0 e a própria variável “teste”.
Propriedades
Além dos comandos anteriores, existem alguns que podem te ajudar na hora de desenvolver o seu código:
- EEPROM.length()
Este comando retorna o tamanho da EEPROM. No caso do UNO sabemos que é 1024 (0 a 1023), mas saber isso por meio da programação pode ser útil.
- sizeof(variável);
O comando acima é da própria linguagem C. Ele retorna o números de bytes ocupados pelo tipo de variável introduzida no parâmetro do comando. Normalmente os valores são conhecidos, mas talvez seja necessário fazer isso de forma programática.
Exemplo limpando e exibindo valores de memória
Limpar os valores de memória da EEPROM pode ser útil em alguns casos, embora não seja recomendado. Enfim, para exemplificar alguns comandos mostrados acima, mostrarei um código que adaptei da página do Arduino. O código abaixo limpa os valores de cada byte da memória EEPROM, mas antes exibe o seu valor anteriormente armazenado no monitor serial.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /* * Referência: https://www.arduino.cc/en/Tutorial/EEPROMClear */ // Inclui a biblioteca dos comandos de EEPROM #include <EEPROM.h> void setup() { // Inicia a comunicação serial Serial.begin(9600); // Cria um delay para dar tempo da comunicação serial iniciar delay(1000); // Cria uma iteração de 0 até o valor limite de memória da EEPROM (no caso do UNO até 1024) for (int i = 0 ; i < EEPROM.length() ; i++) { // A cada iteração, o valor de i serve para definir o endereço de escrita, ou seja, os 1024 endereços são acessados // Antes de limpar o valor do byte, escreve o valor previamente armazenado Serial.println(EEPROM.read(i)); // Para cada endereço 'i', coloca o valor do byte em 0 EEPROM.write(i, 0); } } void loop() { } |
Enfim, com tudo o que foi mostrado, já é possível criar aplicações úteis que gravam dados na memória para acessá-los ou para não perdê-los após um desligamento do Arduino.
EEPROM[] – Não é exatamente uma função, mas um operador que permite usar a EEPROM como uma matriz.
Olá, Dragon. Não cheguei a citar este termo, apenas mostrei as funções da classe “EEPROMClass” presentes na biblioteca EEPROM.h do Arduino.
Cara parabéns pelo posto me ajudou muito era exatamente oque está procurando na Net a dias
Que ótimo, Wager! Obrigado e fico feliz que tenha te ajudado.
Fábio maravilhoso esse poste e estar mim a judando bastante muito o brigado
Por nada, Jordeci! Eu é que agradeço pelo comentário!
Ajudou muito. Obrigado!
Que ótimo, João! Eu é que agradeço pelo comentário!
Parabéns Fábio, todos os artigos por você publicados são claros, objetivos e sem enrolação. A maiorias dos POST que encontro por aí, parece que o autor somente quer mostrar que é o tal e que na realidade não quer passar o conhecimento.
Muito bom! Parabéns.
Caramba, Jose, muitíssimo obrigado pelo comentário! Um dos motivos de criar o site foi justamente ir contra estes posts ruins que você comentou. Fico feliz de saber que estou na direção certa. Abraço!
Cara muito obrigado conteúdo excelente simples prático e objetivo
Que bom que gostou, Jordeci! Muito obrigado pelo feedback!
Artigo muito bom, esclareceu muitas coisas.
Porém, há um pequeno erro na parte sobre a função ‘EEPROM.get( )’: o 2º parâmetro dela não é “uma variável qualquer” mas sim “a variável que receberá o valor lido da EEPROM”. Por isso, no código de exemplo dela não faz sentido você colocar “teste = EEPROM.get(0, teste);”, pois desta forma você está armazenando 2x o mesmo valor dentro da variável ‘teste’. Bastaria ter colocado “EEPROM.get(0, teste);”. Apenas a função “EEPROM.read( )” é que necessita de uma variável fora dela para receber o valor lido.
Muito obrigado, Rodrigo, pelo elogio e por avisar sobre o erro. Realmente, comi barriga neste ponto, mas fiz a correção. Abraço.
Obrigado por compartilhar!!
Eu que agradeço pelo comentário, Wilson!