How to execute functions, interrupts and code in internal SRAM with STM32CubeIDE


1. Introduction

This wiki article aims to show some methods to set up STM32 project firmware to be able to execute a part or the entire firmware into embedded SRAM memory.

Generally, STM32s execute user code directly from their embedded flash memory. Thanks to the mechanism of ART accelerator and prefetch buffer that allows the execution of the code with a minimum flash wait state latency at a maximum CPU and peripheral frequency.

But in some cases, it is useful to execute a part or a full firmware in SRAM. This can motivate an embedded system developer for the following reasons:

  • Improve the execution times of code or interrupts considered critical.
  • Reduce power consumption by placing the flash memory in low power mode.
  • Perform other concurrent read/write operations on flash memory, while the CPU is executing code in SRAM.
  • Speed up the code upload when doing debugging sessions.

All the following examples use:

  • NUCLEO-U575ZI-Q.
  • Latest version of STM32CubeIDE and STM32Cube_FW_U5_Vx.y.z.
  • Latest version of STM32CubeProgrammer.

2. Use case 1: Placing and executing a function in SRAM.

This part describes the steps to link and execute a C function in SRAM using STM32CubeIDE. This example is based on a basic function called Prime_Calc_SRAM() in charge of computing an amount of prime number in a given interval.

  1. Run STM32CubeIDE and create a new STM32 project using NUCLEO-U575ZI-Q board (refer to the getting started with STM32 step by step in STM32 MCU Wiki) Category:Getting started with STM32 : STM32 step by step

#

2. Name it as an example ‘Nucleo-U575ZI_SRAM_Exec’ and generate the project.

3. Leave all the STM32CubeMX configuration to default value except:

See below, the clock configuration from MSI and PLL to obtain HCLK = 160MHz.

IntMemory img stp1 2.png

4. Do not generate MX_USART1_UART_Init function call in project manager tab.

IntMemory img stp1 3.png

5. Save the project and generate the code.

6. Build the project to check if there are no error, only one warning appears below. It can be ignored.:

../Core/Src/main.c:382:13: warning: 'MX_USART1_UART_Init' defined but not used [-Wunused-function]

7. Open main.c file, create the prototype of Prime_Calc_SRAM() function which is in charge of computing prime number in an array.

Note: This function is defined with __attribute__((section(".RamFunc"))) keyword.

/* USER CODE BEGIN PFP */
static void __attribute__((section(".RamFunc"))) Prime_Calc_SRAM(void); 
/* USER CODE END PFP */

8. the following private constant to define the size of the prime number array

/* USER CODE BEGIN PD */
#define PRIM_NUM        64U          /* Size of the prime array */	 
/* USER CODE END PD */

9. Create the following private variable.

Note: This variable array is defined with __attribute__(( aligned(32))) keyword to make sure that the data are well aligned into the memory.

/* USER CODE BEGIN PV */
static __attribute__(( aligned(32))) uint32_t primes_ram[PRIM_NUM]; 
/* USER CODE END PV */

10. Finally create the Prime_Calc_SRAM() function declaration.

This code is in charge of computing the prime numbers from 2 to 311 (PRIM_NUM) and store them into primes_ram array variable.

It produces prime number below and stores them in primes_ram array variable.

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131,137, 139, 149, 151,

157, 163, 167, 173,179, 181, 191, 193, 197, 199, 211, 223,227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311

Note: As for the prototype definition the presence of the __attribute__(( aligned(32))) keyword.

/* USER CODE BEGIN 0 */
/**
  * @brief  Compute an amount of Prime number in SRAM
  *   None
  * @retval None
  */
static void __attribute__((section(".RamFunc"))) Prime_Calc_SRAM(void)
{
  /* Compute prime number in the elements size of the array */
  /* Initial condition */
  primes_ram[0] = 2;  // First prime number
  uint32_t count = 1; // Number of primes found
  uint32_t num = 3;   // Number to test for primality

  while (count < PRIM_NUM)
  {
    uint8_t is_prime = 1;
    for (uint32_t i = 0; i < count; i++)
    {
      if (num % primes_ram[i] == 0)
      {
        is_prime = 0;
        break;
      }
      if (primes_ram[i] * primes_ram[i] > num)
        break;
    }

    if (is_prime)
    {
      primes_ram[count] = num;
      count++;
    }
    num += 2; // Skip even numbers
  }
} 
/* USER CODE END 0 */

