Go Down

Topic: EEPROM Wear Leveling (Read 8651 times) previous topic - next topic

BigBobby

#15
Mar 22, 2016, 02:58 pm Last Edit: Mar 29, 2016, 01:41 am by BigBobby
"Do you understand what the datasheet means, when it says the EEPROM has a 4 byte page size in Table 28-12?"
I think that you can write 4 bytes at a time during one 3.3mS write operation.
I dunno...the EEDR register is only 1 byte so I don't know how 4 bytes could be written at a time.

If there is a way, the AVR library doesn't use it:

Code: [Select]

#include "Arduino.h"

#define ADDRESS_START 0x200
#define WRITE_VAL 0xABCDEF01

void setup()
{
  Serial.begin(9600);
  test_function();
}

void loop()
{
}

void test_function(void)
{
  uint32_t read_val;
  uint32_t measure_time[2];
  for(uint16_t address = 0; address < sizeof(uint32_t); address++)
  {
    eeprom_write_dword((uint32_t*)address, WRITE_VAL);
    measure_time[0] = micros();
    read_val = eeprom_read_dword((uint32_t*)address);
    measure_time[1] = micros();

    Serial.print(F("Address "));
    Serial.print(address, HEX);
    Serial.print(F(" written to "));
    Serial.print(WRITE_VAL, HEX);
    Serial.print(F(" read back "));
    Serial.print(read_val, HEX);
    Serial.println('.');
    Serial.print(F("Measure time = "));
    Serial.print(measure_time[1] - measure_time[0]);
    Serial.println("us.");
  }
}


Quote
Address 0 written to ABCDEF01 read back ABCDEF01.
Measure time = 3416us.
Address 1 written to ABCDEF01 read back ABCDEF01.
Measure time = 3416us.
Address 2 written to ABCDEF01 read back ABCDEF01.
Measure time = 3416us.
Address 3 written to ABCDEF01 read back ABCDEF01.
Measure time = 3416us.
It's a minor detail, but it did have me confused when I read the datasheet.

BTW Again - you answered my last reply very quickly.  Were you able to turn notifications on in this forum, or do you just browse it frequently?

Edited to Add: THERE IS A BUG IN THIS CODE!  LOOK AT RESPONSE #24 TO SEE THE ACTUAL WRITE TIMES!

CrossRoads

Just  browse frequently.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads

Your example shows 4 bytes being written & read on 3.4mS.  That looks like page write to me.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

BigBobby

#18
Mar 22, 2016, 03:17 pm Last Edit: Mar 22, 2016, 04:26 pm by BigBobby
Your example shows 4 bytes being written & read on 3.4mS.  That looks like page write to me.
Oh my.  You are right, and I'm an idiot.

Well, I guess inspecting the assembly should tell me how it's done.  Feel free to ignore this as I doubt many other people like inspecting assembly code (and it certainly goes against everything the Arduino stands for).  I did comment it to make it easy to follow, however:
Code: [Select]

 10e: c0 e0        ldi r28, 0x00 ; Load the address to write into r28-r29.
 110: d0 e0        ldi r29, 0x00 ;
 112: 41 e0        ldi r20, 0x01 ; Load the dword to write into r20-r23.
 114: 5f ee        ldi r21, 0xEF ;
 116: 6d ec        ldi r22, 0xCD ;
 118: 7b ea        ldi r23, 0xAB ;
 11a: ce 01        movw r24, r28 ; Move the address to write into r24-r25.
 11c: 0e 94 d0 04 call 0x9a0 ; 0x9a0 <__eewr_dword_m328p>;Call function to write dword to EEPROM.

000009a0 <__eewr_dword_m328p>:
 9a0: 24 2f        mov r18, r20
 9a2: 0e 94 ee 04 call 0x9dc ; 0x9dc <__eewr_r18_m328p> ; Call function to write LSB to EEPROM.
 9a6: 25 2f        mov r18, r21
 9a8: 0e 94 ee 04 call 0x9dc ; 0x9dc <__eewr_r18_m328p> ; Call function to write MSB of LSW to EEPROM.
 9ac: 0c 94 d8 04 jmp 0x9b0 ; 0x9b0 <__eewr_word_m328p>; Call function to write MSW to EEPROM.

