How to Run Code in RAM Under KEIL Environment

How to Run Code in RAM Under KEIL Environment

Introduction

It is common for users of KEIL to encounter the issue of needing to run part or all of the program code in RAM. This article summarizes several methods to run programs in RAM using an example from the STM32F411 Nucleo.

We will start with the ToggleLED function executing in Flash. Below is the ToggleLED function and its calling situation. It is called in the main function’s while(1).

void ToggleLED(void)

{ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); /* Insert a 100ms delay */

 HAL_Delay(100); 

}

int main(void) 

{ …… /*##-3- Toggle PA05 IO in an infinite loop ######*/ 

while (1) { <strong>ToggleLED</strong>(); }

}

The linker configuration in the compilation environment is shown below:

Flash start address: 0x08000000

RAM start address: 0x20000000

How to Run Code in RAM Under KEIL Environment

After compilation, we can see from the map file that the addresses of ToggleLED and the called functions HAL_GPIO_TogglePin and HAL_Delay are all in FLASH.

How to Run Code in RAM Under KEIL Environment

How to Run Code in RAM Under KEIL Environment

Executing the LED toggle program in SRAM

Method 1: Use #pragma arm section code = “RAMCODE” and #pragma arm section. Refer to Example 1 code.

This method allows multiple functions to be placed in the specified section. The specific method is as follows:

1. Modify the .sct file to define a section called RAMCODE in the RW_IRAM1 execution area, with address range 0x20000000~0x20020000.

LR_IROM1 0x08000000 0x00080000 { ; load region size_region 

ER_IROM1 0x08000000 0x00080000 { ; load address = execution address 

*.o (RESET, +First) 

*(InRoot$$Sections) 

.ANY (+RO) 

} RW_IRAM1 0x20000000 0x00020000 { ; RW data

*.o(RAMCODE) 

.ANY (+RW +ZI)

} 

}

2. Use the modified .sct file in the project.

How to Run Code in RAM Under KEIL Environment

3. Start with #pragma arm section code = “RAMCODE” and end with #pragma arm section. Include all functions that need to be placed in the RAMCODE section. During compilation, the compiler will automatically place these functions in the area starting at 0x20000000.

#pragma arm section code = "RAMCODE" 

void ToggleLED(void)

{ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); /* Insert a 100ms delay */ 

HAL_Delay(100);

} 

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

{ /* Check the parameters */

assert_param(IS_GPIO_PIN(GPIO_Pin)); 

GPIOx->ODR ^= GPIO_Pin; 

} 

uint32_t HAL_GetTick(void)

{ return tick; 

} 

void HAL_Delay(__IO uint32_t Delay) 

{ uint32_t tickstart = 0; 

tickstart = HAL_GetTick(); 

while((HAL_GetTick() - tickstart) < Delay)

{ 

} 

} 

#pragma arm section

4. From the map file, we can see that these four functions have been placed into SRAM.

How to Run Code in RAM Under KEIL Environment

Method 2: Use __attribute__((section(“name”)))

In KEIL, you can place variables at a specified location using __attribute__((at(address))).

By using __attribute__((section(“name”))), you can place variables or functions at a specified location.

Now let’s see how to use this method to execute the program in SRAM.

1. Similarly, we need to modify the .sct file to define a section called RAMCODE and select the defined .sct file in the linker page of project options (see steps 1 and 2 in Method 1).

LR_IROM1 0x08000000 0x00080000 { ; load region size_region ER_IROM1 0x08000000 0x00080000 { ; load address = execution address 

*.o (RESET, +First) 

*(InRoot$$Sections) 

.ANY (+RO)

} RW_IRAM1 0x20000000 0x00020000 { ; RW data

*.o(RAMCODE) 

.ANY (+RW +ZI) 

} 

}

2. Before the function to be placed in RAM, declare it with __attribute__((section(“RAMCODE”))) to indicate that this function should be placed in the RAMCODE section. Note that all functions called within this function should also be placed in the RAMCODE section.

__attribute__((section("RAMCODE"))) 

void <strong>ToggleLED</strong>(void) 

{ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); /* Insert a 100ms delay */ 

HAL_Delay(100); 

} 

__attribute__((section("RAMCODE"))) 

