STM32启动流程全面解析:从上电复位到进入main函数
目录
1 STM32启动总览
2 从 Boot ROM 到 Boot Loader
3 启动文件
3.1 启动文件到底是什么?
3.2 硬件自动执行(上电复位后):
3.3 启动文件的初始化工作
3.4 一图流总结
4 中断向量表
4.1 BOOT 配置:决定 “程序从哪里读”
4.2 向量表的 “唯一性” 与 “偏移”:
4.3 复位向量指向的复位服务函数
4.4 中断向量表里存的是什么?
4.5 函数的地址(入口地址)是固定的吗?
4.6 中断向量表:“程序入口” 的索引表
5 复位向量、Boot Loader 与启动文件详解
5.1 STM32启动代码与Bootloader的关系
两者的区别:
5.2 用户自定义Bootloader的执行流程
典型的双程序区布局:
Bootloader的启动执行过程:
阶段1:硬件自动执行
阶段2:Bootloader的启动代码执行
阶段3:Bootloader主逻辑
5.3 关键技术:从Bootloader跳转到应用程序
跳转函数的实现:
5.4 应用程序的特殊配置
链接脚本修改 (.ld文件):
应用程序中设置向量表偏移并开启中断:
需要注意
5.5 完整的启动序列
5.6 总结:
1 STM32启动总览
上电复位 ↓
硬件复位初始化:复位寄存器值、外设恢复默认状态(如关闭外设时钟) ↓
BootROM层:
执行BootROM(厂商芯片流片时固化的一段代码,固化在芯片内部) ├─→ 初始化基础时钟(如切换到HSI内部时钟) ├─→ 初始化必要外设(如Flash控制器、调试串口) ├─→ 检测启动模式(通过BOOT引脚或选项字节,启动Bootloader) │ ├─→ 从内部Flash(0×08000000)启动:直接跳转至用户程序起始地址 │ └─→ 从系统存储器(System Memory)启动:执行厂商Bootloader(如STM32的USB DFU) │ └─→ 从RAM启动(0x20000000) ↓
Bootloader层:
假设从内部Flash启动:加载用户Bootloader(若有) ├─→ 从内部Flash/外部存储介质读取用户Bootloader到RAM或直接执行 ├─→ 执行Bootloader的启动代码(.s文件) ├─→ 用户Bootloader执行: │ ├─→ 高级时钟配置(如PLL倍频到主频) │ ├─→ 外设深度初始化(如以太网、文件系统) │ ├─→ 固件更新检测(OTA、USB/UART通信) │ └─→ 跳转至应用程序复位向量(即main()的上级入口) ↓
Application层:
执行应用程序的启动代码(startup_xxx.s) ├─→ 初始化C语言环境(如复制.data段、清零.bss段) ├─→ 调用库初始化(如ARM的__main) └─→ 最终跳转至main()函数↓
main()函数
2 从 Boot ROM 到 Boot Loader
单片机如何从boot rom到boot loader的部分我的一篇博客已经讲过了:
STM32启动流程解析:从BootROM到BootLoader
3 启动文件
参考阅读:
stm32--启动文件(.s)与启动过程_stm32 .s-CSDN博客
STM32学习笔记(6): 启动代码(Startup Code)_怎么找stm32的运行代码-CSDN博客
3.1 启动文件到底是什么?
启动文件(通常是汇编文件,比如 STM32 的startup_stm32f103xe.s
)是由芯片厂商提供、直接与硬件内核(如 Cortex-M3/M4)对接的底层代码,它的作用是:
-
定义中断向量表(包括复位向量);
-
完成 CPU 上电后最基础的初始化(栈、堆、全局变量等);
-
最终 “接力” 跳转到用户代码的
main
函数。
具体作用:
3.2 硬件自动执行(上电复位后):
-
CPU从向量表的第一个条目(0x00000000)读取初始栈指针(MSP)
-
从向量表的第二个条目(0x00000004)读取复位向量(Reset_Handler的地址)
-
CPU跳转到
Reset_Handler
函数
3.3 启动文件的初始化工作
启动文件(如startup_stm32xxxx.s
)是一段汇编代码,主要完成以下任务:
startup_stm32xxxx.s 文件:
(下面的代码仅为演示,不代表实际代码); ... ; 定义初始堆栈大小
; ...
; ... ; 建立中断向量表
DCD __initial_sp ; 初始化中断向量表,第一个表项是栈顶地址
DCD Reset_Handler ; 初始化中断向量表,第二个表项是复位中断服务函数地址
DCD NMIException ; 初始化中断向量表
; ... ; 初始化中断向量表...Reset_Handler ; 复位中断服务函数(上电复位后执行)BL SystemInit ; 调用SystemInit函数(初始化系统时钟等)BL __main ; 调用__main函数(由编译器提供,负责初始化全局变量)BX LR ; 跳转返回
-
初始化栈和堆:在 SRAM 中划分栈空间(用于函数调用、局部变量)和堆空间(用于
malloc
等动态内存分配)。 -
初始化中断向量表。
-
初始化全局变量:
-
将 Flash 中
.rwdata
段的 “初始化了特定值的变量” 复制到 SRAM 的.data
段。 -
将 SRAM 中
.bss
段的 “未初始化或初始化为 0 的变量” 清零。
-
-
初始化系统外设:如配置系统时钟(SystemInit 函数,部分启动文件会调用)。
-
启动文件完成所有初始化后,通过汇编指令跳转到用户编写的
main
函数,此时才真正开始执行你写的第一行用户代码。
3.4 一图流总结
4 中断向量表
通过上文的Boot ROM与Boot Loader我们知道:Boot ROM会读取Boot引脚的电平配置来选择启动方式。
4.1 BOOT 配置:决定 “程序从哪里读”
单片机通过BOOT 引脚(如 STM32 的 BOOT0、BOOT1)配置程序的启动介质,这一步决定了 “中断向量表和代码存在哪里”:
-
BOOT0=0:从主 Flash启动(最常用,用户程序存放在这里)。
-
BOOT0=1,BOOT1=0:从系统存储器启动(用于出厂引导程序(厂家的bootloader)或 ISP 在线编程)。
-
BOOT0=1,BOOT1=1:从SRAM启动(多用于调试场景)。
4.2 向量表的 “唯一性” 与 “偏移”:
-
系统中只有一份 “逻辑上的中断向量表”,但它的 “物理存储位置” 可以通过
SCB->VTOR
偏移到 Flash 或 SRAM。 -
也就是说,向量表的 “内容(各中断的服务函数地址)” 是固定的,但 “存储这张表的物理地址” 可以切换 —— 这就是 “偏移” 的本质。
4.3 复位向量指向的复位服务函数
(如Reset_Handler
)是唯一的,但它的 “存储地址” 会随向量表的偏移而变化:
-
场景 1:向量表在 Flash(
SCB->VTOR = 0x08000000
),复位向量(0x08000004
)指向的是Flash 中存储的Reset_Handler
函数的地址。
-
场景 2:向量表在 SRAM(
SCB->VTOR = 0x20000000
)此时需要先将Flash 中的向量表复制到 SRAM(包括Reset_Handler
的地址),然后SCB->VTOR
指向 SRAM 起始地址。复位向量(0x20000004
)指向的是Flash 中存储的Reset_Handler
函数的地址。(与 Flash 中原始地址一致,因为向量表是复制过来的)。
4.4 中断向量表里存的是什么?
中断向量表的每一个条目(比如第0条是初始栈指针MSP,第1条是复位向量),存储的并不是指令代码本身,而是一个地址值(32位指针)。
-
对于复位向量,这个地址值就是
Reset_Handler
函数在内存中的入口地址。
4.5 函数的地址(入口地址)是固定的吗?
在编译和链接阶段,链接器会根据链接脚本(Linker Script)为每一个函数分配一个固定的逻辑地址。假设Reset_Handler
函数被链接器定位到了 Flash 区域的 0x08000C00
这个地址。
那么,无论中断向量表放在哪里(向量表偏移),Reset_Handler
这个函数的本体,其物理位置就在 Flash 的 0x08000C00
处,不会移动。而向量表中的复位向量就永远指向0x08000C00
这个地址。
4.6 中断向量表:“程序入口” 的索引表
上文说过,中断向量表是一个地址列表,存储了复位向量、所有中断服务函数的入口地址。它位于启动介质的起始地址区域(如 Flash 的0x0800 0000
处)。
它的结构(以 Cortex-M 内核为例):
5 复位向量、Boot Loader 与启动文件详解
概要:Bootloader 和 APP 是两个工程,两个工程都有自己的启动文件。bootloader如果存在,就会先进bootloader的启动文件,然后到bootloader的main()函数,执行完bootloader的流程,然后跳转到APP的Reset_Handler,执行APP的启动文件,再进APP的main()函数。
5.1 STM32启动代码与Bootloader的关系
启动代码不是Bootloader的一部分,但Bootloader会包含自己的启动代码。
两者的区别:
特性 | 启动代码 (Startup Code) | Bootloader |
目的 | 芯片基础初始化 | 应用程序管理、固件更新 |
位置 | 任何STM32程序都有 | 可选的独立程序 |
功能 | 栈初始化、变量初始化、时钟配置 | 跳转判断、固件验证、烧录逻辑 |
关系:Bootloader本身也是一个程序,它包含了自己的启动代码,用于初始化Bootloader运行所需的环境。Bootloader 和 APP 是两个工程。
5.2 用户自定义Bootloader的执行流程
典型的双程序区布局:
Flash布局 (0x08000000)
├── Bootloader区 (0x08000000 - 0x08007FFF)
│ ├── Bootloader的向量表
│ ├── Bootloader的启动代码
│ └── Bootloader的主逻辑
└── 应用程序区 (0x08008000 - ...)├── 应用程序的向量表(已偏移)├── 应用程序的启动代码└── 应用程序的主逻辑
Bootloader的启动执行过程:
阶段1:硬件自动执行
// 上电后硬件自动完成:
1. 从0x08000004读取复位向量 → Bootloader的Reset_Handler地址
2. 跳转到Bootloader的Reset_Handler
阶段2:Bootloader的启动代码执行
// startup_bootloader.s (Bootloader的启动文件)
Reset_Handler:/* 初始化栈和变量 */bl SystemInit // 时钟配置bl main_bootloader // 进入Bootloader主逻辑
阶段3:Bootloader主逻辑
// bootloader.c
int main_bootloader(void)
{/* 1. 硬件初始化 */init_uart(); // 串口通信init_flash(); // Flash驱动init_buttons(); // 按键检测/* 2. 检查是否需要进入升级模式 */if (check_update_condition()) {// 进入固件升级模式firmware_update_mode();}/* 3. 验证应用程序完整性 */if (validate_application()) {// 跳转到应用程序jump_to_application();} else {// 应用程序无效,停留在Bootloaderstay_in_bootloader();}
}
5.3 关键技术:从Bootloader跳转到应用程序
跳转函数的实现:
void jump_to_application(void)
{// 应用程序的起始地址(比如0x08008000)uint32_t app_address = 0x08008000;// 1. 获取应用程序的栈指针和复位向量uint32_t *app_vector_table = (uint32_t*)app_address;uint32_t app_sp = app_vector_table[0]; // 应用程序的栈指针uint32_t app_reset = app_vector_table[1]; // 应用程序的复位向量// 2. 重新初始化外设(避免Bootloader的影响)HAL_RCC_DeInit();HAL_DeInit();// 3. 关闭所有中断__disable_irq();// 4. 设置应用程序的向量表偏移SCB->VTOR = app_address;// 5. 设置应用程序的栈指针__set_MSP(app_sp);// 6. 跳转到应用程序的复位处理函数((void (*)(void))app_reset)();// 不会执行到这里
}
5.4 应用程序的特殊配置
为了让应用程序能在偏移地址运行,需要进行特殊配置:
链接脚本修改 (.ld文件):
MEMORY
{FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 224K /* 假设要将应用程序放在0x8008000 */RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
应用程序中设置向量表偏移并开启中断:
// 在SystemInit或main函数开始处
SCB->VTOR = FLASH_BASE | 0x8000; // 设置向量表偏移到0x08008000
__disable_irq(); // 开启中断
需要注意
-
SCB->VTOR
的配置必须放在HAL_Init()
之前(如代码中USER CODE BEGIN 1
处)。因为HAL_Init()
会初始化 SysTick 定时器,若此时向量表未重定向,会使用 Bootloader 的 SysTick 中断服务函数,导致异常。 -
Bootloader 跳转前可能会部分外设(如 RTC),APP 若需使用这些外设,需在
MX_XXX_Init()
后重新启用(如__HAL_RCC_RTC_ENABLE()
)。
5.5 完整的启动序列
上电复位↓
硬件自动从0x08000004读取向量 → Bootloader的Reset_Handler↓
执行Bootloader启动代码↓
执行Bootloader主逻辑↓
判断是否需要更新固件↓
验证应用程序完整性↓
跳转到应用程序的Reset_Handler (0x08008004)↓
执行应用程序启动代码↓
执行应用程序main函数
5.6 总结:
-
启动代码是基础:每个STM32程序(包括Bootloader)都有自己的启动代码
-
Bootloader是特殊程序:它运行在Flash起始位置,负责管理应用程序
-
跳转机制关键:通过修改VTOR寄存器实现从Bootloader到应用程序的平滑切换
-
应用程序需要适配:应用程序的链接地址和向量表都需要相应偏移
这种设计使得STM32可以实现IAP(在应用编程),支持固件远程升级等功能。