Howto: Remanent data after microcontroller software reset

I recently needed to write a special bootloader for the STM32 controller. One requirement was the ability to explicitly start the bootloader from the application. In this case the bootloader stays active until the next power cycle or when a firmware upgrade has been successfully applied.

The basic idea is to set a special “flag” in the application and then trigger a reset of the controller, telling the bootloader to remain in this mode. If the flag is not set (which is the default), the bootloader will just load the application. Setting a flag sounds easy, but the problem is that a reset invalidates most of the data.

Just writing the flag into a CPU register will not work because all registers are reset to zero. Misusing special peripheral registers won’t work either because of the same reason. But you can use the SRAM present on the controller – if you are cautious – because the SRAM retains its contents even after resets. But you cannot just write a flag blindly to some memory address because you might overwrite some important data of the currently running application. Much worse: This special flag might immediately be overwritten by the bootloader after the reset event because it also needs RAM (heap, stack and static data).

In order to solve this problem, I basically told both bootloader and application to leave some space (in my case 8 bytes) at the end of the RAM, just behind the initial stack pointer. For a device with 192 KB RAM this means that the firmware uses memory addresses from 0x00000 to 0x2FFF8 (relative to the RAM start address). The memory content 0x2FFF8-0x2FFFF will be reserved for the special flag (or other kind of data you might find useful). Of course the initial stack pointer must then point to 0x2FFF8 instead of the default 0x30000. When the bootloader reads the flag, it will automatically reset (or invalidate) the flag.

UPDATE: An earlier of this post assumed 4 bytes instead of 8 bytes for special data. While 4 bytes are usually enough, setting the end of the stack pointer to an address which is not aligned to 8 bytes may lead to serious problems in the application. I spent two days debugging a firmware which used this kind of technique with 4 bytes of data. The problem (wrong representation of IEEE 754 floating point numbers) occured while implicitly using some double-word instructions of an STM32F4 processor in combination with floating-point unit code.

Now let’s see some code. 😉
I only show the relevant changes compared to the reference implementation made by STMicroelectronics. I use the GCC ARM Embedded toolchain, but this concept will most likely also work with other controllers and compilers – some code changes required.

Modify the assembler startup file in bootloader and application:

.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb

.global  g_pfnVectors
.global  Default_Handler
.global _blrequest

/* start address for the initialization values of the .data section. 
defined in linker script */
.word  _sidata
/* start address for the .data section. defined in linker script */  
.word  _sdata
/* end address for the .data section. defined in linker script */
.word  _edata
/* start address for the .bss section. defined in linker script */
.word  _sbss
/* end address for the .bss section. defined in linker script */
.word  _ebss
/* stack used for SystemInit_ExtMemCtl; always internal RAM used */

/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called. 
 * @param  None
 * @retval : None
*/
    .section .noinit,"aw",%progbits
_blrequest:
.skip 4

    .section  .text.Reset_Handler
  .weak  Reset_Handler
  .type  Reset_Handler, %function
