STM32 FLash memory – EEPROM Emulator

STM32 Cortex microcontroller are powerful and very popular replace for 8-bit Atmel or Microchip devices. A lot of people switched to stm32 because the availability of free IDE’s, price, easy to use driver library, peripherals and DMIPS are unbeatable. Sometimes it’s necessary to store information which must be available after the next power cycle. Stm32 didn’t integrated EEPROM in their devices, but the user have the full control about the flash memory. So why not to save the data in Flash?

Difference matters
There are some disadvantages when flash is used for storing the data:
– write cycles are limited to 10k-100k, while an eeprom can have up to 1000k and and a fram much more.
– A flash can be written byte wise but before the byte can be rewritten, the whole flash page must be erased.

For applications where just calibration or other parameters must be stored one time only and you will don’t need permanent update, internal flash is a good solution. For this data you will also have very few CPU cycles per read access and access time times shorter compared to an eeprom.

St microelectronics provides an Appnote which describes how to use Flash as an EEPROM emulator. The solution is good but would in some cases blow up you project unnecessary. Therefore I made my own small solution which I’m going to share

Take a look on Flah setting in uc_memory.h The Flash page size may change from uC to uC so you will need some modifications. You have also to specify the count of pages you are going to use for EEPROM Emulation.

#define FEE_DENSITY_PAGES 4 // how many pages are used
#define FEE_PAGE_SIZE 1024 // can be 1k or 2k check manual for used device
#define FEE_PAGE_BASE_ADDRESS 0x0801F000 // select first Emulator Page address

uc_memory.c

#include 
#include 
#include "stm32f10x_usart.h"
#include "stm32f10x_flash.h"
#include "uc_memory.h"
/*****************************************************************************
 * Allows to use the internal flash to store non volatile data. To initialize
 * the functionality use the FEE_Init() function. Be sure that by reprogramming
 * of the controller just affected pages will be deleted. In other case the non
 * volatile data will be lost.
******************************************************************************/

/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Functions -----------------------------------------------------------------*/

uint8_t DataBuf[FEE_PAGE_SIZE];
/*****************************************************************************
*  Delete Flash Space used for user Data, deletes the whole space between
*  RW_PAGE_BASE_ADDRESS and the last uC Flash Page
******************************************************************************/
uint16_t
FEE_Init(void) {
	// unlock flash
	FLASH_Unlock();

	// Clear Flags
	FLASH_ClearFlag(FLASH_FLAG_EOP|FLASH_FLAG_PGERR|FLASH_FLAG_WRPRTERR);

	return FEE_DENSITY_BYTES;
}
/*****************************************************************************
*  Erase the whole reserved Flash Space used for user Data
******************************************************************************/
void
FEE_Erase (void) {

	int page_num = 0;

	// delete all pages from specified start page to the last page
	do {
		FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE));
		page_num++;
	} while (page_num < FEE_DENSITY_PAGES);
}
/*****************************************************************************
*  Writes once data byte to flash on specified address. If a byte is already
*  written, the whole page must be copied to a buffer, the byte changed and
*  the manipulated buffer written after PageErase.
*******************************************************************************/
uint16_t
FEE_WriteDataByte (uint16_t Address, uint8_t DataByte) {

	FLASH_Status FlashStatus = FLASH_COMPLETE;

	uint32_t page;
	int i;

	// exit if desired address is above the limit (e.G. under 2048 Bytes for 4 pages)
	if (Address > FEE_DENSITY_BYTES) {
		return 0;
	}

	// calculate which page is affected (Pagenum1/Pagenum2...PagenumN)
	page = (FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address)) & 0x00000FFF;

	if (page % FEE_PAGE_SIZE) page = page + FEE_PAGE_SIZE;
	page = (page / FEE_PAGE_SIZE) - 1;

	// if current data is 0xFF, the byte is empty, just overwrite with the new one
	if ((*(uint16_t*)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address))) == FEE_EMPTY_WORD) {

		FlashStatus = FLASH_ProgramHalfWord(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address), (uint16_t)(0x00FF & DataByte));
	}
	else {

		// Copy Page to a buffer
		memcpy(DataBuf, (uint8_t*)FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE), FEE_PAGE_SIZE); // !!! Calculate base address for the desired page

		// check if new data is differ to current data, return if not, proceed if yes
		if (DataByte == *(uint8_t*)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address))) {
			return 0;
		}

		// manipulate desired data byte in temp data array if new byte is differ to the current
		DataBuf[FEE_ADDR_OFFSET(Address)] = DataByte;

		//Erase Page
		FlashStatus = FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + page);

		// Write new data (whole page) to flash if data has beed changed
		for(i = 0; i < (FEE_PAGE_SIZE / 2); i++) {
			if ((uint16_t)(0xFF00 | DataBuf[FEE_ADDR_OFFSET(i)]) != 0xFFFF) {
				FlashStatus = FLASH_ProgramHalfWord((FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE)) + (i * 2), (uint16_t)(0xFF00 | DataBuf[FEE_ADDR_OFFSET(i)]));
			}
		}

	}
	return FlashStatus;
}
/*****************************************************************************
*  Read once data byte from a specified address.
*******************************************************************************/
uint8_t
FEE_ReadDataByte (uint16_t Address) {

	uint8_t DataByte = 0xFF;

	// Get Byte from specified address
	DataByte = (*(uint8_t*)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address)));

	return DataByte;
}

