Using EEPROM to Store Data on the Arduino

When collecting or generating data and storing it on a microcontroller, like the one on an Arduino, the data will just be available while the microcontroller is powered. As soon as you pull the plug and the microcontroller loses power, this data will be erased. This is what we call volatile memory. This work in the same way as the RAM in your PC.

In this blogpost we’ll show you how to store this kind of data on the Arduino so that it doesn’t get erased, just like you would store data on the SSD or HDD on your PC.

Our setup for demonstrating the Arduino EEPROM

EEPROM on Arduino

EEPROM stands for Electrically Erasable Programmable Read-Only Memory. The microcontrollers used on most of the Arduino boards have either 512, 1024 or 4096 bytes of EEPROM memory built into the chip. This memory is non-volatile, which means that the data doesn’t get erased when the board loses power.

You can look at the EEPROM on Arduino as an array where each element is one byte. When reading from and writing to this memory, you specify an address which in the Arduino world is equivalent to an array index.

EEPROM has a total lifetime of ~100,000 write cycles. Be careful when writing code so that you don’t write to EEPROM too often! Remember that erasing memory also is a writing operation.

Example

We’ll show you how to use a couple of the built-in Arduino functions in the example below. This program reads a temperature sensor and stores the value in the EEPROM once every two seconds. One button prints the non-empty part of the EEPROM over serial, while another button erases the EEPROM.

We have written two relevant blogposts earlier where we discuss Arduino with buttons and temperature sensors, respectively:

These can be handy to take a look at if you find it difficult to take all of this in.

Now, onwards to the example:

#include <EEPROM.h>

#define SAMPLE_TIME 2000  //The time between each EEPROM write function call in ms

int tempPin = 0;      //the ADC pin
int printPin = 2;     //the print button pin
int erasePin = 4;    //the erase button pin

int address = 0;      //EEPROM address counter

unsigned long timer;

float conv_coeff = 0.0;   //coefficient for converting from 0-1024 to 0-5 range

void printTemp();
void clearEEPROM();
void writeTemp();

void setup(){
  Serial.begin(115200);     //start the serial connection as always
  conv_coeff = 5.0/1024.0;  //find the coefficient to do the conversion
  timer = millis();         //millis() returns the time since program start in ms
}

void loop(){
  if(millis()-timer > SAMPLE_TIME)  //check if it's time to do a temp sensor sample
  {
	writeTemp();
	timer = millis();
  }

  if(!digitalRead(printPin))  //check if print button is pressed
  {
	printTemp();
	delay(500);
  }

  if(!digitalRead(erasePin)) //check if erase button is pressed
  {
	clearEEPROM();
	delay(500);
  }
  
  delay(1);
}

void printTemp()
{
  for (int i = 0 ; i < EEPROM.length() ; i++) {
	byte value = EEPROM.read(i);                //read EEPROM data at address i
	if(value != 0)                              //skip "empty" addresses
	{
	  float temp = value*conv_coeff;            //convert ADC values to temperature
	  temp = (temp - 0.5)*100;                  //take care of the offset

	  Serial.println(temp);
	}
  }
}

void clearEEPROM()
{
  for (int i = 0 ; i < EEPROM.length() ; i++) {
	if(EEPROM.read(i) != 0)                     //skip already "empty" addresses
	{
	  EEPROM.write(i, 0);                       //write 0 to address i
	}
  }
  Serial.println("EEPROM erased");
  address = 0;                                  //reset address counter
}

void writeTemp()
{
  byte value = analogRead(tempPin);		//read sensor value
  
  EEPROM.write(address, value);			//write value to current address counter address

  Serial.print("Sensor value stored at address ");
  Serial.println(address);
  
  address++;                      //increment address counter
  if(address == EEPROM.length())  //check if address counter has reached the end of EEPROM
  {
	address = 0;              //if yes: reset address counter
  }
}

The two functions of interest here are EEPROM.read() and EEPROM.write(). These are pretty self-explanatory. The former takes one parameter in the form of an int which is the address of the byte you want to read. The function returns the data on the address specified.

The EEPROM.write() function takes an int and a so called byte datatype (aka. uint8_t) as parameters. The latter parameter is the actual data you want to store. If you want to store more than integer values between 0-255 you need to use several addresses for each write and read or you can use some of the functions described in the next chapter. However, this will limit your memory capacity and in this example we’ve chosen to keep it simple and just store single bytes. The raw data from the ADC  has a range of 0-1024, but it typically stays between 150 and 200 for room temperature, so we just store the raw data in the EEPROM, assuming the temperature won’t get too high (you should really guard this so the doesn’t exceed 255). Instead of storing the actual floating point temperature values in the EEPROM we convert the raw data to understandable data in the printTemp() function.

One other “flaw” with this program is that every time you reset the microcontroller, the address counter starts from the beginning instead of where it was last time. This can be solved by storing the current address for instance at the start of the EEPROM and start writing actual data after that. The EEPROM size on the Arduino Uno is 1024 bytes, so in our case we would would need to use 2 bytes to store this metadata. Again, we wanted to keep this example as simple as possible, so we left this part out.

Other Arduino EEPROM Functions

  • update() is almost identical to write(), except that it only writes to the EEPROM if the data differs from the data already stored at the specified address. This is something that would’ve fit nicely in our clearEEPROM() function instead of the if statement. Using this function is handy since the EEPROM write cycle typically takes 3.3 ms to complete and due to the limited memory lifetime.
    • Example: update(address, my_byte_variable);
  • put() lets you store other things than only single bytes, such as float, int or struct variables. Works like write(). If you’re incrementing the address when sequencially storing data, you need to write something like address += sizeof(float); for float variables, instead of just address++;.
    • Example: put(address, my_int_variable);
  • get() lets you read datatypes like int, float or struct variables from the EEPROM. This function takes one more parameter than read() and that is the variable you’ll store the data read into. This way, the function knows how many bytes it will read out from the EEPROM.
    • Example: get(address, my_float_variable);
  • EEPROM[] is not exactly a function, but an operator which allows you to use the EEPROM just like an array.
    • Write example: EEPROM[0] = my_byte_variable;
    • Read example: my_byte_variable = EEPROM[0];

Closing Words

With Arduino, the built-in EEPROM is a handy way to store data permanently. The Arduino language has done it super easy to use, as demonstrated in the example above. However, be very careful that you don’t write too often to the EEPROM as it has a limited lifetime.

Related Posts