KEIL环境下如何让代码在 RAM中运行

分享到:

前言
经常遇到有人使用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

1

 

编译后从map文件可以看到,ToggleLED以及其中调用到的HAL_GPIO_TogglePin和HAL_Delay函数的地址都在FLASH中。

2
将翻转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

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中。

4

方法二:通过__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中。

00

方法二可以覆盖方法一,也就是说如果你同时用方法一和方法二对同一个函数的执行区域做了说明。最终起作用的是方法二。还是通过上面提到的代码来说明。
修改.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。

5

从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中。

 

 
 
 
继续阅读
意法半导体推出STM32MP1,支持Linux,加快物联网和智能工业创新

中国,2019年2月26日 – 横跨多重电子应用领域的全球领先的半导体供应商意法半导体 利用多年积累的Arm® Cortex® 研发知识扩大STM32 MCU的功能,使这一市场领先的微控制器产品组合覆盖到处理性能和资源要求更高且需要大型开源软件的应用领域。新推出的STM32MP1多核微处理器系列具有计算和图形处理能力...

意法半导体发布多合一在线工具,简化STM32和STM8 微控制器电机控制设计流程

中国,2019年2月19日 — 意法半导体ST-MC-SUITE可轻松获取STM32和STM8微控制器电机控制应用开发全部资源的入口。该工具允许用户收集教程和文档,存储项目设置(硬件和软件),获取软件解决方案下载链接,包括最新的X-CUBE-MCSDK软件包,以及在线购买评估硬件。

意法半导体在CES 2019上展示下一代先进应用技术和解决方案

中国,2019年1月9日 – 意法半导体将为1月8日 - 10日拉斯维加斯国际消费电子展带来一场非常特别的技术展会。在邀请主要客户和合作伙伴参加的贵宾专场展会上,意法半导体将发布各种产品技术用例,让客户能够开发最有用和最有价值的下一代汽车、工业、个人电子和基础设施应用。

意法半导体推出STM32神经网络开发工具箱,将 AI技术引入边缘和节点嵌入式设备

中国,2019年1月4日 – 横跨多重电子应用领域的全球领先的半导体供应商意法半导体 (STMicroelectronics,简称ST;纽约证券交易所代码:STM)借助STM32系列微控制器的市场领导地位,扩展了STM32微控制器开发生态系统STM32CubeMX,增加了先进的人工智能(AI)功能。

意法半导体扩大对亚马逊FreeRTOS的支持,推出集成蓝牙®、以太网、LTE Cat-M / NB-IoT的开发入门套件

中国,2018年12月10日 – 横跨多重电子应用领域的全球领先的半导体供应商意法半导体 在亚马逊技术大会AWS 2018上发布新的STM32微控制器开发入门套件,扩大业界最受欢迎的STM32家族的32位Arm®Cortex®-M微控制器对Amazon FreeRTOS操作系统的支持。