uc_memory.h

#ifndef UC_MEMORY_H
#define UC_MEMORY_H

	// CAN BE CHANGED
	#define FEE_DENSITY_PAGES	4	    // how many pages are used 
	#define FEE_PAGE_SIZE		1024	    // can be 1k or 2k check manual for used device
	#define FEE_PAGE_BASE_ADDRESS 	0x0801F000  // choose location for the first EEPROMPage address on the top of flash

	// DONT CHANGE
	#define FEE_DENSITY_BYTES		((FEE_PAGE_SIZE / 2) * FEE_DENSITY_PAGES - 1)
	#define FEE_LAST_PAGE_ADDRESS 	(FEE_PAGE_BASE_ADDRESS + (FEE_PAGE_SIZE * FEE_DENSITY_PAGES))
	#define FEE_EMPTY_WORD			((uint16_t)0xFFFF)
	#define FEE_ADDR_OFFSET(Address)(Address * 2) // 1Byte per Word will be saved to preserve Flash

	// use this function to initialize the functionality
	uint16_t FEE_Init(void);
	void FEE_Erase (void);
	uint16_t FEE_WriteDataByte (uint16_t Address, uint8_t DataByte);
	uint8_t FEE_ReadDataByte (uint16_t Address);

#endif

Click to rate this post!
[Total: 3 Average: 3.7]

10 thoughts on “STM32 FLash memory – EEPROM Emulator”

  1. If you use special algorithm, then you dont have every time to erase page after byte is writed to specified address, this will increase write cycle count, to do than you can use virtual address, at first – find not used address, then in this address write virtual address number and in next address write data, and that’s all, the most last virtual address in flash is most newest, if page is full then read most last virtual address numbers and data to buffer, erase page and write back, or write to other page, to read data you have to search for virtual address address and read data from next address, almost similar algorithm is explained in st.com flash emulation documentation.

    1. Hi Darius, it would work for some applications like data logging, but not for applications where you write some calibration data and have to know on which addresses the data is stored.

  2. Hi, Why are you using this?

    #define FEE_ADDR_OFFSET(Address)(Address * 2) // 1Byte per Word will be saved to preserve Flash

    Many thanks,
    Mathias

    1. Hi Mathias,
      it’s because the flash can only be written wordwise. Since flash is quite limited in write cycles, bytewise approach would reduce write cycles. In case you just need some calibration data or limited/predictable number of writes, you can pack bytes into a word.

    1. Hi Sam, I know It’s a bit outdated.
      If you are going to use HAL, please adapt function names to HAL library on yourself.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.