STM32启动流程全面解析:从上电复位到进入main函数

目录

[1 STM32启动总览](#1 STM32启动总览)

[2 从 Boot ROM 到 Boot Loader](#2 从 Boot ROM 到 Boot Loader)

[3 启动文件](#3 启动文件)

[3.1 启动文件到底是什么?](#3.1 启动文件到底是什么?)

[3.2 硬件自动执行(上电复位后):](#3.2 硬件自动执行(上电复位后):)

[3.3 启动文件的初始化工作](#3.3 启动文件的初始化工作)

[3.4 一图流总结](#3.4 一图流总结)

[4 中断向量表](#4 中断向量表)

[4.1 BOOT 配置:决定 "程序从哪里读"](#4.1 BOOT 配置:决定 “程序从哪里读”)

[4.2 向量表的 "唯一性" 与 "偏移":](#4.2 向量表的 “唯一性” 与 “偏移”:)

[4.3 复位向量指向的复位服务函数](#4.3 复位向量指向的复位服务函数)

[4.4 中断向量表里存的是什么?](#4.4 中断向量表里存的是什么?)

[4.5 函数的地址(入口地址)是固定的吗?](#4.5 函数的地址(入口地址)是固定的吗?)

[4.6 中断向量表:"程序入口" 的索引表](#4.6 中断向量表:“程序入口” 的索引表)

[5 复位向量、Boot Loader 与启动文件详解](#5 复位向量、Boot Loader 与启动文件详解)

[5.1 STM32启动代码与Bootloader的关系](#5.1 STM32启动代码与Bootloader的关系)

两者的区别:

[5.2 用户自定义Bootloader的执行流程](#5.2 用户自定义Bootloader的执行流程)

典型的双程序区布局:

Bootloader的启动执行过程:

阶段1:硬件自动执行

阶段2:Bootloader的启动代码执行

阶段3:Bootloader主逻辑

[5.3 关键技术:从Bootloader跳转到应用程序](#5.3 关键技术:从Bootloader跳转到应用程序)

跳转函数的实现:

[5.4 应用程序的特殊配置](#5.4 应用程序的特殊配置)

[链接脚本修改 (.ld文件):](#链接脚本修改 (.ld文件):)

应用程序中设置向量表偏移并开启中断:

需要注意

[5.5 完整的启动序列](#5.5 完整的启动序列)

[5.6 总结:](#5.6 总结:)


1 STM32启动总览

bash 复制代码
上电复位  
   ↓  
硬件复位初始化:复位寄存器值、外设恢复默认状态(如关闭外设时钟)  
   ↓  
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)对接的底层代码,它的作用是:

  1. 定义中断向量表(包括复位向量);

  2. 完成 CPU 上电后最基础的初始化(栈、堆、全局变量等);

  3. 最终 "接力" 跳转到用户代码的main函数。

具体作用:

3.2 硬件自动执行(上电复位后):

  • CPU从向量表的第一个条目(0x00000000)读取初始栈指针(MSP)

  • 从向量表的第二个条目(0x00000004)读取复位向量(Reset_Handler的地址)

  • CPU跳转到Reset_Handler函数

3.3 启动文件的初始化工作

启动文件(如startup_stm32xxxx.s)是一段汇编代码,主要完成以下任务:

cpp 复制代码
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的执行流程

典型的双程序区布局:

cpp 复制代码
Flash布局 (0x08000000)
├── Bootloader区 (0x08000000 - 0x08007FFF)
│   ├── Bootloader的向量表
│   ├── Bootloader的启动代码
│   └── Bootloader的主逻辑
└── 应用程序区 (0x08008000 - ...)
    ├── 应用程序的向量表(已偏移)
    ├── 应用程序的启动代码
    └── 应用程序的主逻辑

Bootloader的启动执行过程:

阶段1:硬件自动执行
cpp 复制代码
// 上电后硬件自动完成:
1. 从0x08000004读取复位向量 → Bootloader的Reset_Handler地址
2. 跳转到Bootloader的Reset_Handler
阶段2:Bootloader的启动代码执行
cpp 复制代码
// startup_bootloader.s (Bootloader的启动文件)
Reset_Handler:
    /* 初始化栈和变量 */
    bl SystemInit           // 时钟配置
    bl main_bootloader      // 进入Bootloader主逻辑
阶段3:Bootloader主逻辑
cpp 复制代码
// 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 {
        // 应用程序无效,停留在Bootloader
        stay_in_bootloader();
    }
}

5.3 关键技术:从Bootloader跳转到应用程序

跳转函数的实现:

cpp 复制代码
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文件):

cpp 复制代码
MEMORY
{
    FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 224K  /* 假设要将应用程序放在0x8008000 */
    RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 64K
}

应用程序中设置向量表偏移并开启中断:

cpp 复制代码
// 在SystemInit或main函数开始处
SCB->VTOR = FLASH_BASE | 0x8000;  // 设置向量表偏移到0x08008000
__disable_irq();                  // 开启中断

需要注意

  1. SCB->VTOR 的配置必须放在 HAL_Init() 之前 (如代码中USER CODE BEGIN 1处)。因为HAL_Init()会初始化 SysTick 定时器,若此时向量表未重定向,会使用 Bootloader 的 SysTick 中断服务函数,导致异常。

  2. Bootloader 跳转前可能会部分外设(如 RTC),APP 若需使用这些外设,需在MX_XXX_Init()后重新启用(如__HAL_RCC_RTC_ENABLE())。

5.5 完整的启动序列

cpp 复制代码
上电复位
    ↓
硬件自动从0x08000004读取向量 → Bootloader的Reset_Handler
    ↓
执行Bootloader启动代码
    ↓
执行Bootloader主逻辑
    ↓
判断是否需要更新固件
    ↓
验证应用程序完整性
    ↓
跳转到应用程序的Reset_Handler (0x08008004)
    ↓
执行应用程序启动代码
    ↓
执行应用程序main函数

5.6 总结:

  1. 启动代码是基础:每个STM32程序(包括Bootloader)都有自己的启动代码

  2. Bootloader 是特殊程序:它运行在Flash起始位置,负责管理应用程序

  3. 跳转机制关键:通过修改VTOR寄存器实现从Bootloader到应用程序的平滑切换

  4. 应用程序需要适配:应用程序的链接地址和向量表都需要相应偏移

这种设计使得STM32可以实现IAP(在应用编程),支持固件远程升级等功能。

相关推荐
big\hero2 小时前
STM32智能加湿器
stm32·单片机·嵌入式硬件
资源开发与学习4 小时前
嵌入式STM32工程师系统养成–实战训练营-9周达成
嵌入式
小猪写代码5 小时前
芯片各种手册概括---以 stm32 为例
stm32·单片机·嵌入式硬件
酷飞飞5 小时前
用trea导入keil的ARM工程
单片机
里予.c6 小时前
ARM(IMX6ULL)——通信(UART)
arm开发·单片机·嵌入式硬件
GilgameshJSS6 小时前
STM32H743-ARM例程6-RS422
arm开发·stm32·单片机·嵌入式硬件·学习
xyx-3v7 小时前
软件IIC和硬件IIC的引脚配置有什么区别?
单片机·嵌入式硬件
百里东风7 小时前
配置AC5(ARM Compiler 5)编译器
arm开发·stm32·单片机
你好,奋斗者!9 小时前
单片机引脚的高电平和低电平范围值
单片机·嵌入式硬件·嵌入式软件