EEPROM Wear Leveling

Hi there,

Has it ever been established whether or not EEPROM wear leveling in any Arduino does any good? Mostly for Uno and Nano, but others would be interesting too.

So say we had a way to use successive addresses in EEPROM rather than the same 8 bit location all the time. That would give us 1024 times the write endurance for an EEPROM that can store 1024 values if we only have to write one 8 bit value for an application. For example, normally we write maybe 0x34 to location 0x001 and then later write 0x8A to location 0x001, but with wear leveling we would write 0x34 to location 0x001 and then later write 0x8A to location 0x002, leaving location 0x001 alone for now. Once we got to the last location we would wrap around back to the location 0x000 or whatever.

There are a couple different ways to implement this but if it does not do any good then it is a waste of time and energy.

Has it ever been established whether or not EEPROM wear leveling in any Arduino does any good?

What? You don't believe in math? :)

Of course, wear leveling will reduce the number of writes to any given location so it will increase the overall life of the eeprom. Most data indicates that the 100,000 write specified life of the eeprom registers is very conservative.

I think your question is really does it make any practical difference, and that really depends on the specific sketch and the number of cycles you are going to put any register through.

Another question is that if wear leveling is required to preserve the life of the eeprom, is the eeprom the optimal storage device for the application?

External FRAM can always be added. 1 Trillion to 100 Trillion write cycles, fast write speed like SRAM (vs 3.3mS for EEPROM) with EEPROM nonvolatility. http://www.digikey.com/product-search/en/integrated-circuits-ics/memory/2556980?k=fram+memory&pv154=29&FV=fff40027%2Cfff80434&mnonly=0&newproducts=0&ColumnSort=0&page=1&stock=1&quantity=0&ptm=0&fid=0&pageSize=25 I2C interface is another (slower) option also. http://www.digikey.com/product-search/en/integrated-circuits-ics/memory/2556980?k=fram+memory&pv154=740&FV=fff40027%2Cfff80434&mnonly=0&newproducts=0&ColumnSort=0&page=1&stock=1&quantity=0&ptm=0&fid=0&pageSize=25

Hello,

Thanks for the replies.

I looked into the FRAM’s and might use one in the future, but let me clarify the question and the reason for it in the first place.

The question is really about a low write frequency application, where the writes might only be 16 times in one day, then not written to for another 7 days. On the other hand, if wear leveling does any good then i might increase the write frequency which would help improve the program somewhat. So it’s partly what i can get away with, not what else i can do about it.

The reason for the question itself is because not all EEPROM’s are equal. Some write to blocks of bits rather than just one 8 bit section. For example, some write to 64 bits all at once even if we only tell it to write to one 8 bit section. That means that for say the first 8 bytes if we write to location 0x02, it will write to 0x02 but will also over write 0x00 to 0x07, and that means that using wear leveling on a byte by byte basis is the wrong way to do it. At best, we would be able to do it on an 8 byte basis which would also improve the endurance but only 1/8 as effective unless we store 7 values and then write on the 8th value occurance. That would still help though, unless it worked on a 16 byte block and then we’d have to resort to only a 16 byte block algorithm in one way or another.

I tend to think the EEPROM in the Uno or Nano probably works on an 8 bit block, so one byte per block, but i have no data to back this up. If this is true, i would not need any other type of memory although i know there are better ones out there. It could even be different on those such as the Due, which has more EEPROM. The larger the EEPROM probably the more likely it is to use multi byte block processing rather than single byte in order to speed the write process up for the user.

Thanks again :slight_smile:

I tend to think the EEPROM in the Uno or Nano probably works on an 8 bit block, so one byte per block, but i have no data to back this up

From section 5.4 "EEPROM Data Memory" in the ATmega328 data sheet ( a useful reference to have on your computer)

The ATmega48P/88P/168P/328P contains 256/512/512/1K bytes of data EEPROM memory. It is organized as a separate data space, in which single bytes can be read and written.

FRAM eh? That's those little Ferris cores that you thread copper wire through to store memory in magnets, right? The kind they used on the moon lander and early shuttle missions? It's supposed to be impervious to high radiation you would find in space, unlike most chips. Maybe I'll get me one. :)

Hi again,

Cattledog: That quote may or may not mean that one byte is written to, but i tend to think it does mean that. I can write one byte to my flash drive too, but it might actually write 64k bits just for that one byte.

Shawnlg: Those little ferrite cores are ferromagnetic, a FRAM is ferroelectric. Slightly different operating principles. I used to have a ferromagnetic core that held about 32 bytes or so. The core was about 5 inches by 5 inches and the PC board with the drive electronics was about 12 inches long by maybe 8 inches wide. That's pretty big for 32 bytes :-) Yes the little 1/8 inch ferro cores had two very thin copper wires running through the center. When both wires passed current the core would flip it's state. Sense amplifiers were used to detect any pulse that would indicate that the tiny core flipped it's magnetic state. Pretty cool, but kinda big for what it did.

