SXSBJSXYT

保持热爱,奔赴山海

 

STM32启动文件剖析

 1.启动文件介绍

图1.1-文件介绍

        启动文件是系统上电复位后第一个执行的程序,从启动文件的介绍可以看出,该文件主要做了5个工作:

1. 初始化堆栈指针 SP__initial_sp
2. 初始化 PC 指针(Reset_Handler
3. 初始化中断向量表(__Vectors
4. 配置系统时钟(SystemInit
5. 调用 C 库函数 _main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界

2.堆栈空间

图2.1-堆栈空间大小

 2.1代码解释_栈

Stack_Size		EQU     0x400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
  1. 定义了栈的大小为0x400(十六进制)字节,即1024字节。
  2. 声明一个名为STACK的内存区域,NOINIT:表示该区域不需要初始化,READWRITE:表示该区域可读可写,ALIGN=3:表示该区域按2^3=8字节对齐。
  3. 在STACK区域中分配Stack_Size大小的空间,Stack_Mem是该空间的标签名。
  4. __initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址(栈是由高向低生长的)。栈顶指针的初始位置标签,用于系统启动时初始化SP寄存器。

 2.2代码解释_堆

Heap_Size      EQU     0x200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit
  1.  定义堆的大小为0x200(十六进制)字节,即512字节。
  2. 声明一个名为HEAP的内存区域,属性与STACK区域相同。
  3. __heap_base 表示堆的起始地址。
  4. 在HEAP区域中分配Heap_Size大小的空间。
  5. __heap_limit 表示堆的结束地址。(堆是由低向高生长的,跟栈的生长方向相反)

2.3字节对齐&指令集 

                PRESERVE8
                THUMB
  1. PRESERVE8指定当前文件的堆栈按照 8 字节对齐。
  2. THUMB:使用Thumb指令集,THUBM ARM 以前的指令集,16bit

3.向量表

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size
  1. 向量表在复位时会被映射到地址 0(Cortex-M 内核特性)。
  2. 定义一个名为 RESET 的只读数据段。
  3. 导出三个符号供链接器或其他文件使用。__Vectors:向量表的起始地址。__Vectors_End:向量表的结束地址。__Vectors_Size:向量表的大小(字节数)。

        向量表从 FLASH 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址,0x04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名, 函数名本质上就是一个地址。

图3.1-向量表源码

 __Vectors 为向量表起始地址,__Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。

 4.复位程序

        复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数 _mian。
图4.1-Reset_Handler
                AREA    |.text|, CODE, READONLY
定义一个名称为 .text 的代码段,属性为可读。
; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
     IMPORT  __main
     IMPORT  SystemInit
                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP
  1. Reset_Handler:复位处理函数的标签,是 Cortex-M 向量表中的第二个条目(地址 0x00000004)。PROC:表示一个子程序的开始。
  2. EXPORT:使 Reset_Handler 符号对其他文件(如链接器)可见。[weak]:弱定义,允许在其它文件中重新定义Reset_Handler 。
  3. IMPORT:声明外部符号(类似 C 的 extern)。__main:C 库的入口函数,负责初始化全局变量、堆栈等,最终调用用户的 main();SystemInit:系统初始化函数。
  4. 将 SystemInit 函数的地址加载到寄存器 R0。然后跳转到 R0 指向的地址(即调          用 SystemInit),并保存返回地址到 LR。
  5. 跳转到 __main(不返回,因为 __main 会最终调用 main())。

拓展1:汇编跳转指令B、BL、BX、BLX 和 BXJ的区别_b和blx指令的区别-CSDN博客

4.1关于__main

4.2-启动流程总览(图片来自网络)

        __main函数里做了什么?鄙人才学疏浅,暂未研究明白。我看到网上有个大佬写的很好,链接:

STM32 _main 里做了什么 - elmagnifico's bloghttps://elmagnifico.tech/2017/04/01/STM32-Startup-_main/STM32_从SystemInit、__main到main() 已修正 - 蓝天上的云℡ - 博客园https://chuna2.787528.xyz/yucloud/p/stm32_SystemInit_to_main.html

4.2关于“复位”

        复位一般分为三种情况:上电复位、外部复位(复位引脚)、软件复位。不管那一种复位,其结果都是一样的,CPU被清零,然后从一个固定地址0x00000000开始执行代码(这个地址存的是Reset_Handler的入口)。

图4.3-复位序列(CM3权威指南)

        处理器上电会首先从0x00000000地址读取8字节内容,前4字节为MSP指针,确认栈的起始地址。后4字节为复位函数的地址,并将其赋给PC指针,从而确定程序下一步执行的跳转地址。通过PC指针程序跳转到Reset_Handler,再由Reset_Handler跳转执行main函数,如下图所示,其比较详细的阐述了程序是如何从FLASH启动的。

图4.4-MSP及PC初始化(来自网络)

        STM32可以有3种不同的启动模式,分别是从FLASH、system memory、SRAM启动。我们一般使用FLASH启动,当选择FLASH启动,处理器会将FLASH的地址重映射到0x00000000,当我们访问0x00000000地址时实际上就是访问FLASH区域的起始地址,所以当我们将bin文件烧录到0x08000000地址,程序也能沿着0x00000000地址一步步执行。(system memory、SRAM启动方式同理)。

图4.5-启动模式(STM32中文参考手册)

5.中断服务函数 

; 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  PVD_IRQHandler             [WEAK]
                EXPORT  TAMPER_IRQHandler          [WEAK]
                EXPORT  RTC_IRQHandler             [WEAK]
                EXPORT  FLASH_IRQHandler           [WEAK]
                EXPORT  RCC_IRQHandler             [WEAK]
                EXPORT  EXTI0_IRQHandler           [WEAK]
                EXPORT  EXTI1_IRQHandler           [WEAK]
                EXPORT  EXTI2_IRQHandler           [WEAK]
                EXPORT  EXTI3_IRQHandler           [WEAK]
                EXPORT  EXTI4_IRQHandler           [WEAK]
                EXPORT  DMA1_Channel1_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel2_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel3_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel4_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel5_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel6_IRQHandler   [WEAK]
                EXPORT  DMA1_Channel7_IRQHandler   [WEAK]
                EXPORT  ADC1_2_IRQHandler          [WEAK]
                EXPORT  USB_HP_CAN1_TX_IRQHandler  [WEAK]
                EXPORT  USB_LP_CAN1_RX0_IRQHandler [WEAK]
                EXPORT  CAN1_RX1_IRQHandler        [WEAK]
                EXPORT  CAN1_SCE_IRQHandler        [WEAK]
                EXPORT  EXTI9_5_IRQHandler         [WEAK]
                EXPORT  TIM1_BRK_IRQHandler        [WEAK]
                EXPORT  TIM1_UP_IRQHandler         [WEAK]
                EXPORT  TIM1_TRG_COM_IRQHandler    [WEAK]
                EXPORT  TIM1_CC_IRQHandler         [WEAK]
                EXPORT  TIM2_IRQHandler            [WEAK]
                EXPORT  TIM3_IRQHandler            [WEAK]
                EXPORT  TIM4_IRQHandler            [WEAK]
                EXPORT  I2C1_EV_IRQHandler         [WEAK]
                EXPORT  I2C1_ER_IRQHandler         [WEAK]
                EXPORT  I2C2_EV_IRQHandler         [WEAK]
                EXPORT  I2C2_ER_IRQHandler         [WEAK]
                EXPORT  SPI1_IRQHandler            [WEAK]
                EXPORT  SPI2_IRQHandler            [WEAK]
                EXPORT  USART1_IRQHandler          [WEAK]
                EXPORT  USART2_IRQHandler          [WEAK]
                EXPORT  USART3_IRQHandler          [WEAK]
                EXPORT  EXTI15_10_IRQHandler       [WEAK]
                EXPORT  RTCAlarm_IRQHandler        [WEAK]
                EXPORT  USBWakeUp_IRQHandler       [WEAK]

WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler

                B       .

                ENDP

                ALIGN

5.1代码概述

        这段代码定义了ARM Cortex-M系列处理器的异常处理程序和中断服务程序(ISR)的默认实现。其主要由两部分程序组成:

  1. 系统异常处理程序:包括NMI、HardFault、MemManage等系统级异常处理
  2. 外设中断处理程序:包括各种外设(如WWDG、PVD、TIMER等)的中断处理

5.2代码结构分析

5.2.1系统异常处理程序

        代码定义了以下系统异常处理程序:

  • NMI_Handler:不可屏蔽中断处理
  • HardFault_Handler:硬件错误处理
  • MemManage_Handler:内存管理错误处理
  • BusFault_Handler:总线错误处理
  • UsageFault_Handler:使用错误处理
  • SVC_Handler:系统服务调用处理
  • DebugMon_Handler:调试监视器处理
  • PendSV_Handler:可挂起系统调用处理
  • SysTick_Handler:系统滴答定时器处理

        每个处理程序的实现都采用了相同的模式:

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
  • PROC /  ENDP:标记处理程序的开始和结束
  • EXPORT xxx_Handler [WEAK]:将处理程序作为弱符号导出,允许在其他文件中重新定义
  • B .:表示无限循环(跳转到当前位置),作为默认的空实现

5.2.2外设中断处理程序

        代码通过Default_Handler导出了所有外设中断处理程序,包括:

  • 窗口看门狗(WWDG)中断
  • 电源电压检测(PVD)中断
  • 定时器(TIM)中断
  • 串口(USART)中断
  • I2C、SPI等通信接口中断
  • 外部中断(EXTI)
  • DMA中断

        这些处理程序同样被定义为弱符号,实现为无限循环。

拓展:为什么程序会卡死在B .?

        答:触发了未处理的中断或异常,检查是否使能了某个中断但未重写其 handler。 

6.用户堆栈初始化 

6.1代码概述

        这段代码处理栈和堆的初始化配置,根据编译时是否定义了__MICROLIB宏(使用MicroLIB库)来选择不同的初始化方式。

6.1.1两种初始化模式

6.1.1.1使用MicroLIB时(IF分支)
IF :DEF:__MICROLIB
    EXPORT __initial_sp
    EXPORT __heap_base
    EXPORT __heap_limit

        当使用MicroLIB库时,代码导出三个关键符号:

  • __initial_sp:栈指针的初始值
  • __heap_base:堆的起始地址
  • __heap_limit:堆的结束地址(限制)

        这种方式更简单,由MicroLIB自行管理内存分配。这个__MICROLIB宏我们在 KEIL 里面配置,然后堆栈的初始化就由 C 库函数 _main 来完成。

图6.1-KEIL配置__MICROLIB宏
6.1.1.2不使用MicroLIB时(ELSE分支)
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

        当不使用MicroLIB时,代码采用标准的ARM两区域内存模型:

  • 导入__use_two_region_memory符号(由C库定义)
  • 导出__user_initial_stackheap函数
  • 函数内设置四个寄存器:
    • R0:堆的起始地址(Heap_Mem)
    • R1:栈顶地址(Stack_Mem + Stack_Size)
    • R2:堆的结束地址(Heap_Mem + Heap_Size)
    • R3:栈的起始地址(Stack_Mem)
  • BX LR指令返回到调用者

7.总结

        STM32的启动流程主要分为三大步骤:

  1. 上电复位:芯片一上电或者被复位,CPU就从一个固定的地址(通常是0x00000000)开始跑代码。
  2. 系统初始化:设置堆栈、准备数据、配置时钟等。
  3. 跳转到main函数:一切就绪后,跳转到用户编写的main函数,开始执行用户的程序逻辑。        

posted on 2025-05-12 08:55  SXSBJSXYT  阅读(23)  评论(0)    收藏  举报  来源

导航