单片机
返回首页

STM32启动分析之main函数是怎样跑起来的

2025-08-18 来源:cnblogs

1、STM32启动规则

STM32根据boot0和boot1的电平决定启动位置,boot0=0时从主Flash启动,即0x08000000地址启动。

按照spec,M3核的中断向量表是不变的(中断向量表每一项为4个字节),中断向量表的顺序:栈顶、复位向量、中断向量.....。所以复位时0x00000000(映射在0x08000000)的值为栈顶指针,0x00000004(映射在0x08000004)是复位向量。

__Vectors     DCD     __initial_sp               ; Top of Stack

              DCD     Reset_Handler              ; Reset Handler

              DCD     NMI_Handler                ; NMI Handler

STM32复位时,CPU从0x00000000处获取栈顶指针MSP(默认使用主堆栈),从0x00000004处获取程序计数器PC(复位向量)。

STM32启动过程主要分5步:

初始化堆栈指针SP=_initial_sp

初始化PC指针=Reset_Handler

初始化中断向量表 Vector Table

配置系统时钟 SystemInit

初始化c的runtime,例如调_main初始化堆栈,最后调main函数进入用户C程序

2、MDK目标文件

1)MDK中C程序编译后的结果,即可执行文件数据分类:


RAM

ZI

bss 存储未初始化的或初始化为0的全局变量和静态变量

heap 堆,系统malloc和free操作的内存

stack 栈,存储函数临时局部变量

RW

data 已经初始化且不为0的全局变量和静态变量

FLASH

RO

text 代码段,CPU指令,字符串字面值、常数等,keil中叫Code段

constdata const常量,keil中叫RO-data

2)目标文件中各类型数据的存储位置


ZI-data 在bss段,ZI数据全为0,所以没有必要占用Flash空间,运行时占用RAM。


RW-data在RAM中,掉电丢失,所以需要启动时从FLASH拷贝到RAM中去,所以RW占FLASH空间。


由上我们得知keil的编译结果:

程序占用 Flash = Code + RO data + RW data

程序运行时候占用 RAM = RW data + ZI data。

Code + RO data + RW data 的大小也是生成的 bin 文件的大小


类似的,GCC的编译结果:


Memory region        Used Size  Region Size  %age Used 

       FLASH:         480 B        32 KB      1.46%

       RAM:           1200 B         4 KB     29.30%


Flash 的大小:Flash = text + data 。

RAM大小:RAM = data + bss。

3、STM32 startup.s 文件分析

1、栈分配

                AREA    STACK, NOINIT, READWRITE, ALIGN=3

Stack_Mem       SPACE   Stack_Size

__initial_sp

其中:


EQU 是伪指令,不生产具体的目标文件,相当于定义了一个宏定义提高可读性。

ARER 开辟一段代码段或数据段,后面的关键字表示这个段的属性:

STACK : 表示这个段的名字,可以任意命名。

NOINIT: 表示此数据段不需要填入初始数据。

READWRITE:表示此段可读可写。

ALIGN=3: 表示首地址按照2的3次方对齐,所以栈空间是8字节对齐的.

SPACE Stack_Memd 段分配 Stack_Size 的空间。

__initial_sp 是标号代表地址位置,即栈顶位置。

3、堆分配

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3

__heap_base

Heap_Mem        SPACE   Heap_Size

__heap_limit


                PRESERVE8

                THUMB

具体的含义和栈分配相似,开辟空间Heap_Mem,大小为Heap_Size。__heap_base和__heap_limit分别表示堆的起点和终点。

PRESERVE8 指当前文件字节对其。ALIGN 伪指令表示对齐填充,可能的取值为2的幂,如 1 、2 、4 、8 、16 等。未跟数字如ALIGN 表示对齐到1个字(2字节)。

THUMB 表示使用的指令集。


4、vector table

; Vector Table Mapped to Address 0 at Reset

      AREA    RESET, DATA, READONLY # 定义了RESET区域为READONLY即存储在Flash。

      EXPORT  __Vectors    # 中断向量表入口地址,EXPORT 是指该变量可以被导出,外部可以使用

      EXPORT  __Vectors_End  # 中断向量表的结束地址

      EXPORT  __Vectors_Size # 中断向量表的大小