Reset_Handler:  
/** Detect and save special flag (_blrequest)
 * when bootloader was requested by application. */
  ldr r0, =_estack /* r0 <-- address of special flag */
  ldr r1, [r0, #0] /* r1 <-- flag content */
  ldr r2, =_blrequest /* make a copy of flag */
  str r1, [r2, #0] /* _blrequest <-- flag content */
  str r0, [r0, #0] /* overwrite/invalidate special flag */

/* Copy the data segment initializers from flash to SRAM */  
  movs  r1, #0
  b  LoopCopyDataInit
// ...

Modify the linker file in both bootloader and application:

/* ... */

MEMORY
{
FLASH (rx)      : ORIGIN = 0x08020000, LENGTH = 1920K
RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 192K
CCMRAM (rw)     : ORIGIN = 0x10000000, LENGTH = 64K
}

_estack = 0x20000000 + LENGTH(RAM) - 8; /* make sure that _estack is aligned to 8 bytes */

/* ... */

/* Put this after .bss section */
.noinit (NOLOAD) :
{
  _snoinit = .;
  KEEP(*(.noinit*))
  _enoinit = .;
  . = ALIGN(4);
} >RAM

/* ... */

This is the application code that sets the flag and resets the device, so that the bootloader will run:

void jump_to_bootloader()
{
  extern volatile uint32_t _estack;

  __DSB();
  // Write the magic flag 0xDEADBEEF
  *((unsigned long *)(_estack)) = 0xDEADBEEF;

  // Reset device, jump to bootloader
  NVIC_SystemReset();
}

This is the bootloader code that detects the reason of invocation:

int main()
{
  /* ... */

  extern volatile uint32_t _blrequest;

  if (0xDEADBEEF == _blrequest)
  {
    // Bootloader was explicitly requested by application
  }
  else
  {
    // Regular start of bootloader
  }

  /* ... */
}

Enjoy. 😉

By André Heßling

I am an electronic engineer living in Voerde, Germany.

12 comments

  1. This was what I were looking for. Very accurate and works smoothly.

    Only a small remark,

    Inside the “Reset_Handler:”

    this couple of instruction were missing…

    ldr r0, =_estack
    mov sp, r0 /* set stack pointer */

    But may be becouse you only show the relevant changes.

    Thanks a million!
    Regards.
    Paolo

    1. Hi Paolo,

      thanks for your comment and I am glad that you found this post useful.

      The setting of the stack pointer actually depends on the “layout” of the startup file.
      The startup file I used was based on the STM32Fxx library examples.
      In this configuration, the address of _estack is loaded at the beginning of the flash (0x08000000) automatically.
      After reset, the MCU takes the address at flash position 0x08000000 as stack pointer and executes the reset handler at 0x08000004.
      That’s why these two instructions you posted were not necessary in my code, although of course they are not wrong.

      See this complete startup file for an example of how this preloading of the stack pointer works.

      Kind regards,
      André

  2. Very useful stuff! Minor comment, I think you don’t need the .noinit section in the application linker script? It’s only used to guard _blrequest variable that is not used on the application.

  3. Hi, very useful information, for bootloader and a special function at reset i use two flags at the end of the ram (0x20000000 – 16) how can i do to preserve them both with the noinit section? i only manage to preserve one.

    1. Hi Hoel,

      if you need two 32 bit words instead of one (as shown in my post), this change in the assembler file should do the job. Please don’t quote me on that because I didn’t test it 😉



      .section .noinit,"aw",%progbits
      _blrequest:
      .skip 4
      _another_flag:
      .skip 4

      ...

      .section .text.Reset_Handler
      .weak Reset_Handler
      .type Reset_Handler, %function

      Reset_Handler:
      ldr r0, =_estack
      ldr r1, [r0, #0]
      ldr r2, =_blrequest
      str r1, [r2, #0]
      str r0, [r0, #0]

      ldr r0, =_estack - 4
      ldr r1, [r0, #0]
      ldr r2, =_another_flag
      str r1, [r2, #0]
      str r0, [r0, #0]

      So basically I copied the assembler code and replaced _blrequest with _another_flag and the load address of _estack becomes “_estack – 4”.
      Also I reserved another 4 bytes in the noinit-section and exported the symbol _another_flag.

      This code is OK for one or two flags but becomes cumbersome if more data has to be copied. In this case, you should look at the assembler functions “CopyDataInit” and “LoopCopyDataInit” which are not shown in my post but are part of the default startup files of the STM32 family. The code in these functions can be used (slightly modified) to copy whole data blocks which should be remanent.

      Kind regards,
      André

  4. We are doing something very similar to this (anyone who has a bootloader which supports software upload and has a fast path probably does), but we are using a STM32H7, and there things don’t just work:

    Because of ECC, writes smaller than 64 bits are not directly applied and disappear after the reset (you get the old value back). See newest reference manual RM0433, chapter 2.5 Embedded SRAM, last sentence:

    “When a half word is written to an internal SRAM and a reset occurs, this half word is not
    really written to the SRAM after reset. This due to the ECC behavior.”

    In this context “half-word” means anything smaller than the bus width. Above it mentions AXI-SRAM and ITCM-RAM being 64-bit, but we’re using DTCM and doing just a 32 bits write isn’t enough, we have to do a dummy 64 bit write.

  5. Thank you very much André ! It works very well, and much better than my previous kludge. I had to modify my application startup assembly file to make it work though.

  6. Hello André
    Thanks for your great tutorial. I need exactly this function. Unfortunately, I had nothing to do with startup and linker files until now and am a bit overwhelmed. In the startupfile it looks exactly the same for me, but the linkerfile is a bit different:

    /* Highest address of the user mode stack */
    _estack = 0x240000 + LENGTH(AXI_SRAM) – 8; /* end of RAM */

    /* Generate a link error if heap and stack don’t fit into RAM */
    _Min_Heap_Size = 0x00; /* required amount of heap */
    _Min_Stack_Size = 0x400; /* required amount of stack */

    /* Specify the memory areas */
    MEMORY
    {
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
    AXI_SRAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
    ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K
    DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
    SRAM1 (xrw) : ORIGIN = 0x30000000, LENGTH = 128K
    SRAM2 (xrw) : ORIGIN = 0x30020000, LENGTH = 128K
    SRAM3 (xrw) : ORIGIN = 0x30040000, LENGTH = 32K
    SRAM4 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
    BKUP_SRAM (xrw) : ORIGIN = 0x38800000, LENGTH = 4K
    }

    I then added the code section of the noinit section as you described under the .bss section. Unfortunately I always run into a hardfault and can’t find the problem. Do you have a tip for me?

    Kind regards from Switzerland
    Manuel

    1. Hi Manuel,

      thanks for your words.
      The controller you are using seems like quite a big one when I look at the memory areas… There are multiple RAM sections. What did the linker file look like before the modifications?
      It seems strange that _estack is 0x240000 + […] The memory area defines AXI_SRAM as ORIGIN = 0x24000000. So you are missing two zeroes in the definition of _estack. That is wrong in any case. Maybe that alone solves your problem.
      Then keep in mind that the .noinit section must not be mapped to the “RAM” section (like in my example), but in your case this must be “AXI_SRAM”. Assuming that this is the right RAM section – there is also ITCMRAM and DTCMRAM – which I don’t know.
      If that does not help you, you could provide your original startup file and your original linker file and I try to find the correct modifications.

      Kind regards (from Germany)
      André

      1. Hello André
        Thank you for your answer and help. The definition of the _estack was a copy past problem into the web. I’ve found out, that the code works properly, when i set a breakpoint on NVIC_SystemReset();. But if i don’t have a breakpoint there, (0xDEADBEEF == _blrequest) will always be false. I have read on the internet that the NVIC_SystemReset(); can have different behaviors when you have the debugger attached or not. I will see if I can find a solution to this.
        Kind regards
        Manuel

Leave a comment

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