Storing a struct into EPROM via put (ESP32)

Hello friends.

I would be happy if someone can explain me the following.

In my Project i can change different settings. To save them into my "Eprom" of the ESP32 i created a struct (for readability i minimalized the example). Then i store this struct into my eprom.

Example:

struct Eprom_Layout
{
    uint32_t eprom_Sonde;
    uint32_t eprom_Messbereich_MIN;
    uint32_t eprom_Messbereich_MAX;
};


Eprom_Layout epromLayout;



void b_Sondenwahl_SavePopCallback(void *ptr){

  uint32_t sonde_temp;
  uint32_t messbereich_min_temp;
  uint32_t messbereich_max_temp;

  n_Sondewahl_Sonde.getValueGlobal("Sondenwahl",&sonde_temp);
  n_Sondenwahl_Min.getValueGlobal("Sondenwahl",&messbereich_min_temp);
  n_Sondenwahl_Max.getValueGlobal("Sondenwahl",&messbereich_max_temp);

  settings.Sonde = sonde_temp;
  settings.Messbereich_MIN = messbereich_min_temp;
  settings.Messbereich_MAX = messbereich_max_temp;

  epromLayout.eprom_Sonde = settings.Sonde;
  epromLayout.eprom_Messbereich_MIN = settings.Messbereich_MIN;
  epromLayout.eprom_Messbereich_MAX = settings.Messbereich_MAX;

  EEPROM.put(0, epromLayout);                                   //put example data in EEPROM
  EEPROM.commit();
  
  }

It works quiet well. But what i do not understand is: epromLayout is an object of my struct. I save all my data into the this struct and save this whole struct into eprom via EEPROM.put(0, epromLayout);
The first parameter of the function eeprom.put() is the adress. Here in this case its the adress 0. How can fit the whole struct into one adress? i always thought that u only can store a number of 0-255 in each adress of the eprom.

Where am i wrong?

thank you

what do you need to be explained?

the temp variables are not necessary and you can assign a struct in one go, no need to go through each member

struct Eprom_Layout
{
    uint32_t eprom_Sonde;
    uint32_t eprom_Messbereich_MIN;
    uint32_t eprom_Messbereich_MAX;
} epromLayout;

void b_Sondenwahl_SavePopCallback(void *ptr) {
  n_Sondewahl_Sonde.getValueGlobal("Sondenwahl",& settings.Sonde);
  n_Sondenwahl_Min.getValueGlobal("Sondenwahl", & settings.Messbereich_MIN);
  n_Sondenwahl_Max.getValueGlobal("Sondenwahl", & settings.Messbereich_MAX);
  epromLayout = settings;
  EEPROM.put(0, epromLayout);                                   //put example data in EEPROM
  EEPROM.commit();
}

and not sure what's the need for epromLayout, just use settings

struct Eprom_Layout
{
    uint32_t eprom_Sonde;
    uint32_t eprom_Messbereich_MIN;
    uint32_t eprom_Messbereich_MAX;
};

void b_Sondenwahl_SavePopCallback(void *ptr) {
  n_Sondewahl_Sonde.getValueGlobal("Sondenwahl",& settings.Sonde);
  n_Sondenwahl_Min.getValueGlobal("Sondenwahl", & settings.Messbereich_MIN);
  n_Sondenwahl_Max.getValueGlobal("Sondenwahl", & settings.Messbereich_MAX);
  EEPROM.put(0, settings);                                   //put example data in EEPROM
  EEPROM.commit();
}

and you might want to consider __attribute__ ((packed)) for the structure if you want to optimise storage

It doesn't. The address that you give the put() function tells it where to start saving. Then it uses as many bytes of EEPROM as required to save the data

NOTE : you cannot, therefore, save one struct at address 0 and another at address 1, for instance

Thank you for the input.

So lets say i have a class DeviceSettings with an object settings:
It looks like this:

class DeviceSettings{

    private:

    public:
        uint32_t ProbeSelection;
        uint32_t MeasuringMin;
        uint32_t MeasuringMax;
        uint32_t  OtherVariable;
};

Back to my function:

void b_Sondenwahl_SavePopCallback(void *ptr){

  //here i get the values from my display and store them diretly into my classmembers
  n_Sondewahl_Sonde.getValue(&settings.ProbeSelection);
  n_Sondenwahl_Min.getValue(&settings.MeasuringMin);
  n_Sondenwahl_Max.getValue(&settings.MeasuringMax);

  //then i store the whole objects (and its classmembervalues into Eprom)
  EEPROM.put(0, settings); 
  EEPROM.commit();
}

So what i asume now is, that all membervariables of the class are now saved into the eprom.

When my device is now restarting i want that the membervariables of my class Devicesettings will be initialized with the stored values from the eprom:

  EEPROM.get(0, settings);
  EEPROM.commit();

is this enough? From my understanding no. Because where do my classmembers are getting the saved values?

Thank you

what is settings's type?

EEPROM.commit() is only needed when you write - that's because all the operations are done in RAM and transferred to the long term storage when you commit. reads are done directly from the RAM mirror.

its a object of the class Devicesettings

DeviceSettings settings;

did you share the full DeviceSettings description? no "deep" objects (ie something you would retain only the pointer to), only the four uint32_t ?

