EEPROM Writing behavior

In the docs for EEPROM, there is this statement:

The object data is first compared to the data written in the EEPROM to avoid writing values that haven't changed.

If you are using EEPROM.put to write a struct into memory, does this statement mean that only the members of the struct that have changed will be written, or that if the struct itself has changed at all, the whole struct will be rewritten?

@Ric, I suspect the comparison is done at a per-byte level.

I tried some experiments to see if I could observe what happens when you change a single byte from 0 to 1. I did this by reading all the bytes in the simulated EEPROM, but didn’t see anything unusual; I’m guessing that when you read a particular location, it’s not necessarily that location in hardware memory because of the wear leveling techniques employed.

I did see one unusual thing that I don’t understand. I used EEPROM.put to write a struct into memory. The last member of that struct was a uint8_t set to 0. If I look at the 4 bytes (the one byte of data plus the padding) that correspond to that member, I see 0 3 0 0. If I set that member to 255, I get 255 3 0 0. If I change that member to a uint16_t or an int set to 0 or 255, I see all zeros or 255 0 0 0. So, what is that 3 doing in there? Any ideas?

@Ric, since it’s a struct, the 3 is most likely the struct size in bytes. When you changed the member to a larger entity with more bytes, it no longer uses 4 bytes so you need to read more to get the full struct. @mdma would be the one to clarify.

No, I don't think that is it. The struct was actually 44 bytes long (I used one from an actual project I'm working on), and both the uint16_t and an int should still use only 4 bytes; the uint16_t because of padding, and the int because it is actually 4 bytes, right?

When the last 3 members of the struct were 7, 90000, and 255 (two ints and the uint8_t), I get this,

7 0 0 0---144 95 1 0---255 3 0 0---255 ..... 255 all the way to the end (byte 2048).

If I change that last member to a uint16_t or an int, the only thing that changes is the 3 goes to a 0.

@Ric, I shouldn’t swim in these waters without first doing my research! If you read back the struct instead of byte by byte, are the values correct?

Yes, the values are correct, so my concern is purely academic at this point. At first, I thought the 3 might refer to 3 bytes of padding, which is the only thing I could think of where a 3 might make sense. But then, I would have expected a 2 with the uint16_t. So, it’s a mystery :confused:

How is your struct initialized?
Could it be that this 3 is only a RAM artefact which doesn’t get set due to not being part of any variable?

I don’t think so. I initialized it the first time without explicitly setting the last two members to anything in setup() because they both have default values set in the struct definition. But, it makes no difference if I do set them explicitly. Here’s the code,

typedef struct {
    int dosingPeriod; // pass either 24 or 12 for this value
    int startTime;
    int endTime;
    int onTime;
    int offTime;
    float totalDose;
    float doseRate; // mL/min
    int numberOfDoses;
    int connectedPin;
    int calibrationEnd = 90000; // a value greater than "now" can ever be
    uint8_t shouldRun = 0;
} Channel;


void setup() {
    EEPROM.clear(); // comment this out after the first run
    Channel c;
     //c = EEPROM.get(0, c);
    c.dosingPeriod = 24;
    c.startTime = 39600;
    c.endTime = 42300;
    c.onTime = 39600;
    c.offTime = 41400;
    c.totalDose = 10.5;
    c.doseRate = 2.5;
    c.numberOfDoses = 12;
    c.connectedPin = D7;
    c.calibrationEnd = 90000; 
    c.shouldRun = 0;
    EEPROM.put(0, c);
    
    Serial.begin(9600);
    delay(3000);
   
    Serial.println("Start");
    for (int i=0; i<2048; i++) {
        if (i % 32 == 0) Serial.println();
        uint8_t a = EEPROM.read(i);
        Serial.printf("%d  ", a);
    }
}

The results are (I added the underscores to make reading the 4 byte groups easier):

Start

24 0 0 0__176 154 0 0__60 165 0 0__176 154 0 0__184 161 0 0__0 0 40 65__0 0 32 64__12 0 0 0__7 0 0 0__144 95 1 0__0 3 0 0__255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255 255…255

After Edit:

I tried switching the last two members and setting shouldRun to 100. When I do that, the end of the print out looks like this (notice the 3 has now changed to a 7),

7 0 0 0__100 7 0 0__144 95 1 0

If I move shouldRun up one more position (third from end of definition), now the end reads,

100 82 6 8__7 0 0 0__144 95 1 0

So maybe you’re correct. Is it reading the RAM wrong, and just picking up those 3 byes from the next 3 positions in RAM? But why would it do that. If that member is typed as a uint8_t, shouldn’t it only read one byte from RAM (and then pad with zeros)?

Ok, I think I understand now. After more testing, and reading the values in RAM, those 3 extra bytes after my uint8_t are just picking up what happens to be in those RAM locations (so it seems the RAM is written to along 4 byte boundaries) . So, I guess that means when you do a EEPROM.put, you’re just writing what’s in that block of memory from the beginning of the struct to its end. That doesn’t matter when you read the struct since when you access the uint8_t member, it only reads that one byte.

2 Likes

Yup, that was my thought.

It doesn’t make a difference to your data, but it does to EEPROM.put() since it might cause a (superfluous) “re-put” since some “don’t care” data has changed.