On an Arduino or other AVR, EEPROM access is a bit fiddly if you want to store different types of data. In this blog post, I’ll show you a quick trick to use when you have lots of structured data to store in EEPROM.

Alternatives

First, the existing alternatives:

  • Arduino has EEPROM, which is simple but it only reads/writes single bytes.
  • Arduino Playground has two templated functions that let you read/write anything. However, you need to know the offset of whatever you are accessing. Also, I find C++ templates a bit icky for this use case.
  • One level deeper, avr-libc has a more complex API for other kinds of integers, and buffers. However, you still need to remember offsets & sizes.
  • The EEMEM keyword works like PROGMEM to let you flag things as stored in the EEPROM. Which is nice, but you still need to pass size and offset whenever you read a value.

Technique

This technique uses a single ‘struct’ to represent the entire contents of your EEPROM, so you can then use macros to read and write fields.

You define a struct called __eeprom_data that describes your EEPROM:

struct __eeprom_data {
  int first;
  int second[64]; 
  boolean third;
  char fourth[buf_len];
}

Then use macros eeprom_read & eeprom_write to read and write each field:

int x;
eeprom_write(-7, first);
eeprom_read(x, first);

Full Example


/*
 * Copy and paste this block of #include & #defines into your code to use
 * this technique.
 *
 * (Don't worry too much about reading the macros, read through the
 * examples below instead.)
 */

#include <avr/eeprom.h>
#define eeprom_read_to(dst_p, eeprom_field, dst_size) eeprom_read_block(dst_p, (void *)offsetof(__eeprom_data, eeprom_field), MIN(dst_size, sizeof((__eeprom_data*)0)->eeprom_field))
#define eeprom_read(dst, eeprom_field) eeprom_read_to(&dst, eeprom_field, sizeof(dst))
#define eeprom_write_from(src_p, eeprom_field, src_size) eeprom_write_block(src_p, (void *)offsetof(__eeprom_data, eeprom_field), MIN(src_size, sizeof((__eeprom_data*)0)->eeprom_field))
#define eeprom_write(src, eeprom_field) { typeof(src) x = src; eeprom_write_from(&x, eeprom_field, sizeof(x)); }
#define MIN(x,y) ( x > y ? y : x )

const int buflen = 32;

/*
 * __eeprom_data is the magic name that maps all of the data we are 
 * storing in our EEPROM
 */
struct __eeprom_data {
  int first;
  int second; 
  boolean third;
  char fourth[buflen];
  char fifth[buflen];
};


void setup()
{
   Serial.begin(57600);

   /*
    * Writing simple variables to the EEPROM becomes simple
    *
    * First argument is the value to write, second argument is which field
    * (in __eeprom_data) to write to.
    */
   int q = 132;
   eeprom_write(q, first);
   eeprom_write(5958, second);
   eeprom_write(false, third);
   eeprom_write("Hello from EEPROM!", fourth);

   /*
    * You can even write from a pointer address if need be
    *
    * First argument is the pointer to write from.
    * Second argument is the field (in __eeprom_data) 
    * to write to.
    * Third argument is the buffer length
    */
    const char * buf = "Another hello looks like this";
    eeprom_write_from(buf, fifth, strlen(buf)+1);


    int a, b;
    boolean c;
    char d[buflen], e[buflen];
    char *e_p = e;
    
    /*
     * Reading back is just as simple. First argument is the variable to read
     * back to, the second argument is the field (in __eeprom_data) to read
     * from.
     */
    eeprom_read(a, first);
    eeprom_read(b, second);
    eeprom_read(c, third);
    eeprom_read(d, fourth);
    
    /*
     * You can read back to a pointer address, if you need to.
     */
    eeprom_read_to(e_p, fifth, buflen);
    

    Serial.println(a);
    Serial.println(b);
    Serial.println(c ? "TRUE" : "FALSE");
    Serial.println(d);
    Serial.println(e_p);
    
    /*
     * The eeprom_write macros do bounds checking, 
     * so you can't overrun a buffer.
     *
     * In __eeprom_data, 'third' is a one-byte boolean, but 
     * eeprom_write knows this so only the first char 'T' is written
     * to EEPROM
     */
    eeprom_write("This is a buffer overflow", third);
    
    /*
     * If you have an array, like char[], you can write & read a single
     * array entry from a particular constant index
     *
     * Unfortunately, it only works for constant indexes not variables.
     * eeprom_write('X', fourth[x]) does not work with these macros.
     */
    eeprom_write('X', fourth[3]);
    eeprom_read(d, fourth);
    char x;
    eeprom_read(x, fourth[3]);
    Serial.println(d);
    Serial.println(x);
}

void loop() { }

(This is Arduino code, obviously if you’re using avr-libc directly then you can rewrite it for that.)

Downsides

The downsides of this technique (as I see them) are:

  • Uses macro magic (so a bit icky.)
  • Overkill if you only need to store one type of data in EEPROM, but useful if you have lots of different types.