000009b0 <__eewr_word_m328p>:
 9b0: 0e 94 ed 04 call 0x9da ; 0x9da <__eewr_byte_m328p>; Call function to write LSB of MSW to EEPROM.
 9b4: 27 2f        mov r18, r23
 9b6: 0c 94 ee 04 jmp 0x9dc ; 0x9dc <__eewr_r18_m328p> ; Call function to write MSB of MSW to EEPROM.

000009da <__eewr_byte_m328p>:
 9da: 26 2f        mov r18, r22

000009dc <__eewr_r18_m328p>:
 9dc: f9 99        sbic 0x1f, 1 ; 31 ; Wait for completion of previous write
 9de: fe cf        rjmp .-4      ; 0x9dc <__eewr_r18_m328p>
 9e0: 1f ba        out 0x1f, r1 ; I assume this sets up EEPM[], although I don't see where r1 was set.
 9e2: 92 bd        out 0x22, r25 ; Set up address in address register
 9e4: 81 bd        out 0x21, r24 ;
 9e6: 20 bd        out 0x20, r18 ; Write data (r16) to Data Register
 9e8: 0f b6        in r0, 0x3f ; Save SREG
 9ea: f8 94        cli ; Disable interrupts.
 9ec: fa 9a        sbi 0x1f, 2 ; Set EEMPE, EEPROM Master Write Enable.
 9ee: f9 9a        sbi 0x1f, 1 ; Set EEPE, within 4 cycles of EEMPE as stated in the data sheet.  Shouldn't this start the write?
 9f0: 0f be        out 0x3f, r0 ; Restore SREG
 9f2: 01 96        adiw r24, 0x01 ; Increment the address by one.
 9f4: 08 95        ret


So, I'm actually more confused now.  All of the real action occurs in __eewr_r18_m328p and it seems to follow the example on page 24 of the 328P datasheet.  Reading the datasheet, however, it seems that the code should begin its 3.4ms cycle at instruction 9ee and that we should get stuck in the wait loop at 9dc when the subsequent bytes are written .

