Passing a structure to class

About 12 months ago I read an article that provided a neat way to deal with EEPROM (I’d provide the link, but it appears as though its no longer available).

We create a structure (shown in the code extract as EEPROMSTORAGE). Within the structure, we can create a set of members with all sorts of different data types.

When the class is instatiated (statement “G2V_EEMgr eeprom” in void setup()), EEPROM contents are read into a UINT8_T array called buffer that is sized according to the size of the structure EEPROMSTORAGE.

A pointer called store is created, and the contents of the EEPROM become accessible as part of the structure (eeprom.store->EE_INIT_FLAG as an example).

#include <EEPROM.h>

typedef struct EEPROM_T   /* EEPROM Storage Structure */
{
  uint8_t         EE_INIT_FLAG;
  boolean         EE_DEBUG;      
  //types         //variables
} EEPROMSTORAGE ;

// a class to manage the struct EEPROMSTORAGE
class G2V_EEMgr {
  private:
    //  the actual eeprom gets stored here
    //  the struct is overlayed onto this storage
    //  uint8_t because "buffer" is an array, with each element represented 1 byte of EEPROM storage.
    uint8_t     buffer[sizeof(EEPROMSTORAGE)];

  public:
    G2V_EEMgr();   //constructor - reads the eeprom
    void        write();  //a function to write to the eeprom
    void        update(); //a function to update the eeprom

    //this pointer makes the buffer's contents easily accessed
    EEPROMSTORAGE * store;
};

//read the eeprom into the buffer
G2V_EEMgr::G2V_EEMgr() {
  
  for (int i = 0; i < sizeof(EEPROMSTORAGE); i++ ) {
    buffer[i] = EEPROM.read(i);
  }
  
  //overlay the pointer to the storage onto the buffer
  store = (EEPROMSTORAGE *) buffer;

  return; 
}

//write the buffer into the eeprom
//be careful writing too often to the eeprom. It only has 100,000 writes
void G2V_EEMgr::write() {
  for (int i = 0; i < sizeof(EEPROMSTORAGE); i++ ) {
    EEPROM.write(i, buffer[i]);
  }
  return; 
}

//use "update" the eeprom (only write when data is different).
void G2V_EEMgr::update() {
  for (int i = 0; i < sizeof(EEPROMSTORAGE); i++ ) {
    EEPROM.update(i, buffer[i]);
  }
  return; 
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(3000); //arbitrary delay for debugging.
  Serial.print("Cpp version:");
  Serial.println(__cplusplus);

  G2V_EEMgr eeprom; 

  Serial.println(eeprom.store->EE_INIT_FLAG, HEX); 

}
void loop() {
  // put your main code here, to run repeatedly:

}

And the output: Note that I am working on an arduino that has already had the EEPROM initialised.

14:58:29.492 -> Cpp version:201103
14:58:29.492 -> 55

I have a number of projects that use EEPROM quite heavily, so I created my own library to include in projects as I see fit. At the moment, the class depends on a structure being created in the main sketch called EEPROMSTORAGE - and works quite happily.

I would prefer to abstract that in the class by passing the struct (of any name) by reference … but I seem to fall at the first (well … every) hurdle.

My goal is to either
(1) pass the structure name (EEPROMSTORAGE in this example) by reference from the main sketch.
(2) pass the structure type (EEPROM_T in this example) from the main sketch and then create the EEPROMSTORAGE object in the library.

Can anyone advise ?

Many thanks,

Glenn.

This seems to work. I would add some code to hash the struct address and size to see if the contents of the EEPROM matches before loading whatever is in EEPROM into the struct.

#include <EEPROM.h>


/* EEPROM Storage Structure */
struct {
  uint8_t         EE_INIT_FLAG;
  boolean         EE_DEBUG;
  //types         //variables
} MyVariables;


// a class to manage a struct in EEPROM
class G2V_EEMgr
{
  private:
    // The pointer to the struct being mirrored in EEPROM and the size of the struct.
    const void *data;
    const size_t size;


  public:
    G2V_EEMgr(void *data, size_t size);   //constructor - reads the eeprom
    void        write();  //a function to write to the eeprom
    void        update(); //a function to update the eeprom
};


//read the eeprom into the buffer
G2V_EEMgr::G2V_EEMgr(void *_data, size_t _size) : data(_data), size(_size)
{
  // Fill the passed structure from EEPROM
  for (size_t i = 0; i < size; i++ )
  {
    ((byte *)data)[i] = EEPROM.read(i);
  }
}


// write the buffer into the eeprom
// be careful writing too often to the eeprom. It only has 100,000 writes
void G2V_EEMgr::write()
{
  for (size_t i = 0; i < size; i++ )
  {
    EEPROM.write(i, ((byte *)data)[i]);
  }
}


// use "update" the eeprom (only write when data is different).
void G2V_EEMgr::update()
{
  for (size_t i = 0; i < size; i++ )
  {
    EEPROM.update(i, ((byte *)data)[i]);
  }
}


void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);


  G2V_EEMgr eeprom(&MyVariables, sizeof MyVariables);


  Serial.println(MyVariables.EE_INIT_FLAG, HEX);
}


