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.
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 towrite()
, 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 ourclearEEPROM()
function instead of theif
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);
- Example:
put()
lets you store other things than only single bytes, such asfloat
,int
orstruct
variables. Works likewrite()
. If you’re incrementing the address when sequencially storing data, you need to write something likeaddress += sizeof(float);
forfloat
variables, instead of justaddress++;
.- Example:
put(address, my_int_variable);
- Example:
get()
lets you read datatypes likeint
,float
orstruct
variables from the EEPROM. This function takes one more parameter thanread()
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);
- Example:
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];
- Write example:
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.