7 years, 11 months ago.

Is there a way to store variables in a non-volatile memory without EEPROM?

So I'm using the STM32F411 Nucleo-64. I'm making a system for my PC that controls the cooling system as well as other things. The problem I have is that I want to store things like the previously selected fan speed when the PC is powered down so that upon start up the fan is at the same speed. Normally I would use EEPROM but this board doesn't have any available, I was wondering if it was possible to use one of the memory blocks used for the program memory for data storage.

int Fanspeed __attribute__((at(0x08060000)));// store pwm values for power off

I was thinking of using something like this, the location shown above is in the final block of memory so shouldn't ever be used as my program is fairly small. I'm just not sure that I can simply write and read like this as if it was just a variable.

4 Answers

7 years, 8 months ago.

Don't know if you got it yet but I've just come across the same issue. Here is the code that emulates EEPROM using Flash for STM32F103

https://developer.mbed.org/users/olympux/code/eeprom_flash/

This code use HAL flash from mbed rather than AN2594 application note from ST, which you can have a look here:

https://developer.mbed.org/users/olympux/code/eeprom/

The latter one, based on AN2594, worked fine for me, but recently has broken with recent mbed updates. It's still fine with previous mbed revisions though.

Accepted Answer
7 years, 11 months ago.

You have to use the IAP options of your device. Since they didn't standardize it for STM32s it was a bit hard to write a library for it and I don't think one exists.

As example which can get you started (alternative is using the STM32 hal drivers), the .cpp file of a library I once started for this purpose:

#include "StmIAP.h"

#ifdef TARGET_STM
void unlock_flash(bool unlock);         //(Un)lock flash memory
IAPCode check_error(void);              //Return errors
uint8_t sector_number(uint32_t addr);   //Return sector number of the given flash address

IAPCode erase_sector(int address) {
    unlock_flash(true);
    
    //Clear current errors
    FLASH->SR = FLASH_SR_WRPERR | FLASH_SR_PGAERR | FLASH_SR_PGPERR | FLASH_SR_PGSERR;
    
    //Run command
    FLASH->CR = FLASH_CR_SER | (sector_number(address) << 3);
    FLASH->CR |= FLASH_CR_STRT;
    
    IAPCode retval = check_error();
    
    unlock_flash(false);
    
    return retval;
}

IAPCode program_flash(int address, char *data, unsigned int length) {
    unlock_flash(true);
    
    FLASH->CR = FLASH_CR_PG;
    
    uint8_t* write_addr = (uint8_t*)address;
    for (int i = 0; i<length; i++) {
        while (FLASH->SR & FLASH_SR_BSY);
        write_addr[i] = data[i];
    }
        
    
    IAPCode retval = check_error();
    FLASH->CR = 0;
    
    unlock_flash(false);
    
    return retval;
}

uint8_t sector_number(uint32_t addr) {
    uint8_t retval = 0;
    while(1) {
        //If start address of next sector is higher than wanted address, return current value
        if (addr < flash_sectors[retval + 1])
            return retval;
        
        retval++;
    }
}

IAPCode check_error(void) {    
    //Wait until done
    while (FLASH->SR & FLASH_SR_BSY);
    
    //Check for errors
    if (FLASH->SR & FLASH_SR_WRPERR)
        return WriteProtError;
    if (FLASH->SR & FLASH_SR_PGAERR)
        return AllignmentError;
    if (FLASH->SR & FLASH_SR_PGPERR)
        return ParallelismError;
    if (FLASH->SR & FLASH_SR_PGSERR)
        return SequenceError;
    
    return Success;
}

uint32_t flash_size(void) {
    return *flash_size_addr * 1024;
}

uint32_t sector_size(uint32_t addr) {
    uint8_t sector = sector_number(addr);
    return flash_sectors[sector+1] - flash_sectors[sector];
}

void unlock_flash(bool unlock) {
    if (unlock) {
        __disable_irq();
        //Wait until not busy
        while (FLASH->SR & FLASH_SR_BSY);
        FLASH->KEYR = 0x45670123;
        FLASH->KEYR = 0xCDEF89AB;
    } else {
        FLASH->CR |= FLASH_CR_LOCK;
        __enable_irq();
    }
}
    

#endif

Remember flash you can always read, writing needs something special (although on the STM32F4s it is quite nice made where you can write to the address the normal way after you gave some commands), but before writing you need to make sure it is erased, which goes per sector.

7 years, 11 months ago.