(when you write in EEPROM, only the memory representation of the variable is saved. So if you have a pointer, then it's a bit meaningless as the data pointed won't be saved and you'll get back a memory address pointing at random data)

no there are my more other membervariables of Devicesettings (strings, floats and so on), that will change over time, when changes of the device are done. (Via a save changes button)
i just shrinked it for understanding purpose

anything "flat" will do (including a fixed size char array) but if you use the String class that won't work as this is just a pointer to dynamic data

if the object becomes complex, then you need a serialize method that will walk the structure to create a suitable memory buffer representation that will be stored and then a deserialize that will do the opposite - read the memory and rebuild the object's data structure

ok thank you

So do i understand you correct, that i cant just simply use put and get? There are 3 char that have to be stored: for exapmle:

char eprom_lastService[40];
char eprom_nextService[40];
char eprom_lastCalibrcation[40];

And sorry. Still dont get it. Is this enough to get my data back from erprom

  EEPROM.get(0, settings);
  EEPROM.commit();

Sry for stupid questions

Thanks

No question is stupid. It is always good to try to understand.

if you use fixed length char arrays for the string, then you are fine. This is "flat data", the memory is allocated within the struct/class.

so that works

struct Eprom_Layout {
  uint32_t eprom_Sonde;
  uint32_t eprom_Messbereich_MIN;
  uint32_t eprom_Messbereich_MAX;
  char eprom_lastService[40];
  char eprom_nextService[40];
  char eprom_lastCalibrcation[40];
} epromLayout;

but that does not

struct Eprom_Layout {
  uint32_t eprom_Sonde;
  uint32_t eprom_Messbereich_MIN;
  uint32_t eprom_Messbereich_MAX;
  String eprom_lastService;
  String eprom_nextService;
  String eprom_lastCalibrcation;
} epromLayout;

Non flat is when the struct has a member pointing to something else that is elsewhere in memory (a pointer, a reference). Then saving the struct/class would end up writing the pointer's value in EEPROM, not the actual data that was pointed at/referred to.

Side note, seems your text strings are dates, they could be stored likely as a unit32_t as well (unix time for example).

I think i understand what u mean.

Do i really need a struct? I save all my data into my class Devicesettings ( DeviceSettings settings.

So when i save my data via:

  EEPROM.put(0, settings); 
  EEPROM.commit();

this should be fine as well or?

in C++ class and struct are the same thing

I always tend to have a struct in my classes if I want to archive data because you might have other working instance variables that you don't want to save. this keeps things "clean".

Thank you sir.

Last question until i have to reevaluate my new knowledge.

How do i get now my data from eeprom back when my device is restarting?

  EEPROM.get(0, settings);

Here i get (starting) from adress 0 all data brack from eprom. But where "see" my membervariables of my class Devicesettings this info?

here is an example, typed here and fully untested - but should get you going

#include <EEPROM.h>
size_t EEPROM_SIZE = 200;
class GPS
{
  private:
    struct __attribute ((packed)) tStorage {
      float latitude;
      float longitude;
      uint32_t lastFix;
    };
    tStorage settings;

  public:
    Stream* gpsSerial;


    GPS(Stream* serial) : gpsSerial(serial) {
      settings.latitude  = 48.8583942343038;
      settings.longitude = 2.2944263999322025;
      settings.lastFix   = 1630490553ul;
    }    // constructor

    void printSettings() {
      Serial.print("Latitude: "); Serial.println(settings.latitude,6);
      Serial.print("Longitude: "); Serial.println(settings.longitude,6);
      Serial.print("Last fix: "); Serial.println(settings.lastFix);
    }

    void saveSettings() {
      EEPROM.put(0, settings);
      EEPROM.commit();
    }
    void getSettings() {
      EEPROM.get(0, settings);
    }

    void clearSettings() {
      settings.latitude  = 0;
      settings.longitude = 0;
      settings.lastFix   = 0;
    }
};

GPS myGPS(&Serial);

void setup() {
  Serial.begin(115200);
  EEPROM.begin(EEPROM_SIZE);

  Serial.println("\n\nSaving default Values");
  myGPS.saveSettings();
  myGPS.printSettings();

  Serial.println("\nClearing default Values");
  myGPS.clearSettings();
  myGPS.printSettings();

  Serial.println("\nReading back default Values");
  myGPS.getSettings();
  myGPS.printSettings();

  EEPROM.end();
}

void loop() {}

if all goes well you should see in the Serial monitor at 115200 bauds something like

Saving default Values
Latitude: 48.858395
Longitude: 2.294426
Last fix: 1630490553

Clearing default Values
Latitude: 0.000000
Longitude: 0.000000
Last fix: 0

Reading back default Values
Latitude: 48.858395
Longitude: 2.294426
Last fix: 1630490553

as you can see I've additional instance variables in my class (the Stream from which a GPS would acquire data for example) that does not need archiving so I've a structure for the settings that matter and then other random public or private (the stream could be private too) instance variables.

side note, the first time you run a code on a new ESP, the memory will not be initialized. that's something you need to detect.

An often used way is to store a magic keyword at the start of the structure for example

    const uint32_t magicKey = 0xDEADBEEF;
    struct __attribute ((packed)) tStorage {
      uint32_t magic;
      float latitude;
      float longitude;
      uint32_t lastFix;
    };

the idea is that when saving the structure, you initialize magic with the magicKey. When you read the EEPROM back, if the magicvalue is not magicKey you know you probably read garbage from the EEPROM and your code needs to handle that (provide hardcoded defaults value for example).

Thank you sir for the help. i followed your tip and implemented also a struct into my class.
Thank your for sharing your knowledge!

Good. Have fun