I remember when I wanted to use EEPROM in STM32 for the first time. Everything went great until the moment I actually wanted to use that memory. It turned out that… not all STM32s have EEPROM! Those lacking this feature are especially the F series. What to do in such a situation? You can use an external I2C/SPI chip or… emulate EEPROM in the built-in FLASH memory. Come, I’ll show you how to do it.

How does EEPROM work?

I think the first thing is to know what EEPROM is. It is non-volatile memory, meaning its contents are not lost when power is removed. Of course, you can read from it and it has no effect on the data stored in the memory or their lifetime. It is also possible to write, starting from a single byte. An advantage of EEPROM is also that you can quickly write both “zeros” and “ones,” which is not so obvious in the case of FLASH memory. Access to each byte, both for reading and writing, is random.

In this post I deal with EEPROM emulation in the microcontroller’s internal FLASH memory. How does FLASH memory work? The behavior is very similar to EEPROM with one “small” difference. Erasing memory (which is most often writing 0xFF into a cell) is not so free. It is performed in larger areas called sectors or pages depending on the documentation. This is the so-called FLASH erase. These sectors can have different sizes, e.g. 1 kB or even 128 kB. Writing to FLASH is actually setting “zeros” among a fence of “ones.”

Because FLASH memory requires specific erasing, it is not possible to emulate EEPROM behavior 1:1. You cannot allocate a few bytes of FLASH for a few variables because we are not able to “write” ones. Example: attempting to overwrite 0xF0 with the value 0xFF in FLASH memory will not succeed.

What does EEPROM emulation in FLASH look like?

Fortunately, ST provides extensive documentation on how EEPROM emulation in FLASH should look. Today I will discuss it using the STM32F103C8T6 known, among others, from BluePill boards and the STM32F401RE from the Nucleo kit

For these two microcontrollers, ST has prepared appropriate EEPROM emulation documentation:

The principle is the same for both families. I will briefly discuss how the emulation works.

Data representation

Data in FLASH is stored in 32-bit cells. These cells are divided into two parts. One is a 16-bit virtual address of the EEPROM variable. The other part is the value of that variable. Why so? Because each variable update writes its new value to the next free FLASH cell. We do not overwrite the old value.

Why do we need two memory pages?

If each new value of a variable is written to a new cell, eventually those cells will run out. When that happens, the old page is designated for data transfer to a new page. We do not transfer all data. We are not interested in historical data of variables, so we scan the full page from the end and transfer the newest values of variables to the new page. After moving to the new page, it is marked as active, and the old one will be erased entirely (erasing the whole FLASH page).

Reading from the emulated EEPROM

To read a variable at the appropriate virtual address, you need to scan the entire page written so far. Each time a variable with the virtual address we require is encountered, its value is remembered. The next encounter of the same variable updates the remembered temporary value. The search continues until we encounter an erased cell, i.e., one with a virtual address of 0xFFFF. Hence the use of this virtual address is forbidden. The last remembered value is returned as the one currently stored in our EEPROM.

Writing to the emulated EEPROM

Writing searches for the first free cell on the memory page. If it finds one, it writes the given virtual address and the value we provide. If there are no free cells on the page, the procedure of transferring the newest copies of variables with unique virtual addresses begins. Only after that will our variable be written to the first free cell on the new page. The old one will be erased. The pages are used alternately in this way.

Data update flow

You will find the entire process of writing sample data and transferring it to a new page illustrated in the documentation for F1 (p. 8) and F4 (p. 11).

EEPROM emulation library

Reading the documentation and devising a library for correct EEPROM emulation behavior can make your head spin a bit. Fortunately, ST has prepared an appropriate library. In addition, it works out of the box with our favorite HAL. The entire library boils down to only three functions, which makes it incredibly simple to use. You can, however, stretch your brain a bit with its configuration in the eeprom.h file. That’s where the complete configuration of the EEPROM emulator is located. Let me first discuss this configuration. The definitions we are interested in are:

  • #define PAGE_SIZE – defines the size of the pages to be used. You can find this information in the microcontroller’s Reference Manual or in the HAL libraries.
  • #define EEPROM_START_ADDRESS – from which address in memory the EEPROM emulation will be located. I recommend using the end of FLASH so as not to collide with the main program. This address will be the address of the first FLASH page used.
  • #define PAGE0_BASE_ADDRESS 
    #define PAGE0_END_ADDRESS 
    #define PAGE0_ID  – the start and end addresses of the zeroth emulation page and the ID of the first page in FLASH. The same exists for PAGE1 of the EEPROM.
  • #define PAGE0
    #define PAGE1 – the description in the header is misleading and talks about how many FLASH pages are used for EEPROM pages. In fact, the important definition is PAGE1, which says how many pages away it is relative to the start of PAGE0. If you want to use 2 FLASH pages per one EEPROM page, you will enter the number 0x2 in PAGE1.
  • #define NB_OF_VAR  – the number of unique variables we want to store in the emulated EEPROM. This is important because the library needs a table of virtual addresses and you have to create it earlier in the program.