MrAl: Shawnlg: Those little ferrite cores are ferromagnetic, a FRAM is ferroelectric. Slightly different operating principles. I used to have a ferromagnetic core that held about 32 bytes or so. The core was about 5 inches by 5 inches and the PC board with the drive electronics was about 12 inches long by maybe 8 inches wide. That's pretty big for 32 bytes :-)

So, I wonder how you could connect one of those to an arduino :-)

shawnlg: So, I wonder how you could connect one of those to an arduino :-)

Hi,

Now that you mention it, it would be fun to try i think.

Going back in memory to early to mid 70's, the cores are arranged on a grid say N x M. If you want to write a '1' to one bit, you have to energize the Nth column and Mth row with half the current it takes to set the state of the core. That individual core then gets the full current sum and so the state changes to a '1' unless it was already a '1' and then it would not change. To write a '0' use negative currents.

To read, do the same thing, but after the current pulses have ended read the sense amplifiers and see if any of them sensed a pulse after the energizing pulses have ended. If they sensed a pulse then the core changed state so it was a '0', and then the '0' has to be written back to that core, but if no sensed pulse then no extra write is needed and it must have been a '1'.

This is going back many years now, so it may be that the sensed pulse comes on top of the current pulse, i cant remember for sure. Have to look it up :-)

To do this with an Arduino would mean addressing it with i/o lines where the half current is set somehow, maybe with resistors. For a 4x4 core matrix we'd need 8 i/o lines. We'd also have to see if we can sense the pulse though, which may be very fast and of low level so we'd need sense amplifiers too and if the pulse was too fast we might need an external latch to catch the pulse too, then reset when done. So it would take a little doing but is most likely possible. I dont have that core anymore though but i would imagine we could find tiny cores somewhere on the web and make our own matrix with very thin magnetic wire (like 32 gauge perhaps).

For proof of concept, all we would have to do is learn how to energize one single core and be able to read back the stored state. So we'd only need one tiny toroid core to start with. I am not sure however if a little 'bead' core used for noise suppression would work or not. I think the core has to have a square hysteresis loop and i am not sure how those bead cores are. We'd have to look at the data sheet or something.

This would be a very interesting project though, because often we just have to store a tiny amount of data so that when the application starts up from power up it can remember one little thing like one byte. If we could find an easy enough way to do this, it would be a good thing to have and i am sure other people would like to do it too for their own projects.

Oh yeah another thing would be the level of current required. If the current had to be higher than maybe 20ma then we might have to use transistors or a transistor array to drive the cores. That would require 8 transistors for a 4x4 array which is only 2 bytes of data.

This is something i might look into just for the fun of it even if it does not turn out to be practical.

cattledog:
From section 5.4 “EEPROM Data Memory” in the ATmega328 data sheet ( a useful reference to have on your computer)

Ok, but section 28.7.5 says

The EEPROM is organized in pages, see Table 28-12 on page 285. When programming the EEPROM, the program data is latched into a page buffer. This allows one page of data to be programmed simultaneously.

Table 28-12 says that the EEPROM has a 4 byte page size. When I first used my Arduino EEPROM the datasheet seemed contradictory, so I proved to myself that I could write to individual EEPROM bytes. I don’t know if behind-the-scenes somehow the AVR was read-modify-writing 4 physical bytes when I wrote one byte, but if I ever get an Arduino I don’t care about I might write a sketch to purposely beat down one EEPROM location to see if it has any effect on the 3 bytes around it.

Hello again,

Oh ok so they are using something other than 8 bit internal organization. Apparently it is 32 bit. So writing 8 bits would not be any more beneficial than writing 32 bits, which is 4 bytes to us.

So the next question then is, are these contiguous bytes? That is, are location 0x000, 0x001, 0x002, and ox003 one of those groups of 32 bits? I would hope so, but have no way to prove this, yet. Maybe it is written down somewhere.

If it is not contiguous, then it might be: 0x000, 0x002, 0x004, 0x006

as the first group of 32 bits and: 0x001, 0x003, 0x005, 0x007

as the next group of 32 bits, etc.

If you decide to burn one out for the sake of knowing for sure,you might want to check the whole EEPROM if possible just to make sure you can see all the changed bits,if any. You might want to use test bit patterns like the binary 01010101 and 10101010 for the testing, or something like that. Would be interesting.

I have one Nano with the USB connector broken off, so if i decide to burn that one maybe i'll try it too. I would think it would still program using another Arduino as programmer and the typical 4 wire connection to the pins of the Nano. I'll have to look that up again though as i have not done that for probably two years now, using the USB interface for almost everything.

True.

Best thing would be to write/read one memory location until it no longer reads back what was written.