Initial Values

When you start up, you need to know if the data in EEPROM is data that your program saved, or something else. There are a few different ways to deal with this.

EEMEM

EEMEM provides a way for you to set default values easily in your code:

EEMEM struct __eeprom_data initial_data EEMEM = { 
  1, // first
  2, // second
  false, // third
  "Initial fourth", // fourth
   "Initial fifth" // fifth
};

Via avr-objcopy & avrdude you can generate a .eep file and flash it to the AVR. This is nice, but it won’t work if you’re using the Arduino IDE because (as of version 0018) it doesn’t generate .eep files properly, and it also doesn’t support flashing them.

It’s a good option if you’re using your own Makefile, though.

“Magic” number

The other way is to check for and expect a “magic” value somewhere in the EEPROM data. Something like:

// Change this any time the EEPROM content changes
const long magic_number = 0x5432;

struct __eeprom_data {
  long magic; // should be set to magic_number
  int first;
  int second; 
}

void setup() {
  long magic;
  eeprom_read(magic, magic);
  if(magic != magic_number)
    initialise_eeprom();
}

void initialise_eeprom() {
  eeprom_write(0, first);
  eeprom_write(0, second);
  eeprom_write(magic_number, magic);
}

4 thoughts on “Structured EEPROM access with Arduino/AVRs

  1. Hi 13oR,

    So this is a bit tricky because, as I said above, one of the bigger limitations of this technique is that you can’t do dynamic access to the EEPROM using a variable (ie array indexing).

    You haven’t said exactly what datatype you want to save, but if it’s a small array (ie 5 integers) then you can load/save the entire array from EEPROM in one hit. This means holding the entire array in RAM, so it won’t work if
    it’s a lot of data.

    This code compiles but I haven’t tested it: http://gist.github.com/499528

    However, if you only need to load/store 5 of the same datatype, and nothing else, then you may really be better off using one of the other techniques described at the top of the post.

    Hth.

  2. hi Gus, first, thank you for your quick reply.
    Yes you are right i don’t describe properly.
    I’m using keypad library with arduino but in standalone mode with battery (not connected to computer).
    The diffents arrays of 5 digits depends of entries made on the keypad and each arrays must be store in the built-in eeprom after a special key is pressed(“#” or “*”). Data is retained on eeprom for later use. After, i will need to connect the arduino on computer by usb to upload data from eeprom. I think there is need to use processing or another way but i don’t know how exactly coz i’m a newbie coder.
    This is an example of the keypad sketch but it is to use with serial output on computer and the problem is to transform it to use it as standalone mode.
    ——————————————————————–
    // EventSerialKeypad.pde

    #include

    const byte ROWS = 4; //four rows
    const byte COLS = 4; //four columns
    char keys[ROWS][COLS] = {
    {‘1′,’2′,’3′,’A’},
    {‘4′,’5′,’6′,’B’},
    {‘7′,’8′,’9′,’C’},
    {‘#’,’0′,’*’,’D’}
    };
    byte rowPins[ROWS] = {2,3,4,5}; //connect to the row pinouts of the keypad
    byte colPins[COLS] = {6,7,8,9}; //connect to the column pinouts of the keypad

    Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
    byte ledPin = 13;

    boolean blink = false;

    void setup(){
    Serial.begin(9600);
    pinMode(ledPin, OUTPUT); // sets the digital pin as output
    digitalWrite(ledPin, HIGH); // sets the LED on
    keypad.addEventListener(keypadEvent); //add an event listener for this keypad
    }

    void loop(){
    char key = keypad.getKey();

    if (key != NO_KEY) {
    Serial.println(key);
    }
    if (blink){
    digitalWrite(ledPin,!digitalRead(ledPin));
    delay(100);
    }
    }

    //take care of some special events
    void keypadEvent(KeypadEvent key){
    switch (keypad.getState()){
    case PRESSED:
    switch (key){
    case ‘#’: digitalWrite(ledPin,!digitalRead(ledPin)); break;
    case ‘*’:
    digitalWrite(ledPin,!digitalRead(ledPin));
    break;
    }
    break;
    case RELEASED:
    switch (key){
    case ‘*’:
    digitalWrite(ledPin,!digitalRead(ledPin));
    blink = false;
    break;
    }
    break;
    case HOLD:
    switch (key){
    case ‘*’: blink = true; break;
    }
    break;
    }
    }
    ———————————————————————

  3. Hi Gus.

    Can I use it for any type of data that supported by Arduino ?
    Your example only use : int, char, boolean.
    What about “byte” ?
    What about array of the same data type (i.e array of bytes) ?

    Well , actualy i’m looking of a way to store :
    – 3 ip address
    – 1 MAC address
    – 1 hostname
    – 1 port number

    Sincerely
    -bino-

Leave a Reply

Your email address will not be published. Required fields are marked *