Library functions

As I mentioned, the user has 3 simple functions at their disposal. Here they are:

  • uint16_t EE_Init(void) – initializes the EEPROM emulator. It checks whether pages were previously written (by the header) and whether they need to be erased. Restores usability after power loss.
  • uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t* Data) – Reads a variable from EEPROM. The variable at the virtual address VirtAddress will be placed in the memory pointed to by Data.
  • uint16_t EE_WriteVariable(uint16_t VirtAddress, uint16_t Data) – Writes to EEPROM. The variable at the virtual address VirtAddress will be written with the value Data.

All these functions return status codes compatible with HAL.

Simple to use, right? Time to work with real devices.

EEPROM emulation on STM32F103C8T6

First, I took the popular BluePill. Allow me to skip the Cube configuration and the schematic this time. They’re not important in this exercise. I only configured the built-in LED and UART2 to send the contents of the emulated EEPROM to the terminal.

Configuration

For proper configuration I will need the Reference Manual with the FLASH memory map of the microcontroller.

We are interested in Main memory. We have 128 pages of 1 kB each. For tests I can use the last two. In general, I would recommend using the ending pages. 

In the eeprom.h header file you can notice that the addresses of each page are already predefined. You could even skip looking at the documentation to configure it correctly, but it’s better to make sure no one made a mistake in the code.

The page size is already defined in the HAL library. You can just make sure it is about 1 kB, i.e., 0x400.

We want to use the last two pages, so I set EEPROM_START_ADDRESS to the penultimate page, which is ADDR_FLASH_PAGE_126.

Now the addresses of the two EEPROM emulation pages and the number of pages used. You can use the previously predefined addresses or play with offsets.

#define PAGE0_BASE_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + 0x0000))
#define PAGE0_END_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + (PAGE_SIZE - 1)))
#define PAGE0_ID ADDR_FLASH_PAGE_126

#define PAGE1_BASE_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + 0x400))
#define PAGE1_END_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + 0x400 + PAGE_SIZE - 1))
#define PAGE1_ID ADDR_FLASH_PAGE_127

/* Used Flash pages for EEPROM emulation */
#define PAGE0 ((uint16_t)0x0000)
#define PAGE1 ((uint16_t)0x0001)

And the number of stored variables.

#define NB_OF_VAR ((uint8_t)27)

Program code

For the library to work, you need to create an array of virtual addresses.

uint16_t VirtAddVarTab[NB_OF_VAR];

Later I fill this array with consecutive values.

// Fill EEPROM variables addresses
for(VarIndex = 1; VarIndex <= NB_OF_VAR; VarIndex++)
{
  VirtAddVarTab[VarIndex-1] = VarIndex;
}

It is very important to unlock FLASH from the program level before using the function that initializes the library.

HAL_FLASH_Unlock();

Now you can initialize the EEPROM. 

if( EE_Init() != HAL_OK)
{
  Error_Handler();
}

From now on, you can use the EEPROM. In the program I created an array with sample data that I will write to EEPROM. It contains the string “Mateusz Salamon msalamon.pl”. It consists of 27 characters, which I previously took into account in the configuration.

In the program I write and read this data from the EEPROM according to the scheme:

  1. Write the data to EEPROM. Each character at its unique virtual address.
  2. Read and print to the terminal
  3. Write the data to EEPROM in reverse order.
  4. Read and print to the terminal. The text should be reversed.
  5. Write the data to EEPROM. 
  6. Read and print to the terminal. The effect is the same as in pt. 2

You can see the effects from the terminal in the screenshot.

And what happens to FLASH memory? What do I expect? According to the EEPROM emulation documentation, the text should land under variables with virtual addresses 1–27 first in normal order, then reversed, and finally normal again. The result is below.

Data marked 1 are from the first write. 2 is the reversed write. 3 is a subsequent write in normal order. Please analyze this data. The first cell at the top left is the page header. It is in the ERASED state, so you can write to this page.

Then there is the data. The first 16 bits (4 digits) are the virtual address of each variable. The second 16 bits are the value, i.e., the characters from the string.

EEPROM emulation on STM32F401RE

The organization of memory in microcontrollers can vary. For this reason, you should always check the documentation. In the F4 series we can be slightly surprised because the FLASH memory looks like this:

We have only 8 sectors and they are not equal in size. Remember that FLASH erasing is done per entire sector. It would be sad to use 256 kB of FLASH for a few EEPROM variables. That’s why I will use sectors 2 and 3. Configuration:

/* Define the size of the sectors to be used */
#define PAGE_SIZE (uint32_t)0x4000 /* Page size = 16KByte */

/* Device voltage range supposed to be [2.7V to 3.6V], the operation will
be done by word */
#define VOLTAGE_RANGE (uint8_t)VOLTAGE_RANGE_3