下面开始建立中断向量表。

中断向量表类似一个全是函数指针的数组,每个函数指针代表对应中断号的中断处理程序入口。

向量表的起点是栈顶。DCD是定义一个word(4字节)的空间。


__Vectors       DCD     __initial_sp               ; Top of Stack

                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

                DCD     0                          ; Reserved

                DCD     0                          ; Reserved

                DCD     0                          ; Reserved

                DCD     0                          ; Reserved

                DCD     SVC_Handler                ; SVCall Handler

                DCD     DebugMon_Handler           ; Debug Monitor Handler

                DCD     0                          ; Reserved

                DCD     PendSV_Handler             ; PendSV Handler

                DCD     SysTick_Handler            ; SysTick Handler

//省略中断向量表

__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors  # 相减计算中断向量表的size

                

AREA    |.text|, CODE, READONLY  # 定义了一个只读的名为 .txt的代码段

; Reset handler

Reset_Handler   PROC   # PROC 汇编程序开始,ENDP汇编程序结束

                EXPORT  Reset_Handler             [WEAK]  # WEAK说明此函数可以被用户重写

                IMPORT  __main      # 从外部文件import一个函数

                IMPORT  SystemInit

                LDR     R0, =SystemInit  # load SystemInit 函数地址,做系统时钟初始化

                BLX     R0   # 跳转到 SystemInit 函数执行

                LDR     R0, =__main   # load _main

                BX      R0  # 跳转到_main执行,此_main非彼main

                ENDP

其他一些异常中断函数,简单起见直接使用死循环代替(在可靠性系统中用户应该检测并做特殊处理)。


; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC

                EXPORT  NMI_Handler                [WEAK]

                B       .

                ENDP

外设中的一些中断函数:


Default_Handler PROC


                EXPORT  WWDG_IRQHandler            [WEAK]

                EXPORT  PVD_IRQHandler             [WEAK]

                EXPORT  TAMPER_IRQHandler          [WEAK]

// 省略

DMA2_Channel3_IRQHandler

DMA2_Channel4_5_IRQHandler

                B       .


                ENDP     #先都用死循环代替,标记了WEAK用户可以重写,在外设驱动文件中已经包含了这些中断服务程序。


                ALIGN

5、堆栈配置与初始化

;------------------------------------------------------------------

; User Stack and Heap initialization

;------------------------------------------------------------------

                 IF      :DEF:__MICROLIB  # 如果启用了MDK的微库microLib

               

                 EXPORT  __initial_sp    # 导出这三个变量给外部使用

                 EXPORT  __heap_base

                 EXPORT  __heap_limit

                

                 ELSE  # 如果没有启动微库

                

                 IMPORT  __use_two_region_memory   # 导入__use_two_region_memory 标号

                 EXPORT  __user_initial_stackheap  # 导出__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

                 END

4、Keil MDK main函数启动

main()函数是第一个被执行的函数吗?

除了system_init初始化了系统时钟,mian函数启动前还发生了什么?

在startup.s文件中,跳转到_main,这个_main并不是c的main函数,而是编译器内置的一个c库函数,内部执行了三个步骤:初始化rw段,初始化zi段,调用另一个c库函数__rt_entry()。

__rt_entry()该函数先初始化堆栈和库函数,然后即调用主函数main(),从而进入用户程序。可以看出主函数main()若退出,则在__rt_entry()最后会再调用exit()函数进行退出操作。


什么是__rt_entry?标准库或ARM文档有如下描述。

详情见:https://developer.arm.com/documentation/dui0475/m/the-c-and-c---library-functions-reference/--rt-entry

所以main函数既不是c程序第一个执行的函数,也不是c程序最后一个执行的函数。在main启动前,标准库已经做了很多工作,当跳转mian时,堆栈已经完成了初始化、C运行时环境已经就绪。


__rt_entry

The symbol __rt_entry is the starting point for a program using the ARM C library.

Control passes to __rt_entry after all scatter-loaded regions have been relocated to their execution addresses.

Usage