Then, try to write/read all of the other memory locations in the EEPROM.

Track which 3 (if any) EEPROM locations can no longer be written after the 1st location was burnt out.

Figure 2-1. Block Diagram shows the EEPROM on the 8-bit bus side of the AVR CPU, not the wider side where the FLASH and SRAM sit.

CrossRoads:
Figure 2-1. Block Diagram
shows the EEPROM on the 8-bit bus side of the AVR CPU, not the wider side where the FLASH and SRAM sit.

Do you understand what the datasheet means, when it says the EEPROM has a 4 byte page size in Table 28-12?

I still don’t understand the significance of the 4 byte page size. I actually found an Arduino that I could destroy (well…one EEPROM location anyway) with this code:

#include "Arduino.h"
#define EEPROM_SIZE                 1024
#define EEPROM_LOCATION_TO_RUIN     666

#define PRINT_INTERVAL              1000
#define PRINT_INTERVAL2             128

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

void loop()
{
  uint8_t toggle = 0;
  uint8_t read_value;
  uint8_t write_value;
  uint32_t num_writes = 0;

  // Write new values to the EEPROM location to ruin and read back until failure.
  do
  {
    write_value = toggle ? 0xAA : 0x55;
    toggle = 1 - toggle;
    eeprom_write_block(&write_value, (void*)EEPROM_LOCATION_TO_RUIN, sizeof(write_value));
    eeprom_read_block(&read_value, (void*)EEPROM_LOCATION_TO_RUIN, sizeof(read_value));
    num_writes++;
    if((num_writes % PRINT_INTERVAL) == 0)
    {
      Serial.print(num_writes);
      Serial.print(" writes performed to address 0x");
      Serial.println(EEPROM_LOCATION_TO_RUIN, HEX);
    }
  } while (read_value == write_value);

  // Report how many writes were needed for failure.
  Serial.println(F("====================================================="));
  Serial.print(F("EEPROM write failed after "));
  Serial.print(num_writes);
  Serial.println(F(" writes."));
  Serial.print(F("Written = "));
  Serial.print(write_value);
  Serial.print(F(", Read = "));
  Serial.print(read_value);
  Serial.println(".");
  Serial.println(F("====================================================="));

  // Attempt to write to every byte in the EEPROM and report if any fail.

  for(uint16_t address = 0; address < EEPROM_SIZE; address++)
  {
    for(toggle = 0; toggle <= 1; toggle++)
    {
      write_value = toggle ? 0xAA : 0x55;
      eeprom_write_block(&write_value, (void*)address, sizeof(write_value));
      eeprom_read_block(&read_value, (void*)address, sizeof(read_value));
      if(read_value != write_value)
      {
        Serial.print("Could not read from address 0x");
        Serial.println(address, HEX);
        Serial.println(".");
        Serial.print(F("Written = "));
        Serial.print(write_value);
        Serial.print(F(", Read = "));
        Serial.print(read_value);
        Serial.println(".");
      }
    }

    if(((address % PRINT_INTERVAL2) == 0) || (address == EEPROM_LOCATION_TO_RUIN) || (address == EEPROM_SIZE - 1))
    {
      Serial.print("0x");
      Serial.print(address, HEX);
      Serial.println(" written/read.");
    }
  }

 while(1);
}

void EEPROM_Erase(void)
{
  uint16_t address = 0;
  uint8_t erase_row[16];

  while (address < EEPROM_SIZE)
  {
// Enable to clear the entire EEPROM.
    memset(erase_row, 0xFF, sizeof(erase_row));
    eeprom_write_block(erase_row, (void*)address, sizeof(erase_row));
    address += sizeof(erase_row);
  }
}

These were the results:


9582000
9583000
9584000

EEPROM write failed after 9584038 writes.
Written = 170, Read = 162.

0x0 written/read.
0x80 written/read.
0x100 written/read.
0x180 written/read.
0x200 written/read.
0x280 written/read.
Could not read from address 0x29A
.
Written = 170, Read = 162.
0x29A written/read.
0x300 written/read.
0x380 written/read.
0x3FF written/read.

This seems to prove that you can burn out one location, and it has no effect on any other 3 locations. I don’t know why Atmel put that note about the 4 byte page size in the datasheet, however.

Also something to note: I expected to get more than 100k writes, but I didn’t expect to get almost 10M!

BTW - We’re neighbors-ish (I’m in Lowell). Don’t mean to be creepy, but you did put your location in your profile.

"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.

"BTW - We're neighbors-ish (I'm in Lowell). Don't mean to be creepy, but you did your location in your profile." Not a problem. We have Arduino-ites all over the state.

CrossRoads:
“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:

#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.");
  }
}

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!

Just browse frequently.

Your example shows 4 bytes being written & read on 3.4mS. That looks like page write to me.

CrossRoads:
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:

 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).

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.