High-level templatized class library for built-in EEPROM data

Perhaps somebody has already done something similar, but in case this particular style is useful to someone...

Here is a library class I wrote and have been using for quite a while now. I use it to store and validate all my setup() and sketch constants in EEPROM. It's especially useful if/when you need to just tweak a single pin location or maintain differing data structures for different versions/layouts of your hardware.

More than just an wrapper, this class template lets you bundle and store multiple sizes/types of data and automatically allocates them in the EEPROM area. Data is validated on load, optionally reset from default values (stored in program space), and properly accessed to account for power-fail on write scenarios.

The code snippet below shows condensed usage, though for more detail please see the commented library example.

#include <Eep.h>

class EepromData {
    public:
    char h[7] = "hello";
    char w[7] = "world";
    uint32_t answer = 42; 
};

const EepromData defaults PROGMEM;

const uint8_t eepromVersion = 1;

typedef Eep::Eep<EepromData, eepromVersion> Eep_type;

Eep_type::Block eemem EEMEM;

EepromData* eepromData = NULL;

void setup(void) {
    Eep_type eep(defaults, &eemem);
    eepromData = eep.data();
    if (eepromData) {  // contents valid
        ...
    } else {  // contents reset to defaults
        ...
    }
}

Feedback and patches are welcome here or on the github repo..

Eep.h (11.8 KB)

keywords.txt (1.68 KB)

What does this do that EEPROMex doesn't?

MorganS:
What does this do that EEPROMex doesn't?

Eep is a higher-level interface for one. So it operates with an arbitrary chunk of data, instead of individual bits and bytes. However you choose to organize that chunk of data is your business. Secondly, it transparently validates the data on access, with the option to set default values when storage is uninitialized or corrupt. It provides format versioning, and protects against power-fail on write conditions (which can silently corrupt data). Finally, it simplifies addressing by allowing the tool-chain to perform allocations, so there is nearly zero possibility for overruns or accidental overlaps.

It's not a replacement for EEPromex, which appears to be an extended / enhanced flavour of the built-in EEPROM utilities. In some situations, this low-level of control is required, but for simple setup() constants, it's a more verbose interface to use (especially if you also need versioning, validation & defaults).

However, it does provide write-counting / limiting which is a handy feature, and perhaps something Eep could incorporate. The Eep class/library only uses the very basic avr/eeprom calls, so replacing those with EEPROMex under-the-hood wouldn't be very difficult at all. I'll consider doing that, and/or gladly accept pull requests if the code is solid.

Thanks for asking!

In case github is unavailable, or to cut down on tab-clutter, here's the annotated example code:

#include <Eep.h>

// Arbitrary class of data to persist w/in EEProm.
// Must be "standard layout" class, or have static initialized
// members.  The amount of eeprom space is the only limiting factor.
// There is only a small (5-byte) overhead per data structure, though
// this can be scaled down to as little as 2-bytes.
class EepromData {
    public:
    char h[7] = "hello";
    char w[7] = "world";
    uint32_t answer = 42;
};

const EepromData defaults PROGMEM;  // Used when contents are unset/invalid
                                    // stored in program-space.

// Increment version number whenever data format changes (above)
const uint8_t eepromVersion = 1;

// Make the customized type easier to reference from sketch
typedef Eep::Eep<EepromData, eepromVersion> Eep_type;

// Staticly allocate space in EEProm area w/ EEMEM macro from <avr/eeprom.h>
// This will cause HelloWorld.eep to be generated, and can be flashed using ISP
// programmer, or uploaded directly if using a mega or other *duino w/ eeprom
// upload support in bootloader (i.e. NOT optiboot).
Eep_type::Block eemem EEMEM;  // Allocate space in EEProm area.  NEVER dereference
                              // this address in sketch, "Bad Things Will Happen" (TM)

void setup(void) {
    Serial.begin(115200);
    delay(1);  // Serial client sometimes needs extra time to initialize
    Serial.println(F("\nSetup()"));

    // DO NOT USE eemem any other way! (address is relative to .eeprom section)
    Eep_type eep(defaults, &eemem);

    #ifdef EEPDEBUG
    // Serial.print() out contents 8 bytes at a time
    eep.dump();
    #endif //EEPDEBUG

    // EEPRom contents was loaded during initialization (above)
    // no need to use eep.load() again, just grab a static buffer reference.
    EepromData* data = eep.data();
    if (data) {  // NULL pointer signals load/reset failure
        Serial.println(F("EEPRom content is valid"));
        Serial.print(F("The data is: "));
        Serial.print(data->h);
        Serial.print(F(" "));
        Serial.print(data->w);
        Serial.print(F(" answer is: "));
        Serial.println(data->answer);
        // Since this sketch defined EepromData, it's
        // safe to modify static buffer directly.
        if (data->answer == 24) {
            data->answer = 42;
            memcpy(&data->h, "hello", 7);
            memcpy(&data->w, "world", 7);
            // eep.save() operates semi-atomicly, a power-loss during
            // save will cause defaults to be restored.
            if (eep.save())
                Serial.println(F("Data updated"));
            else
                Serial.println(F("Update failed"));
        } else {
            // Possible to save from local data instead of static buffer.
            EepromData newdata;
            newdata.answer = 24;
            memcpy(&newdata.h, "world", 7);
            memcpy(&newdata.w, "hello", 7);
            if (eep.save(&newdata))
                Serial.println(F("Data updated"));
            else
                Serial.println(F("Update failed"));
        }
    } else
        // Because defaults are automatically restored,
        // execution should never make it here.
        Serial.println(F("Eeprom content is invalid!"));
}

void loop(void) {
    // This alternate constructor will NOT restore defaults automaticaly.
    Eep_type eep(&eemem);  // DO NOT USE eemem any other way!

    #ifdef EEPDEBUG
    eep.dump();
    #endif //EEPDEBUG

    EepromData* data = eep.data();  // Grab static buffer reference
    if (data) {
        Serial.println(F("EEPRom content is still valid"));
        Serial.print(F("The data is: "));
        Serial.print(data->h);
        Serial.print(F(" "));
        Serial.print(data->w);
        Serial.print(F(" answer is: "));
        Serial.println(data->answer);
    } else
        // Alternate constructor can be used to detect power-loss during save
        // condition.
        Serial.println(F("Eeprom content is invalid!"));

    while (true)
        delay(1000);
}