My test_function in C, however, does show that it only takes 3.4ms to write 4 bytes.  It seems some info must be missing from the datasheet (or I'm just not seeing it).

MrAl

Hi,

After reading the preceding posts, maybe what is happening is this...

The page size is 4 bytes, so say we first write data 0x10 to location 0x000, 0x11 to location 0x001, 0x12 to location 0x002, and 0x13 to location 0x003.  We now have:
0x000 0x10
0x001 0x11
0x002 0x12
0x003 0x13

Next we write 0x20 to location 0x000, and that means it reads locations 0x001 to 0x003 and rewrites them and writes the new data to 0x000 so we have:
0x000 0x20
0x001 0x11
0x002 0x12
0x003 0x13

Maybe what it does is when it rewrites locations 0x001 to 0x003 it does not have the flip any bits so there is no change.  But then again that doesnt seem to make sense either because i think when it writes data it first erases the old data and then writes the new data.  But maybe if the data in locations 0x001 to 0x003 is already in the erased state nothing changes.  So that would mean that the data in those three locations would have to be data with some bits 1 and some bits 0 in order to make sure the test was effective.
I did not study the code in detail to make sure this is what was done but perhaps you can check.

It doesnt seem like it would make too much sense to specify a page size if they had some algorithm that could check to see if the other bytes really had to be changed, unless maybe if we could save 4 bytes in the same write time which would save time in some algorithms.  That would mean larger data types (like type long) would write maybe four times faster.

But the test did prove one thing already though, and that is that wear leveling would help, even if not for every single byte.  If we had to rewrite type long a lot and only say one or two of them at a time, then we could use successive locations instead of the same location all the time.
If we did use type long, one variable, and we got 1 milliion writes before failure, then with a 1024 byte memory we'd get 256 million writes before failure, which is a lot.  Of course if we had to write two longs then we'd only get 128 million writes before failure, but that's still good.
For my purpose for example, i'd want to save the hours, minutes and seconds, which could be stuck into one var of type unsigned long.

So now that we are pretty sure that wear leveling would work for the Arduino and we need to retrieve the stored data on startup, how do we actually retrieve that data?  We wont know what location we wrote to last because we cant store that in one location either.  Any ideas?

Here's one idea for example...

Start a counter in ram, starting at counter=0x00000000.
Store 0x00000001 to the first four byte locations, and that will be our pointer.
Write new data values to location pointer*4, which in this case would be locations 0x004 through 0x007.
Increment the counter.
If the counter gets to some high value like 0x00020000, move to the next four locations by incrementing the pointer to 0x00000002, store the value in the location pointer*4 again, clear the counter back to zero.

On startup, read locations 0x000 to 0x003 for the pointer, then use that to read the data at pointer*4.
Read back the actual data.
Start the counter over at 0x00000000, increment the pointer, store it back, store the data into the new location.

So on startup we'd always read the pointer, use that to find the data, start a new counter, increment the pointer, store the data in the new location.

We'd also have to make sure this always works no matter when the power goes out.

Another idea would be to load the entire memory with zeros, and use the first non zero value found as the data bytes.  We'd loose some writes this way though as we'd have to zero the bytes on startup.
This would have to be done so that it would work even if the power went out while we were doing the housekeeping.

Something to think about.




Coding Badly

I don't know how 4 bytes could be written at a time.
ISP.  The page size is relevant when writing to EEPROM using a programmer.

It has been established using distructive testing that the page size is irrelevant when writing to EEPROM from the CPU.  Writes from the CPU are bytewise.


Coding Badly

Your example shows 4 bytes being written & read on 3.4mS.  That looks like page write to me.
I believe that is the maximum time necessary to complete a write.  Given Atmel's very conservative specifications and the advancements they have made it is reasonable to assume the actual time is less.


BigBobby

#22
Mar 28, 2016, 05:51 pm Last Edit: Mar 28, 2016, 06:04 pm by BigBobby
I did some testing of the EEPROM write time in this thread -> http://forum.arduino.cc/index.php?topic=382266.msg2635374#msg2635374

It seems that the ATmega328P takes almost exactly 3.4ms/byte in all cases.

Hmm...if I changed my test code here to write 4 bytes instead of 1 dword, I bet the total write time would become 13.6ms.  I could also then check the assembly code to see how it is different than that for the dword writes, to understand how the dword write can take 3.4ms for all 4 bytes.

BigBobby

#23
Mar 28, 2016, 06:00 pm Last Edit: Mar 28, 2016, 06:01 pm by BigBobby
Here's one idea for example...

Start a counter in ram, starting at counter=0x00000000.
Store 0x00000001 to the first four byte locations, and that will be our pointer.
Write new data values to location pointer*4, which in this case would be locations 0x004 through 0x007.
Increment the counter.
If the counter gets to some high value like 0x00020000, move to the next four locations by incrementing the pointer to 0x00000002, store the value in the location pointer*4 again, clear the counter back to zero.

On startup, read locations 0x000 to 0x003 for the pointer, then use that to read the data at pointer*4.
Read back the actual data.
Start the counter over at 0x00000000, increment the pointer, store it back, store the data into the new location.

So on startup we'd always read the pointer, use that to find the data, start a new counter, increment the pointer, store the data in the new location.

We'd also have to make sure this always works no matter when the power goes out.

Another idea would be to load the entire memory with zeros, and use the first non zero value found as the data bytes.  We'd loose some writes this way though as we'd have to zero the bytes on startup.
This would have to be done so that it would work even if the power went out while we were doing the housekeeping.
While I have never investigated how wear leveling is actually performed by people who've thought tons about it (like SD card manufacturers), I have used multiple nonvolatile memory locations before to increase the number of writes available for my data.

They way I handled it was to have two EEPROM spaces reserved for my data, and one byte to flag which one contained the data.  I never ping-ponged between the two spaces when saving, however, since that'd mean I'd bee writing the flag byte more often than the spaces containing my data!  Instead I wrote to the first space until it failed to verify after writing.  Then, I'd write to the flag byte (only 1 write over the life of the product) and then start using the second space for my data.

If there's a benefit to alternating between the data spaces, I'm not aware of it, and it's much easier to write to them sequentially.

BigBobby

#24
Mar 28, 2016, 06:26 pm Last Edit: Mar 28, 2016, 06:28 pm by BigBobby
Ugh...now it seems I'm an idiot for two reasons.  In the test code I posted in response #15, I put the time measurement around eeprom_read_dword(), not eeprom_write_dword().  All I was measuring is the 3.4ms it took to be able to read the EEPROM after writing the last byte.

Here is the code corrected to measure the EEPROM write time correctly, and also to try writing both 4 bytes and 1 dword at a time:
Code: [Select]


#include "Arduino.h"

#define ADDRESS_START 0x200
#define WRITE_VAL 0xABCDEF01

void setup()
{
  Serial.begin(9600);
  test_function();
  test_function2();
}

void loop()
{
}

void test_function(void)
{
  uint32_t read_val;
  uint32_t measure_time[2];

  Serial.println(F("Writing 4 bytes using eeprom_write_dword()"));
  for(uint16_t address = 0; address < sizeof(uint32_t); address++)
  {
    measure_time[0] = micros();
    eeprom_write_dword((uint32_t*)address, WRITE_VAL);
    read_val = eeprom_read_dword((uint32_t*)address);
    measure_time[1] = micros();

    Serial.print(F("Address "));
    Serial.print(address, HEX);
    Serial.print(F(" written to "));
    Serial.print(WRITE_VAL, HEX);
    Serial.print(F(" read back "));
    Serial.print(read_val, HEX);
    Serial.println('.');
    Serial.print(F("Measure time = "));
    Serial.print(measure_time[1] - measure_time[0]);
    Serial.println("us.");
  }
}

void test_function2(void)
{
  uint32_t write_val = WRITE_VAL;
  uint32_t read_val;
  uint32_t measure_time[2];

  Serial.println(F("Writing 4 bytes using eeprom_write_block()"));

  for(uint16_t address = 0; address < sizeof(uint32_t); address++)
  {
    measure_time[0] = micros();
    eeprom_write_block((uint8_t*)&write_val, (void*)address, sizeof(write_val));
    eeprom_read_block((uint8_t*)&read_val, (void*)address, sizeof(read_val));
    measure_time[1] = micros();

    Serial.print(F("Address "));
    Serial.print(address, HEX);
    Serial.print(F(" written to "));
    Serial.print(WRITE_VAL, HEX);
    Serial.print(F(" read back "));
    Serial.print(read_val, HEX);
    Serial.println('.');
    Serial.print(F("Measure time = "));
    Serial.print(measure_time[1] - measure_time[0]);
    Serial.println("us.");
  }
}


These are the results:

Quote
Writing 4 bytes using eeprom_write_dword()
Address 0 written to ABCDEF01 read back ABCDEF01.
Measure time = 13644us.
Address 1 written to ABCDEF01 read back ABCDEF01.
Measure time = 13644us.
Address 2 written to ABCDEF01 read back ABCDEF01.
Measure time = 13648us.
Address 3 written to ABCDEF01 read back ABCDEF01.
Measure time = 13644us.
Writing 4 bytes using eeprom_write_block()
Address 0 written to ABCDEF01 read back ABCDEF01.
Measure time = 13644us.
Address 1 written to ABCDEF01 read back ABCDEF01.
Measure time = 13640us.
Address 2 written to ABCDEF01 read back ABCDEF01.
Measure time = 13652us.
Address 3 written to ABCDEF01 read back ABCDEF01.
Measure time = 13648us.
So, it seems my understanding of the assembly was correct:  it does take 3.4ms/byte to write to the EEPROM regardless of whether you write 4 bytes on a page or not.

In retrospect, I should have noticed this.  If I was getting some page write benefit when I wrote 1 dword at a time, I should have only gotten that benefit with 1 address...not all 4.

Oh well, I make mistakes sometimes.  Hopefully anyone reading this thread in the future will read all of the way to the bottom.

MrAl

While I have never investigated how wear leveling is actually performed by people who've thought tons about it (like SD card manufacturers), I have used multiple nonvolatile memory locations before to increase the number of writes available for my data.

They way I handled it was to have two EEPROM spaces reserved for my data, and one byte to flag which one contained the data.  I never ping-ponged between the two spaces when saving, however, since that'd mean I'd bee writing the flag byte more often than the spaces containing my data!  Instead I wrote to the first space until it failed to verify after writing.  Then, I'd write to the flag byte (only 1 write over the life of the product) and then start using the second space for my data.

If there's a benefit to alternating between the data spaces, I'm not aware of it, and it's much easier to write to them sequentially.
Hi again,


That's a very interesting and thoughtful way to handle this.  The only thing i have to add is that i am not sure i would want to rely solely on a failure test to determine where i am writing to with important data.
My question would be a technical one, where we want to know the relationship between the non failed write test and the likelihood that a bit will flip later, long after the write was performed.  For example, if the write was a success and later the temperature rose by 10 degrees C, is there any chance that a bit could flip?
If not, then we have a good method here.

We'd have to find some technical data on the failure modes of these kinds of memories somewhere.  That might tell us more about this.  We might also find out if there is a more specific failure mode such as the statistics on how often one bit might flip, and how often two bits might flip, etc., up to the full 8 bits.  If it was only one or two bits we might be able to apply some error detection and correction and then switch to the next location.

Perhaps you have some life data on this, such as how long your algorithm ran without a problem, but we'd also have to know the failure mode of the application if there was a failure of say one bit so we would know if it was really detected or just overlooked over the, presumably, 'years'.

I might consider doing this myself too, i'll just have to figure out how important that data is when it is read back.  If it is too important then maybe pull a Space Shuttle "Majority Rules" trick: write to 7 locations and then if there is any difference between any of the 7 use the majority as the correct data, then of course switch to a new set of locations.  So if 5 locations show data 0x12 and 1 shows 0x34 and one shows 0x56, use the 0x12 and disregard the 0x34 and 0x56 and of course switch locations.
This would also improve the "surprise power failure" application shut down, where the data in one byte does not get written to in time before the power goes completely away and therefore we never wrote the correct data to begin with.  Looking at several (odd number of) locations we should be able to at least get the older data back.

This just gets more and more interesting hearing the other views here :-)



