启动文件

启动文件

0. 它是谁、什么时候跑

  • 文件名:startup_stm32f407xx.s,汇编编写,放在 MDK-ARM 目录下。

  • 上电复位后第一个执行 的程序,在 main() 之前。

  • 干 5 件事:CPU 上电后从向量表第0项取栈指针、从第1项取 Reset_Handler 地址跳转 → 调 SystemInit() → 调 __main(C 库初始化)→ 跳进 main()。

1. 栈和堆

复制代码
 Stack_Size  EQU 0x00000400    ; 栈 1KB
 Heap_Size   EQU 0x00000200    ; 堆 512B
栈(Stack) 堆(Heap)
用途 局部变量、函数调用、形参 malloc() 动态分配
生长方向 高地址 → 低地址 低地址 → 高地址
大小默认值 1KB 512B
不够了会怎样 溢出 → HardFault malloc 返回 NULL

局部变量多了/递归深了,栈不够会莫名其妙进 HardFault。遇到这种事先把 Stack_Size 改大。

2. 向量表

向量表就是一个函数地址数组,存在 Flash 0 地址。

复制代码
 __Vectors  DCD __initial_sp       ; 第0个:栈顶地址(不是函数!)
            DCD Reset_Handler     ; 第1个:复位
            DCD NMI_Handler       ; 第2个:不可屏蔽中断
            DCD HardFault_Handler ; 第3个:硬错误
            ...
            DCD WWDG_IRQHandler   ; 外部中断从这里开始
            ...

CPU 响应中断的机制:中断号 × 4 → 到向量表里查对应地址 → 跳过去执行。

位置 内容 说明
0x00 __initial_sp 栈顶地址,复位后直接加载到 SP
0x04 Reset_Handler 复位后第一条要跑的代码
0x08~0x3C 系统异常 NMI / HardFault / SVC / PendSV / SysTick 等
0x40 起 外部中断 WWDG / PVD / 各种外设中断

3. Reset_Handler:复位后干了什么

复制代码
 Reset_Handler PROC
     LDR R0, =SystemInit    ; ① 调 SystemInit()
     BLX R0
     LDR R0, =__main        ; ② 调 __main(C 库初始化,最终调你的 main)
     BX  R0
 ENDP
  • SystemInit():在 system_stm32f4xx.c 里。HAL 工程里它不配时钟 ,只做 FPU 使能、向量表地址设置、外部 SRAM 配置。时钟靠你自己写的 SystemClock_Config()(见 4. 时钟.md 第6节)。

  • __main:C 库函数,初始化堆和栈,然后跳进你的 main()。

4. WEAK(弱定义)------最关键的概念

启动文件里所有中断服务函数都是空实现,标了 [WEAK]:

复制代码
 NMI_Handler PROC
     EXPORT NMI_Handler [WEAK]
     B .          ; 死循环
 ENDP

规则:

  • 你在 C 文件里写了同名函数(比如 void SysTick_Handler(void) {})→ 用你的。

  • 没写 → 用这个空的死循环。

后果:开了中断但忘了写中断服务函数 / 函数名拼错 → 中断来了直接跳进这个死循环,程序卡死。

你自己写中断函数时,名字必须和向量表里的完全一致(如 USART1_IRQHandler),错一个字母就等于没写。

5. 常用汇编指令速查

指令 作用
EQU 给常量起名字,相当于 C 的 #define
SPACE 分配内存(单位字节)
DCD 以字为单位分配并初始化内存(向量表用它存函数地址)
EXPORT 把本文件的标号导出给别的模块用,相当于 C 里非 static 的全局定义(对外可见)
IMPORT 声明标号来自外部文件,相当于 C 的 extern(引用)
B 跳转。B . = 跳到自己 = 死循环
BLX 跳转到函数并保存返回地址
WEAK 弱定义(不是 ARM 指令,是编译器伪指令)
ALIGN 地址对齐

6. MicroLIB

Keil 里 Target 选项卡勾的 "Use MicroLIB",对应的就是启动文件里的 __MICROLIB 宏:

复制代码
 IF :DEF:__MICROLIB
     EXPORT __initial_sp
     EXPORT __heap_base
     EXPORT __heap_limit
 ELSE
     ; 不勾 MicroLIB 时需要用户自己实现 __use_two_region_memory
 ENDIF
  • 勾了:Keil 自带精简 C 库,启动文件直接导出栈顶/堆起止地址,省事。

  • 不勾 :用标准 C 库,需要自己实现 __use_two_region_memory。

  • 嵌入式开发一般都勾 MicroLIB,代码小、启动快。

相关推荐
深圳老胡1 小时前
STM32CubeMX 生成 CMake 工程,用 VSCode 编译与调试的完整流程
笔记·stm32·单片机·代码规范
2401_862880822 小时前
iMX6ULL 驱动工程搭建 + LED 灯驱动
c语言·驱动开发·嵌入式硬件
hahaha60162 小时前
FPGA+ARM实现AWB的全流程
人工智能·嵌入式硬件·算法·计算机视觉·fpga开发
老孟说禅2 小时前
硬件测试 - 测试用例设计:测试需求分析、测试点提取、测试用例编写方法
单片机·嵌入式硬件·智能硬件
逐光者9333 小时前
STM32通信协议——I2C
stm32·单片机·嵌入式硬件
千秋岁rr3 小时前
15.PWR电源控制
单片机·嵌入式硬件
额额额对了3 小时前
嵌入式 Linux 启动程序文件
开发语言·嵌入式硬件·php
李日华大战鸡红3 小时前
FOC SVPWM过调制(学习记录)
stm32·单片机·学习
独孤九剑打醒他3 小时前
【原创开源·修订版】源-栅-漏-栅-源横向双栅MOS:从“被误解的短路”到“电流路径多值逻辑与顶层供电架构”
前端·嵌入式硬件·架构·开源·硬件工程