To EEPROM emulation in the STM32, where there is no hardware EEPROM, there is a very useful library from STM. Using this library has many advantages, eg. increasing the lifetime of flash memory and higher speed by eliminating every time memory erase. And it is very easy to use
The description is here:
https://my.st.com/content/my_st_com/en/products/embedded-software/mcus-embedded-software/stm32-embedded-software/stm32-standard-peripheral-libraries-expansions/stsw-stm32066.html
However, in this application note are the source based on the SPL. But I tested a different version adapted to the library HAL (from CubeMX) and it works very well with mbed. I did a quick and simple test and there are also the source of this library:

Import program00_eeprom_emulation_f401

simple test EEPROM emulation for STM32F401 (STM algorithm described in the application notes: AN4061, AN3969, AN2594, AN3390, AN4056).


The only restriction is that the variables are only the size of two bytes (uint16_t), but you can use them to store the variables of a different size. If necessary, in a file eeprom.h you need to redefine the address of free space in flash memory (EEPROM_START_ADDRESS) and the number of used variables (NB_OF_VAR).

What modifications would be needed to work with STM32F091RC?

posted by DOUG BELL 22 Sep 2016

Try this version. It is based on code from the STM, for your Nucleo F091. Unfortunately I do not have the possibility to test this code.

Import program00_eeprom_emulation_f091

simple test EEPROM emulation (STM algorithm described in the application notes: AN4061, AN3969, AN2594, AN3390, AN4056) for STM32F091



Note that in the F091 flash pages are smaller than in the F401.
2KB which gives less than 1KB for variables.

posted by Nothing Special 22 Sep 2016

Thanks, NS. I will test and report back... It works as is! I will try in my program...

QUESTION: Why does your printf work? For printing, I have to do: Serial pc(USBTX, USBRX);

..and then print with: pc.print()

posted by DOUG BELL 23 Sep 2016

QUESTION 2: How can I increase EEPROM emulation (flash) size? I need more than 2 pages. (2KB) The app not AN4061 says it should be done, but not how.

posted by DOUG BELL 23 Sep 2016

1. This version of the printf function works much like the class method Serial. It operates at Nucleo F091 on default parameters, ie. USBTX, USBRX 9600.
It should work. If this does not work, use the Serial class.

2. In this program here above(00_eeprom_emulation_f091) I added the possibility of increasing the size of EEPROM. Download the new version.
I have not tested this on the F091. But a similar program (in F030 flash pages are smaller than the F091) I have tested at Nucleo F030 and works well.

posted by Nothing Special 24 Sep 2016

Thanks, NS!

posted by DOUG BELL 25 Sep 2016

I will set NB_OF_VAR to the number of 16-bit variables that I need for storage (eg. 1000), right? And so VirtAddVarTab[NB_OF_VAR] must have 1000 elements, that is: VirtAddVarTab[1000] = { 1, 2, 3, ..., 999, 1000 }; Right?

What is maximum number (NB_OF_VAR) of 16-bit variables ? 65535?

posted by DOUG BELL 20 Dec 2016

Yes. If you need to increase this variable (in eeprom.h), then at the same time increase the number of array elements in VirtAddVarTab.
Although NB_OF_VAR is declared in #define macro as uint8_t, I suspect that you can use numbers range 1 ... 32768.
But the main limitation is the size of the "virtual" page ie. number of variables is the size of "virtual" page (in bytes) divided by four.
Where the virtual page size, is the size of flash pages multiplied by PAGE_NB_PVP. What can you change.

posted by Nothing Special 20 Dec 2016

I set EEPROM_START_ADDRESS and PAGE0.. = ADDR_FLASH_PAGE_118 and PAGE1_.. to ADDR_FLASH_PAGE_121 to move flash data away from code. NB_OF_VAR = 1000 reads and writes 1000 words ok. But NB_OF_VAR = 2000 fails

posted by DOUG BELL 20 Dec 2016

If you do not have not changed PAGE_NB_PVP = 4, then page1 should start from (at least) ADDR_FLASH_PAGE_122, not 121.

PAGE0 - flash page 118,119,120,121
PAGE1 - Flash pages 122,123,124,125

posted by Nothing Special 20 Dec 2016

With NB_OF_VAR = 1000, all works ok.. no delays. Writing takes a second or two. With NB_OF_VAR = 2000, then writing takes 2-3 minutes, using EE_WriteVariable.

Here is my eeprom.h ....................................

define PAGE_NB_PVP ((uint32_t) 4)

