
核心要点速览
复位后CPU从0x08000000地址取向量表,第一个是栈顶地址,第二个是复位向量
启动文件(startup_xxx.s)用汇编写,负责中断向量表和初始化流程
SystemInit是ST库提供的函数,负责时钟配置和系统初始化
__main是C库函数,负责初始化数据段、清零BSS段、构造函数执行
最后才跳转到用户编写的main函数
面试题
面试官问:STM32上电复位后,到main函数执行之间都发生了什么?你能详细说一下吗?
面试官考察点
是否真正理解启动过程,而不是只会写main里的代码
是否看过启动文件,了解汇编层面的初始化
是否理解C语言运行环境的建立过程
是否知道向量表的结构和作用
遇到程序启动异常时是否有排查思路
回答思路
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等
业务逻辑初始化和主循环
常见追问
向量表在哪个文件里定义?有多少个中断向量?
什么是BSS段?为什么要清零?哪些变量在BSS段?
数据段(.data)和代码段(.text)有什么区别?存在哪里?
栈和堆的区别是什么?启动文件中怎么设置它们的大小?
程序下载后运行不起来,你会从哪些角度排查启动问题?
失分表达
避免这样说:
"上电就直接运行main函数"——完全不了解启动过程
"SystemInit就是初始化GPIO的"——功能理解错误
"向量表就是中断函数"——混淆了向量表和中断处理函数
准备动作
打开一份启动文件(startup_stm32fxxx.s),从头到尾读一遍
搞清楚向量表的结构,能说出前5个异常向量分别是什么
理解.data段、.bss段、.text段、堆、栈各自的作用和位置
准备一个"程序跑不起来怎么排查"的完整思路
了解Boot模式(BOOT0/BOOT1引脚)对启动地址的影响
核心关键词
STM32启动流程、启动文件、Reset_Handler、SystemInit、__main函数、向量表、MSP主栈指针、BSS段清零、数据段拷贝、C运行环境初始化

扫码关注



