BigBobby

#26
Mar 29, 2016, 12:10 am Last Edit: Mar 29, 2016, 12:42 am by BigBobby
That's a very interesting and thoughtful way to handle this.  The only thing i have to add is that i am not sure i would want to rely solely on a failure test to determine where i am writing to with important data.
My question would be a technical one, where we want to know the relationship between the non failed write test and the likelihood that a bit will flip later, long after the write was performed.  For example, if the write was a success and later the temperature rose by 10 degrees C, is there any chance that a bit could flip?
If not, then we have a good method here.
I asked a guy in my office who has EEPROM in his IC and he says that he guarantees I should get the full retention period from every write within his number-of-writes spec.  He also said, however, that another company may not spec their parts such that every write is valid for the full retention period (especially over temperature).

It sounds like you're interested in two reasons for using multiple EEPROM locations for your data:  1.  you want redundancy and 2. you want an increased number of writes.  Both are legitimate reasons, but if you're using the multiple locations for reason #1 then you aren't going to get the benefit of reason #2.

When I've used multiple locations for redundancy, I used a CRC on each copy of the data.  The CRC would tell me which copy to use in case one got corrupted.

MrAl

I asked a guy in my office who has EEPROM in his IC and he says that he guarantees I should get the full retention period from every write within his number-of-writes spec.  He also said, however, that another company may not spec their parts such that every write is valid for the full retention period (especially over temperature).