void loop()
{
  // put your main code here, to run repeatedly:
}

If you would like to go OOP, consider the following code:

#include <EEPROM.h>


struct EEPROMBase
{
   //hold the start address in EEPROM
  uint16_t start_addr;

  //hold the end address in EEPROM
  uint16_t end_addr;

  //initialize data in struct
  virtual void init(uint16_t in_start, uint16_t in_end=0)
  {
    start_addr = in_start;
    end_addr = in_end;
    if (in_end == 0)
    {
      end_addr = start_addr + get_storage_size();
    }
    read_from_EEPROM();
  }

  //override this function to return the actual size of the struct.
  //note: this function is a ultility function to determine the "end_addr" value.
  //      if you explicitly set value of "end_addr", this function can return 0;
  virtual uint8_t get_storage_size() = 0;
  
  virtual void read_from_EEPROM() = 0;
  virtual void write_to_EEPROM() = 0;
  virtual void update_to_EEPROM() = 0;
};


struct EEPROM_StorageA : public EEPROMBase
{
  uint8_t         EE_INIT_FLAG;
  boolean         EE_DEBUG;   
  virtual uint8_t get_storage_size() override
  {
    return sizeof(EE_INIT_FLAG) + sizeof(EE_DEBUG);
  }

  virtual void read_from_EEPROM() override
  {
    EE_INIT_FLAG = EEPROM.read(start_addr);
    EE_DEBUG = EEPROM.read(start_addr + 1);
  }

  virtual void write_to_EEPROM() override
  {
    EEPROM.write(start_addr, EE_INIT_FLAG);
    EEPROM.write(start_addr + 1, EE_DEBUG);
  }

  virtual void update_to_EEPROM() override
  {
    EEPROM.update(start_addr, EE_INIT_FLAG);
    EEPROM.update(start_addr + 1, EE_DEBUG);
  }
};


/*
  New struct that reuse EEPROM_StorageB
  This struct will have 3 fields
    EE_INIT_FLAG  (from EEPROM_StorageA)
    EE_DEBUG      (from EEPROM_StorageA)
    EXTRA_FLAG
*/
struct EEPROM_StorageB : public EEPROM_StorageA
{
  uint16_t EXTRA_FLAG;
  virtual uint8_t get_storage_size() override
  {
    return EEPROM_StorageA::get_storage_size() + sizeof(EXTRA_FLAG);
  }

  virtual void read_from_EEPROM() override
  {
    //call to fill EE_INIT_FLAG && EE_DEBUG
    EEPROM_StorageA::read_from_EEPROM();

    //read EX_FLAG from EEPROM here
    //note: EXTRA_FLAG is 2 bytes in size (uint16_t)
    
  }

  virtual void write_to_EEPROM() override
  {
    //call to write EE_INIT_FLAG && EE_DEBUG
    EEPROM_StorageA::write_to_EEPROM();
    
    //write EXTRA_FLAG to EEPROM here
    
  }

  virtual void update_to_EEPROM() override
  {
    //call to update EE_INIT_FLAG && EE_DEBUG
    EEPROM_StorageA::update_to_EEPROM();
    
    //update EXTRA_FLAG to EEPROM here
    
  }
};

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(3000); //arbitrary delay for debugging.
  Serial.print("Cpp version:");
  Serial.println(__cplusplus);

  EEPROM_StorageA StorageA;
  StorageA.init(0);

  EEPROM_StorageB StorageB;
  StorageB.init(StorageA.end_addr);
  
  Serial.println(StorageA.EE_INIT_FLAG, HEX);

}
void loop() {
  // put your main code here, to run repeatedly:

}

Then for each new storage type with new storage data, you can explicit handle how data is read/write from/to EEPROM by overriding those virtual functions.
I removed the “G2V_EEMgr” since it looks like this class is an unnecessary wrapper for the struct.

@arduino_new - thanks! I need to study your solution a little more I think.

@johnwasser - thanks also! Looks to do exactly as I had hoped.

Can I ask you to elaborate on "I would add some code to hash the struct address and size to see if the contents of the EEPROM matches before loading whatever is in EEPROM into the struct."

FWIW - the source of truth is the contents of EEPROM. The structure just provides me an easy way to handle it.

When a fresh arduino is used, the EEPROM is primed with a set of defaults through an initialisation routine.

The EE_INIT_FLAG is a magic value that I test against to work out of EEPROM needs to be initialised.

Many thanks again !

Glenn.

bobandverns:
Can I ask you to elaborate on "I would add some code to hash the struct address and size to see if the contents of the EEPROM matches before loading whatever is in EEPROM into the struct."

FWIW - the source of truth is the contents of EEPROM. The structure just provides me an easy way to handle it.

When a fresh arduino is used, the EEPROM is primed with a set of defaults through an initialisation routine.

The EE_INIT_FLAG is a magic value that I test against to work out of EEPROM needs to be initialised.

You'll be fine as long as you change the value that you expect in EE_INIT_FLAG any time you change the definition of the struct.