14
\$\begingroup\$

For example: The datasheet for ATtiny2313 (as do most Atmel AVR datasheets) states:

128 Bytes In-System Programmable EEPROM Endurance: 100,000 Write/Erase Cycles

Imagine a program only requires two bytes to store some configuration, the other 126 bytes are effectively wasted. What concerns me is that regular updates of the two configuration bytes may wear out the device's EEPROM and render it useless. The whole device would become unreliable, because at a certain moment you just can't keep track of which bytes in EEPROM are unreliable.

Is there a smart way to do wear leveling on a microcontroller's EEPROM when you effectively use only one or two bytes out of available 128?

\$\endgroup\$
  • 1
    \$\begingroup\$ If 100k write cycles were a constraint, would it make sense to use some other technology instead? Either a mechanism that incorporates leveling internally, or something with an order of magnitude or more greater endurance? \$\endgroup\$ – Anindo Ghosh Mar 9 '13 at 11:26
  • 1
    \$\begingroup\$ @AnindoGhosh I just don't want to waste my small stock of microcontrollers just because of wearing out EEPROM due to my testing a proof of concept. I don't want to worry about which byte I've been using on an previous project when reusing the controller. And it just feels good to know I make optimum use of the hardware available. \$\endgroup\$ – jippie Mar 9 '13 at 11:34
  • 3
    \$\begingroup\$ This might help: AVR101: High Endurance EEPROM Storage \$\endgroup\$ – m.Alin Mar 9 '13 at 11:44
  • 1
    \$\begingroup\$ Maybe have a look at my answer at stackoverflow. \$\endgroup\$ – JimmyB Mar 27 '13 at 18:53
  • \$\begingroup\$ Have a look at TI's MSP430 FRAM series... 10^13 writes!!! \$\endgroup\$ – geometrikal May 25 '15 at 7:09
20
\$\begingroup\$

The technique I normally use is to prefix the data with a 4-byte rolling sequence number where the largest number represents the lastest / current value. In the case of storing 2 bytes of actual data that would give 6 bytes total and then I form into a circular queue arrangement so for 128 bytes of EEPROM it would contain 21 entries and increase endurance 21 times.

Then when booting the largest sequence number can be used to determine both the next sequence number to be used and the current tail of the queue. The following C pseudo-code demonstrates, this assumes that upon initial programming the EEPROM area has been erased to values of 0xFF so I ignore a sequence number of 0xFFFF:

struct
{
  uint32_t sequence_no;
  uint16_t my_data;
} QUEUE_ENTRY;

#define EEPROM_SIZE 128
#define QUEUE_ENTRIES (EEPROM_SIZE / sizeof(QUEUE_ENTRY))

uint32_t last_sequence_no;
uint8_t queue_tail;
uint16_t current_value;

// Called at startup
void load_queue()
{
  int i;

  last_sequence_no = 0;
  queue_tail = 0;
  current_value = 0;
  for (i=0; i < QUEUE_ENTRIES; i++)
  {
    // Following assumes you've written a function where the parameters
    // are address, pointer to data, bytes to read
    read_EEPROM(i * sizeof(QUEUE_ENTRY), &QUEUE_ENTRY, sizeof(QUEUE_ENTRY));
    if ((QUEUE_ENTRY.sequence_no > last_sequence_no) && (QUEUE_ENTRY.sequence_no != 0xFFFF))
    {
      queue_tail = i;
      last_sequence_no = QUEUE_ENTRY.sequence_no;
      current_value = QUEUE_ENTRY.my_data;
    }
  }
}

void write_value(uint16_t v)
{
  queue_tail++;
  if (queue_tail >= QUEUE_ENTRIES)
    queue_tail = 0;
  last_sequence_no++;
  QUEUE_ENTRY.sequence_no = last_sequence_no;
  QUEUE_ENTRY.my_data = v;
  // Following assumes you've written a function where the parameters
  // are address, pointer to data, bytes to write
  write_EEPROM(queue_tail * sizeof(QUEUE_ENTRY), &QUEUE_ENTRY, sizeof(QUEUE_ENTRY));
  current_value = v;
}

For a smaller EEPROM a 3-byte sequence would be more efficient, although would require a bit of bit slicing instead of using standard data types.

