本文概述:STM32从复位到进入main函数,大致经历六个阶段:复位异常向量读取、栈指针初始化、跳转到复位处理函数、执行SystemInit初始化时钟和系统配置、执行C库__main函数进行全局变量初始化和BSS清零、最后跳转到main函数。整个过程由启动文件(startup_stm32xxx.s)主导,理解启动流程对排查硬件复位、程序跑飞等问题非常关键。

核心要点速览

  • 复位后CPU从0x08000000地址取向量表,第一个是栈顶地址,第二个是复位向量

  • 启动文件(startup_xxx.s)用汇编写,负责中断向量表和初始化流程

  • SystemInit是ST库提供的函数,负责时钟配置和系统初始化

  • __main是C库函数,负责初始化数据段、清零BSS段、构造函数执行

  • 最后才跳转到用户编写的main函数

面试题

面试官问:STM32上电复位后,到main函数执行之间都发生了什么?你能详细说一下吗?

面试官考察点

  1. 是否真正理解启动过程,而不是只会写main里的代码

  2. 是否看过启动文件,了解汇编层面的初始化

  3. 是否理解C语言运行环境的建立过程

  4. 是否知道向量表的结构和作用

  5. 遇到程序启动异常时是否有排查思路

回答思路

STM32的启动流程可以按时间顺序分成六个关键阶段,我按先后顺序来说明。

第一阶段:复位向量读取

上电复位后,CPU处于复位状态,时钟源是内部高速时钟HSI,频率约16MHz。CPU从向量表的起始地址读取两个值:

  • 第一个字(地址0x08000000):栈指针MSP的初始值,一般是RAM的最高地址

  • 第二个字(地址0x08000004):复位异常处理函数的地址,也就是Reset_Handler的入口地址

读取完成后,CPU自动设置MSP,然后跳转到Reset_Handler执行。

第二阶段:Reset_Handler汇编初始化

Reset_Handler是启动文件中定义的汇编函数,它主要做几件事:

  • 设置栈指针(有些系列由硬件自动完成)

  • 调用SystemInit函数,进行系统级初始化

  • 调用__main函数(注意是C库的__main,不是用户的main)

启动文件中还定义了所有中断向量的入口地址,形成完整的向量表,每个中断都对应一个弱定义的处理函数,用户可以重写。

第三阶段:SystemInit系统初始化

SystemInit是ST标准库或HAL库提供的函数,主要功能包括:

  • 配置时钟系统:设置PLL,将系统时钟从内部HSI切换到外部高速晶振HSE或内部PLL输出

  • 配置总线分频:设置AHB、APB1、APB2的分频系数

  • 设置向量表偏移:根据程序实际运行地址设置VTOR寄存器

  • 其他系统级配置:如Cache、MPU等(高性能系列才有)

执行完SystemInit后,系统时钟就达到了我们配置的频率,比如72MHz或168MHz。

第四阶段:__main函数初始化C运行环境

__main是C标准库的入口函数,不是用户写的main。它的主要工作是建立C语言的运行环境:

  • 数据段初始化:把Flash中存储的全局变量初值拷贝到RAM中

  • BSS段清零:将未初始化的全局变量区域全部清零

  • 堆栈初始化:设置栈和堆的边界

  • 执行全局对象构造函数(C++环境下)

这一步很重要,如果没有正确初始化,全局变量的值就是随机的,程序行为不可预测。

第五阶段:跳转到main函数

所有初始化完成后,__main会调用用户的main函数,程序正式进入用户代码。

在main函数里,我们通常先初始化HAL库,然后配置各个外设(GPIO、UART、定时器等),最后进入主循环。

第六阶段:main函数中的应用初始化

这部分是用户代码,虽然不属于"启动到main之间"的部分,但面试官可能会追问。一般顺序是:

  • HAL_Init:初始化HAL库,设置系统滴答定时器

  • 外设初始化:MX_GPIO_Init、MX_USART_UART_Init等

  • 业务逻辑初始化和主循环

常见追问

  1. 向量表在哪个文件里定义?有多少个中断向量?

  2. 什么是BSS段?为什么要清零?哪些变量在BSS段?

  3. 数据段(.data)和代码段(.text)有什么区别?存在哪里?

  4. 栈和堆的区别是什么?启动文件中怎么设置它们的大小?

  5. 程序下载后运行不起来,你会从哪些角度排查启动问题?

失分表达

避免这样说:

  • "上电就直接运行main函数"——完全不了解启动过程

  • "SystemInit就是初始化GPIO的"——功能理解错误

  • "向量表就是中断函数"——混淆了向量表和中断处理函数

准备动作

  1. 打开一份启动文件(startup_stm32fxxx.s),从头到尾读一遍

  2. 搞清楚向量表的结构,能说出前5个异常向量分别是什么

  3. 理解.data段、.bss段、.text段、堆、栈各自的作用和位置

  4. 准备一个"程序跑不起来怎么排查"的完整思路

  5. 了解Boot模式(BOOT0/BOOT1引脚)对启动地址的影响

核心关键词

STM32启动流程、启动文件、Reset_Handler、SystemInit、__main函数、向量表、MSP主栈指针、BSS段清零、数据段拷贝、C运行环境初始化

FAQ常见问题

Q1: 向量表为什么放在Flash的最开头?
因为ARM Cortex-M内核规定,复位后CPU从0x00000000地址读取MSP初始值和复位向量。STM32通过存储器映射把Flash映射到0x00000000,所以向量表放在Flash起始位置。
Q2: 全局变量的初始值存在哪里?为什么要从Flash拷到RAM?
全局变量有初值的,数据存在Flash的.data段中,程序运行时变量在RAM里。所以启动时需要把初值从Flash拷贝到RAM对应的位置。未初始化的全局变量在.bss段,直接清零即可。
Q3: main函数返回后会发生什么?
正常嵌入式程序的main函数是死循环,不会返回。如果意外返回,启动文件中一般会设置一个死循环(如B .)来防止程序跑飞,或者触发错误处理。