STM32F407在flash中执行代码的速度比在ram中执行代码的速度快。因为STM32F407有一颗“自适应实时存储器加速器”,这里不讨论ART Accelerator的加速方案。
把代码放在RAM中执行纯粹是为了学习。
将个别函数的代码放到RAM中运行
使用自己编写的链接脚本(sct文件)。
再来看自己编写的分散加载文件内容:
LR_IROM1 0x08000000 0x00004000 { ; load region size_region
ER_IROM1 0x08000000 0x00004000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00100000 { ; RW data
.ANY (+RW +ZI)
*.o (RAM_SPEED)
}
}
LR_ROM1后边的0x08000000是加载地址(代码存储时的地址),0x00004000是加载域的大小。
ER_IROM1 0x08000000是链接地址(代码执行时的地址,PC寻址的地址),0x00004000是执行域的大小。
在链接脚本中定义了一个代码段RAM_SPEED。
程序中将函数指定到代码段RAM_SPEED。
#pragma arm section code = "RAM_SPEED"
void my_delay(void)
{
volatile uint16_t i,j;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++);
}
#pragma arm section
编译链接后的加载地址和链接地址如下:
运行时PC指针如下图:
PC指针确实指向了SRAM区域,而不是FLASH区域。
通过打印发现my_delay在SRAM中运行时,耗时89453us。my_delay在FLASH中运行时,耗时71542us。也证实了在F407芯片上SRAM运行程序比FLASH运行程序快。
F407有一块CCM,我们把程序放到CCM中是否可行?
修改链接脚本
LR_IROM1 0x08000000 0x00004000 { ; load region size_region
ER_IROM1 0x08000000 0x00004000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00100000 { ; RW data
.ANY (+RW +ZI)
}
RW_IRAM2 0x10000000 0x00010000 { ; RW data
*.o (RAM_SPEED)
}
}
答案是不行,程序执行到my_delay的时候就hardfault了,原因是CCM呢M4内核只通过D-BUS总线相连,M4内核是通过I-BUS执行取指令的,所以不行。
还有一种方式是通过__attribute__声明函数,将函数声明在RAM_SPEED代码段,sct文件和上边那种方式一样。
__attribute__((section("RAM_SPEED"))) void my_delay(void)
{
volatile uint16_t i,j;
for(i=0;i<1000;i++)
for(j=0;j<1000;j++);
}
将整个工程代码放到RAM中运行
这种方案思路是写两套代码,一套是Boot代码,一套是APP代码。Boot代码在flash中存储在flash中执行,Boot上电将APP的代码拷贝到SRAM中,再跳转到SRAM中执行APP代码。APP代码是在flash中存储,在SRAM中执行。
Boot程序:使用默认的分散加载文件,target选项卡如下:
/*其它必要操作*/
/*从flash中把代码搬运到RAM中 共60K*/
memcpy(p,(uint8_t *)0x08004000,60*1024);
jump2app = (IapFun)*(vu32*)(0x20000800 + 4);
MSR_MSP(*(vu32*)0x20000800);//存0x20000800地址处开始存放程序
jump2app(); //跳转到APP
由上图和代码可以看出,我们给Boot分配了16KByte flash,2Kbyte RAM,这个根据个人实际情况分配。
APP程序:
先看一下分散加载文件
LR_IROM1 0x08004000 0x000FC000{ ; load region size_region
ER_IROM1 0x20000800 0x0000F000{
*.o (RESET, +First)
; *(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 +0 { ; RW data
.ANY (+RW +ZI)
}
}
Boot程序占用了16K Byte flash,那么我们应用程序就从0x08004000开始存储,也就是APP 分散加载文件的加载地址是0x08004000,给APP分配了0x000FC000个Bytes flash空间。APP的执行域(链接地址)从0x20000800开始,这里想一下能不能从0x20000000开始,肯定不行的呀,如果我们改成了0x20000000,那么Boot程序拷贝的时候就往0x20000000开始的地址处拷贝数据,但是拷贝的那一刻是在Boot中完成的,Boot会使用0x20000000到0x20000800这一段RAM,所以不行。
ER_ROM1中,把RESET链接到执行域(0x20000800)最前边,RO(只读数据段)和XO(执行段)也链接到执行域。
RW_IRAM1中,把初始化不为0的数据段和BSS段放到执行域RW_IRAM1中。
到这里理论上就可以了,我们需要做的核心工作:
1.在Boot程序从Flash中拷贝APP代码到SRAM。
2.跳转到SRAM,运行APP。
3.编写APP的分散加载文件。
但是实际上由于ST官方的启动文件中调用了__main,导致我们修改APP的分散加载文件后会链接报错。如下图
报错的意思就是entry那些数据段不能放到非启动区域,
由于__main是库文件,所以我们无法修改,也不知道如何修改。所以我放弃了使用__main,我大概知道__main库函数做了那些工作,__main函数就是把RW数据段从flash中搬运到RAM中,把RAM中的BSS数据段清零,最后跳转到main函数,那我们就自己在启动文件中实现这些工作。
汇编文件修改成如下代码:
; Amount of memory (in bytes) allocated for Stack
; Tailor this value to your application needs
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD 0x20000800 ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window WatchDog
;省略***
DCD FPU_IRQHandler ; FPU
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT mymain
IMPORT my_memcpy
IMPORT my_memset
IMPORT SystemInit
IMPORT |Image$$RW_IRAM1$$RW$$Base|
IMPORT |Load$$RW_IRAM1$$RW$$Base|
IMPORT |Image$$RW_IRAM1$$RW$$Length|
IMPORT |Image$$RW_IRAM1$$ZI$$Base|
IMPORT |Image$$RW_IRAM1$$ZI$$Length|
;重定位RW段
LDR R0, =|Image$$RW_IRAM1$$RW$$Base|
LDR R1, =|Load$$RW_IRAM1$$RW$$Base|
LDR R2, =|Image$$RW_IRAM1$$RW$$Length|
BL my_memcpy
;清除BSS段
LDR R0, =|Image$$RW_IRAM1$$ZI$$Base|
LDR R1, =0
LDR R2, =|Image$$RW_IRAM1$$ZI$$Length|
BL my_memset
; BL SystemInit
LDR R0, =SystemInit
BLX R0
; BL mymain
LDR R0, =mymain
BX R0
;IMPORT SystemInit
;IMPORT __main
;LDR R0, =SystemInit
;BLX R0
;LDR R0, =__main
;BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
;省略
EXPORT FPU_IRQHandler [WEAK]
WWDG_IRQHandler
;省略
FPU_IRQHandler
B .
ENDP
ALIGN
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****
main函数的名字我修改了mymain。在汇编中调用了my_memcpy和my_memset函数,代码实现如下
void my_memcpy(void *pcdest,void *pcsrc,int len)
{
uint8_t *dest = pcdest;
uint8_t *src = pcsrc;
while(len--)
{
*dest++ = *src++;
}
}
void my_memset(void *pcdest,int val,int len)
{
uint8_t *dest = pcdest;
while(len--)
{
*dest++ = 0;
}
}
我栈顶指针故意写成0x20000800,因为此时此刻已经来到了APP的世界,0x20000000到0x20000800属于Boot,我们在APP中又把这2K Bytes空间利用起来了,分配给了APP的栈空间。
这个APP程序虽然可以在RAM运行,但是有个问题,就是链接的时候把工程中的所有代码都链接到映像文件了,导致映像文件特别大,我们需要的是只链接那些调用到的代码,把所有都链接进去的原因是程序缺少指定一个入口点,这是在ARM LINK手册中看到的,如下图
我不知道如何解决,有知道的告诉一下。