STM32启动流程详解

STM32启动流程详解

本文档详细介绍STM32微控制器从上电到main函数执行的完整启动流程。

1. 上电与复位过程

当STM32芯片上电或复位时,硬件会执行以下步骤:

  1. 上电复位(POR)/低电平复位(PDR): 芯片接通电源或NRST引脚置低时触发
  2. 初始PC值设置: 程序计数器(PC)被设置为0x00000000
  3. 首地址读取: CPU从地址0x00000000读取栈顶指针(MSP)的值
  4. 向量表读取: CPU从地址0x00000004读取复位向量(Reset_Handler的地址),并跳转执行

2. 启动文件执行流程

启动文件(startup_stm32f103xb.s)是STM32启动过程的关键部分,主要执行以下操作:

assembly 复制代码
Reset_Handler:
  ldr   r0, =_estack      ; 加载栈顶指针到r0寄存器
  mov   sp, r0            ; 设置栈指针
  
  ; 初始化数据段
  ldr r0, =_sdata         ; 加载数据段目标起始地址
  ldr r1, =_edata         ; 加载数据段目标结束地址
  ldr r2, =_sidata        ; 加载数据段源起始地址(Flash中)
  movs r3, #0
  b LoopCopyDataInit
  
  ; ... 数据段复制循环 ...
  
  ; 初始化BSS段(清零)
  ldr r2, =_sbss          ; 加载BSS段起始地址
  ldr r4, =_ebss          ; 加载BSS段结束地址
  movs r3, #0
  b LoopFillZerobss
  
  ; ... BSS段清零循环 ...
  
  bl SystemInit           ; 调用SystemInit函数初始化系统时钟
  bl __libc_init_array    ; 调用C库初始化函数
  bl main                 ; 调用main函数
  bx lr                   ; 返回(实际上main函数不会返回)

3. 向量表

向量表定义在启动文件中,存储在Flash起始位置,包含:

  1. 栈顶指针(MSP): 向量表第一项是主栈指针初始值
  2. 复位向量: Reset_Handler函数地址
  3. 异常向量: 各种异常处理函数的地址(NMI、HardFault等)
  4. 中断向量: 外设中断处理函数的地址
assembly 复制代码
__Vectors:
  .word _estack           ; 栈顶指针
  .word Reset_Handler     ; 复位处理函数
  .word NMI_Handler       ; NMI处理函数
  .word HardFault_Handler ; 硬件错误处理函数
  ; ... 其他异常和中断向量 ...

4. SystemInit函数详解

SystemInit函数定义在system_stm32f1xx.c文件中,主要负责系统时钟配置:

c 复制代码
void SystemInit(void)
{
  /* 重置RCC时钟配置为默认复位状态 */
  /* 设置HSION位 */
  RCC->CR |= 0x00000001U;

  /* 复位SW、HPRE、PPRE1、PPRE2、ADCPRE和MCO位 */
  RCC->CFGR &= 0xF8FF0000U;
  
  /* 复位HSEON, CSSON和PLLON位 */
  RCC->CR &= 0xFEF6FFFFU;

  /* 复位HSEBYP位 */
  RCC->CR &= 0xFFFBFFFFU;

  /* 复位PLLSRC, PLLXTPRE, PLLMUL和USBPRE位 */
  RCC->CFGR &= 0xFF80FFFFU;

  /* 禁用所有中断 */
  RCC->CIR = 0x00000000U;

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 若向量表在SRAM中 */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 向量表在Flash中 */
#endif
}

主要功能:

  1. 配置系统时钟回到默认状态(通常使用内部RC振荡器HSI,8MHz)
  2. 设置向量表偏移地址(VTOR寄存器)

5. C库初始化

__libc_init_array()函数负责调用所有构造函数并初始化C/C++运行环境:

  1. 调用.preinit_array段中的所有函数
  2. 调用.init_array段中的所有函数
  3. 初始化全局变量和静态变量

    我找了一下,在这里。反汇编代码如下。

    这是__libc_init_array函数的ARM汇编代码:
    这个函数负责在main函数执行前初始化C/C++运行时环境,具体执行两个主要任务:

第一部分(处理.preinit_array段)

assembly 复制代码
0-14:   保存寄存器并加载.preinit_array段的起始和结束地址
18-1c:  初始化循环计数器,计算数组元素数量(将字节差除以4)
24-38:  循环遍历.preinit_array中的每个函数指针并调用:
        - 加载函数指针到r3
        - 更新计数器
        - 通过bx指令调用函数
        - 比较是否已处理完所有函数

中间调用_init函数

assembly 复制代码
3c:     bl _init  // 调用标准的初始化函数

第二部分(处理.init_array段)

assembly 复制代码
40-54:  加载.init_array段的起始和结束地址,计算元素数量
58-74:  循环遍历.init_array中的每个函数指针并调用,过程与第一部分类似

结束处理

assembly 复制代码
78-7c:  恢复保存的寄存器并返回

这个函数的主要作用是:

  1. 调用所有注册在.preinit_array段的预初始化函数
  2. 调用系统的_init函数
  3. 调用所有注册在.init_array段的初始化函数(包括全局C++对象的构造函数)

这样确保了在main函数开始执行前,所有必要的全局对象和C/C++运行时环境都已正确初始化。

6. main函数执行

在完成上述所有初始化步骤后,启动代码通过bl main指令跳转到用户定义的main函数,开始执行应用程序代码。

7. 数据段和BSS段

  • 数据段(.data): 已初始化的全局变量和静态变量,从Flash复制到RAM
  • BSS段(.bss): 未初始化的全局变量和静态变量,在RAM中清零

8. 启动流程总结

复制代码
上电/复位 → 加载MSP → 跳转到Reset_Handler → 初始化栈指针 → 
复制.data段 → 清零.bss段 → SystemInit() → __libc_init_array() → main()

9. STM32启动流程的优化与自定义

在实际项目中,可以通过以下方式优化或自定义启动流程:

  1. 修改启动文件以支持特定的内存布局
  2. 自定义SystemInit函数实现特定的时钟配置
  3. 在main函数之前添加自定义的初始化代码

通过理解这一完整的启动过程,开发者可以更好地控制STM32应用程序的初始化行为,优化系统性能,并解决启动相关的问题。

相关推荐
小毛驴85024 分钟前
Linux 后台启动java jar 程序 nohup java -jar
java·linux·jar
枯萎穿心攻击31 分钟前
响应式编程入门教程第二节:构建 ObservableProperty<T> — 封装 ReactiveProperty 的高级用法
开发语言·unity·c#·游戏引擎
竹照煜_ysn1 小时前
蓝桥杯51单片机设计
单片机·嵌入式硬件·51单片机
好好学习啊天天向上1 小时前
世上最全:ubuntu 上及天河超算上源码编译llvm遇到的坑,cmake,ninja完整过程
linux·运维·ubuntu·自动性能优化
Eiceblue2 小时前
【免费.NET方案】CSV到PDF与DataTable的快速转换
开发语言·pdf·c#·.net
tan180°2 小时前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
Electron-er2 小时前
汽车LIN总线通讯:从物理层到协议栈的深度解析
单片机·汽车电子·lin总线·lin总线通讯
Do vis8243 小时前
STM32第十六天蓝牙模块
stm32·单片机·嵌入式硬件
学不动CV了3 小时前
ARM单片机启动流程(二)(详细解析)
c语言·arm开发·stm32·单片机·51单片机
典学长编程3 小时前
Linux操作系统从入门到精通!第二天(命令行)
linux·运维·chrome