1. The default implementation of __rt_entry:

2. Sets up the heap and stack.

3. Initializes the C library by calling __rt_lib_init.

4. Calls main().

5. Shuts down the C library, by calling __rt_lib_shutdown.

6. Exits.

__rt_entry must end with a call to one of the following functions:


exit()

Calls atexit()-registered functions and shuts down the library.


__rt_exit()

Shuts down the library but does not call atexit() functions.


_sys_exit()

Exits directly to the execution environment. It does not shut down the library and does not call atexit() functions.

startup.s文件中,DCD定义了76个中断服务函数入口,76*4=304=0x130。所以在汇编文件中,Flash地址0x08000000起点是DEC中断函数入口,0x08000130 位置是代码起点。如果进行代码调试跟踪,可以发现这段汇编代码实现的是堆和栈的初始化。


启用微库时的初始化过程,其中__scatterload 即对堆栈进行初始化,对比不启用微库的汇编程序,指令条目数已大幅缩减:


0x080007D8 2000      MOVS     r0,#0x00

0x080007DA E001      B        0x080007E0

0x080007DC C101      STM      r1!,{r0}

0x080007DE 1F12      SUBS     r2,r2,#4

0x080007E0 2A00      CMP      r2,#0x00

0x080007E2 D1FB      BNE      0x080007DC

0x0800012C 015F      DCW      0x015F

0x0800012E 0800      DCW      0x0800

                 __main:

