STM32启动过程详解:从上电复位到main,程序到底是如何从Flash运行起来的?

前言
学习 STM32 时,我们写程序通常都是从:
int main(void)
{
while (1)
{
}
}
开始。
这很容易让人产生一个误解:
MCU 上电以后执行的第一条代码是不是 main()?
答案是:不是。
STM32 在真正进入 main() 之前,已经完成了一系列非常重要的工作:
上电 / 复位
↓
判断启动区域
↓
读取中断向量表
↓
加载初始栈指针 MSP
↓
获取 Reset_Handler 地址
↓
执行启动代码
↓
初始化系统运行环境
↓
初始化 .data 和 .bss
↓
进入 main()
与此同时,我们下载到 STM32 中的程序通常保存在 Flash 中。
那么又会产生另一个问题:
程序既然存储在 Flash 中,CPU 是怎么开始执行它的?变量为什么又放到 RAM 中?
本文就从 STM32 上电开始,把整个启动过程完整讲清楚。
一、程序最终存在哪里?
STM32 程序编译、链接完成后,会生成:
.elf
.hex
.bin
等固件文件。
通过 ST-Link、J-Link、串口 BootLoader 等方式下载时,本质上就是把程序写入 MCU 内部 Flash。
以很多 STM32 为例,用户 Flash 起始地址通常类似:
0x08000000
程序镜像大致可以理解为:
Flash
0x08000000
┌──────────────────────┐
│ 中断向量表 │
├──────────────────────┤
│ .text 程序代码 │
├──────────────────────┤
│ .rodata 只读常量 │
├──────────────────────┤
│ .data 初始值 │
├──────────────────────┤
│ 其他程序内容 │
└──────────────────────┘
因此我们平时所说的:
把程序烧进单片机。
更准确地说,就是:
把编译后的程序镜像写入 MCU 的 Flash。
二、Flash 和 RAM 有什么区别?
在理解启动过程之前,必须先分清 Flash 和 RAM。
1. Flash
Flash 是非易失性存储器。
也就是说:
掉电以后,数据仍然存在。
Flash 通常用于保存:
- 程序代码;
- 中断向量表;
- 常量;
- 字符串;
- 已初始化变量的初始值;
- BootLoader;
- 固件参数。
例如:
const char message[] = "Hello STM32";
这种只读数据通常可以放在 Flash 中。
2. RAM
RAM 是易失性存储器。
也就是说:
掉电以后,数据通常会丢失。
但是 RAM 可以快速读写,因此主要用于保存程序运行过程中的数据,例如:
- 全局变量;
- 静态变量;
- 局部变量;
- Stack;
- Heap;
- DMA 缓冲区;
- UART 接收缓冲区;
- ADC 采样数组;
- 各种运行状态。
可以简单理解:
Flash:负责长期保存
RAM:负责运行时读写
三、程序是不是全部搬到RAM以后再运行?
通常不是。
大多数 STM32 都支持 CPU 直接从内部 Flash 中读取指令并执行。
也就是:
CPU
↓
从 Flash 读取机器指令
↓
执行
这种方式可以理解为:
Execute In Place
也就是常说的 XIP。
因此,大多数 STM32 程序并不需要在启动时把整个 .text 代码段复制到 RAM。
通常是:
代码:主要在 Flash 中取指执行
变量:主要在 RAM 中读写
所以程序运行时实际上是 Flash 和 RAM 配合工作的。
四、STM32上电后第一步发生什么?
STM32 上电以后,芯片首先进入复位状态。
导致 MCU 复位的原因可能有很多,例如:
上电复位
NRST 外部复位
软件复位
独立看门狗复位
窗口看门狗复位
低功耗唤醒
以上电为例,过程可以简单理解为:
电源上升
↓
内部电源监控
↓
芯片保持复位
↓
电压稳定
↓
释放复位
↓
CPU开始启动
CPU 并不会在电源刚刚出现时立即执行 main()。
五、第二步:判断从哪里启动
STM32 并不一定只能从 User Flash 启动。
常见启动区域包括:
User Flash
System Memory
SRAM
经典 STM32 中经常通过 BOOT0、BOOT1 等配置决定启动区域。
较新的 STM32 还可能通过:
Option Bytes
nBOOT0
nBOOT1
BOOT_LOCK
等方式控制。
具体规则必须根据具体 STM32 型号的数据手册和参考手册判断。
六、三种常见启动方式
1. 从 User Flash 启动
这是普通 STM32 工程最常见的启动方式。
也就是启动我们自己下载进去的程序。
例如:
0x08000000
附近存放用户程序。
2. 从 System Memory 启动
System Memory 中通常存放 ST 出厂时烧录好的 BootLoader。
它可以支持某些接口下载程序,例如:
UART
USB DFU
CAN
I2C
SPI
具体支持哪些接口,要看具体 STM32 型号。
3. 从 SRAM 启动
部分 STM32 支持从 SRAM 启动。
这种方式通常用于:
调试
测试
特殊程序运行
普通产品程序一般还是从 Flash 启动。
七、为什么Flash地址是0x08000000,却又经常看到0x00000000?
这是 STM32 启动过程中一个非常重要的知识点。
用户 Flash 的实际物理地址通常类似:
0x08000000
但是 Cortex-M 复位后,需要从启动地址区域读取向量表。
STM32 可以通过内部地址映射,把当前选择的启动存储区域映射到:
0x00000000
所以从 User Flash 启动时,可以简单理解为:
实际 Flash
0x08000000
↓
启动地址映射
↓
0x00000000
这并不代表 Flash 真的位于 0x00000000。
而是 MCU 在启动时做了地址映射。
八、中断向量表是什么?
STM32 Cortex-M 启动最重要的结构之一就是:
Interrupt Vector Table
也就是中断向量表。
如果程序位于:
0x08000000
那么向量表通常就在程序镜像最前面。
例如:
0x08000000 初始 MSP
0x08000004 Reset_Handler
0x08000008 NMI_Handler
0x0800000C HardFault_Handler
0x08000010 MemManage_Handler
…
很多人会认为:
中断向量表就是一堆中断函数地址。
这种说法并不完全准确。
因为向量表第一项不是函数地址。
九、向量表第一项是什么?
向量表第一个 32 位数据保存的是:
初始 MSP
MSP 全称:
Main Stack Pointer
也就是主栈指针。
例如:
0x08000000 → 0x20020000
可以理解为:
MSP = 0x20020000
也就是说,CPU 复位后首先要建立一个有效的栈。
十、为什么启动时必须先设置栈?
C 语言程序大量依赖栈。
例如:
- 函数调用;
- 返回地址;
- 局部变量;
- 寄存器现场保存;
- 中断进入;
- 异常处理。
如果栈还没有准备好,就直接进入:
main();
那么函数调用和异常处理都可能出问题。
所以 Cortex-M 在执行普通程序之前,会先从向量表第一项取出初始 MSP。
可以记住:
向量表第1项 = 初始栈顶
十一、向量表第二项是什么?
向量表第二项保存的是:
Reset_Handler
地址。
例如:
0x08000004 → Reset_Handler 地址
CPU 会把这个地址加载到:
PC
Program Counter
程序计数器。
于是:
PC = Reset_Handler
然后程序从 Reset_Handler 开始执行。
所以启动过程中最重要的两步就是:
第1项 → MSP
第2项 → PC
可以记成:
第一项装栈,第二项装 PC。
十二、启动文件中的向量表大概是什么样?
STM32 启动文件中经常可以看到类似:
__Vectors
DCD __initial_sp
DCD Reset_Handler
DCD NMI_Handler
DCD HardFault_Handler
DCD MemManage_Handler
DCD BusFault_Handler
DCD UsageFault_Handler
其中:
DCD __initial_sp
表示保存初始栈地址。
DCD Reset_Handler
表示保存复位入口地址。
后面才是各种异常和外设中断函数地址。
十三、Reset_Handler到底是什么?
Reset_Handler 是 MCU 复位后的启动入口。
可以理解为:
main() 之前的总管家。
CPU 设置好 MSP 和 PC 后,就开始执行 Reset_Handler。
Reset_Handler 一般会完成:
系统底层初始化
.data 初始化
.bss 清零
C/C++ 运行库初始化
最后进入 main()
不过不同编译器、不同工具链的具体实现顺序可能不同。
例如:
Keil
GCC
IAR
ARMClang
启动文件结构并不完全一样。
十四、SystemInit是什么?
STM32 工程中经常会看到:
SystemInit();
通常定义在:
system_stm32xxxx.c
中。
它用于完成一些最基础的系统初始化,例如:
内核相关设置
FPU设置
部分时钟初始化
向量表相关设置
外部内存设置
不同 STM32 系列的实现差别会很大。
需要注意:
SystemInit();
和 CubeMX 中经常出现的:
SystemClock_Config();
并不是同一个东西。
CubeMX 工程中常见:
int main(void)
{
HAL_Init();
SystemClock_Config();
while (1)
{
}
}
这里的 SystemClock_Config() 通常用于进一步配置:
PLL
SYSCLK
HCLK
PCLK1
PCLK2
等系统业务时钟。
十五、什么是.text段?
.text 通常保存程序机器指令。
例如:
int add(int a, int b)
{
return a + b;
}
最终会被编译成 ARM Thumb 机器指令。
这些机器指令通常存放在:
Flash
CPU 运行时直接从 Flash 取指执行。
因此:
.text → Flash
十六、什么是.rodata段?
.rodata 全称:
Read Only Data
也就是只读数据。
例如:
const char message[] = "Hello STM32";
字符串和 const 常量通常不会在程序运行期间修改,因此通常可以直接保存在 Flash。
所以:
.rodata → Flash
十七、什么是.data段?
例如定义:
int speed = 100;
speed 是一个:
有初始值的全局变量
程序运行时:
speed = 200;
是允许的。
所以 speed 运行时必须位于可以修改的 RAM 中。
但是 RAM 掉电后内容会丢失。
那么初始值:
100
从哪里来?
答案是:
初始值保存在 Flash,启动时复制到 RAM。
也就是说:
Flash
.data初始镜像
↓
启动代码复制
↓
RAM
.data运行区
所以 .data 同时和 Flash、RAM 都有关系。
十八、为什么.data不能只放在Flash?
因为 .data 中的变量是可修改的。
例如:
int count = 10;
int main(void)
{
count = 20;
}
如果 count 一直直接位于普通 Flash 中,那么程序不能像操作 RAM 一样随时修改它。
所以需要:
Flash保存初始值
RAM保存运行值
启动代码负责把两者连接起来。
十九、什么是.bss段?
例如:
int count;
static int flag;
这些变量没有显式指定一个非零初始值。
按照 C 语言规则,静态存储期变量启动时应该自动初始化为 0。
这些变量通常放在:
.bss
段。
启动时不需要从 Flash 搬一堆 0。
只需要:
把RAM中.bss对应区域全部清零
即可。
二十、为什么.bss不需要占大量Flash?
例如:
uint8_t buffer[10000];
如果这个数组位于 .bss,并且默认初始化为 0。
如果 Flash 真保存 10000 个 0:
00 00 00 00 00 00 …
会非常浪费。
实际上链接器只需要知道:
.bss开始地址
.bss结束地址
启动时执行类似:
memset(bss_start, 0, bss_size);
即可。
所以 .bss 可以显著减少固件镜像大小。
二十一、Flash和RAM中的典型内容
可以简单总结为:
Flash
┌─────────────────────────┐
│ 中断向量表 │
├─────────────────────────┤
│ .text 程序代码 │
├─────────────────────────┤
│ .rodata 常量 │
├─────────────────────────┤
│ .data 初始镜像 │
└─────────────────────────┘
运行后的 RAM:
RAM
┌─────────────────────────┐
│ .data │
├─────────────────────────┤
│ .bss │
├─────────────────────────┤
│ Heap │
│ │
│ │
│ Stack │
└─────────────────────────┘
二十二、Stack是什么?
Stack 就是:
栈
主要用于:
函数调用
函数返回地址
局部变量
寄存器保存
中断现场
例如:
void Test(void)
{
int a;
uint8_t buffer[100];
}
这里的局部变量通常会占用栈空间。
函数执行结束以后,对应栈空间可以重新使用。
二十三、Heap是什么?
Heap 就是:
堆
主要用于动态内存分配。
例如:
uint8_t *buffer;
buffer = malloc(1024);
C++ 中:
new
通常也需要堆。
在嵌入式实时系统中,是否频繁使用动态内存,需要根据项目可靠性和实时性要求决定。
二十四、Reset_Handler如何初始化.data?
假设代码中:
int speed = 100;
int mode = 2;
那么 Flash 中会保存:
100
2
启动时:
Flash
↓
复制
↓
RAM
最终进入 main 后:
speed == 100;
mode == 2;
启动代码本质上可能执行类似:
while (src < data_end)
{
*dst++ = *src++;
}
不同工具链具体实现不同。
二十五、Reset_Handler如何初始化.bss?
假设:
int count;
static int error_flag;
启动代码会将 .bss 对应 RAM 区域全部清零。
概念上类似:
while (bss_start < bss_end)
{
*bss_start++ = 0;
}
因此进入:
main();
以后:
count == 0;
error_flag == 0;
二十六、为什么main不是第一条执行的代码?
现在原因就非常清楚了。
因为在执行:
main();
之前,系统至少需要准备:
有效栈
中断向量表
Reset_Handler
.data
.bss
系统基础环境
C/C++运行环境
所以:
Reset
↓
Vector Table
↓
Reset_Handler
↓
Runtime Initialization
↓
main
才是真正的流程。
因此:
main 是应用程序入口,但不是 MCU 的复位入口。
二十七、Keil工程中的启动过程
某些 Keil 工程中,Reset_Handler 概念上可能类似:
Reset_Handler PROC
EXPORT Reset_Handler
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
大致流程:
Reset_Handler
↓
SystemInit
↓
__main
↓
C运行环境初始化
↓
main()
这里的:
__main
和我们自己写的:
main()
不是一个函数。
二十八、GCC启动流程可能是什么样?
GCC 工程中可能更加直接地看到:
Reset_Handler
↓
SystemInit
↓
复制 .data
↓
清零 .bss
↓
__libc_init_array
↓
main
因此实际项目中最可靠的方法是:
直接查看自己的 startup 文件。
例如:
startup_stm32f103xb.s
startup_stm32f407xx.s
startup_stm32h743xx.s
二十九、启动文件为什么重要?
启动文件通常负责定义:
初始栈
中断向量表
Reset_Handler
Default_Handler
异常入口
中断入口
如果你真正想理解 STM32 启动过程,建议一定要阅读:
startup_stm32xxxx.s
三十、什么是VTOR?
Cortex-M 中经常存在:
VTOR
Vector Table Offset Register
也就是:
向量表偏移寄存器
它用于告诉 CPU:
当前中断向量表到底在哪里。
在普通单应用程序中:
Vector Table → Flash起始区域
但在 BootLoader + APP 架构中就非常重要。
三十一、BootLoader为什么必须处理向量表?
假设 Flash 分区:
BootLoader
0x08000000
APP
0x08008000
BootLoader 运行时,向量表位于:
0x08000000
跳到 APP 后,如果还没有修改向量表:
中断发生
↓
CPU仍然去BootLoader向量表查入口
那么 APP 中的:
USART中断
TIM中断
SysTick
HardFault
都可能跑错位置。
所以 APP 启动以后通常需要:
VTOR = APP_ADDRESS
具体方式根据芯片和工程实现确定。
三十二、BootLoader跳转APP为什么不能只跳到APP地址?
假设 APP 地址:
#define APP_ADDRESS 0x08008000U
APP 开头不是普通函数代码。
而是:
APP_ADDRESS + 0
→ 初始MSP
APP_ADDRESS + 4
→ Reset_Handler
所以 BootLoader 要真正启动 APP,需要获取:
APP初始栈指针
APP复位入口
概念代码:
#define APP_ADDRESS 0x08008000U
typedef void (*AppEntry_t)(void);
void JumpToApplication(void)
{
uint32_t app_stack;
uint32_t app_reset;
AppEntry_t app_entry;
app_stack =
*(volatile uint32_t *)APP_ADDRESS;
app_reset =
*(volatile uint32_t *)(APP_ADDRESS + 4U);
__disable_irq();
SCB->VTOR = APP_ADDRESS;
__set_MSP(app_stack);
app_entry = (AppEntry_t)app_reset;
app_entry();
}
实际工程还需要处理:
SysTick
NVIC
DMA
外设
Cache
MPU
RTOS状态
所以不能简单把上面的代码直接复制到所有项目中。
三十三、链接脚本有什么作用?
编译器负责:
C代码 → 机器代码
链接器负责:
这些机器代码和变量最终放在哪里。
例如 GCC 链接脚本中可能出现:
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
}
这里明确告诉链接器:
Flash从0x08000000开始
RAM从0x20000000开始
三十四、BootLoader工程为什么要修改APP的Flash起始地址?
例如:
BootLoader:
0x08000000 ~ 0x08007FFF
APP:
0x08008000 开始
那么 APP 链接地址必须改成:
FLASH ORIGIN = 0x08008000
否则编译出来的 APP 仍然认为:
自己的向量表应该在0x08000000
这就会和 BootLoader 冲突。
所以 BootLoader 分区不仅要修改:
下载地址
还要修改:
链接地址
向量表地址
三十五、程序到底从Flash还是RAM执行?
最准确的说法是:
大多数 STM32 裸机程序的机器指令主要位于内部 Flash,由 CPU 直接从 Flash 取指执行;可修改的变量、Stack、Heap 和运行时数据主要位于 RAM。
所以:
代码 → 多数在Flash
数据 → 多数在RAM
但不是绝对。
某些特殊情况下,也会让代码放在 RAM 中运行。
三十六、为什么有些函数要放到RAM执行?
例如:
高性能关键代码
特殊时序代码
Flash擦除程序
Flash编程程序
某些中断关键函数
可能需要放在 RAM 中执行。
GCC 中可以通过类似:
__attribute__((section(".RamFunc")))
void FlashProgramFunction(void)
{
}
然后通过链接脚本安排。
三十七、为什么Flash擦除时可能需要RAM执行代码?
如果 CPU 正在从某个 Flash Bank 中读取指令,同时又擦除同一个 Flash Bank,可能产生:
总线等待
取指阻塞
系统停顿
甚至异常
因此部分芯片在进行 Flash 编程或擦除时,会把关键代码放到 RAM 中执行。
具体行为必须查看对应 STM32 的 Flash 控制器章节。
三十八、为什么程序下载成功却不运行?
可以按照启动流程逐步排查。
第一步:检查BOOT配置
确认 MCU 是否真的从用户 Flash 启动。
第二步:检查程序烧录地址
确认程序下载到了链接脚本配置的地址。
例如:
0x08000000
或者 BootLoader 项目中的:
0x08008000
第三步:检查向量表第一项
读取:
APP_ADDRESS + 0
应该是一个合理的 RAM 地址。
例如:
0x200xxxxx
第四步:检查向量表第二项
读取:
APP_ADDRESS + 4
应该是合理的代码地址。
例如:
0x080xxxxx
第五步:检查Reset_Handler
调试器能否进入:
Reset_Handler
如果完全进不去,需要重点检查:
BOOT
Flash地址
向量表
第六步:检查SystemInit
如果卡在 SystemInit:
可能是:
HSE不起振
PLL等待失败
时钟配置错误
第七步:检查.data和.bss
如果 Reset_Handler 能运行,但进入 main 前异常:
需要检查:
链接脚本
.data复制范围
.bss范围
RAM地址
三十九、为什么启动错误可能直接HardFault?
如果启动阶段出现:
MSP错误
Reset_Handler地址错误
向量表地址错误
.data地址错误
.bss地址错误
Flash链接地址错误
CPU 就可能访问非法地址。
例如 MSP 如果变成:
0xFFFFFFFF
一旦函数调用或者进入中断,需要向栈写入数据:
CPU访问非法地址
↓
BusFault / HardFault
所以很多:
程序一上电就HardFault。
本质上可能不是业务代码,而是启动环境本身就有问题。
四十、如何用调试器观察真正的启动流程?
可以在:
Reset_Handler
处设置断点。
然后复位程序。
观察:
SP
PC
VTOR
例如:
SP
通常应该指向 RAM 高地址附近。
PC
应该进入 Reset_Handler。
然后单步执行,就可以真正看到:
SystemInit
.data
.bss
main
整个过程。
四十一、如何直接查看Flash前两个Word?
在 Keil、STM32CubeIDE 或其他调试器的 Memory 窗口中查看:
0x08000000
可能看到:
0x20020000
0x080012D1
…
第一项:
0x20020000
可能是:
初始MSP
第二项:
0x080012D1
可能是:
Reset_Handler
四十二、为什么Reset_Handler地址最低位可能是1?
Cortex-M 使用 Thumb 指令集。
函数指针最低位通常用于表示:
Thumb State
所以你可能看到:
0x080012D1
而不是:
0x080012D0
这是正常现象。
四十三、程序从源码到运行的完整流程
整个过程可以拆成两个阶段。
第一阶段:生成并保存程序
编写C代码
↓
编译
↓
生成目标文件
↓
链接
↓
生成 ELF / HEX / BIN
↓
下载
↓
写入Flash
第二阶段:程序真正启动
上电
↓
复位
↓
BOOT判断
↓
选择启动存储区
↓
读取向量表
↓
初始化MSP
↓
获取Reset_Handler
↓
执行启动代码
↓
SystemInit
↓
复制.data
↓
清零.bss
↓
初始化C/C++运行环境
↓
main()
↓
用户程序运行
四十四、典型内存分布总结
Flash中通常有什么?
中断向量表
.text
.rodata
.data初始镜像
BootLoader
固件常量
RAM中通常有什么?
.data运行副本
.bss
Stack
Heap
局部变量
DMA Buffer
UART Buffer
ADC Buffer
运行状态
四十五、最常见的几个误区
误区1:main是上电后第一条代码
错误。
真正流程:
Reset
↓
Vector Table
↓
Reset_Handler
↓
main
误区2:整个程序都先复制到RAM运行
错误。
大多数 STM32 可以直接从内部 Flash 取指。
误区3:所有变量都放在Flash
错误。
普通可修改变量运行时主要位于 RAM。
误区4:.data只在RAM里
不准确。
.data:
初始值保存在Flash
运行变量位于RAM
误区5:.bss在Flash里存了一堆0
通常不是。
启动代码直接把对应 RAM 区域清零。
误区6:BootLoader跳APP只需要跳到APP首地址
错误。
APP首地址第一项其实是 MSP。
真正入口通常来自:
APP地址 + 4
中的 Reset_Handler。
四十六、面试:STM32上电后是怎样进入main的?
可以这样回答:
STM32复位后首先根据BOOT配置确定启动存储区域。Cortex-M内核会从启动向量表读取前两个32位数据,第一个数据用于初始化主栈指针MSP,第二个数据是Reset_Handler地址,并加载到PC。CPU随后执行Reset_Handler,完成SystemInit、已初始化数据段复制、BSS段清零以及C/C++运行环境初始化,最后才调用main函数。
四十七、面试:.data和.bss有什么区别?
可以这样回答:
.data 保存:
有初始值的全局变量和静态变量
例如:
int speed = 100;
它的初始值保存在 Flash,启动时复制到 RAM。
.bss 保存:
未显式初始化或零初始化的全局变量和静态变量
例如:
int count;
static int flag;
启动时直接把对应 RAM 区域清零。
四十八、面试:程序到底运行在Flash还是RAM?
可以这样回答:
大多数STM32程序的机器指令保存在内部Flash中,CPU直接从Flash取指执行。可修改的全局变量、静态变量、Stack、Heap以及运行时数据主要位于RAM。.data的初始值保存在Flash,启动时复制到RAM;.bss则在启动阶段直接在RAM中清零。
四十九、实际开发最值得看的几个文件
如果想真正搞懂 STM32 启动,建议重点阅读下面几个文件。
1. startup_stm32xxxx.s
重点看:
Vector Table
Reset_Handler
Default_Handler
2. system_stm32xxxx.c
重点看:
SystemInit();
SystemCoreClockUpdate();
3. linker script / scatter file
重点看:
FLASH
RAM
.text
.data
.bss
Stack
Heap
4. map文件
Map 文件可以告诉你:
某个函数放在哪里
某个变量放在哪里
Flash用了多少
RAM用了多少
这是分析内存布局非常重要的文件。
五十、最终总结
STM32 程序真正的启动过程不是:
上电
↓
main
而是:
上电 / 复位
↓
BOOT启动判断
↓
选择启动存储区域
↓
读取中断向量表
↓
第1项初始化MSP
↓
第2项得到Reset_Handler
↓
执行Reset_Handler
↓
SystemInit
↓
.data从Flash复制到RAM
↓
.bss在RAM中清零
↓
C/C++运行环境初始化
↓
main()
Flash 和 RAM 的职责可以简单记成:
Flash:
掉电不丢
保存代码和常量
RAM:
掉电丢失
保存运行时变量和数据
最重要的三个知识点:
1. main不是上电后的第一条代码
2. 向量表第一项是初始MSP,
第二项才是Reset_Handler地址
3. 代码通常在Flash中保存并取指,
运行数据主要位于RAM
一句话总结整个 STM32 启动流程:
STM32上电后先根据BOOT配置确定启动区域,Cortex-M从向量表取得初始栈指针和Reset_Handler入口,执行启动代码完成系统和RAM运行环境初始化,最后才进入main函数;程序代码通常保存在Flash并直接取指执行,而可修改的数据主要放在RAM中。





