Language: EN

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

Save variables in Arduino and non-volatile EEPROM memory

By now we should already know that when we restart Arduino, all the memory where we store the variables is erased, so it is not possible to preserve the values.

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

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

Therefore, in Arduino we have three types of memory

  • FLASH, non-volatile, where we upload 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 the EEPROM

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

On the other hand, EEPROM memory is a scarcer resource than the rest of 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 writing process of a cell (byte) costs around 3.3 ms. The reading process is much faster (although still slower than SRAM), reading 1024 bytes costs around 0.3ms, that is, 10,000 times faster than writing.

Another peculiarity of EEPROM memory is that it has a limited lifespan, which is reduced with each writing operation. There are no limits for reading operations.

The specifications guarantee that each cell has a lifespan of at least 100,000. Although 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 it should be noted that they are

  • Barely 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.

In other words, EEPROM memory is designed for writing with long intervals between them, not constant use of it.

Functions to use the EEPROM

The Standard Arduino IDE incorporates an EEPROM.h library 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 to 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 in Arduino Uno and Nano, 0 to 4095 in Arduino Mega).

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

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

The above functions store a single byte, but often we will need to store variables that are larger than a byte. For this, we have the Put, Get, and Update functions, which are the ones we will use most frequently.

// Functions for complete variables (takes into account the variable size)
//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, first it reads it, and only writes it if its value is different from the one we are going to save. This helps reduce the number of writes, and extend the life 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 have to take into account the size of the variable to know what the next address to write is, and avoid the variables “overlapping”.

Code examples

Iterate over the entire EEPROM

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

But the goal is to present the use of the EEPROM.length() function 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 the 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.

Note 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 the EEPROM

In C++ and, therefore, in Arduino, we can define our own variable types and structures. The EEPROM functions work equally well 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 writes only if the value is different from the stored one.

This represents a small performance loss (small, because the read operation is much faster than the write operation) but it helps to extend the life of the memory.

In the following example, the analog read A0 is written to the EEPROM.

#include <EEPROM.h>

int eeAddress = 0;

void setup()
{
}

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

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

Read variables with Get

Finally, we need to retrieve the saved 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 an example structure, which we must have previously saved in the 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 in this post is available for download on Github. github-full