Skip to main content
Graduate
November 17, 2024
Question

how to use flash memory "the bare metal way".

  • November 17, 2024
  • 11 replies
  • 6064 views

Hello, everybody! I'm relatively new to STM32 microcontrollers and have a moderate knowledge of C programming and a fair understanding of electronics.

Over the past few months, I've been learning to properly set up STM32F4 series MCUs, specifically the F401RET6U (Nucleo board) and the F411CEU6 (Black Pill board), using bare-metal approach. I've spent considerable time watching tutorial videos and reading online guides to help me get started.

I'm now at a point where I can set up the clock and read from and write to GPIOs.

From what I understand, setting up peripherals involves configuring the correct values in the appropriate registers in the right order.

I would like to learn how to use embedded flash memory to store information that needs to be retained after a power loss. So far, I've struggled to find a guide or tutorial that I can fully understand. Could someone please assist me with this or point me in the right direction?

Thank you very much, and have a blessed week, everyone!

    This topic has been closed for replies.

    11 replies

    Graduate II
    November 17, 2024

    Bare metal means you read the Reference Manual (RM) and protocol docs related to the bus in question.

    You can check the HAL / CubeF4 examples for writing the Internal FLASH, once you have that working you can review the library source to confirm the mechanics described in the RM

    HellasTAuthor
    Graduate
    November 27, 2024

    Sorry for my late response. Yes that i do. I read the reference manuals. I've learned for example how to setup the latency and prefetch. Im not finished with the reference manual yet though.

    Super User
    November 17, 2024

     how to use embedded flash memory to store information that needs to be retained after a power loss. 

    Can you describe your requirements with more details? Do you want to save small amounts of data not frequently, for example calibration or configuration data - or make something like logger or recorder?  For the former you will find many examples on github and youtube.  But for the latter use the internal flash of STM32 is not optimal. 

     

    HellasTAuthor
    Graduate
    November 27, 2024

    That's right. To save small amount of data like for example a user defined pin number for an alarm system.

    Super User
    November 18, 2024

    @HellasT wrote:

    From what I understand, setting up peripherals involves configuring the correct values in the appropriate registers in the right order.


    Yes, that's a very good summary!

     


    @HellasT wrote:

    I would like to learn how to use embedded flash memory to store information that needs to be retained after a power loss. So far, I've struggled to find a guide or tutorial that I can fully understand.!


    As @Tesla DeLorean said, the whole point of "The Bare Metal Way" is that you commit to studying the device documentation (Datasheet, Reference Manual, Application Notes, etc)  in detail, understanding it, and then implementing what it tells you.

    "The HAL Way" exists so that you don't have to go into all that arcane detail.

    The HAL is all open-source - so you can always look into how it does its stuff at the register level.

    See this current thread on using Flash for non-volatile storage on STM32F4:

    https://community.st.com/t5/stm32-mcus-products/use-stm32f4-flash-for-non-volatile-data-storage-eeprom/m-p/744042

     


    @HellasT wrote:

    F411CEU6 (Black Pill board)


    Note that there is a high likelihood that the STM32 on a Black (or Blue or other coloured) Pill board will not be genuine ...

    HellasTAuthor
    Graduate
    November 27, 2024

    As @Tesla DeLorean said, the whole point of "The Bare Metal Way" is that you commit to studying the device documentation (Datasheet, Reference Manual, Application Notes, etc)  in detail, understanding it, and then implementing what it tells you.

    I sure try hard.


    "The HAL Way" exists so that you don't have to go into all that arcane detail.

    The HAL is all open-source - so you can always look into how it does its stuff at the register level

     HAL is awesome. Its just me thinking that if i try hard to learn "bare metal techniques" it will be easier and more clear for me to use HAL later on.

     

    https://community.st.com/t5/stm32-mcus-products/use-stm32f4-flash-for-non-volatile-data-storage-eeprom/m-p/744042

    Im going to check it out soon ! 


    Note that there is a high likelihood that the STM32 on a Black (or Blue or other coloured) Pill board will not be genuine ...


     Agreed but to play with and learn, i use what i can get my hands on. The original gear which by the way i usually get from reputable dealers like mouser etc, i keep for projects to be developed and implemented 

     

    HellasTAuthor
    Graduate
    November 27, 2024

    Thank you all for taking the time to reply. I truly value your input and understand how precious your time is. Although I’m not a professional, I’m putting a lot of effort into learning how to work with STM32s properly. Your guidance and suggestions mean a great deal to me. Thank you again for your support!

    Graduate II
    November 27, 2024

    For the F4's use some of the earlier sectors for smaller data.

    The primary sector needs to manage the initial boot, but subsequent 16KB and 64KB are better than 128KB

    Sectors need to be erased before use. The writes need to be aligned, here at least 32-bits, other architectures it's wider.

    HellasTAuthor
    Graduate
    December 1, 2024

    Well said !

    I am looking for ways to bind the sector that i will be using  so that the compiler will not use it for any other purpose. 

    Im planning to use F401RET6U's sector 3 which is the last 16k sector. I have actually been thinking to declare an array that it's start address would be the start address of sector 3 and initialize it with 0xFF. something like this :

    const char data[16*1024] = {0xFF}; But i have no idea how to implement it.

    Super User
    December 2, 2024

    It's not the Compiler which allocates storage; that's the Linker's job.

    Therefore you'd be looking to adjust the Linker script ...

    HellasTAuthor
    Graduate
    December 17, 2024

    I have read alot on the reference manual and i think i am on the right track. I have one question. When uploading a new code on the MCU using the cubeide, does the whole mcu's flash get erased before the new code is uploaded ?

    Based on my experiments i would say that ony the sectors occupied by the code are being erased and then re programmed.

    Here are parts of my code concerning the use of the mcu's flash : 

     

     

    #include <stdint.h>
    #define STM32F401xE
    #include "stm32f4xx.h"
    #include <MyClock.h>
    #include <MyFlash.h>
    // 16KB, Word length is 4 bytes (32 bits).
    uint32_t MyFlashData[4096] __attribute__((section("MyFlashSection"))) = {0xFF};
    
    int main(void)
    {	
     Clk_speed (SCALE_2_MODE,2,1,1,1,HPRE_0,PPRE1_2,PPRE2_0,4,84,PLLP_2,1,HSE);
     MyFlashWriteWord(0x0800C000,0xF0F0F0F0);
    	while(1)
    	{
    		
    	}
    }

     

     

    and here is the linkerscript :

     

     

    /*
    ******************************************************************************
    **
    ** @file : LinkerScript.ld (debug in RAM dedicated)
    **
    ** @author : Auto-generated by STM32CubeIDE
    **
    ** Abstract : Linker script for NUCLEO-F401RE Board embedding STM32F401RETx Device from stm32f4 series
    ** 512KBytes FLASH
    ** 96KBytes RAM
    **
    ** Set heap size, stack size and stack location according
    ** to application requirements.
    **
    ** Set memory bank area and size if external memory is used
    **
    ** Target : STMicroelectronics STM32
    **
    ** Distribution: The file is distributed as is, without any warranty
    ** of any kind.
    **
    ******************************************************************************
    ** @attention
    **
    ** Copyright (c) 2024 STMicroelectronics.
    ** All rights reserved.
    **
    ** This software is licensed under terms that can be found in the LICENSE file
    ** in the root directory of this software component.
    ** If no LICENSE file comes with this software, it is provided AS-IS.
    **
    ******************************************************************************
    */
    
    /* Entry Point */
    ENTRY(Reset_Handler)
    
    /* Highest address of the user mode stack */
    _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
    
    _Min_Heap_Size = 0x200; /* required amount of heap */
    _Min_Stack_Size = 0x400; /* required amount of stack */
    
    /* Memories definition */
    MEMORY
    {
     RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
     FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
     MY_FLASH_SECTION (rw) : ORIGIN = 0x0800C000, LENGTH = 16K /* My custom section at Sector 3*/ 
    }
    
    /* Sections */
    SECTIONS
    {
     /* The startup code into "RAM" Ram type memory */
     .isr_vector :
     {
     . = ALIGN(4);
     KEEP(*(.isr_vector)) /* Startup code */
     . = ALIGN(4);
     } >RAM
    
     /* The program code and other data into "RAM" Ram type memory */
     .text :
     {
     . = ALIGN(4);
     *(.text) /* .text sections (code) */
     *(.text*) /* .text* sections (code) */
     *(.glue_7) /* glue arm to thumb code */
     *(.glue_7t) /* glue thumb to arm code */
     *(.eh_frame)
     *(.RamFunc) /* .RamFunc sections */
     *(.RamFunc*) /* .RamFunc* sections */
    
     KEEP (*(.init))
     KEEP (*(.fini))
    
     . = ALIGN(4);
     _etext = .; /* define a global symbols at end of code */
     } >RAM
    
     /* Constant data into "RAM" Ram type memory */
     .rodata :
     {
     . = ALIGN(4);
     *(.rodata) /* .rodata sections (constants, strings, etc.) */
     *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
     . = ALIGN(4);
     } >RAM
    
     .ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     *(.ARM.extab* .gnu.linkonce.armextab.*)
     . = ALIGN(4);
     } >RAM
    
     .ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     __exidx_start = .;
     *(.ARM.exidx*)
     __exidx_end = .;
     . = ALIGN(4);
     } >RAM
    
     .preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array*))
     PROVIDE_HIDDEN (__preinit_array_end = .);
     . = ALIGN(4);
     } >RAM
    
     .init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT(.init_array.*)))
     KEEP (*(.init_array*))
     PROVIDE_HIDDEN (__init_array_end = .);
     . = ALIGN(4);
     } >RAM
    
     .fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     PROVIDE_HIDDEN (__fini_array_start = .);
     KEEP (*(SORT(.fini_array.*)))
     KEEP (*(.fini_array*))
     PROVIDE_HIDDEN (__fini_array_end = .);
     . = ALIGN(4);
     } >RAM
    
     /* Used by the startup to initialize data */
     _sidata = LOADADDR(.data);
    
     /* Initialized data sections into "RAM" Ram type memory */
     .data :
     {
     . = ALIGN(4);
     _sdata = .; /* create a global symbol at data start */
     *(.data) /* .data sections */
     *(.data*) /* .data* sections */
    
     . = ALIGN(4);
     _edata = .; /* define a global symbol at data end */
    
     } >RAM
    
     /* Uninitialized data section into "RAM" Ram type memory */
     . = ALIGN(4);
     .bss :
     {
     /* This is used by the startup in order to initialize the .bss section */
     _sbss = .; /* define a global symbol at bss start */
     __bss_start__ = _sbss;
     *(.bss)
     *(.bss*)
     *(COMMON)
    
     . = ALIGN(4);
     _ebss = .; /* define a global symbol at bss end */
     __bss_end__ = _ebss;
     } >RAM
    
     /* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */
     ._user_heap_stack :
     {
     . = ALIGN(8);
     PROVIDE ( end = . );
     PROVIDE ( _end = . );
     . = . + _Min_Heap_Size;
     . = . + _Min_Stack_Size;
     . = ALIGN(8);
     } >RAM
     
     .MyFlashSection :
     {
     *(.MyFlashSection)
     } > MY_FLASH_SECTION
    
     /* Remove information from the compiler libraries */
     /DISCARD/ :
     {
     libc.a ( * )
     libm.a ( * )
     libgcc.a ( * )
     }
    
     .ARM.attributes 0 : { *(.ARM.attributes) }
    }

     

     

    HellasTAuthor
    Graduate
    December 17, 2024

    After uploading this code i opened Cubeprogrammer and verified that address 800c000 indeed contained F0F0F0F0 while the rest of the 3rd sector was clear (FFFF). now i opened cubeide again and modified the code so that i write the same value to 800c004 address (that would be the next 4 bytes of sector3) and again opened Cubeprogrammer. So then both 800C000 and 800C004 had F0F0F0F0. That is how i came to the assumption that upon each new code upload, the flash memory is not fully erased.

     

    #include <stdint.h>
    #define STM32F401xE
    #include "stm32f4xx.h"
    #include <MyClock.h>
    #include <MyFlash.h>
    
    uint32_t MyFlashData[4096] __attribute__((section("MyFlashSection"))) = {0xFF}; // 16KB, Word length is 4 bytes (32 bits).
    
    int main(void)
    {	
    	
    	Clk_speed (SCALE_2_MODE,2,1,1,1,HPRE_0,PPRE1_2,PPRE2_0,4,84,PLLP_2,1,HSE);
    	MyFlashWriteWord(0x0800C004, 0xF0F0F0F0);
    	while(1)
    	{		
    	}
    }

     

    I have to admit though that i had the feeling that this part of the code would somehow clean sector 3 upon code upload.

     

    uint32_t MyFlashData[4096] __attribute__((section("MyFlashSection"))) = {0xFF};

     

    specially due to the = {0xFF}; part

    Super User
    December 18, 2024

    @HellasT wrote:

     That is how i came to the assumption that upon each new code upload, the flash memory is not fully erased.


    Yes, that's what CubeIDE does - other IDEs vary:

    https://community.st.com/t5/stm32cubeide-mcus/partial-or-full-chip-erase-before-programming/m-p/734869/highlight/true#M31829

     


    @HellasT wrote:

    I have to admit though that i had the feeling that this part of the code would somehow clean sector 3 upon code upload.

     

    uint32_t MyFlashData[4096] __attribute__((section("MyFlashSection"))) = {0xFF};

     

    specially due to the = {0xFF}; part


    No.

    You can't erase flash by writing 0xFF- that's not how Flash works.

    The erase is a separate operation - distinct from writing - which is why we have to go through all this faff when trying to write to Flash!

    Follow the links from here:

    https://community.st.com/t5/stm32-mcus-products/store-values-in-internal-flash/m-p/667055/highlight/true#M242290

    HellasTAuthor
    Graduate
    December 18, 2024

    That makes sense. Well now i just perform sector erase when needed. Ofcourse i also plan to implement some wear leveling technique etc. Thanks !

    HellasTAuthor
    Graduate
    January 20, 2025

    Hello everyone. Im back with some problem concerning what i want to do. I turned out that the code in my earlier post just by luck, happened to work.

     

    I created an other project using cubemx feature and HAL library. I edited the STM32F411CEUX_FLASH.ld the same way i did before :

     

    /*
    ******************************************************************************
    **
    ** @file : LinkerScript.ld
    **
    ** @author : Auto-generated by STM32CubeIDE
    **
    ** @brief : Linker script for STM32F411CEUx Device from STM32F4 series
    ** 512KBytes FLASH
    ** 128KBytes RAM
    **
    ** Set heap size, stack size and stack location according
    ** to application requirements.
    **
    ** Set memory bank area and size if external memory is used
    **
    ** Target : STMicroelectronics STM32
    **
    ** Distribution: The file is distributed as is, without any warranty
    ** of any kind.
    **
    ******************************************************************************
    ** @attention
    **
    ** Copyright (c) 2024 STMicroelectronics.
    ** All rights reserved.
    **
    ** This software is licensed under terms that can be found in the LICENSE file
    ** in the root directory of this software component.
    ** If no LICENSE file comes with this software, it is provided AS-IS.
    **
    ******************************************************************************
    */
    
    /* Entry Point */
    ENTRY(Reset_Handler)
    
    /* Highest address of the user mode stack */
    _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
    
    _Min_Heap_Size = 0x200; /* required amount of heap */
    _Min_Stack_Size = 0x400; /* required amount of stack */
    
    /* Memories definition */
    MEMORY
    {
     RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
     FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
     MY_FLASH_SECTION (rw) : ORIGIN = 0x0800C000, LENGTH = 16K /* My custom section at Sector 3*/
    }
    
    /* Sections */
    SECTIONS
    {
     /* The startup code into "FLASH" Rom type memory */
     .isr_vector :
     {
     . = ALIGN(4);
     KEEP(*(.isr_vector)) /* Startup code */
     . = ALIGN(4);
     } >FLASH
    
     /* The program code and other data into "FLASH" Rom type memory */
     .text :
     {
     . = ALIGN(4);
     *(.text) /* .text sections (code) */
     *(.text*) /* .text* sections (code) */
     *(.glue_7) /* glue arm to thumb code */
     *(.glue_7t) /* glue thumb to arm code */
     *(.eh_frame)
    
     KEEP (*(.init))
     KEEP (*(.fini))
    
     . = ALIGN(4);
     _etext = .; /* define a global symbols at end of code */
     } >FLASH
    
     /* Constant data into "FLASH" Rom type memory */
     .rodata :
     {
     . = ALIGN(4);
     *(.rodata) /* .rodata sections (constants, strings, etc.) */
     *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
     . = ALIGN(4);
     } >FLASH
    
     .ARM.extab (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     *(.ARM.extab* .gnu.linkonce.armextab.*)
     . = ALIGN(4);
     } >FLASH
    
     .ARM (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     __exidx_start = .;
     *(.ARM.exidx*)
     __exidx_end = .;
     . = ALIGN(4);
     } >FLASH
    
     .preinit_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     PROVIDE_HIDDEN (__preinit_array_start = .);
     KEEP (*(.preinit_array*))
     PROVIDE_HIDDEN (__preinit_array_end = .);
     . = ALIGN(4);
     } >FLASH
    
     .init_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     PROVIDE_HIDDEN (__init_array_start = .);
     KEEP (*(SORT(.init_array.*)))
     KEEP (*(.init_array*))
     PROVIDE_HIDDEN (__init_array_end = .);
     . = ALIGN(4);
     } >FLASH
    
     .fini_array (READONLY) : /* The "READONLY" keyword is only supported in GCC11 and later, remove it if using GCC10 or earlier. */
     {
     . = ALIGN(4);
     PROVIDE_HIDDEN (__fini_array_start = .);
     KEEP (*(SORT(.fini_array.*)))
     KEEP (*(.fini_array*))
     PROVIDE_HIDDEN (__fini_array_end = .);
     . = ALIGN(4);
     } >FLASH
    
     /* Used by the startup to initialize data */
     _sidata = LOADADDR(.data);
    
     /* Initialized data sections into "RAM" Ram type memory */
     .data :
     {
     . = ALIGN(4);
     _sdata = .; /* create a global symbol at data start */
     *(.data) /* .data sections */
     *(.data*) /* .data* sections */
     *(.RamFunc) /* .RamFunc sections */
     *(.RamFunc*) /* .RamFunc* sections */
    
     . = ALIGN(4);
     _edata = .; /* define a global symbol at data end */
    
     } >RAM AT> FLASH
    
     /* Uninitialized data section into "RAM" Ram type memory */
     . = ALIGN(4);
     .bss :
     {
     /* This is used by the startup in order to initialize the .bss section */
     _sbss = .; /* define a global symbol at bss start */
     __bss_start__ = _sbss;
     *(.bss)
     *(.bss*)
     *(COMMON)
    
     . = ALIGN(4);
     _ebss = .; /* define a global symbol at bss end */
     __bss_end__ = _ebss;
     } >RAM
    
     /* User_heap_stack section, used to check that there is enough "RAM" Ram type memory left */
     ._user_heap_stack :
     {
     . = ALIGN(8);
     PROVIDE ( end = . );
     PROVIDE ( _end = . );
     . = . + _Min_Heap_Size;
     . = . + _Min_Stack_Size;
     . = ALIGN(8);
     } >RAM
    
    .MyFlashSection :
     {
     *(.MyFlashSection)
     } > MY_FLASH_SECTION
    
     /* Remove information from the compiler libraries */
     /DISCARD/ :
     {
     libc.a ( * )
     libm.a ( * )
     libgcc.a ( * )
     }
    
     .ARM.attributes 0 : { *(.ARM.attributes) }
    }

     

     and in the main.c file i added this : 

     

    /* USER CODE BEGIN PV */
    
    uint32_t MyFlashData[4096] __attribute__((section("MyFlashSection"))) = { [0 ... (4095)] = 0xFFFFFFFF }; // 16KB, Word length is 4 bytes (32 bits).
    //other variables here
    /* USER CODE BEGIN PV */

     

    This time the code was larger. Using cube programmer i checked sector 3 and to my surprise it was used by the compiler to store parts of the program.

    I searched into the .map file for (MyFlashSection) and discovered this:

    MyFlashSection
     0x00000000 0x4000 ./Core/Src/main.o

    What am i doing wrong ? Could please someone point me to the right direction ? Thank you !

     

    Super User
    January 21, 2025

    Here you can find some wise folks that know the useful.

     

    HellasTAuthor
    Graduate
    January 21, 2025

    Good day. Maybe you got me wrong. I am not looking for someone to do things for me. Im not a professional. I am building my own projects trying to learn and i ask questions when i get stuck. Is it that i've got something wrong ? Am i at the wrong place ? Maybe this forum intended for profesionals only? I believe that asking is not a "shame" and neither is telling someone that he is at the wrong place. Thank you.