11. Call this function in main.

/* USER CODE BEGIN 2 */
Prime_Calc_SRAM();
/* USER CODE END 2 */

12. Open the linker file STM32U575ZITXQ_FLASH.ld and take a look at the MEMORY definition below:

/* Memories definition */
MEMORY
{
  RAM   (xrw)	: ORIGIN = 0x20000000,	LENGTH = 768K
  SRAM4 (xrw)	: ORIGIN = 0x28000000,	LENGTH = 16K
  FLASH (rx)	: ORIGIN = 0x08000000,	LENGTH = 2048K
}

The STM32U5 embeds several memories, these definitions are used as physical location to inform the linker what section to place each firmware symbol, code and data. Refer to STM32U5 reference manual chapter memory organization (RM0456) for a full description of the memory organization in the device.

Then the linker file contains predefined sections. Users can create their own sections. The section dedicated to the interrupt vector (.isr_vector), is placed by default at the beginning of the flash memory, the program code (.text) is also in flash memory, while variables, the bss, heap, and stack are in RAM (.data, .bss, ._user_heap_stack)

The goal is to use a section placement that is defined in section .data, and it is name: .RamFunc see below:

/* Initialized data sections into "RAM" Ram type memory */
.data :
{
  _sdata = .;        /* create a global symbol at data start */
  *(.data)           /* .data sections */
  *(.data*)          /* .data* sections */
  *(.RamFunc)        /* .RamFunc sections */
  *(.RamFunc*)       /* .RamFunc* sections */
  _edata = .;        /* define a global symbol at data end */
} >RAM AT> FLASH

The command >RAM AT> FLASH is used to inform the linker to resolve by copying from the flash memory to RAM at startup initialization, all the symbols defined with these placement section configurations. In our case, the function Prime_Calc_SRAM() is copied from flash memory to SRAM1 after reset. This task is managed by the Reset_Handler: LoopCopyDataInit in the startup_stm32u575zitxq.s assembly file.

13. Build the project.

14. Locate Prime_Calc_SRAM symbol in build analyzer, this is the hierarchical representation of the map file generated during the linker phase.

IntMemory img stp1 4.png

The point here as described in step 12, the linker resolves the function from flash address 0x0800317C and copies it at startup in SRAM1 at address 0x2000000C.

15. Place two breakpoints:

  • In the Reset_Handler at line bl SystemInit in startup_stm32u575zitxq.s
  • In the Prime_Calc_SRAM() function at line primes_ram[0] = 1;

16. Start STM32CubeIDE debugger. The first breakpoint Halt the CPU in Reset_Handler: in startup_stm32u575zitxq.

17. Open windows → show view → memory and monitors the address location in flash memory and in SRAM1 of the Prime_Calc_SRAM() function.

18. This shows that the linker placed this function at the beginning of 0x0800317C and with a size of 96 bytes, in the flash memory section.

IntMemory img stp1 5.png

19. While the SRAM1 contains random data (or not allocated) at 0x2000000C address.

IntMemory img stp1 6.png

20. Check the registers → general registers watch expression; the PC counter is in flash memory.

That means that the code is executed in flash.

IntMemory img stp1 7.png

21. Resume (F8) the execution. The second breakpoint Halt again the CPU at the beginning of Prime_Calc_SRAM() function in main.c.

In addition, the 96 bytes of code related to the Prime_Calc_SRAM() function is copied from flash memory at 0x0800317C to SRAM at 0x2000000C.

The copy was done by LoopCopyDataInit: in startup_stm32u575zitxq.s

IntMemory img stp1 8.png

22. Now, check again the PC counter. The function is now executed in SRAM1.

IntMemory img stp1 9.png

23. Select Run → remove all breakpoints and stop the debug session.

Conclusion: Using the gcc __attribute__((section(".RamFunc"))) keyword combined with linker sub section definition, it helps us to easily place and execute a function or a part of code in the SRAM memory.

