STM32启动过程和启动文件分析

    xiaoxiao2021-03-25  185

    一、概述

    1、说明

      每一款芯片的启动文件都值得去研究,因为它可是你的程序跑的最初一段路,不可以不知道。通过了解启动文件,我们可以体会到处理器的架构、指令集、中断向量安排等内容,是非常值得玩味的。

      STM32作为一款高端Cortex-M3系列单片机,有必要了解它的启动文件。打好基础,为以后优化程序,写出高质量的代码最准备。

      本文以一个实际测试代码--START_TEST为例进行阐述。

    2、整体过程概括

      STM整个启动过程是指从上电开始,一直到运行到main函数之间的这段过程,步骤为(以使用微库为例):

    ①上电后硬件设置SP、PC

    ②设置系统时钟

    ③软件设置SP

    ④加载.data、.bss,并初始化栈区

    ⑤跳转到C文件的main函数

    3、整个启动过程涉及的代码

      启动过程涉及的文件不仅包含startup_stm32f10x_hd.s,还涉及到了MDK自带的连接库文件entry.o、entry2.o、entry5.o、entry7.o等(从生成的map文件可以看出来)。

    二、程序在Flash上的存储结构

      在真正讲解启动过程之前,先要讲解程序下载到Flash上的结构和程序运行时(执行到main函数)时的SRAM数据结构。程序在用户Flash上的结构如下图所示。下图是通过阅读hex文件和在MDK下调试综合提炼出来的。

    MSP初始值        编译器生成,主堆栈的初始值

    异常向量表        不多说

    外部中断向量表      不多说

    代码段          存放代码

    初始化数据段       .data

    未初始化数据段      .bss 

    加载数据段和初始化栈的参数

      加载数据段和初始化栈的参数分别有4个,这里只讲解加载数据段的参数,至于初始化栈的参数类似。

    0x0800 033c  Flash上的数据段(初始化数据段和未初始化数据段)起始地址

    0x2000 0000  加载到SRAM上的目的地址

    0x0000 000c  数据段的总大小

    0x0800 02f4  调用函数_scatterload_copy

      需要说明的是初始化栈的函数--0x0800 0304与加载数据段的函数不一样,为_scatterload_zeroinit,它的目的就是将栈空间清零。

    三、数据在SRAM上的结构

      程序运行时(执行到main函数)时的SRAM数据结构

    四、详细过程分析

      有了以上的基础,现在详细分析启动过程。

    1、上电后硬件设置SP、PC

      刚上电复位后,硬件会自动根据向量表偏移地址找到向量表,向量表偏移地址的定义如下:

      调试现象如下:

      看看我们的向量表内容(通过J-Flash打开hex文件)

      硬件这时自动从0x0800 0000位置处读取数据赋给栈指针SP,然后自动从0x0800 0004位置处读取数据赋给PC,完成复位,结果为:

    SP = 0x0800 0810

    PC = 0x0800 0145

     2、设置系统时钟

      上一步中令PC=0x0800 0145的地址没有对齐,硬件自动对齐到0x0800 0144,执行SystemInit函数初始化系统时钟。

    3、软件设置SP

      LDR R0,=__main   BX   R0

      执行上两条之类,跳转到__main程序段运行,注意不是main函数,___main的地址是0x0800 0130。

      可以看到指令LDR.W sp,[pc,#12],结果SP=0x2000 0810。

    4、加载.data、.bss,并初始化栈区

    BL.W __scatterload_rt2

      进入 __scatterload_rt2代码段。

    __scatterload_rt2: 0x08000168 4C06 LDR r4,[pc,#24] ; @0x08000184 0x0800016A 4D07 LDR r5,[pc,#28] ; @0x08000188 0x0800016C E006 B 0x0800017C 0x0800016E 68E0 LDR r0,[r4,#0x0C] 0x08000170 F0400301 ORR r3,r0,#0x01 0x08000174 E8940007 LDM r4,{r0-r2} 0x08000178 4798 BLX r3 0x0800017A 3410 ADDS r4,r4,#0x10 0x0800017C 42AC CMP r4,r5 0x0800017E D3F6 BCC 0x0800016E 0x08000180 F7FFFFDA BL.W _main_init (0x08000138)

       这段代码是个循环(BCC 0x0800016e),实际运行时候循环了两次。第一次运行的时候,读取“加载数据段的函数(_scatterload_copy)”的地址并跳转到该函数处运行(注意加载已初始化数据段和未初始化数据段用的是同一个函数);第二次运行的时候,读取“初始化栈的函数(_scatterload_zeroinit)”的地址并跳转到该函数处运行。 相应的代码如下:

    0x0800016E 68E0 LDR r0,[r4,#0x0C] 0x08000170 F0400301 ORR r3,r0,#0x01 0x08000174 0x08000178 4798 BLX r3    当然执行这两个函数的时候,还需要传入参数。至于参数,我们在“加载数据段和初始化栈的参数”环节已经阐述过了。当这两个函数都执行完后,结果就是“数据在SRAM上的结构”所展示的图。最后,也把事实加载和初始化的两个函数代码奉上如下: __scatterload_copy: 0x080002F4 E002 B 0x080002FC 0x080002F6 C808 LDM r0!,{r3} 0x080002F8 1F12 SUBS r2,r2,#4 0x080002FA C108 STM r1!,{r3} 0x080002FC 2A00 CMP r2,#0x00 0x080002FE D1FA BNE 0x080002F6 0x08000300 4770 BX lr __scatterload_null: 0x08000302 4770 BX lr __scatterload_zeroinit: 0x08000304 2000 MOVS r0,#0x00 0x08000306 E001 B 0x0800030C 0x08000308 C101 STM r1!,{r0} 0x0800030A 1F12 SUBS r2,r2,#4 0x0800030C 2A00 CMP r2,#0x00 0x0800030E D1FB BNE 0x08000308 0x08000310 4770 BX lr

    5、跳转到C文件的main函数

    _main_init: 0x08000138 4800 LDR r0,[pc,#0] ; @0x0800013C 0x0800013A 4700 BX r0

    五、异常向量与中断向量表 

      View Code

       这段代码就是定义异常向量表,在之前有一个“J-Flash打开hex文件”的图片跟这个表格是一一对应的。编译器根据我们定义的函数 Reset_Handler、NMI_Handler等,在连接程序阶段将这个向量表填入这些函数的地址。

    startup_stm32f10x_hd.s内容: NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP stm32f10x_it.c中内容: void NMI_Handler(void) { }

      在启动汇编文件中已经定义了函数NMI_Handler,但是使用了“弱”,它允许我们再重新定义一个NMI_Handler函数,程序在编译的时候会将汇编文件中的弱函数“覆盖掉”--两个函数的代码在连接后都存在,只是在中断向量表中的地址填入的是我们重新定义函数的地址。 

    六、使用微库与不使用微库的区别

     

      使用微库就意味着我们不想使用MDK提供的库函数,而想用自己定义的库函数,比如说printf函数。那么这一点是怎样实现的呢?我们以printf函数为例进行说明。

    1、不使用微库而使用系统库

      在连接程序时,肯定会把系统中包含printf函数的库拿来调用参与连接,即代码段有系统库的参与。

      在启动过程中,不使用微库而使用系统库在初始化栈的时候,还需要初始化堆(猜测系统库需要用到堆),而使用微库则是不需要的。

    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

      另外,在执行__main函数的过程中,不仅需要完成“使用微库”情况下的所有工作,额外的工作还需要进行库的初始化,才能使用系统库(这一部分我还没有深入探讨)。附上__main函数的内容:

      View Code

    2、使用微库而不使用系统库

      在程序连接时,不会把包含printf函数的库连接到终极目标文件中,而使用我们定义的库。

      启动时需要完成的工作就是之前论述的步骤1、2、3、4、5,相比使用系统库,启动过程步骤更少。

    ======================代码分析=================================

    ;// <h> Stack Configuration   ;//   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>   ;// </h>   Stack_Size      EQU     0x00000200                           ;//定义堆栈大小                   AREA    STACK, NOINIT, READWRITE, ALIGN=3    ;//定义一个数据段 按8字节对齐   Stack_Mem       SPACE   Stack_Size                           ;//保留Stack_Size大小的堆栈空间   __initial_sp                                                 ;//标号,代表堆栈顶部地址,后面有用      ;// <h> Heap Configuration   ;//   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>   ;// </h>   Heap_Size       EQU     0x00000020                            ;//定义堆空间大小                   AREA    HEAP, NOINIT, READWRITE, ALIGN=3      ;//定义一个数据段,8字节对齐   __heap_base   Heap_Mem        SPACE   Heap_Size                             ;//保留Heap_Size的堆空间   __heap_limit                                                  ;//标号,代表堆末尾地址,后面有用                      PRESERVE8                                     ;//指示编译器8字节对齐                   THUMB                                         ;//指示编译器为THUMB指令      ; Vector Table Mapped to Address 0 at Reset                   AREA    RESET, DATA, READONLY                  ;//定义只读数据段,其实放在CODE区,位于0地址                                      EXTERN  NMIException                   EXTERN  HardFaultException                   EXTERN  MemManageException                   EXTERN  BusFaultException                   EXTERN  UsageFaultException                   EXTERN  SVCHandler                   EXTERN  DebugMonitor                   EXTERN  PendSVC                   EXTERN  SysTickHandler                         ;//声明这些符号在外部定义,同C                                                                  ;//在××it.c中实现这些函数 ,中断就能自动调用了                   EXPORT  __Vectors   __Vectors       DCD     __initial_sp              ; Top of Stack         //Cotex-M  要求此处为堆栈顶部地址                   DCD     Reset_Handler             ; Reset Handler                   DCD     NMIException              ; NMI Handler                   DCD     HardFaultException        ; Hard Fault Handler                   DCD     MemManageException        ; MPU Fault Handler                   DCD     BusFaultException         ; Bus Fault Handler                   DCD     UsageFaultException       ; Usage Fault Handler                   DCD     0                         ; Reserved                   DCD     0                         ; Reserved                   DCD     0                         ; Reserved                   DCD     0                         ; Reserved                   DCD     SVCHandler                ; SVCall Handler                   DCD     DebugMonitor              ; Debug Monitor Handler                   DCD     0                         ; Reserved                   DCD     PendSVC                   ; PendSV Handler                   DCD     SysTickHandler            ; SysTick Handler      //一大堆的异常处理函数地址                   ; External Interrupts                   EXTERN  WWDG_IRQHandler                   EXTERN  PVD_IRQHandler                   EXTERN  TAMPER_IRQHandler                   EXTERN  RTC_IRQHandler                   EXTERN  FLASH_IRQHandler                   EXTERN  RCC_IRQHandler                   EXTERN  EXTI0_IRQHandler                   EXTERN  EXTI1_IRQHandler                   EXTERN  EXTI2_IRQHandler                   EXTERN  EXTI3_IRQHandler                   EXTERN  EXTI4_IRQHandler                   EXTERN  DMAChannel1_IRQHandler                   EXTERN  DMAChannel2_IRQHandler                   EXTERN  DMAChannel3_IRQHandler                   EXTERN  DMAChannel4_IRQHandler                   EXTERN  DMAChannel5_IRQHandler                   EXTERN  DMAChannel6_IRQHandler                   EXTERN  DMAChannel7_IRQHandler                   EXTERN  ADC_IRQHandler                   EXTERN  USB_HP_CAN_TX_IRQHandler                   EXTERN  USB_LP_CAN_RX0_IRQHandler                   EXTERN  CAN_RX1_IRQHandler                   EXTERN  CAN_SCE_IRQHandler                   EXTERN  EXTI9_5_IRQHandler                   EXTERN  TIM1_BRK_IRQHandler                   EXTERN  TIM1_UP_IRQHandler                   EXTERN  TIM1_TRG_COM_IRQHandler                   EXTERN  TIM1_CC_IRQHandler                   EXTERN  TIM2_IRQHandler                   EXTERN  TIM3_IRQHandler                   EXTERN  TIM4_IRQHandler                   EXTERN  I2C1_EV_IRQHandler                   EXTERN  I2C1_ER_IRQHandler                   EXTERN  I2C2_EV_IRQHandler                   EXTERN  I2C2_ER_IRQHandler                   EXTERN  SPI1_IRQHandler                   EXTERN  SPI2_IRQHandler                   EXTERN  USART1_IRQHandler                   EXTERN  USART2_IRQHandler                   EXTERN  USART3_IRQHandler                   EXTERN  EXTI15_10_IRQHandler                   EXTERN  RTCAlarm_IRQHandler                   EXTERN  USBWakeUp_IRQHandler                    ;//同上,                                      DCD     WWDG_IRQHandler           ; Window Watchdog                   DCD     PVD_IRQHandler            ; PVD through EXTI Line detect                   DCD     TAMPER_IRQHandler         ; Tamper                   DCD     RTC_IRQHandler            ; RTC                   DCD     FLASH_IRQHandler          ; Flash                   DCD     RCC_IRQHandler            ; RCC                   DCD     EXTI0_IRQHandler          ; EXTI Line 0                   DCD     EXTI1_IRQHandler          ; EXTI Line 1                   DCD     EXTI2_IRQHandler          ; EXTI Line 2                   DCD     EXTI3_IRQHandler          ; EXTI Line 3                   DCD     EXTI4_IRQHandler          ; EXTI Line 4                   DCD     DMAChannel1_IRQHandler    ; DMA Channel 1                   DCD     DMAChannel2_IRQHandler    ; DMA Channel 2                   DCD     DMAChannel3_IRQHandler    ; DMA Channel 3                   DCD     DMAChannel4_IRQHandler    ; DMA Channel 4                   DCD     DMAChannel5_IRQHandler    ; DMA Channel 5                   DCD     DMAChannel6_IRQHandler    ; DMA Channel 6                   DCD     DMAChannel7_IRQHandler    ; DMA Channel 7                   DCD     ADC_IRQHandler            ; ADC                   DCD     USB_HP_CAN_TX_IRQHandler  ; USB High Priority or CAN TX                   DCD     USB_LP_CAN_RX0_IRQHandler ; USB Low  Priority or CAN RX0                   DCD     CAN_RX1_IRQHandler        ; CAN RX1                   DCD     CAN_SCE_IRQHandler        ; CAN SCE                   DCD     EXTI9_5_IRQHandler        ; EXTI Line 9..5                   DCD     TIM1_BRK_IRQHandler       ; TIM1 Break                   DCD     TIM1_UP_IRQHandler        ; TIM1 Update                   DCD     TIM1_TRG_COM_IRQHandler   ; TIM1 Trigger and Commutation                   DCD     TIM1_CC_IRQHandler        ; TIM1 Capture Compare                   DCD     TIM2_IRQHandler           ; TIM2                   DCD     TIM3_IRQHandler           ; TIM3                   DCD     TIM4_IRQHandler           ; TIM4                   DCD     I2C1_EV_IRQHandler        ; I2C1 Event                   DCD     I2C1_ER_IRQHandler        ; I2C1 Error                   DCD     I2C2_EV_IRQHandler        ; I2C2 Event                   DCD     I2C2_ER_IRQHandler        ; I2C2 Error                   DCD     SPI1_IRQHandler           ; SPI1                   DCD     SPI2_IRQHandler           ; SPI2                   DCD     USART1_IRQHandler         ; USART1                   DCD     USART2_IRQHandler         ; USART2                   DCD     USART3_IRQHandler         ; USART3                   DCD     EXTI15_10_IRQHandler      ; EXTI Line 15..10                   DCD     RTCAlarm_IRQHandler       ; RTC Alarm through EXTI Line                   DCD     USBWakeUp_IRQHandler      ; USB Wakeup from suspend   ;//同上                      AREA    |.text|, CODE, READONLY        ;//定义代码段      ; Reset Handler   Reset_Handler   PROC                                        ;//Rset_Handler的实现                   EXPORT  Reset_Handler             [WEAK]    ;//在外部没有定义该符号时导出该符号,见HELP中[WEAK]                   IMPORT  __main                              ;//导入符号,__main为 运行时库提供的函数;完成堆栈,堆的初始话                   LDR     R0, =__main                         ;//等工作,会调用下面定义的__user_initial_stackheap;                   BX      R0                                  ;//跳到__main,进入C的世界                   ENDP                      ALIGN      ; User Initial Stack & Heap                   IF      :DEF:__MICROLIB                     ;//如果使用micro lib,micro lib 描述见armlib.chm                                      EXPORT  __initial_sp                   EXPORT  __heap_base                   EXPORT  __heap_limit                        ;//只导出几个定义                                      ELSE                                        ;//如果使用默认C运行时库                                      IMPORT  __use_two_region_memory                   EXPORT  __user_initial_stackheap   __user_initial_stackheap                                    ;//则进行堆栈和堆的赋值,在__main函数执行过程中调用。                   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                                         ;//OK ,完了  

    转载请注明原文地址: https://ju.6miu.com/read-863.html

    最新回复(0)