26
\$\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\$
5
  • 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\$ Mar 9, 2013 at 11:26
  • 4
    \$\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, 2013 at 11:34
  • 4
    \$\begingroup\$ This might help: AVR101: High Endurance EEPROM Storage \$\endgroup\$
    – m.Alin
    Mar 9, 2013 at 11:44
  • 2
    \$\begingroup\$ Maybe have a look at my answer at stackoverflow. \$\endgroup\$
    – JimmyB
    Mar 27, 2013 at 18:53
  • 1
    \$\begingroup\$ Have a look at TI's MSP430 FRAM series... 10^13 writes!!! \$\endgroup\$ May 25, 2015 at 7:09

5 Answers 5

29
\$\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\$
9
  • 1
    \$\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, 2013 at 12:50
  • 1
    \$\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\$ May 25, 2015 at 2:13
  • 1
    \$\begingroup\$ @marshaul, thanks yes that was a typo and it should have been that way, I've just updated it. \$\endgroup\$
    – PeterJ
    May 25, 2015 at 2:19
  • 5
    \$\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, 2015 at 18:10
  • 3
    \$\begingroup\$ On the other hand, if you properly handle rollover, then you can reduce the tag to 1 or 2 bytes., which in a small EEProm like this would be a big savings if your data size is only 2 bytes. \$\endgroup\$
    – Evan
    May 9, 2017 at 17:38
8
\$\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\$
1
  • 2
    \$\begingroup\$ For me it was a bit hard to understand the description, but after I simulated how it would go in my head, it became clear. The solution is superior to the accepted answer in terms of storage efficiency / wear reduction. E.g. with 21 buckets as in the accepted answer, you can store 5 bytes of data instead of 2. \$\endgroup\$ Oct 6, 2020 at 12:13
5
\$\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\$
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\$

I believe there's a simple way to use only n cells and a single dedicated bit pattern to achieve n/2 increase in durability.

The dedicated bit pattern (e.g. 0xffff) is a marker which indicates an unused cell, and in normal operation at most one cell at a time has any values besides this marker value:

uint16_t readWord (const uint16_t *address);
void writeWord (uint16_t *address, uint16_t value);

#define CELL_COUNT   42
#define BASE_ADDRESS 12
#define MARKER       0xffff  // Best to use EEPROM default for this

static uint8_t curCell = 0;

static void wlInit (void)
{
  for ( uint8_t ii = 0 ; ii < CELL_COUNT ; ii++ ) {
    if ( readWord (BASE_ADDRESS + ii) != MARKER ) {
      curCell = ii;
      // No early return/break here to keep init() execution time 
      // constant.  Could also check for corruption here by checking for
      // > 1 cells without MARKER (see below).
    }
  }
  // If we didn't find a non-marker cell we'll end up with curCell = 0 
  // per the initial value of curCell
}

static uint16_t wlRead (void)
{
  return readWord (BASE_ADDRESS + curCell);
}

static void wlWrite (uint16_t value)
{
  assert (value != MARKER);   // Storing marker value is big no-no

  uint8_t nextCell = (curCell + 1) % CELL_COUNT;
  writeWord (BASE_ADDRESS + nextCell, value);
  writeWord (BASE_ADDRESS + curCell, MARKER);
  curCell = nextCell;
}

The same approach can be used with structured data provided a bit pattern can be reserved (either in a dedicated byte or from some other field that has a spare pattern).

This approach also has a useful property with respect to interrupted (e.g. power drop) writes: if there are ever two cells without the MARKER value an incomplete (i.e. corrupted) write has occurred. This seems useful, because e.g. the ATmega328P datasheet contains the following unclear and not-particularly-reassuring paragraph about using brown-out detection to avoid corruption:

Keep the AVR RESET active (low) during periods of insufficient power supply voltage. This can be done by enabling the internal Brown-out Detector (BOD). If the detection level of the internal BOD does not match the needed detection level, an external low V CC reset Protection circuit can be used. If a reset occurs while a write operation is in progress, the write operation will be com- pleted provided that the power supply voltage is sufficient.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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