0x08000130 F8DFD00C  LDR.W    sp,[pc,#12]  ; @0x08000140

                 _main_scatterload:

0x08000134 F000F82E  BL.W     __scatterload (0x08000194)

                 __main_after_scatterload:

0x08000138 4800      LDR      r0,[pc,#0]  ; @0x0800013C

0x0800013A 4700      BX       r0

0x0800013C 0969      DCW      0x0969

0x0800013E 0800      DCW      0x0800

                 __rt_final_cpp:

0x08000140 0428      DCW      0x0428

0x08000142 2000      DCW      0x2000

   151:                 LDR     R0, =SystemInit 

0x08000144 4806      LDR      r0,[pc,#24]  ; @0x08000160

   152:                 BLX     R0                

0x08000146 4780      BLX      r0

   153:                 LDR     R0, =__main 

0x08000148 4806      LDR      r0,[pc,#24]  ; @0x08000164

   154:                 BX      R0 

   155:                 ENDP 

   156:                  

   157: ; Dummy Exception Handlers (infinite loops which can be modified) 

   158:  

   159: NMI_Handler     PROC 

   160:                 EXPORT  NMI_Handler                [WEAK] 

0x0800014A 4700      BX       r0

   161:                 B       . 

   162:                 ENDP 


不启用微库时的初始化过程,启动代码大大增加。


0x08000130 F000F802  BL.W     __scatterload (0x08000138)

0x08000134 F000F847  BL.W     __rt_entry (0x080001C6)

0x08000138 A00A      ADR      r0,{pc}+4  ; @0x08000164

0x0800013A E8900C00  LDM      r0,{r10-r11}

0x0800013E 4482      ADD      r10,r10,r0

0x08000140 4483      ADD      r11,r11,r0

0x08000142 F1AA0701  SUB      r7,r10,#0x01

0x08000146 45DA      CMP      r10,r11

0x08000148 D101      BNE      0x0800014E

0x0800014A F000F83C  BL.W     __rt_entry (0x080001C6)

0x0800014E F2AF0E09  ADR.W    lr,{pc}-0x07  ; @0x08000147

0x08000152 E8BA000F  LDM      r10!,{r0-r3}

0x08000156 F0130F01  TST      r3,#0x01

0x0800015A BF18      IT       NE

0x0800015C 1AFB      SUBNE    r3,r7,r3

0x0800015E F0430301  ORR      r3,r3,#0x01

0x08000162 4718      BX       r3

0x08000164 1244      DCW      0x1244

0x08000166 0000      DCW      0x0000

0x08000168 1264      DCW      0x1264

0x0800016A 0000      DCW      0x0000

0x0800016C 3A10      SUBS     r2,r2,#0x10

0x0800016E BF24      ITT      CS

0x08000170 C878      LDMCS    r0!,{r3-r6}

0x08000172 C178      STMCS    r1!,{r3-r6}

0x08000174 D8FA      BHI      __scatterload_copy (0x0800016C)

0x08000176 0752      LSLS     r2,r2,#29

0x08000178 BF24      ITT      CS

0x0800017A C830      LDMCS    r0!,{r4-r5}

0x0800017C C130      STMCS    r1!,{r4-r5}

0x0800017E BF44      ITT      MI

0x08000180 6804      LDRMI    r4,[r0,#0x00]

0x08000182 600C      STRMI    r4,[r1,#0x00]

0x08000184 4770      BX       lr

0x08000186 0000      MOVS     r0,r0

0x08000188 2300      MOVS     r3,#0x00

0x0800018A 2400      MOVS     r4,#0x00

0x0800018C 2500      MOVS     r5,#0x00

0x0800018E 2600      MOVS     r6,#0x00

0x08000190 3A10      SUBS     r2,r2,#0x10

0x08000192 BF28      IT       CS

0x08000194 C178      STMCS    r1!,{r3-r6}

0x08000196 D8FB      BHI      0x08000190

0x08000198 0752      LSLS     r2,r2,#29

0x0800019A BF28      IT       CS

0x0800019C C130      STMCS    r1!,{r4-r5}

0x0800019E BF48      IT       MI

0x080001A0 600B      STRMI    r3,[r1,#0x00]

0x080001A2 4770      BX       lr

                 _printf_d:

0x080001A4 2964      CMP      r1,#0x64

0x080001A6 F000807D  BEQ.W    _printf_int_dec (0x080002A4)

                 _printf_percent_end:

0x080001AA 2000      MOVS     r0,#0x00

0x080001AC 4770      BX       lr

                 __rt_lib_init:

0x080001AE B51F      PUSH     {r0-r4,lr}

                 __rt_lib_init_fp_1:

0x080001B0 E89D0003  LDM      sp,{r0-r1}

0x080001B4 F000FB6C  BL.W     _init_alloc (0x08000890)

                 __rt_lib_init_atexit_1:

0x080001B8 F000F93A  BL.W     _initio (0x08000430)

                 __rt_lib_init_alloca_1:

0x080001BC BD1F      POP      {r0-r4,pc}

                 __rt_lib_shutdown:

0x080001BE B510      PUSH     {r4,lr}

                 __rt_lib_shutdown_stdio_2:

0x080001C0 F000F99F  BL.W     _terminateio (0x08000502)

                 __rt_lib_shutdown_fp_trap_1:

0x080001C4 BD10      POP      {r4,pc}

                 __rt_entry:

0x080001C6 F000FA02  BL.W     __user_setup_stackheap (0x080005CE)

0x080001CA 4611      MOV      r1,r2

                 __rt_entry_li:

0x080001CC F7FFFFEF  BL.W     __rt_lib_init (0x080001AE)

                 __rt_entry_main:

0x080001D0 F001F89A  BL.W     main (0x08001308)

0x080001D4 F000FB06  BL.W     exit (0x080007E4)

                 __rt_exit:

0x080001D8 B403      PUSH     {r0-r1}

                 __rt_exit_ls:

0x080001DA F7FFFFF0  BL.W     __rt_lib_shutdown (0x080001BE)

                 __rt_exit_exit:

0x080001DE BC03      POP      {r0-r1}

0x080001E0 F000FCE8  BL.W     _sys_exit (0x08000BB4)

   151:                 LDR     R0, =SystemInit 

0x080001E4 4809      LDR      r0,[pc,#36]  ; @0x0800020C

   152:                 BLX     R0                

0x080001E6 4780      BLX      r0

   153:                 LDR     R0, =__main 

0x080001E8 4809      LDR      r0,[pc,#36]  ; @0x08000210

   154:                 BX      R0 

   155:                 ENDP 


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦四级调频发射机

  • 500W MOS场效应管电源逆变器,12V转110V/220V

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • 12V转110V/220V 500W逆变器

  • DS1669数字电位器

    相关电子头条文章