一个工程代码总是以main作为入口函数,芯片上电时需要完成软硬件初始化工作,为执行main函数做准备。
复位流程
下图为MCU的映像文件部分内容:
所谓映像文件,就是工程编译完成后生成的bin文件。
映像文件起始位置存放中断向量表(Vector Table),然后依次存放程序代码和其他数据。中断向量表的第一项为栈顶指针MSP(Main Stack Pointer)的初值,第二项为复位向量,它指向了程序的第一个指令,即复位处理函数Reset_Handler。
如上图所示,MCU复位时,依次完成三个任务:
- 从向量表的第一项(@0x00000000)中取出MSP初值
- 从复位向量(@0x00000004)中取出Reset_Handler函数地址
- 跳转到Reset_Handler函数位置并执行
在Reset_Handler函数中,MCU执行软硬件初始化,包括Flash和RAM位置、时钟和PLL、静态和全局变量等。
下面详细介绍Reset_Handler的来龙去脉。
启动文件
启动文件定义了中断向量表、Reset_Handler函数,它直接决定了启动流程。
启动文件可以是C语言源文件文件(.c),也可以是汇编语言文件(.s)。IAR平台下启动文件通常是个汇编文件。
汇编格式的启动文件名字通常为startup_<< part_number >>.s,同一个型号芯片的启动文件一般相同。
下面以STM32L152芯片为例,打开startup_stm32l152xe.s文件,去掉注释和重复内容后,如下:
MODULE ?cstartup
;; Forward declaration of sections.
SECTION CSTACK:DATA:NOROOT(3)
SECTION .intvec:CODE:NOROOT(2)
EXTERN __iar_program_start
EXTERN SystemInit
PUBLIC __vector_table
DATA
__vector_table
DCD sfe(CSTACK)
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
<<Many More>>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Default interrupt handlers.
;;
THUMB
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER:NOROOT(2)
Reset_Handler
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
PUBWEAK NMI_Handler
SECTION .text:CODE:REORDER:NOROOT(1)
NMI_Handler
B NMI_Handler
<<Many More>>
END
这段程序实现了一个cstartup的模块,该模块分为三个部分,第一部分做全局声明以及数据的导入和导出,第二部分为DATA数据,定义了向量表,第三部分为Thumb指令,实现了各个中断处理函数。
第一部分,声明CSTACK段和.intvec段,各自的类型为DATA和CODE,EXTERN行表示导入外部函数,PUBLIC行表示其他文件可以使用该向量表。
第二部分,定义向量表__vector_table的内容,第一项SFE(CSTACK)表示CSTACK内存块的末尾地址,该项将赋给栈顶指针MSP,第二项Reset_Handler的实现部分在第三部分中被定义,后续各项分别对应着向量表的各个中断向量。DCD指令表示生成一个32-bit常数。
第三部分给出了向量表中各个中断处理函数的实现,其中Reset_Handler的实现如下:
Reset_Handler
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
其中BLX和BX均表示跳转指令,这几行代码可以理解为先调用SystemInit函数,再调用__iar_program_start函数。
SystemInit函数是ST的库函数,它完成基本的时钟配置,同时设置中断向量表在Flash中的基地址。该地址可以通过IAR的Option -> Linker -> Config -> Edit设置窗口进行设置,如下所示:
也可以通过链接文件(.icf)进行手动设置。打开链接文件(stm32l152xe_flash.icf),找到相应的代码行:
define symbol __ICFEDIT_intvec_start__ = 0x08000000;
__iar_program_start函数是IAR平台自带的函数,它的源文件为C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.5\arm\src\lib\thumb\cstartup_M.c,其内容如下:
void __iar_program_start( void )
{
__iar_init_core();
__iar_init_vfp();
__cmain();
}
前两个函数被封装在C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.5\arm\lib\rt7M_tl.a库文件中,无法得知其函数内容。
在Windows下可以使用命令行工具nm.exe查看.a库里面的符号列表,使用下列命令可以将符号列表输出到指定的文件中:
nm rt7M_tl.a >>symbol_list.txt
__cmain函数可以在C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.5\arm\src\lib\thumb\cmain.s文件中找到源码,但是汇编代码不易理解,这里通过反汇编代码,查看跳转逻辑。
打开IAR调试界面,使用反汇编窗口,通过搜索定位到__iar_program_start函数,可以看到它的前两行是空操作,第三行跳转到?main函数。
继续跟踪该函数,将其反汇编代码复制出来:
__cmain:
0x8000328: 0xf000 0xf80b BL __low_level_init ; 0x8000342
0x800032c: 0x2800 CMP R0, #0
0x800032e: 0xd001 BEQ.N _call_main ; 0x8000334
0x8000330: 0xf7ff 0xffdc BL __iar_data_init3 ; 0x80002ec
第一行__low_level_init函数总是返回常数1,第二行比较R0寄存器是否为0,第三行在R0 != 0时候执行,__call_main函数直接跳转到C语言的main函数,第四行在R0 == 0时执行__iar_data_init3函数。
__iar_data_init3函数也被封装在rt7M_tl.a中,通过单步追踪发现它调用了两个子函数:__iar_zero_init3和__ iar_copy_init3。
__iar_zero_init3给.bss段内的数据赋0,__ iar_copy_init3给.data段内的赋值。
这两个子函数能够在C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.5\arm\src\lib\init目录下找到各自源码。
赋值结束后,跳转至_call_main函数,进而转到C语言的main函数。
相关参考
The Definitive Guide to the ARM® Cortex-M3
IAR Help File
(完)
楼主 可以来一篇分析PSoC 4启动过程的文章么?
我大致看过creator平台,其实是一样的,psoc的启动文件用c写的,更好懂。你去找reset_handler,然后跟文中一样的追代码。