An other method is to use the existing __RAM_FUNC define (in stm32u5xx_hal_def.h) to declare a function to be executed in SRAM.

#define __RAM_FUNC HAL_StatusTypeDef  __attribute__((section(".RamFunc")))

3. Use case 2: placing and executing an interrupt in SRAM.

This second part describes the steps to link and execute interrupts in SRAM using STM32CubeIDE.

It is based on the same project used in the previous steps.

1. Open Nucleo-U575ZI_SRAM_Exec.ioc in order to configure an EXTI interrupts for the User button on the NUCLEO-U575ZI-Q board.

IntMemory img stp2 1.png

2. Open NVIC categories and simply check EXTI Line 13 interrupt Enabled. Keep all the other existing configuration.

3. Save the project and regenerate the code.

4. Build the project and run the debugger.

5. Place a breakpoint in stm32u5xx_it.c at line:HAL_GPIO_EXTI_IRQHandler(USER_BUTTON_Pin);

6. Press the user button (blue button) on the Nucleo board, the CPU is halted into EXTI13_IRQHandler() in stm32u5xx_it.c.

7. Check the registers → general registers watch expression; the PC counter is in flash memory.

That is means the interrupt handler function is executed in flash.

IntMemory img stp2 2.png

8. Stop the debugger.

9. Change the EXTI13_IRQHandler name by applying the same attribute keyword than the previous case.

Add the __attribute__((section(".RamFunc"))) keyword.

/**
  * @brief This function handles EXTI Line13 interrupt.
  */
void __attribute__((section(".RamFunc"))) EXTI13_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI13_IRQn 0 */

  /* USER CODE END EXTI13_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(USER_BUTTON_Pin);
  /* USER CODE BEGIN EXTI13_IRQn 1 */

  /* USER CODE END EXTI13_IRQn 1 */
}

10. Rebuild the project and run the debugger. Keep the breakpoint at the same line as step 5.

11. Press again, the user button (blue button) on the NUCLEO board, the CPU is halted intoEXTI13_IRQHandler().

12.Check the registers → general registers watch expression; the PC counter is at address 0x20000070.

This means that the interrupt handler function is executed in SRAM1.

IntMemory img stp2 3.png

13. Select Run → remove all breakpoints and stop the debug session.

Conclusion: This example is equivalent to the previous chapter. It can be used to place and execute a single or several interrupts’ handlers in the embedded SRAM.

4. Use case 3: placing and executing an entire project in SRAM (all the code and the interrupts).

This third part describes the steps to link and execute a full STM32 project in SRAM using STM32CubeIDE.

It is still based on the same project used in the previous steps.

1. Remove the __attribute__((section(".RamFunc"))) in the Prime_Calc_SRAM() and in the EXTI13_IRQHandler().

Note: For Prime_Calc_SRAM() the prototype and the definition of this function must also be changed.

2. Open system_stm32u5xx.c and search for #define VECT_TAB_SRAM symbol.

Uncomment the corresponding line (see below)

/************************* Miscellaneous Configuration ************************/
/*!< Uncomment the following line to relocate the vector table in
     Internal SRAM. */
#define VECT_TAB_SRAM
#define VECT_TAB_OFFSET  0x00000000UL /*!< Vector Table base offset field.
                                   This value must be a multiple of 0x200. */
/******************************************************************************/

3. Add the following code in the main while(1) loop, it will be useful later:

/* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
     HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin);
     HAL_Delay(250);
  /* USER CODE END WHILE */

4. Check and locate in project explorer the linker file generated by CubeMX.

IntMemory img stp3 1.png

5. As explained in the first case, CubeMX generates two types of linker file.

  • STM32U575ZITXQ_FLASH.ld is related to defining all physicals memory of the device, like flash memory and all SRAM bank, and to place all the symbols in these sections.
  • STM32U575ZITXQ_RAM.ld define only the RAM memory of the device (see below)
/* Memories definition */
MEMORY
{
  RAM	  (xrw)	: ORIGIN = 0x20000000,	LENGTH = 768K
  SRAM4   (xrw)	: ORIGIN = 0x28000000,	LENGTH = 16K
}