\$\endgroup\$
  • \$\begingroup\$ +1, Nice approach. Can storage be optimized a bit by using less "tag" bytes, and possibly depending on some form of hash bucket mechanism to provide additional distribution? A hybrid between no leveling, and your approach? \$\endgroup\$ – Anindo Ghosh Mar 9 '13 at 11:23
  • \$\begingroup\$ @AnindoGhosh, yes I believe it could. I've normally used this approach on small micros for code simplicity plus personally have mainly used it on larger devices like DataFLASH. One other simple idea that comes to mind also is that the sequence numbers could be periodically lowered to keep them to smaller values. \$\endgroup\$ – PeterJ Mar 9 '13 at 11:29
  • \$\begingroup\$ The Atmel application note mentioned by @m.Alin has a smart simplification: After a RESET it is then possible to look through the [...] buffer, finding the last [...] buffer element changed by finding the location where the difference between a buffer element and the next buffer element is bigger than 1. \$\endgroup\$ – jippie Mar 9 '13 at 12:50
  • \$\begingroup\$ Shouldn't write_value() put the entry at queue_tail*sizeof(QUEUE_ENTRY) ? i will be correct the first time, but shouldn't it continue advancing if there are multiple writes? i is not incremented outside of load_queue(). \$\endgroup\$ – Marshall Eubanks May 25 '15 at 2:13
  • 2
    \$\begingroup\$ @DWORD32: Yes, that's technically correct, but irrelevant in practice. By the time that happens, the wear limit on the EEPROM will have been exceeded by a factor of 2000! \$\endgroup\$ – Dave Tweed Oct 22 '15 at 18:10
5
\$\begingroup\$

Following is a method that uses buckets and about one overhead byte per bucket. The bucket bytes and overhead bytes get about the same amount of wear. In the example at hand, given 128 EEPROM bytes this method allocates 42 2-byte buckets and 44 status bytes, increasing wear capability about 42-fold.

Method:

Divide the EEPROM address space into k buckets, where k =⌊E/(n+1)⌋, with n = setup-data-array size = bucket size, and E = EEPROM size (or, more generally, the number of EEPROM cells to be devoted to this data structure).

Initialize a directory, an array of m bytes all set to k, with m = E-n·k. When your device starts up, it reads through the directory until it finds the current entry, which is a byte not equal to k. [If all directory entries equal k, initialize the first directory entry to 0, and go on from there.]

When the current directory entry contains j, bucket j contains current data. When you need to write a new setup-data entry, you store j+1 into the current directory entry; if that makes it equal to k, initialize the next directory entry to 0, and go on from there.

Note that directory bytes get about the same amount of wear as bucket bytes because 2·k > mk.

(I adapted the above from my answer to Arduino SE question 34189, How to increase life of EEPROM?.)

\$\endgroup\$
2
\$\begingroup\$

I have used a rolling sequence number for this (similar to Peter's answer). The sequence number can actually be as little as 1 bit, providing the number of elements in the cue is odd. The head and tail are then marked by the 2 consecutive 1's or 0's

For example if want to roll through 5 elements the sequence numbers would be:

{01010} (write to 0) {11010} (write to 1) {10010} (write to 2) {10110} (write to 3) {10100} (write to 4) {10101} (write to 5)

\$\endgroup\$
1
\$\begingroup\$

There's a couple of options depending on the kind of EEPROM you have and the size of your data.

  1. If your EEPROM has individually erasable pages and you use 1 page (or more), simply keep all pages erased except the ones in use, and reuse pages in a circular manner.

  2. If you only use a fraction of a page which has to be erased at once, partition that page into data entries. Use a clean entry every time you're writing, and erase once you run out of clean entries.

Use a "dirty" bit to tell between clean and dirty entries if necessary (usually, you have at least one byte which is guaranteed to be different from 0xFF, which can be used to track dirty entries).

If your EEPROM library doesn't expose the erase function (like Arduino), here's a neat trick for algorithm #2: since your first EEPROM entry is always used, you can determine the value of "dirty" bit by reading it. Then once you run out of clean entries, you just start again from the first entry, inverting the "dirty" bit, and the rest of your entries automatically become marked as "clean".

Sequence numbers and catalogs are a waste of space unless you want to be able to track bad pages or update different parts of your EEPROM data independently.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.