前语
常常遇到有人运用KEIL时需求将部分或许悉数程序代码放到RAM中运转的问题,现将其总结在本文中。经过STM32F411Nucleo的一个例子来介绍几种让程序在RAM中运转的办法。
咱们先从ToggleLED函数在Flash中履行亮灭开端。下面是ToggleLED函数和它的调用状况。在main函数的while(1)里调用ToggleLED。
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) { ToggleLED(); }
}
编译环境的Linker的装备见下图:
Flash开端地址:0x08000000
RAM开端地址:0x20000000
编译后从map文件能够看到,ToggleLED以及其间调用到的HAL_GPIO_TogglePin和HAL_Delay函数的地址都在FLASH中。
将翻转LED的程序放到SRAM中履行
办法一:经过#pragma arm section code = “RAMCODE ”和#pragma arm section。参阅Example1代码。
这种办法,能够一起将多个函数放到指定的section。详细办法如下:
1. 修正.sct文件,自界说一个叫做RAMCODE的section,放在RW_IRAM1履行区域,地址规模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. 在工程中运用前面修正的.sct文件
3.以#pragma arm section code = “RAMCODE” 最初,以#pragma arm section结束。将一切需求放到RAMCODE section的函数包含进来。编译时,编译器会主动将这些函数放到RAMCODE地点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.从map文件里,能够看到这四个函数都现已被放到了SRAM中。
办法二:经过__attribute__((section(“name ”)))
在KEIL中能够经过__attribute__((at(address)))的办法将变量放到指定的方位。
经过__attribute__((section(“name ”)))的办法将变量或许函数放到指定的方位。
下面咱们来看看怎么经过这种办法将程序放到SRAM中履行。
1.相同,咱们需求修正.sct文件,自界说一个叫做RAMCODE的section,并在工程选项的linker页面中,挑选界说好的.sct文件。(见办法一中的第1,2步)
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.在需求放到RAM中的函数前,用__attribute__((section(“RAMCODE”)))声明该函数放在RAMCODE section中。留意,该函数中调用到的一切函数也要放到RAMCODE section中。
__attribute__((section(“RAMCODE”)))
void ToggleLED(void)
{ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); /* Insert a 100ms delay */
HAL_Delay(100);
}__attribute__((section(“RAMCODE”)))
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;
}__attribute__((section(“RAMCODE”)))
__weak uint32_t HAL_GetTick(void)
{ return uwTick; }__attribute__((section(“RAMCODE”)))
__weak void HAL_Delay(__IO uint32_t Delay)
{ uint32_t tickstart = 0;
tickstart = HAL_GetTick();
while((HAL_GetTick() – tickstart) < Delay)
{ }
}
3.从编译后的map文件能够看出,ToggleLED以及它调用到的一切函数都被到了RAM中。
办法二能够掩盖办法一,也就是说假如你一起用办法一和办法二对同一个函数的履行区域做了阐明。终究起效果的是办法二。仍是经过上面说到的代码来阐明。
修正.sct文件。将SRAM分为两个履行区RW_IRAM1和RW_IRAM2。Section RAMCODE1,RAMCODE2别离坐落0x20000000开端,和0x20010000开端的两个64KB的区域。
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.在代码中, HAL_GetTick被放在了#pragma的效果域内被声明放在RAMCODE1 section,一起又用__attribute__( ( section ( “RAMCODE2” ) ) ) 将其放在RAMCODE2的section内。
#pragma arm section code = “RAMCODE1”
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;
}__attribute__( ( section ( “RAMCODE2” ) ) )
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
3.编译完成后,咱们看看map文件中HAL_GetTick被放到了哪个section。
从map里能够看到,终究HAL_GetTick被放在了RAMCODE2 section中。
**怎么将整个程序放到SRAM中履行
前面介绍了将一个或多个程序放到指定地址履行的办法。假如需求放到指定地址的程序比较多,咱们还能够将这些需求放到指定地址的程序会集放到一个或几个C文件中,然后在.sct文件中将这些C文件生成的方针文件放到指定地址。
在这儿,咱们将测验将整个程序放到SRAM中履行。复位后程序从FLASH发动,之后将从SRAM履行一切的程序。下面是详细的过程。
1.将中止向量表和中止处理程序放到SRAM中
新建一个startup_stm32f411xe_ram.s文件,放到0x20000000开端的方位(在.sct文件中修正)。留意这儿是新建,而不是直接将本来的文件放到SRAM中,为什么呢?我们能够考虑一下。在startup_stm32f411xe_ram.s里界说新的SECTION,叫做RESET_ram(还有其他的修正,请对照参阅代码)。在后面的.sct中将把RESET_ram这个section放到SRAM开端的方位上(见第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.在SystemInit中将中止向量表的偏移地址设置为0x20000000。使能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.修正.sct文件,将运转时需求的一切方针文件都放到SRAM履行区中。这儿中止向量表有相同的两份,一份在0x08000000开端的方位,一份在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. 编译完成后,从map文件或许盯梢调试的成果都能够看到。体系复位今后,从main函数开端,一切的程序都在RAM中运转了。
别的,假如你的程序中有用到ARM底层的库,能够在.sct文件中参加*armlib*(+RO)来将一切用到的库文件放到SRAM中。