欢迎光临
我们一直在努力

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

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中。


赞(0)
未经允许不得转载:171主机测评 » STM32启动过程详解:从上电复位到main,程序到底是如何从Flash运行起来的?
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址