STM32是怎么跑起来的?启动流程 + 时钟树一次讲透(面试高频)
文章摘要
本文从底层角度讲解 STM32 的启动流程与时钟树机制,解析 MCU 从复位到进入 main() 的全过程,以及 BOOT 启动模式的作用。结合 STM32F103,重点说明时钟来源、PLL 倍频与 72MHz 主频实现,并梳理 SYSCLK 到 AHB/APB 及外设的分配逻辑,解答定时器倍频、ADC 与 USB 时钟限制等常见问题,帮助建立完整的运行机制认知。
文章目录
- 文章摘要
- 本栏系列文章
- 其他专栏精品文章
- 前言
- 一. 先回答面试官最爱问的:main 函数之前发生了什么?
- 二、STM32启动流程,到底做了哪些事?
- 三、把启动过程拆开讲,你面试就稳了
- 四、为什么启动阶段要先关看门狗?
- 五、中断向量表为什么必须先建好?
- 六、堆栈为什么也是启动阶段必须初始化的?
- 七、BOOT0 / BOOT1 到底决定什么?
- 八、为什么说“时钟树”是 STM32 的命门?
- 九、STM32F103 有哪四个独立时钟源?
- 十、F103 为什么能跑到 72MHz?
- 十一、如果不用外部晶振,只用 HSI 可以吗?
- 十二、时钟树里最关键的分发链路是什么?
- 十三、APB1 和 APB2 有什么区别?
- 十四、为什么 APB1 是 36MHz,但定时器还能到 72MHz?
- 十五、ADC 时钟为什么不能乱配?
- 十六、USB 时钟为什么总是绕不过 48MHz?
- 十七、MCO 是干什么的?
- 十八、如果外部晶振坏了,STM32 会不会直接死机?
- 十九、把“启动流程 + 时钟树”串起来,你就真正懂了
- 二十、面试时可以怎么总结回答?
- 小结
本栏系列文章
其他专栏精品文章
大厂嵌入式代码规范(二):命名规范与数据结构
单片机开发环境搭建看这里
CSDN高赞项目:工程训练竞赛之垃圾分类垃圾桶
SLAMCraft:自主导航机器人DIY(一)如何设计一个自己的SLAM机器人
前言
很多人学 STM32,都是从点灯、串口、定时器开始的。程序能跑,外设也能用,但一到面试官问下面内容就开始卡壳:
- “STM32 上电后是怎么进入 main 的?”
- “为什么 F103 能跑到 72MHz?”
- “APB1 明明是 36MHz,为什么定时器还能跑 72MHz?”
- “BOOT0/BOOT1 到底决定了什么?”
其实这些问题,归根结底都指向两件事:
第一,芯片是怎么启动起来的;
第二,芯片里的时钟是怎么分配的
把这两件事搞明白,STM32 的很多外设问题都会豁然开朗。STM32 的启动过程大致是上电/复位 → 主时钟起振 → 执行启动代码 → 进入用户 main 函数,而启动阶段既要完成硬件执行环境初始化,也要完成软件运行环境初始化。
一. 先回答面试官最爱问的:main 函数之前发生了什么?
很多初学者以为 MCU 上电后直接从 main() 开始执行,实际上不是。STM32 上电或复位后,会先从固定启动地址取指执行。资料里提到,一般 MCU 可能从 0x00000000 启动,而 STM32 默认 Flash 启动地址是 0x08000000。随后芯片进入启动流程,完成硬件环境与软件环境准备,最后才跳转到 main。
你可以把它理解成:
芯片不是“直接运行你的程序”,而是先把“跑程序的舞台”搭好,再把 main() 这个演员请上台。所以,main 之前至少做了两大类工作。
二、STM32启动流程,到底做了哪些事?
1. 硬件执行环境初始化
启动阶段首先要把硬件基础搭起来。资料中列出的关键动作包括:
初始化内核时钟、主时钟和各外设时钟
关闭看门狗
看门狗是用来监控程序的异常跑飞而复位CPU,在初始化阶段,由于没有“喂狗”这一动作,有可能导致CPU不断复位,因此,首先会关闭看门狗,初始化完,再开启。
建立中断向量表
中断向量表,中断源的识别标志,可用来形成相应的中断服务程序的入口地址,或者中断服务程序入口地址的偏移量和段基值。CPU利用中断向量表转入中断服务程序处理相关事务。
初始化堆栈寄存器
堆栈的作用一个就是保存现场,如函数调用或者中断发送时,将当前执行地址压栈,调用完成再返回此处执行程序。另一个作用就是保存参数,如临时变量。因此,在启动阶段需初始化堆栈寄存器、堆栈的大小、起始地址等。
进行内存初始化,选择内部或外部 RAM
2. 软件运行环境初始化
除了硬件,C 程序本身也需要运行时环境。资料里给出的软件环境准备包括:
把 RO、RW 段从加载域复制到运行域
清零 ZI 段
初始化堆栈指针
初始化 C 库环境
这也是为什么很多全局变量在 main 之前就已经有值了:因为启动代码已经替你把数据段搬好、把未初始化变量清零了。
三、把启动过程拆开讲,你面试就稳了
第一步:上电或复位
芯片刚上电时,CPU 并不知道你的业务逻辑是什么,它只知道去预定义的地址取第一条指令。对 STM32F103 来说,最常见的情况是从 Flash 区启动,也就是 0x08000000。
第二步:时钟起振
芯片不能在“没有节拍”的状态下工作。启动初期要先让时钟系统建立起来,包括内核时钟、主时钟和外设时钟的初始配置。
第三步:执行启动代码
这部分通常不在你的业务代码里,而在启动文件里。它负责:设置堆栈、设置中断向量表、初始化数据段、初始化运行库环境。
第四步:进入 main()
当硬件和软件环境都准备好了,CPU 才会真正跳到 main(),从此开始执行你的应用层逻辑
四、为什么启动阶段要先关看门狗?
这个点很容易被忽略,但面试里很加分。看门狗的作用是监控程序是否跑飞。如果程序长时间不“喂狗”,看门狗就会复位 CPU。资料里明确提到:在初始化阶段,因为此时程序还没有开始正常调度“喂狗”动作,如果不先关闭看门狗,CPU 可能不断复位,所以通常会先关闭,初始化完成后再开启。
一句话总结:启动阶段不是业务稳定运行阶段,不适合让看门狗立即参与监管。
五、中断向量表为什么必须先建好?
因为 MCU 不是只会“顺着跑”,它还会“被打断”。中断向量表本质上是中断服务程序入口地址的集合。CPU 发生中断时,需要依赖向量表找到对应 ISR 的入口,否则连“跳到哪处理”都不知道。所以,启动代码必须尽早把中断向量表准备好。否则中断一来,系统就乱了。
你在面试里可以这样回答:中断向量表相当于“中断处理函数目录”。没有这个目录,CPU 收到中断请求时就无法定位处理入口。
六、堆栈为什么也是启动阶段必须初始化的?
堆栈作用较为清楚:一是保存现场,比如函数调用和中断进入时保存返回地址;二是保存参数和临时变量。启动阶段必须初始化堆栈寄存器、堆栈大小和起始地址。这意味着,连最普通的函数调用,本质上都依赖堆栈已经被初始化。所以你以后再看到“程序刚起步就设 SP”,就知道不是多余,而是运行 C 程序的前提条件。
七、BOOT0 / BOOT1 到底决定什么?
这是 STM32 面试经典题。常见的三种启动方式是:
BOOT1=1, BOOT0=1
从 SRAM 启动,中断向量表定位在 SRAM,复位后 PC 指针位于 0x20000000。
BOOT1=x, BOOT0=0
从 Flash 启动,中断向量表定位在 Flash,复位后 PC 指针位于 0x08000000。这也是最常见的用户程序启动方式。
BOOT1=0, BOOT0=1
从系统 Bootloader 启动,可通过串口下载程序到 Flash。
如果面试官问“为什么开发板一般把 BOOT0 拉低”,你就答:因为我们希望芯片复位后直接从 Flash 启动,执行已经烧录好的用户程序。
八、为什么说“时钟树”是 STM32 的命门?
如果说启动流程决定“程序能不能开始跑”,那时钟树决定的就是:芯片里的每个模块跑多快。
时钟信号推动单片机内各部分执行指令。STM32 外设很多,不是所有外设都需要同样高的频率。如果全部都跑高速,不仅浪费功耗,还会削弱抗电磁干扰能力,因此复杂 MCU 会用多时钟源和多级分频/倍频来做时钟分配。
九、STM32F103 有哪四个独立时钟源?
STM32F103外设非常多,任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,如果都用高速时钟势必造成浪费。同一个电路,时钟越快功耗越大、抗电磁干扰能力越弱。复杂的MCU采用多时钟源的方法来解决这些问题。如下图,是STM32的时钟系统框图:
- 如上图左边的部分,看到STM32有4个独立时钟源,HSI、HSE、LSI、LSE。
- HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
- HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
- LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。
- LSE是低速外部时钟,接频率为32.768kHz的石英晶体。时钟树的右边红色框中,则是系统时钟通过AHB预分频器,给相对应的外设设置相对应的时钟频率。
其中LSI、LSE是作为IWDGCLK(独立看门狗)时钟源和RTC时钟源使用。而HSI、HSE以及PLLCLK经过分频或者倍频作为系统时钟SYSCLK来使用。
PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。通过倍频之后作为系统时钟的时钟源。
十、F103 为什么能跑到 72MHz?
PLL 是锁相环倍频输出,输入源可以选择 HSI/2、HSE 或 HSE/2,倍频系数可选 2~16,但输出最大不能超过 72MHz。
F103 最常见的 72MHz 配置路径是:外部高速晶振 HSE 提供 8MHz、经过 PLL 选择与倍频、倍频 x9,得到 72MHz 的 SYSCLK。
Keil 默认常见配置下,HSE 的 8MHz 经过 PLLMUL 倍频 x9,形成 72MHz 系统时钟。所以你在面试里可以直接说:F103 常见 72MHz 来自外部 8MHz 晶振经过 PLL ×9。
十一、如果不用外部晶振,只用 HSI 可以吗?
可以,但频率和精度都受影响。HSI 本身是 8MHz,经过 2 分频后变成 4MHz,再送入 PLL,最大倍频到 64MHz。
也就是说:用 HSE 路线,更容易得到稳定的 72MHz;用 HSI 路线,通常最多做到 64MHz,而且精度不如外部晶振。这也是很多板子上会专门放外部晶振的原因。
十二、时钟树里最关键的分发链路是什么?
最核心的一条链路是:SYSCLK → AHB → APB1 / APB2 → 外设。系统时钟经过 AHB 分频器,再进入各个外设分频/倍频链路,最终给不同模块提供合适频率。AHB 分频器可选 1、2、4、8、16、64、128、256、512 分频。
AHB 输出后,主要分到几类模块:
HCLK:给 AHB 总线、内核、内存和 DMA
SysTick 相关时钟
APB1 外设时钟
APB2 外设时钟
ADC 等特殊分支时钟
这个地方你一定要建立一个观念:系统时钟不是直接“喂给所有外设”,而是层层分配。
十三、APB1 和 APB2 有什么区别?
APB1 连接低速外设,比如电源接口、备份接口、CAN、USB、I2C1、I2C2、USART2、USART3、UART4、UART5、SPI2、SPI3 等。
APB2 连接高速外设,比如 USART1、SPI1、Timer1、ADC1、ADC2、ADC3、普通 GPIO(PA~PE)、AFIO 等。
在芯片手册中明确写明:PCLK1 最大频率是 36MHz、PCLK2 最大频率是 72MHz。
所以面试官问你“为什么 USART1 和 USART2 不在同一条总线上”,你就知道这不是随便分的,而是按外设速度等级分组。
十四、为什么 APB1 是 36MHz,但定时器还能到 72MHz?
这道题是高频陷阱题。芯片手册中明确指出:如果 APB 预分频系数为 1,那么定时器时钟 TIMxCLK = PCLKx。如果 APB 预分频系数不为 1,那么定时器时钟会变成 2 × PCLKx。
而在 F103 默认配置下:AHB = 72MHz、APB1 = 36MHz
APB1 分频系数 = 2,所以通用定时器时钟 = 2 × 36MHz = 72MHz。这就是为什么你明明看到 APB1 只有 36MHz,但 TIM2~TIM5 仍然能按72MHz 去算时基。
十五、ADC 时钟为什么不能乱配?
因为 ADC 对时钟频率有上限要求。芯片手册中,ADC 输入时钟不得超过 14MHz,它的时钟来自 PCLK2 分频。同时在时钟树部分也提到,ADC 分频器可选 2、4、6、8 分频。所以如果 PCLK2 = 72MHz,常见做法就是继续分频,确保 ADC 时钟落在允许范围内。
这也是为什么外设初始化时,RCC 配置不能只看“主频够不够高”,还要看“某个外设能不能吃得下”
十六、USB 时钟为什么总是绕不过 48MHz?
资料里提到,STM32 的 USB 时钟不能超过 48MHz。如果系统时钟是 72MHz,就需要再进行 1.5 分频;如果时钟源本身就是 48MHz,则可以 1 分频直接使用。
所以 USB 相关配置经常会牵扯到整套时钟方案,而不是简单开个外设时钟就行。
十七、MCO 是干什么的?
MCO 是主时钟输出脚。芯片手册中指出,STM32 可以把某一路时钟输出到 PA8(MCO),可选 PLL 输出的 2 分频、HSI、HSE 或系统时钟,供外部使用。
这个功能常用在:调试时观察系统时钟是否正确,给外部模块提供参考时钟。虽然面试不一定每次都问,但答出来会显得你对时钟树更熟。
十八、如果外部晶振坏了,STM32 会不会直接死机?
&emsp不一定。资料中提到 STM32 提供了 CSS(时钟监视系统),用于监视 HSE 的工作状态。如果 HSE 失效,会自动切换到 HSI 作为系统时钟输入,以保证系统继续运行。
这说明 STM32 在时钟系统设计上,不只是“会配频率”,还考虑了“失效保护”。
十九、把“启动流程 + 时钟树”串起来,你就真正懂了
先解决“能不能启动也就是:从哪启动,启动代码做什么,为什么要配堆栈、向量表、数据段,为什么 main 不是第一站。
再解决“启动后跑多快”。也就是:时钟源来自哪,PLL 怎么倍频,SYSCLK 怎么分给 AHB / APB1 / APB2。
某些特殊外设为什么有额外限制?定时器为什么可能是 2×PCLK?
面试官真正想听的,不是你背几个名词,而是你能不能把这条链路讲顺
二十、面试时可以怎么总结回答?
STM32 上电或复位后,不会直接进入 main,而是先从启动地址取指执行。启动代码会先完成硬件环境初始化,比如时钟、堆栈、中断向量表、内存等;再完成软件运行环境初始化,比如 RW 段搬运、ZI 段清零和 C 库初始化,最后才跳转到 main。
时钟方面,F103 常见是外部 8MHz 晶振经 PLL ×9 得到 72MHz SYSCLK,然后再通过 AHB、APB1、APB2 分发到各外设。APB1 最大 36MHz,APB2 最大 72MHz;但如果 APB 分频系数不为 1,定时器时钟会变成 2×PCLK,所以 APB1 上的通用定时器依然可能工作在 72MHz。ADC 和 USB 这类外设还有独立时钟约束,不能只看系统主频。
小结
你会发现,STM32 真正难的不是“外设函数怎么调用”,而是:
-
你知不知道芯片在你看不见的地方做了什么。
-
启动流程,决定了程序为什么能开始运行。
时钟树,决定了整个系统为什么以这样的频率运行。
这两个问题吃透之后,后面的 GPIO、中断、定时器、串口、ADC、DMA,都会变得顺很多。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)