In this linker configuration all the symbols are placed in the memories definition, in this case in SRAM1 (0x2000 0000). This can be checked in the linker command >RAM of each SECTIONS.

Example for the .isr_vector (interrupts) and .text (Code) Sections

/* The startup code into "RAM" Ram type memory */
  .isr_vector :
  {
    KEEP(*(.isr_vector)) /* Startup code */
  } >RAM

  /* The program code and other data into "RAM" Ram type memory */
  .text :
  {
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */


    KEEP (*(.init))
    KEEP (*(.fini))

    _etext = .;        /* define a global symbols at end of code */
  } >RAM

6. Open project properties, right click on Nucleo-U575ZI_SRAM_Exec in project explorer.

7. Go to C/C++ Build → settings and select tool settings tab.8. Select MCU GCC Linker → general (see below)

9. Change the linker script (-T) path with following: ${workspace_loc:/${ProjName}/STM32U575ZITXQ_SRAM.ld}

IntMemory img stp3 2.png

10. Apply and close to save the new configuration.

11. Clean the project then rebuild it.

12. Check the build analyzer that provides an analysis of the new map file generated by the linker configuration.


IntMemory img stp3 3.png


The flash memory section is not present, and all the symbols are placed in SRAM1 (interrupt vector, code, and data)

13. To be sure the new project will be executed from SRAM1, use STM32CubeProgrammer to perform a full chip erase of the flash memory.

14. Open STM32CubeProgrammer, connect to the board and select full chip erase Icon on the bottom left corner.

IntMemory img stp3 4.png

15. The flash memory is erased to 0xFFFFFFFF value.

16. Disconnect the Nucleo board from STM32Cube programmer.

17. In STM32CubeIDE, start the debugger and step over to check the same features as the case 1 and 2 as:

  • EXTI13_IRQHandler when User button is pressed.
  • The result of the Prime_Calc_RAM() function result.
  • The blue LED blinking

All the code is placed and executed from SRAM1 and can be debugged as long the NUCLEO-U575ZI-Q is powered on. If the Nucleo board is powered off, the SRAM lose all the data, a new debug session must be launched to load and execute the code again.

18. Stop the debugger. And simply press the Reset button on the board (black button) to reboot and restart the firmware: The blue LED2 blinking.

19. The blue LED does not blink, the code is not executed? The board was not powered off, why the code does not restart?

20. In addition to all the project configuration in SRAM, all STM32 devices have several boot modes that must be configured so that the correct memory address at which the processor begins execution is properly selected.

For the STM32U5 family, refer to RM0456 in chapter 4 boot modes. The same chapter is also present for all other STM32 in their respective reference manual.

The table 25 in RM0456 explains the boot mode when TZ=0 (nonsecure mode)

IntMemory img stp3 5.png

By default, on a new fresh STM32U5, the memory used for boot mode is the flash memory.

It depends on BOOT0 PH3 pin level, in our case, this pin is connected to VSS through a resistor.  

In this example, it is necessary to change the NSBOOTADD0[24:0], which are defined by the user option bytes, to configure the boot in SRAM1.

21. Open STM32CubeProgrammer and establish a connection with the board.

22. On the left, select option bytes icon.

23. Select boot configuration and refer to the NSBOOTADD0 field (see below)

IntMemory img stp3 6.png


24. Change the value of NSBOOTADD0 Address to 0x20000000. And click apply to write the new option byte value in the flash memory of the STM32.

IntMemory img stp3 7.png


25. Observe the blue LED2, it blinks again. This is because a reset of the STM32U5 has been performed after the write sequence of the NSBOOTADD0.

As the board was still powered on during this sequence, the contents of the SRAM1 are not lost, and the firmware can start the execution in SRAM1 again.

5. Conclusion

This tutorial howed the important steps to place and run a project and its entire code from SRAM1 as example:

  • A in application programing firmware.
  • A low power application that powers down the flash memory to reduce the power consumption.

This practice requires some minor changes in the configuration of the STM32CubeIDE.Any other IDE environments such as IAR-EWARM or KEIL-ARM may handle the same project configuration, please refer to their respective documentation to understand the syntax of the linkers.