配置开发板的BOOT,从EFLASH(即片内Flash存储器)启动。
在KEIL中也需要进行相应的设置,将接下来要编写的bootloader烧写至EFLASH(bootloader段需要设置从0x8000000开始,空间根据bootloader大小分配)。
我们将bootloader程序反汇编可以查看EFLASH分配占用情况。
结合startup_ac78xx.s启动文件来分析MCU上电的启动流程。
__Vectors DCD __initial_sp ; Top of Stack 0
DCD Reset_Handler ; Reset Handler 04
MCU上电后首先建立从0x8000000开始建立中断向量表,随后因为复位进入Reset_Handler函数(中断向量0x08000135),下面是Reset_Handler函数的执行的具体操作。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
; enable lockup reset
LDR R1, =0x4000000C
LDR R0, =0x0301FF00
STR R0, [R1]
; enable xosc monitor
LDR R1, =0x40000000
LDR R0, [R1]
ORR R0, R0,#0x00010000
STR R0, [R1]
; enable xosc ready flag
LDR R1, =0x400088b0
LDR R0, [R1]
BIC R0, R0,#0x10000000
STR R0, [R1]
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
关注到在进入_main函数前,SystemInit的函数首先被执行。SystemInit函数定义在system_ac78xx.c文件中。
void SystemInit (void)
{
SetEflashClock();
//SPM_EnableLVD(0); //disable LVD if need
}
SetEflashClock函数从函数名理解,应该是设置内部Flash时钟的,为我们后续烧写EFLASH做初始化工作。我们暂且不去深究SetEflashClock函数的具体操作。在SystemInit函数执行完返回到下一条指令,这时才会进入_main函数。
在map文件中可以查看Reset_Handler、_main、SystemInit函数入口地址,以及_initial_sp的数据内容。
有趣的是_main_stk和_main有着同样的入口地址,我们来看下有关它们的反汇编代码。
这段代码执行的操作是将0x20002518赋值给sp指针。
__main
_main_stk
0x0800011c: f8dfd010 .... LDR sp,__lit__00000000 ; [0x8000130] = 0x20002518
.ARM.Collect$$$$00000004
_main_scatterload
0x08000120: f000fa9c .... BL __scatterload ; 0x800065c
.ARM.Collect$$$$00000008
.ARM.Collect$$$$0000000A
.ARM.Collect$$$$0000000B
__main_after_scatterload
_main_clock
_main_cpp_init
_main_init
0x08000124: 4800 .H LDR r0,[pc,#0] ; [0x8000128] = 0x800302d
0x08000126: 4700 .G BX r0
$d
0x08000128: 0800302d -0.. DCD 134230061
$t
.ARM.Collect$$$$0000000E
__rt_lib_shutdown_fini
0x0800012c: f3af8000 .... NOP.W
$d
.ARM.Collect$$$$00002712
__lit__00000000
.ARM.Collect$$$$0000000F
.ARM.Collect$$$$00000011
__rt_final_cpp
__rt_final_exit
0x08000130: 20002518 .%. DCD 536880408
重点关注以下三条指令。
由于ARM采用三级流水线架构(取指——译指——执行),当CPU执行到0x08000124处时,此时PC指针已经指向0x08000128处,接下来执行跳转指令,程序从0x0800302d处开始执行。我们可以从反汇编文件中查看对应的函数为用户定义的main函数。
因此,MCU上电后并非从用户定义的main函数开始执行!!!
而是按如下步骤进行:
(1)从0x8000000(启动位置)开始定义中断向量表。
(2)通过中断向量表执行Reset_Handler中断服务函数。
(3)执行SystemInit函数。(进行系统初始化工作)
(4)返回执行_main函数。(设置SP指针,跳转到用户定义main函数)