/* EEPROM start address in Flash */
#define EEPROM_START_ADDRESS ((uint32_t)0x08008000) /* EEPROM emulation start address:
from sector2 : after 16KByte of used
Flash memory */

/* Pages 0 and 1 base and end addresses */
#define PAGE0_BASE_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + 0x0000))
#define PAGE0_END_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + (PAGE_SIZE - 1)))
#define PAGE0_ID FLASH_SECTOR_2

#define PAGE1_BASE_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + 0x4000))
#define PAGE1_END_ADDRESS ((uint32_t)(EEPROM_START_ADDRESS + (2 * PAGE_SIZE - 1)))
#define PAGE1_ID FLASH_SECTOR_3

/* Used Flash pages for EEPROM emulation */
#define PAGE0 ((uint16_t)0x0000)
#define PAGE1 ((uint16_t)0x0001) /* Page nb between PAGE0_BASE_ADDRESS & PAGE1_BASE_ADDRESS*/

The number of variables I set the same as for F1

/* Variables' number */

#define NB_OF_VAR ((uint8_t)27)

The code in main.c is the same. The result on the terminal is identical to F1, and in FLASH memory it looks similar, except at a different address.

I also checked how long it takes to write and read my character array.

173 ms for write and 3.24 ms for read. Interestingly, these values do not change significantly with increasing FLASH occupancy. There is one moment when this time will be very large. This is the transfer of data to a new EEPROM page and the erasing of FLASH sectors.

EEPROM emulation before the main program

As you probably noticed, I defined EEPROM emulation in the middle of the F401RE FLASH. I would be reluctant to use half of the available memory for EEPROM if I defined it on the last two sectors.

For this reason, I came up with the idea to put the EEPROM on the first two sectors and move the main program to sector 2. Unfortunately, performing this operation causes some error in the program’s operation and the microcontroller crashes… I dug into the code a bit and found a problem in the function that verifies whether the page is erased. It is used in the initialization where the program crashes. There is even a thread on ST’s forum regarding this bug.

Unfortunately, my attempts to fix this snippet did not help. Perhaps the problem is still somewhere else, or it is simply not possible to swap EEPROM with the main program.

The only thing that comes to my mind while writing this article is to split the project into a bootloader and a main program. The bootloader would be run from the starting address and would jump to the main application, which is located after the next two empty sectors. Then the piece of FLASH between the bootloader and the main program could be used for EEPROM emulation 🙂

Summary

Is it worth using EEPROM emulation? Judging by the fact that the code has had easy-to-find bugs for many years, I suspect few people use it. The operation of EEPROM emulation is also not ideal. It takes at least two sectors (pages) of FLASH memory.

As you saw, in F4 series microcontrollers, EEPROM emulation can be very troublesome due to the different sizes of memory sectors.

There is one more issue I did not address. The lifetime of this solution. FLASH memory is not a longevity champion. A single memory cell can withstand only about 10 thousand write cycles. This is not a large number, especially compared to the lifetime of EEPROM at around one million cycles (AT24C32 memories from Atmel).

So the question arises — is it worth emulating EEPROM? Sometimes yes, sometimes no 🙂 You need to answer this individually. It depends on the project. You can use an STM32 that already has EEPROM in its structure. You can use an external chip. There are at least several solutions. 

However, it is worth knowing that in extreme situations you can use FLASH memory as an emulated EEPROM. It works correctly, so for rarely changed data it can be quite a good way.

You can find the test codes along with libraries on my GitHub: F103C8, F401RE

Do you think I made a mistake somewhere? Do you have an interesting idea for what could be improved? Share it in the comments! Remember that the discussion should be polite and in accordance with the rules of the Polish language.

kurs stm32 — content is in Polish.

Contest results

Recently I announced a small contest. From the entries I chose three winners. They are:

  1. Leoneq ;3  with the comment 
    “Hmm, I would most like to see an article about generating a VGA image. There is some library on Arduino, but 120×60 pixels is poor, not to mention two timers occupied. On an STM, generating an image shouldn’t be so demanding, and it would give looooots of possibilities for new projects
    ?
     Btw, I’ve been following your blog for a long time, and I have to say that it maintains a high level. Nicely written, to the point and interesting. Regards 
    ?
  2. Mruczek  with the comment
    “Communication! Ethernet – the one on the big Nucleo boards, using ESP8266 as WiFi, NRF24L01+, which is great for low-power applications with wireless communication.”
  3. Mateusz with the comment
    “Hello 
    ?
     I am delighted that the blog already has topics that interested me, including displays and the accelerometer. But I would be more interested in using both the FPU and the ADC options in ‘more advanced’ signal processing. The topic could also include simple processing procedures such as FFT to change from the time domain to the frequency domain, as well as simple/more complex signal conditioning systems into ones friendly for the processor, measurement relative to a virtual ground, etc. I think that despite the omnipresent digitization, such a topic could be useful 
    ?

Congratulations to the winners! Contact me by email: mateusz@msalamon.pl to finalize the delivery of the prizes 🙂

Podobne artykuły

.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *