MCU启动过程

一个工程代码总是以main作为入口函数,芯片上电时需要完成软硬件初始化工作,为执行main函数做准备。

复位流程

下图为MCU的映像文件部分内容:

MCU_Startup_Image

所谓映像文件,就是工程编译完成后生成的bin文件。

映像文件起始位置存放中断向量表(Vector Table),然后依次存放程序代码和其他数据。中断向量表的第一项为栈顶指针MSP(Main Stack Pointer)的初值,第二项为复位向量,它指向了程序的第一个指令,即复位处理函数Reset_Handler。

MCU_Startup_Reset_Sequence

如上图所示,MCU复位时,依次完成三个任务:

  1. 从向量表的第一项(@0x00000000)中取出MSP初值
  2. 从复位向量(@0x00000004)中取出Reset_Handler函数地址
  3. 跳转到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设置窗口进行设置,如下所示:

MCU_Startup_intvec_Setting

也可以通过链接文件(.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函数。

MCU_Startup_Deassembly_Window

继续跟踪该函数,将其反汇编代码复制出来:

__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

Booting Process

cortex-m3启动代码详解

STM32 Main里做了什么

(完)