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).

CI de memória EEPROM c/ comunicação I2C.
Fonte: Wikipedia

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:

Estrutura da memória EEPROM

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.