Electronic – Flash EEPROM Emulation

eepromembeddedemulatorflashmicrocontroller

Given that there are many differences in the way data is written to EEPROM and Flash, how exactly is EEPROM emulation done with internal flash?

Also, flash needs to be erased in large blocks before data can be written over. In that case, if we want to write only a small amount of data in emulated EEPROM without disturbing the other contents, how is it achieved? What are the software techniques for this?

Best Answer

There are probably many different algorithms to accomplish EEPROM emulation without forcing a lot of unnecessary writes. The main concept is like a ledger (except values don't depend on previous values). The data set or a variable has many "records" in the ledger. When a new value is written, the old value must be invalidated. For example, you can invalidate the old value by writing some magic value which means "that record is invalid". Then, a new record can be created. Looking up a value means reading through the ledger to find the current record. All invalidated records can be ignored. Flash pages can be erased when full (the values must all be invalid or be migrated to a new flash page before erasing).

Here's a simple, concrete algorithm example:

Let's say I have a data set of 6 bytes. I will devote 2 flash pages to emulated EEPROM for my 6 byte data set. Make the following assumptions:

  • Assume my flash pages are significantly larger than 6 bytes.
  • Assume that flash after being erased is value 0xFF.
  • Assume that I can re-write flash repeatedly without erasing, but only change ones to zeros (1->0).
  • Assume that my flash page is full when I can't fit any more full records.
  • I am not paying attention to endianness (you'll get the picture).

Flash page header

I'll start my flash page with some sort of header to indicate that I am actively storing data there (or not). Let me use 4 bytes at the start of the flash page which indicate one of the following conditions:

  1. 0x5555FFFF: This page is currently in use (the current value is one of the records).
  2. 0x55555555: This page is completely full (no current values can be found in this page). (At this point, the page should be erased before it is needed again).
  3. 0xFFFFFFFF: This page is completely erased.
  4. Anything else: Error. (Should not happen.)

Record header

For each record, I will start with a header also. Let me use 2 bytes, indicating one of the following:

  1. 0x55FF: This is the current record.
  2. 0x5555: This record is invalidated.
  3. 0xFFFF: This record slot has not been filled since the last erase.
  4. Anything else: Error. No record header slot should have anything else.

Flash pages start erased

Flash Page #0
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Flash Page #1
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

System initialization

After detecting that the EEPROM data has not been initialized, I will begin by initializing my data set to all 0s:

Flash Page #0
5555FFFF // Page Header (current value here: 0x5555FFFF header.)
55FF0000 // Record slot #0 (current value: 0x55FF header.)
00000000
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Flash Page #1
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Notice that bytes 2-3 were not written and remain 0xFFFF. When this flash page fills up and I start a new flash page, I will write 0x5555 over bytes 2-3.

The record header essentially works the same. Byte 1 of the record #0 header remains 0xFF indicating that it is the current record (because byte 0 is 0x55).

Write the value 0xDEADBEEFCAFE

In this order:

  1. Write the new record's data.
  2. Write 0x55 to byte 0 of the new record (record slot #1) to indicate that it is the current record.
  3. Finally, write 0x55 to byte 1 of the old record (record slot #0) to invalidate it.

Order is important for recovery from spurious resets (this is similar to a journal in a filesystem).

Flash Page #0
5555FFFF // Page Header (current value here: 0x5555FFFF header.)
55550000 // Record slot #0 (now invalid: 0x5555 header.)
00000000
55FFDEAD // Record slot #1 (current value: 0x55FF header.)
BEEFCAFE
...

Flash Page #1
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Write the value 0x12345678ABCD

Flash Page #0
5555FFFF // Page Header (current value here: 0x5555FFFF header.)
55550000 // Record slot #0 (now invalid: 0x5555 header.)
00000000
5555DEAD // Record slot #1 (now invalid: 0x5555 header.)
BEEFCAFE
55FF1234 // Record slot #2 (current value: 0x55FF header.)
5678ABCD
...

Flash Page #1
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

The current page is full

This is what the pages look like when the current page (Page #0) is completely full (the current value is 0xAAAA5555BBBB):

Flash Page #0
5555FFFF // Page Header (current value here: 0x5555FFFF header.)
55550000 // Record slot #0 (now invalid: 0x5555 header.)
00000000
5555DEAD // Record slot #1 (now invalid: 0x5555 header.)
BEEFCAFE
55551234 // Record slot #2 (now invalid: 0x5555 header.)
5678ABCD
...
55FFAAAA // Final record slot (current value: 0x55FF header.)
5555BBBB

Flash Page #1
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Notice that flash page #0 is not yet invalidated (the current record is the last record in flash page #0).

Write the value 0x80009000ABCD

The next value must go into flash page #1. After writing the new record in flash page #1 and creating the page header to indicate that flash page #1 now contains the current value, we invalidate flash page #0 (write 0x55 to bytes 2-3) and the last record in flash page #0 (write 0x55 to the last record's header byte 1).

Flash Page #0
55555555 // Page Header (page is all now invalid: 0x55555555 header.)
55550000 // Record slot #0 (now invalid: 0x5555 header.)
00000000
5555DEAD // Record slot #1 (now invalid: 0x5555 header.)
BEEFCAFE
55551234 // Record slot #2 (now invalid: 0x5555 header.)
5678ABCD
...
5555AAAA // Final record slot (now invalid: 0x5555 header.)
5555BBBB

Flash Page #1
5555FFFF // Page Header (current value here: 0x5555FFFF header.)
55FF8000 // Record slot #0 (current value: 0x55FF header.)
9000ABCD
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Now, flash page #0 is completely invalid, and the new page is Flash page #1.

Flash page #0 is garbage collected (erased)

Finally, flash page #0 can be erased when the system finds it convenient.

Flash Page #0
FFFFFFFF // Page Header (unused: 0xFFFFFFFF header.)
FFFFFFFF // Record slot #0 (unused: 0xFFFF header.)
FFFFFFFF
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Flash Page #1
5555FFFF // Page Header (current value here: 0x5555FFFF header.)
55FF8000 // Record slot #0 (current value: 0x55FF header.)
9000ABCD
FFFFFFFF // Record slot #1 (unused: 0xFFFF header.)
FFFFFFFF
...

Record lookup, real-world complexities

In the real world, the data set would be larger and more complex than a single 6-byte chunk. There are many complexities I glossed over. This is a really simple example, but it would work. It illustrates how EEPROM can be emulated in flash without excessive erasing by creating a ledger of value records.

To look up the current data set values, find which flash page is current (one and only one flash page should have the header 0x5555FFFF). Then, read through the records in that page sequentially until finding the current record (one and only one record should have record header 0x55FF). Errors could invalidate the header configuration, potentially leading to multiple pages and/or records which appear to be current. A real application would need to handle such errors.