void <strong>HAL_GPIO_TogglePin</strong>(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

{ /* Check the parameters */ 

assert_param(IS_GPIO_PIN(GPIO_Pin)); 

GPIOx->ODR ^= GPIO_Pin; 

} 

__attribute__((section("RAMCODE"))) 

__weak <strong>uint32_t HAL_GetTick</strong>(void) 

{ return uwTick; } 

__attribute__((section("RAMCODE"))) 

__weak <strong>void HAL_Delay</strong>(__IO uint32_t Delay) 

{ uint32_t tickstart = 0;

tickstart = HAL_GetTick();

while((HAL_GetTick() - tickstart) < Delay)

{ } 

} 

3. From the compiled map file, we can see that ToggleLED and all functions it calls have been placed in RAM.

How to Run Code in RAM Under KEIL Environment

Method 2 can override Method 1, meaning if you specify the execution area for the same function using both methods, Method 2 takes effect. We will illustrate this with the code mentioned above.

  1. Modify the .sct file. Divide SRAM into two execution areas, RW_IRAM1 and RW_IRAM2. Sections RAMCODE1 and RAMCODE2 are located at the start of 0x20000000 and 0x20010000, respectively.

LR_IROM1 0x08000000 0x00080000 { ; load region size_region ER_IROM1 0x08000000 0x00080000 { ; load address = execution address 

*.o (RESET, +First)

*(InRoot$$Sections) 

.ANY (+RO)

} RW_IRAM1 0x20000000 0x00010000 { ; RW data 

*.o(RAMCODE1) 

.ANY (+RW +ZI) 

} 

RW_IRAM2 0x20010000 0x00010000 { 

*.o(RAMCODE2) }

}

2. In the code, HAL_GetTick is placed within the scope of #pragma and declared to be placed in the RAMCODE1 section, while also being placed in the RAMCODE2 section with __attribute__((section(“RAMCODE2”))).

#pragma arm section code = "RAMCODE1" 

void <strong>ToggleLED</strong>(void)

{ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);

/* Insert a 100ms delay */ 

HAL_Delay(100); } 

void <strong>HAL_GPIO_TogglePin</strong>(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

{ /* Check the parameters */ 

assert_param(IS_GPIO_PIN(GPIO_Pin));

GPIOx->ODR ^= GPIO_Pin; 

} 

__attribute__((section("RAMCODE2"))) 

uint32_t <strong>HAL_GetTick</strong>(void) 

{ return tick; } 

void <strong>HAL_Delay</strong>(__IO uint32_t Delay) 

{ uint32_t tickstart = 0; 

tickstart = HAL_GetTick(); 

while((HAL_GetTick() - tickstart) < Delay) { }

} 

#pragma arm section

3. After compilation, we will check the map file to see which section HAL_GetTick was placed in.

How to Run Code in RAM Under KEIL Environment From the map, we can see that HAL_GetTick was ultimately placed in the RAMCODE2 section.

**How to Execute the Entire Program in SRAM**

Previously, methods for placing one or multiple programs at specified addresses were introduced. If there are many programs to place at specified addresses, we can also group these programs into one or several C files, and then place the object files generated from these C files at specified addresses in the .sct file.

Here, we will attempt to run the entire program in SRAM. After reset, the program starts from FLASH, and then executes all programs from SRAM. Below are the specific steps.

1. Place the interrupt vector table and interrupt handler in SRAM.

Create a new file startup_stm32f411xe_ram.s and place it at the starting position of 0x20000000 (modify it in the .sct file). Note that this is new, not directly placing the original file in SRAM. Why? Please think about it. In startup_stm32f411xe_ram.s, define a new SECTION called RESET_ram (and other modifications, please refer to the code). In the following .sct, we will place this RESET_ram section at the beginning of SRAM (see step 3).

; Vector Table Mapped to Address 0 at Reset 

AREA RESET_ram, DATA, READONLY 

EXPORT __Vectors_ram 

EXPORT __Vectors_End_ram

EXPORT __Vectors_Size_ram 

__Vectors_ram DCD 0 ; Top of Stack 

DCD 0 ; Reset Handler 

DCD NMI_Handler ; NMI Handler

……

2. In SystemInit, set the offset address of the interrupt vector table to 0x20000000. Enable VECT_TAB_SRAM.

#ifdef VECT_TAB_SRAM

SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */

#else SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */ 

#endif

3. Modify the .sct file to place all necessary object files for runtime into the SRAM execution area. Here, the interrupt vector table has two copies, one starting at 0x08000000 and the other at 0x20000000.

LR_IROM1 0x08000000 0x00080000 { ; load region size_region 

ER_IROM1 0x08000000 0x00080000 { ; load address = execution address

*.o (RESET, +First) 

*(InRoot$$Sections) 

.ANY (+RO) 

} RW_IRAM1 0x20000000 0x00020000 { ; RW data 

*.o (RESET_ram, +First) 

startup_stm32f411xe_ram.o(+RO) 

main.o(+RO +RW) 

stm32f4xx_it.o(+RO +RW)

stm32f4xx_hal.o(+RO +RW) 

stm32f4xx_hal_gpio.o(+RO +RW) 

stm32f4xx_hal_rcc.o(+RO +RW) 

stm32f4xx_hal_cortex.o(+RO +RW) 

.ANY (+RW +ZI) 

} 

}

4. After compilation, we can see from the map file or through debugging that after system reset, all programs start from the main function and run in RAM.

Additionally, if your program uses ARM low-level libraries, you can add *armlib*(+RO) to the .sct file to place all used library files in SRAM.

***************************************************************

Friendly Reminder: You can click on the related menu of this public account to browse past topics.

How to Run Code in RAM Under KEIL Environment

Leave a Comment