It sounds like you're interested in two reasons for using multiple EEPROM locations for your data:  1.  you want redundancy and 2. you want an increased number of writes.  Both are legitimate reasons, but if you're using the multiple locations for reason #1 then you aren't going to get the benefit of reason #2.

When I've used multiple locations for redundancy, I used a CRC on each copy of the data.  The CRC would tell me which copy to use in case one got corrupted.
Hi again,

Very good ideas again.  This is what i was looking for.  Not only did i want to get more writes if possible ( longer lasting product) i also have to make sure the data is valid, which is very important.

I do have to wonder one more thing though, and that is, did you go over your algorithm to find out what would happen in the event that your program was writing to a location at the same time the power went out?  Would you still be able to get the data back?  For example, if the data is written and then the CRC code is calculated and then written, the power might fail during the time the CRC code is being calculated.  If the data is not written while the code is being calculated and then the data and code is written, that sounds better but the power could still fail just before the code part is written.  This is just one possibility though.  So i wonder if you have thought that through yet and would like to hear your comments.

Yes redundancy helps a lot, and i dont think it is too wasteful although yes it does reduce the number of writes before total failure, but that's the way it goes.  If i write to just 3 locations then that reduces the cycle life to 1/3 of what it could be, but then again using any wear leveling would still be a benefit.  For example, if we can get 100k writes to one location, then with 1024 write locations we can write 100 million times with no redundancy before total failure, but with 3 fold redundancy we can still write 33 million times which is still a vast improvement.