define PAGE_SIZE ((uint32_t)FLASH_PAGE_SIZE * PAGE_NB_PVP) /* Page size */

define EEPROM_START_ADDRESS ((uint32_t)ADDR_FLASH_PAGE_118)

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

define PAGE1_BASE_ADDRESS ((uint32_t)(ADDR_FLASH_PAGE_122)) define PAGE1_END_ADDRESS ((uint32_t)(ADDR_FLASH_PAGE_122 + PAGE_SIZE - 1))

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

PAGE1_BASE_ADDRESS*/

/* No valid page define */ define NO_VALID_PAGE ((uint16_t)0xFFFF)

/* Page status definitions */ define ERASED ((uint16_t)0xFFFF) /* Page is empty */ define RECEIVE_DATA ((uint16_t)0xEEEE) /* Page is marked to receive data */ define VALID_PAGE ((uint16_t)0x0000) /* Page containing valid data */

/* Valid pages in read and write defines */ define READ_FROM_VALID_PAGE ((uint8_t)0x00) define WRITE_IN_VALID_PAGE ((uint8_t)0x01)

/* Page full define */ define PAGE_FULL ((uint8_t)0x80)

/* Variables' number */ define NB_OF_VAR ( 2000 )

posted by DOUG BELL 25 Dec 2016

I tried to create a situation like yours, but I don't have the F091. I did tests on the Nucleo-F030 (the only difference is a different flash page size) using this program:

#include "mbed.h"
#include "eeprom.h"


Timer t;

uint16_t VirtAddVarTab[NB_OF_VAR];


int main()
{
    int i;

    printf("\n\n*** EEPROM test ***\n");
    printf("NB_OF_VAR=%d, PAGE_SIZE=%d, FLASH_PAGE_SIZE=%d\n\n",
           NB_OF_VAR,PAGE_SIZE,FLASH_PAGE_SIZE);

    for (i=0; i<NB_OF_VAR; i++) {
        VirtAddVarTab[i]=i;
    } // virtual addreses in sequence 0..NB_OF_VAR-1

    printf("EEPROM init  ");
    t.reset();
    t.start();
    /* Unlock the Flash Program Erase controller */
    HAL_FLASH_Unlock();
    EE_Init();
    t.stop();
    printf("- time used= %f seconds\n", t.read());


    printf("First writing (all variables)  ");
    t.reset();
    t.start();
    for (i=0; i<NB_OF_VAR; i++) {
        EE_WriteVariable(VirtAddVarTab[i], 10000+i);
    } //
    t.stop();
    printf("- time used= %f seconds\n", t.read());

    printf("Second writing (all variables)  ");
    t.reset();
    t.start();
    for (i=0; i<NB_OF_VAR; i++) {
        EE_WriteVariable(VirtAddVarTab[i], 20000+i);
    } //
    t.stop();
    printf("- time used= %f seconds\n", t.read());

    printf("Third writing (all variables)  ");
    t.reset();
    t.start();
    for (i=0; i<NB_OF_VAR; i++) {
        EE_WriteVariable(VirtAddVarTab[i], 30000+i);
    } //
    t.stop();
    printf("- time used= %f seconds\n", t.read());

    printf("The end.\n");
}


where I used 2,000 variables and size of virtual page 8kB.
Results:

*** EEPROM test ***
NB_OF_VAR=2000, PAGE_SIZE=8192, FLASH_PAGE_SIZE=1024

EEPROM init  - time used= 0.001267 seconds
First writing (all variables)  - time used= 0.700537 seconds
Second writing (all variables)  - time used= 2.010621 seconds
Third writing (all variables)  - time used= 0.964252 seconds
The end.


*** EEPROM test ***
NB_OF_VAR=2000, PAGE_SIZE=8192, FLASH_PAGE_SIZE=1024

EEPROM init  - time used= 0.000498 seconds
First writing (all variables)  - time used= 0.964254 seconds
Second writing (all variables)  - time used= 0.964421 seconds
Third writing (all variables)  - time used= 0.964248 seconds
The end.


The first pass is after flashing the program, when the EEPROM is uninitialized. The second after the reset.

Therefore, I don't know what might be in your case, that you have such long writing times (minutes).

posted by Nothing Special 31 Dec 2016
7 years, 11 months ago.

If I were you, I'll use RTC backup registers.

https://developer.mbed.org/users/gregeric/notebook/using-stm32-rtc-backup-registers/

not big enough

posted by DOUG BELL 26 Dec 2016