Language: EN

guardar-variables-entre-reinicios-con-arduino-y-la-memoria-no-volatil-eeprom

Save variables in Arduino and non-volatile EEPROM memory

At this point, we should already know that when restarting Arduino, all the memory where we store variables is erased, so it is not possible to retain the values.

However, sometimes we want certain values to persist across restarts. For example, calibration values, measurements, and dates or times for making data loggers, saving a counter, or knowing what the state of the processor was when it lost power, among many others.

For such purposes, Arduino incorporates non-volatile storage memory called EEPROM (Electrically Erasable Programmable Read-Only Memory).

Therefore, on Arduino, we have three types of memory:

  • FLASH, non-volatile, where we store the sketch (including the bootloader).
  • SRAM (static random access memory), volatile, where variables are stored during operation.
  • EEPROM, non-volatile, which we can use to store information between restarts.

Characteristics of EEPROM

The EEPROM memory has its own characteristics and peculiarities that distinguish it from the rest of the memories. First, and most evident, it is non-volatile (that is, it retains the stored values when power is lost).

On the other hand, EEPROM memory is a more limited resource than other memories. Most Arduino models have 1KB, while the Mega has 4KB.

A disadvantage of EEPROM memory is that it is much slower than SRAM memory. The process of writing to a cell (byte) takes about 3.3 ms. The reading process is much faster (although still slower than SRAM); reading 1024 bytes takes about 0.3 ms, meaning it is 10,000 times faster than writing.

Another peculiarity of EEPROM memory is that it has a limited lifespan, which decreases with each write operation. There are no limits on read operations.

Specifications guarantee that each cell has a lifespan of at least 100,000. However, in practice, it can be much higher, up to a million operations; above 100,000, the operation is not guaranteed.

100,000 read operations may seem like a lot, but keep in mind that they are

  • Just 5 minutes if, by mistake, we constantly write.
  • Approximately one day if we write every second.
  • About 27 years if we write 10 times a day.

That is, EEPROM memory is designed for writes with long intervals between them, not for constant use.

Functions to use EEPROM

The Standard Arduino IDE incorporates a library EEPROM.h that includes the necessary functions to manipulate Arduino’s non-volatile memory.

The simplest functions are the Read and Write functions that, respectively, read and write a byte at a memory address.

The memory address can have values from 0 to N-1, where N is the number of available bytes (for example, 0 to 1023 on Arduino Uno and Nano, 0 to 4095 on Arduino Mega).

//Reads a single byte from the address
EEPROM.Read(address) 

//Writes a single byte to the address
EEPROM.Write(address)

The previous functions write a single byte, but often we will need to store variables larger than a byte. For this, we have the Put, Get, and Update functions, which we will use more frequently.

// Functions for complete variables (takes the size of the variable into account)
//Reads a variable at the address
EEPROM.Put(address, variable) 

//Reads a variable at the address
EEPROM.Get(address, variable) 

EEPROM.Update(address, variable) //Updates the value of a variable, that is, it first reads it, and only writes it if its value is different from the one we are going to store. This helps reduce the number of writes and extend the lifespan of the memory.

The Put, Get, and Update functions take into account the size of the variable and work even with variables and structures defined by us. However, we will need to keep track of the variable size to know the next address to write to, avoiding “overlapping” variables.

Code Examples

Iterate through the entire EEPROM

In the first example, we use the EEPROM library to write 0 to the entire memory. Generally, we will not use this type of code because it involves a write operation to the entire memory, which has a limited write cycle.

But the goal is to present the use of the function EEPROM.length() to obtain the amount of available memory (regardless of the model) and the use of EEPROM[index] to access memory positions.

#include <EEPROM.h>

void setup()
{
  for( int index = 0 ; index < EEPROM.length() ; index++ )
  {
    EEPROM[ index ] = 0;
  } 
} 

void loop()
{
}

Write a variable to EEPROM

In this example, we perform the writing of a variable. In the example, we write a float variable, but it could be any other type of variable. We use a ReadSensor() function that emulates a function that could, for example, read a temperature sensor, a power sensor, or any other type of sensor.

Notice the use of the sizeof() and length() functions to obtain the position of the next cell to write.

#include <EEPROM.h>

float sensorValue;
int eeAddress = 0;

//Function that simulates reading a sensor
float ReadSensor()
{
  return 10.0f;
}

void setup()
{
}

void loop()
{
  sensorValue = ReadSensor(); //Simulated sensor reading
  EEPROM.put( eeAddress, sensorValue );  //Write the value
  eeAddress += sizeof(float);  //Get the next position to write
  if(eeAddress >= EEPROM.length()) eeAddress = 0;  //Check for overflow

  delay(30000); //wait 30 seconds
}

Write a structure to EEPROM

In C++ and, therefore, in Arduino, we can define our own types of variables and structures. The EEPROM functions also work with these custom variables.

In this example, we write a structure, in a similar way to any other variable.

#include <EEPROM.h>

struct MyStruct{
  float field1;
  byte field2;
  char name[10];
};

void setup()
{
  int eeAddress = 0;
  MyStruct customVar = {
    3.14f,
    65,
    "Working!"
  };

  eeAddress += sizeof(MyStruct);
  
  EEPROM.put( eeAddress, customVar ); 
}

void loop()
{
}

Use Update to update values

As we have mentioned, the Update() function checks before writing the existing value in memory, and only writes if the value is different from the stored one.

This results in a slight loss of performance (slight, because the read operation is much faster than the write operation) but helps extend the lifespan of the memory.

In the following example, we write the analog reading from A0 to EEPROM.

#include <EEPROM.h>

int eeAddress = 0;

void setup()
{
}

void loop()
{
   int val = analogRead(0) / 4;
   EEPROM.update(eeAddress, val);
  
  eeAddress += sizeof(int);
  if(eeAddress == EEPROM.length()) eeAddress = 0;

  delay(10000);  //Wait for 10 seconds
}

Read variables with Get

Finally, we need to retrieve the stored variables, for which we will use the Get() function. Of course, we will need to know the address where the variable is stored, as well as its type.

The following example reads a float variable and a sample structure, which we should have previously stored in EEPROM memory.

#include <EEPROM.h>

struct MyStruct{
  float field1;
  byte field2;
  char name[10];
};

void setup(){
  
  float f;
  int eeAddress = 0; //EEPROM address to start reading from    
  EEPROM.get( eeAddress, f );
  Serial.print( "Float read: " );
  Serial.println( f, 3 );  
 
  eeAddress += sizeof(float);

  MyStruct customVar;
  EEPROM.get( eeAddress, customVar );  
  Serial.println( "Structure read: " );
  Serial.println( customVar.field1 );
  Serial.println( customVar.field2 );
  Serial.println( customVar.name );
}

void loop()
{
}

Download the code

All the code from this entry is available for download on Github. github-full