Since i am looking into these reliability issues, i also wonder if a single byte that failed on one write could somehow start to work again at some time in the future, even if just one more time before it failed again.  Maybe not as important however.

BigBobby

I do have to wonder one more thing though, and that is, did you go over your algorithm to find out what would happen in the event that your program was writing to a location at the same time the power went out?  Would you still be able to get the data back?  For example, if the data is written and then the CRC code is calculated and then written, the power might fail during the time the CRC code is being calculated.  If the data is not written while the code is being calculated and then the data and code is written, that sounds better but the power could still fail just before the code part is written.  This is just one possibility though.  So i wonder if you have thought that through yet and would like to hear your comments.
Possibly the only times I've ever seen CRCs expose bad data in my EEPROM, was due to the power going out during a write.  The data changing on its own really isn't something you should see for the 10 year retention period, and I've yet to perform a test that long.  If you write to the EEPROM regularly, however, you are always going to have some time when you lose power in the middle of a write.

You are mostly protected with redundant copies, however.  If the power goes out while writing the 2nd copy, you still successfully wrote the 1st copy which can be validated with its CRC.  If the power goes out while writing the 1st copy, the 2nd copy still contains the data from the last time it was written.

Getting back the data from the last time you wrote isn't perfect, but at least you don't lose everything and it's the best you can do without adding some kind of capacitor backup.

Since i am looking into these reliability issues, i also wonder if a single byte that failed on one write could somehow start to work again at some time in the future, even if just one more time before it failed again.  Maybe not as important however.
When I made the code in this thread that burnt out a memory location, I actually did rerun it a few times.  Interestingly, the burnt-out memory location did pass another ~10k write/read cycles before it failed again.  Even after running the sketch several times, the burnt-out location would write/read a few times before failing again.

dmilton2004

Very interesting!

Do you think that applies to ATMEL chips in general? Like ATTinys?

I appreciate your sacrifice for the good of knowledge. :)


Go Up