3.2 启动文件解析
2026/4/26大约 15 分钟启动流程启动文件汇编STM32
3.2 启动文件解析
📚 本节导读
学习时长: 约 1 小时
难度级别: ⭐⭐☆☆☆(基础级)
前置知识: 3.1 复位和向量表
🎯 学习目标
- 理解启动文件的作用
- 掌握启动文件的主要部分
- 了解栈和堆的配置
- 理解数据段和 BSS 段的初始化
一、启动文件概述
1.1 什么是启动文件?
启动文件是系统上电后执行的第一段代码,由汇编语言编写。
为什么要用汇编?
- 必须直接操作 CPU 寄存器和内存
- 此时 C 语言运行环境还没准备好
- 需要设置栈指针(SP)等硬件级操作
1.2 启动文件的作用
启动文件主要完成以下工作:
1. 定义栈和堆
2. 定义向量表
3. 调用 SystemInit()
4. 跳转到 __main (C 库)
5. C 库初始化数据段和 BSS 段
6. 调用 main()1.3 我们使用的启动文件
文件位置:
projects/stm32f103zet6-atk-elite/board/startup/
├── startup_stm32f103xe_arm.s ; Keil 版本
└── startup_stm32f103xe_gcc.s ; GCC 版本本章节讲解 Keil 版本:startup_stm32f103xe_arm.s
二、启动文件结构解析
2.1 第一部分:栈和堆配置(第 33-52 行)
📝 说明: 内存分配分两步:
AREA- 声明段属性SPACE- 真正分配内存
; 配置栈大小
Stack_Size EQU 0x00000400 ; 栈大小 = 1KB
; 定义栈段
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
___initial_sp
; 配置堆大小
Heap_Size EQU 0x00000200 ; 堆大小 = 512 字节
; 定义堆段
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
; 汇编器设置
PRESERVE8
THUMB逐行解释
Stack_Size EQU 0x00000400- 解释: 使用 EQU 伪指令定义常量
- 含义: 定义栈的大小为 1KB(0x400 = 1024 字节)
- 作用: 告诉汇编器我们需要多大的栈空间
AREA STACK, NOINIT, READWRITE, ALIGN=3- 解释: 使用 AREA 伪指令定义一个段
- 含义: 定义一个名为 "STACK" 的段,特性为:NOINIT(不初始化)、READWRITE(可读写)、ALIGN=3(8 字节对齐)
- 作用: 声明栈段的属性,但不分配实际内存
Stack_Mem SPACE Stack_Size- 解释: 使用 SPACE 伪指令分配内存
- 含义: 在栈段中分配 Stack_Size 大小的内存
- 作用: 真正为栈预留 1KB 的空间
__initial_sp
___initial_sp- 解释: 定义两个标签
- 含义: 这两个标签都标记栈顶地址(栈是向下增长的,栈顶是最高地址)
- 作用: 向量表的第一个位置就是 __initial_sp,复位时 CPU 会把这个值加载到 SP 寄存器
Heap_Size EQU 0x00000200- 解释: 使用 EQU 伪指令定义常量
- 含义: 定义堆的大小为 512 字节(0x200 = 512 字节)
- 作用: 告诉汇编器我们需要多大的堆空间
AREA HEAP, NOINIT, READWRITE, ALIGN=3- 解释: 使用 AREA 伪指令定义一个段
- 含义: 定义一个名为 "HEAP" 的段,特性与栈段相同
- 作用: 声明堆段的属性,但不分配实际内存
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit- 解释: 定义三个标签并分配内存
- 含义:
__heap_base: 堆起始地址Heap_Mem SPACE Heap_Size: 真正分配 512 字节给堆__heap_limit: 堆结束地址
- 作用: 标记堆的边界,让 C 库知道哪里可以放动态分配的内存
PRESERVE8- 解释: 告诉汇编器保持栈 8 字节对齐
- 含义: 确保栈指针始终是 8 的倍数
- 作用: ARM Cortex-M3 要求栈 8 字节对齐,这是 AAPCS(ARM 架构过程调用标准)的要求
THUMB- 解释: 告诉汇编器使用 Thumb 指令集
- 含义: Thumb 是 ARM 的 16 位指令集(部分 32 位),代码密度更高
- 作用: Cortex-M3 只支持 Thumb 指令集,这告诉汇编器生成正确的指令
2.2 第二部分:向量表(第 56-142 行)
; 定义 RESET 段
AREA RESET, DATA, READONLY, ALIGN=10
; 导出符号
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
EXPORT ___initial_sp
; 异常向量表
__Vectors DCD __initial_sp ; 初始栈指针
DCD Reset_Handler ; 复位处理函数
DCD NMI_Handler ; 不可屏蔽中断
DCD HardFault_Handler ; 硬件错误
DCD MemManage_Handler ; 内存管理错误
DCD BusFault_Handler ; 总线错误
DCD UsageFault_Handler ; 使用错误
DCD 0 ; 保留
DCD 0
DCD 0
DCD 0
DCD SVC_Handler ; SVC 调用
DCD DebugMon_Handler ; 调试监控
DCD 0
DCD PendSV_Handler ; PendSV 处理
DCD SysTick_Handler ; SysTick 处理
; 外部中断向量表
DCD WWDG_IRQHandler ; 窗口看门狗
DCD PVD_IRQHandler ; 电压检测
DCD TAMPER_IRQHandler ; 篡改检测
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI 0
DCD EXTI1_IRQHandler ; EXTI 1
DCD EXTI2_IRQHandler ; EXTI 2
DCD EXTI3_IRQHandler ; EXTI 3
DCD EXTI4_IRQHandler ; EXTI 4
DCD DMA1_Channel1_IRQHandler ; DMA1 通道1
DCD DMA1_Channel2_IRQHandler ; DMA1 通道2
DCD DMA1_Channel3_IRQHandler ; DMA1 通道3
DCD DMA1_Channel4_IRQHandler ; DMA1 通道4
DCD DMA1_Channel5_IRQHandler ; DMA1 通道5
DCD DMA1_Channel6_IRQHandler ; DMA1 通道6
DCD DMA1_Channel7_IRQHandler ; DMA1 通道7
DCD ADC1_2_IRQHandler ; ADC1 和 ADC2
DCD USB_HP_CAN1_TX_IRQHandler ; USB 高优先级或 CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB 低优先级或 CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI 线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 线15-10
DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
DCD TIM8_BRK_IRQHandler ; TIM8 Break
DCD TIM8_UP_IRQHandler ; TIM8 Update
DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
DCD ADC3_IRQHandler ; ADC3
DCD FSMC_IRQHandler ; FSMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Channel1_IRQHandler ; DMA2 通道1
DCD DMA2_Channel2_IRQHandler ; DMA2 通道2
DCD DMA2_Channel3_IRQHandler ; DMA2 通道3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 通道4和5
__Vectors_End
; 计算向量表大小
__Vectors_Size EQU __Vectors_End - __Vectors逐行解释
AREA RESET, DATA, READONLY, ALIGN=10- 解释: 使用 AREA 伪指令定义一个段
- 含义: 定义一个名为 "RESET" 的段,特性为:DATA(数据段)、READONLY(只读)、ALIGN=10(1024 字节对齐)
- 作用: 向量表必须放在只读区域,且需要按 1024 字节对齐(VTOR 的要求)
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
EXPORT ___initial_sp- 解释: 使用 EXPORT 伪指令导出符号
- 含义: 让这些标签对其他文件可见
- 作用: 链接器可以找到向量表的位置,C 库可以用 __initial_sp 来初始化栈
__Vectors DCD __initial_sp ; 初始栈指针- 解释: 定义标签 __Vectors 并使用 DCD 伪指令分配第一个向量
- 含义:
DCD(Define Constant Data)是汇编伪指令,用于分配 4 字节(32 位)的内存空间并初始化- 这里把
__initial_sp(栈顶地址)的值存储到这个位置 - 标记向量表的起始位置
- 作用: 这是整个向量表的第一个位置,CPU 复位时自动把这个值加载到 SP 寄存器
DCD Reset_Handler ; 复位处理函数- 解释: 使用 DCD 伪指令分配 4 字节内存
- 含义: 第二个向量是 Reset_Handler 函数的地址
- 作用: CPU 复位时自动把这个值加载到 PC 寄存器,开始执行 Reset_Handler
DCD NMI_Handler ; 不可屏蔽中断
DCD HardFault_Handler ; 硬件错误
DCD MemManage_Handler ; 内存管理错误
DCD BusFault_Handler ; 总线错误
DCD UsageFault_Handler ; 使用错误
DCD 0 ; 保留
DCD 0
DCD 0
DCD 0
DCD SVC_Handler ; SVC 调用
DCD DebugMon_Handler ; 调试监控
DCD 0
DCD PendSV_Handler ; PendSV 处理
DCD SysTick_Handler ; SysTick 处理- 解释: 一系列 DCD 指令
- 含义: 异常向量表,包含各种异常的处理函数地址
- 作用: 当异常发生时,CPU 根据异常类型跳转到对应的处理函数
; 外部中断向量表
DCD WWDG_IRQHandler ; 窗口看门狗
DCD PVD_IRQHandler ; 电压检测
DCD TAMPER_IRQHandler ; 篡改检测
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI 0
DCD EXTI1_IRQHandler ; EXTI 1
DCD EXTI2_IRQHandler ; EXTI 2
DCD EXTI3_IRQHandler ; EXTI 3
DCD EXTI4_IRQHandler ; EXTI 4
DCD DMA1_Channel1_IRQHandler ; DMA1 通道1
; ... (省略其他中断向量)
DCD DMA2_Channel4_5_IRQHandler ; DMA2 通道4和5- 解释: 一系列 DCD 指令
- 含义:
- 使用 DCD 伪指令为每个外部中断分配 4 字节内存
- 存储各个外部中断处理函数的地址
- 包含 STM32F103 所有 60 个外部中断,包括窗口看门狗、电压检测、RTC、Flash、RCC、外部中断线、DMA、定时器、串口等
- 作用: 当外部中断发生时,CPU 根据中断号跳转到对应的处理函数
__Vectors_End- 解释: 定义标签 __Vectors_End
- 含义: 标记向量表的结束位置
- 作用: 用来计算向量表大小
__Vectors_Size EQU __Vectors_End - __Vectors- 解释: 使用 EQU 伪指令计算常量
- 含义: 向量表大小 = 结束地址 - 起始地址
- 作用: 如果需要动态重定位向量表,可以用这个值
2.3 第三部分:Reset_Handler(第 144-155 行)
AREA |.text|, CODE, READONLY
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
; 1. 调用 SystemInit()
LDR R0, =SystemInit
BLX R0
; 2. 跳转到 __main
LDR R0, =__main
BX R0
ENDP逐行解释
AREA |.text|, CODE, READONLY- 解释: 使用 AREA 伪指令定义代码段
- 含义: 定义名为 ".text" 的段,特性为:CODE(代码段)、READONLY(只读)
- 作用: 存放可执行代码(Reset_Handler 就在这里)
Reset_Handler PROC- 解释: 使用 PROC 伪指令定义一个过程
- 含义: 开始定义 Reset_Handler 函数
- 作用: PROC 标记函数开始,ENDP 标记函数结束
EXPORT Reset_Handler [WEAK]- 解释: 使用 EXPORT 伪指令导出符号
- 含义: 导出 Reset_Handler,且使用 WEAK(弱定义)
- 作用: 用户可以在其他文件中重新定义 Reset_Handler 来覆盖启动文件中的版本
IMPORT __main
IMPORT SystemInit- 解释: 使用 IMPORT 伪指令导入符号
- 含义: 告诉汇编器 __main 和 SystemInit 在其他文件中定义
- 作用: 链接器会在链接时找到这些函数的地址
LDR R0, =SystemInit- 解释: 加载立即数到 R0 寄存器
- 含义: 把 SystemInit 函数的地址加载到 R0
- 作用: 为调用 SystemInit 做准备
BLX R0- 解释: 分支并交换指令集
- 含义: 跳转到 R0 中的地址执行,返回地址保存在 LR(链接寄存器)
- 作用: 调用 SystemInit 函数
LDR R0, =__main- 解释: 加载立即数到 R0 寄存器
- 含义: 把 __main 的地址加载到 R0
- 作用: 为跳转到 __main 做准备
BX R0- 解释: 分支并交换指令集
- 含义: 跳转到 R0 中的地址执行,不保存返回地址
- 作用: 跳转到 __main,C 库接管,不再回到 Reset_Handler
ENDP- 解释: 使用 ENDP 伪指令结束过程
- 含义: 标记 Reset_Handler 函数结束
- 作用: 结束函数定义
重要: 启动文件调用的函数只有两个:
SystemInit()- 系统初始化__main- C 库入口
2.4 第四部分:默认异常处理(第 157-199 行)
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逐行解释
NMI_Handler PROC- 解释: 使用 PROC 定义函数
- 含义: 开始定义 NMI_Handler 函数
- 作用: 不可屏蔽中断(NMI)发生时,CPU 会跳转到这里
EXPORT NMI_Handler [WEAK]- 解释: 导出符号
- 含义: 导出 NMI_Handler,WEAK弱定义
- 作用: 用户可以覆盖这个函数
B .- 解释: 分支指令
- 含义: 无条件跳转到当前地址(
.表示当前地址) - 作用: 无限循环(死循环),如果没有自定义处理,就停在这里
ENDP- 解释: 结束函数定义
- 含义: 标记 NMI_Handler 函数结束
- 作用: 结束函数
说明:
- 上述结构适用于所有异常处理函数(HardFault_Handler, MemManage_Handler 等)
[WEAK]表示弱定义,用户可以覆盖- 默认处理是死循环
B .
2.5 第五部分:默认中断处理(第 201-326 行)
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 RTC_Alarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
EXPORT TIM8_BRK_IRQHandler [WEAK]
EXPORT TIM8_UP_IRQHandler [WEAK]
EXPORT TIM8_TRG_COM_IRQHandler [WEAK]
EXPORT TIM8_CC_IRQHandler [WEAK]
EXPORT ADC3_IRQHandler [WEAK]
EXPORT FSMC_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT UART4_IRQHandler [WEAK]
EXPORT UART5_IRQHandler [WEAK]
EXPORT TIM6_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT DMA2_Channel1_IRQHandler [WEAK]
EXPORT DMA2_Channel2_IRQHandler [WEAK]
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_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
RTC_Alarm_IRQHandler
USBWakeUp_IRQHandler
TIM8_BRK_IRQHandler
TIM8_UP_IRQHandler
TIM8_TRG_COM_IRQHandler
TIM8_CC_IRQHandler
ADC3_IRQHandler
FSMC_IRQHandler
SDIO_IRQHandler
TIM5_IRQHandler
SPI3_IRQHandler
UART4_IRQHandler
UART5_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
DMA2_Channel1_IRQHandler
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B .
ENDP逐行解释
Default_Handler PROC- 解释: 定义 Default_Handler 函数
- 含义: 这是所有外部中断的默认处理函数
- 作用: 所有外部中断默认都跳转到这里
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
; ... (省略其他中断声明)
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]- 解释: 导出所有外部中断向量,使用 WEAK 弱定义
- 含义: 给每个中断向量都定义一个标签
- 作用: 用户可以在其他文件中重新定义这些中断处理函数来覆盖默认版本
WWDG_IRQHandler
PVD_IRQHandler
; ... (省略其他标签)
DMA2_Channel4_5_IRQHandler- 解释: 定义多个标签
- 含义: 这些标签都指向同一个地方(Default_Handler)
- 作用: 这样所有中断向量都指向同一个默认处理函数
B .- 解释: 无限循环
- 含义: 死循环
- 作用: 所有外部中断的默认处理都是死循环
ENDP- 解释: 结束函数定义
- 含义: Default_Handler 结束
- 作用: 结束函数
说明: 所有外部中断的默认处理都是死循环。
⚠️ 重要细节:为什么中断标签没有 PROC?
仔细观察会发现:
- 异常处理函数(如 NMI_Handler、HardFault_Handler)都使用了
PROC/ENDP - 中断处理标签(如 WWDG_IRQHandler、DMA2_Channel2_IRQHandler)没有使用 PROC
为什么这样设计?
Default_Handler PROC ; ← 只有 Default_Handler 使用 PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
; ... (省略其他 EXPORT)
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
WWDG_IRQHandler ; ← 这些只是标签,不是独立函数
PVD_IRQHandler
; ... (省略其他标签)
DMA2_Channel2_IRQHandler
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B . ; 统一执行死循环
ENDP ; ← Default_Handler 结束设计思路:
| 特性 | 说明 |
|---|---|
| 标签即别名 | 所有中断标签都定义在 Default_Handler 的 PROC/ENDP 之间 |
| 共享代码 | 所有中断向量最终都指向同一段代码(B . 死循环) |
| 简洁高效 | 不需要为 60 个中断都重复写 PROC/ENDP |
| 易于覆盖 | 用户只需要重新定义某个标签就能覆盖默认处理 |
向量表中的使用:
__Vectors DCD __initial_sp
DCD Reset_Handler
; ...
DCD DMA2_Channel2_IRQHandler ; 指向标签
DCD DMA2_Channel3_IRQHandler ; 指向标签
DCD DMA2_Channel4_5_IRQHandler ; 指向标签这样既满足了向量表需要独立函数地址的要求,又保持了代码的简洁性!
2.6 第六部分:用户栈和堆初始化(第 330-356 行)
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
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
END逐行解释
IF :DEF:__MICROLIB- 解释: 条件汇编
- 含义: 如果定义了 __MICROLIB 符号
- 作用: 区分是用标准库还是 MicroLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit- 解释: 导出三个符号
- 含义: 导出栈顶、堆起始、堆结束
- 作用: MicroLIB 直接用这些符号,不用调用函数
ELSE- 解释: Else 分支
- 含义: 否则用标准 C 库
IMPORT __use_two_region_memory- 解释: 导入 C 库内部符号
- 含义: 告诉链接器我们用双区域内存模型(栈和堆是分开的两个区域)
EXPORT __user_initial_stackheap- 解释: 导出函数
- 含义: 导出 __user_initial_stackheap 给 C 库
__user_initial_stackheap- 解释: 定义函数入口标签(注意:这里没有使用 PROC 伪指令)
- 含义: 函数名
- 作用: C 库的 __main 会调用这个函数
- 为什么不用 PROC:
PROC/ENDP是用于标记函数边界的伪指令,会生成一些额外信息__user_initial_stackheap是一个叶子函数(Leaf Function),非常简单:- 只设置四个寄存器(R0-R3)作为返回值
- 直接返回(
BX LR) - 没有栈操作、没有局部变量、不需要保存恢复寄存器
- 这是 C 库的特殊约定接口,调用方式非常简单
- 不用 PROC/ENDP 可以让代码更简洁高效
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR- 解释: 加载四个地址
- 含义: 把堆起始、栈顶、堆结束、栈底地址分别加载到 R0-R3
- 作用: 返回值给 C 库
ALIGN- 解释: 对齐指令
- 含义: 保证下一条指令对齐
- 作用: 让汇编器生成对齐
ENDIF- 解释: 结束条件汇编
- 含义: IF ... ELSE ... ENDIF 结束
END- 解释: 汇编文件结束
- 含义: 告诉汇编器文件结束了
- 作用: 结束整个启动文件
__user_initial_stackheap 的作用
这个函数告诉 C 库栈和堆的位置:
__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- 解释: 这是 __user_initial_stackheap 的实际代码
- 含义: 四个寄存器返回四个地址给 C 库
- 作用: C 库用这些来知道堆和栈在哪里
调用者: C 库的 __main 在初始化时调用这个函数。
三、完整启动流程
⚠️ 关键点说明
为什么有两种情况?
查看启动文件最后部分:
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
ENDIF| 库类型 | 方式 | 说明 |
|---|---|---|
| MicroLIB | 直接使用符号 | 导出 __initial_sp、__heap_base、__heap_limit,C 库直接读取这些地址 |
| 标准 C 库 | 调用函数 | 导出 __user_initial_stackheap 函数,C 库调用它来获取栈和堆位置 |
💡 本节总结
重点回顾
- 启动文件的作用: 上电后执行的第一段代码
- 启动文件的内容: 栈/堆定义、向量表、Reset_Handler、异常/中断处理
- Reset_Handler 调用的函数:
SystemInit()和__main - __user_initial_stackheap 的作用: 告诉 C 库栈和堆的位置
下一步
接下来请学习:3.3 SystemInit详解