在嵌入式开发中,我们习惯了将 main() 函数作为程序的起点。然而,当STM32微控制器上电复位的那一瞬间,CPU真的会直接跳进 main() 吗?答案是否定的。在 main() 被调用之前,芯片内部其实已经默默完成了一场精密而严谨的“接力赛”。理解这条从硬件复位到软件执行的完整链路,是掌握STM32底层运行机制的关键。
硬件复位与启动模式选择
一切始于上电或复位信号的触发。当STM32的电源电压稳定后,芯片内部的复位管理单元会释放复位信号。此时,CPU并不会盲目执行代码,而是首先去采样 BOOT0 和 BOOT1 引脚的电平状态,以此决定程序的启动地址。
以最常见的从主闪存(Main Flash)启动为例,STM32会通过内部的内存重映射机制,将 Flash 的起始物理地址 0x08000000 映射到逻辑地址 0x00000000。这意味着,无论实际从哪种介质启动,CPU复位后永远从 0x00000000 这个“黄金入口”开始读取数据。
向量表:决定命运的前两个地址
与传统ARM架构不同,Cortex-M内核在 0x00000000 处存放的并不是第一条跳转指令,而是一个中断向量表。向量表的前两个32位数据决定了系统的初始命运:
● 地址 0x00000000:存储的是初始主堆栈指针(MSP)的值。CPU读取该值后,自动将其加载到 SP 寄存器中,为后续的函数调用和局部变量分配准备好栈空间。
● 地址 0x00000004:存储的是复位异常处理函数(Reset_Handler)的入口地址。CPU将其加载到 PC(程序计数器)寄存器中,从而实现了真正的“第一条代码执行”。
启动文件:汇编层的桥梁
PC指针跳转后,程序进入了由汇编语言编写的启动文件(如 startup_stm32f103xe.s)。这是连接裸机硬件与C语言运行环境的桥梁。在 Reset_Handler 中,系统主要完成了以下核心任务:
1. 初始化堆与栈:在汇编层划定栈(Stack)和堆(Heap)的内存边界,确保C语言的动态内存分配和函数调用有充足的运行空间。
2. 数据段搬移与清零:将已初始化的全局变量(.data段)从非易失性的 Flash 复制到 RAM 中,并将未初始化的全局变量(.bss段)在 RAM 中全部清零。
3. 系统级初始化:调用 SystemInit() 函数,完成系统时钟树配置、Flash预取指等底层硬件环境的搭建。
C运行环境与 main() 的登场
当汇编层面的环境准备就绪后,启动文件会调用编译器提供的 __main 函数。__main 负责进一步初始化C标准库环境。当这一切尘埃落定,__main 才会执行最后一步:跳转到用户编写的 main() 函数。
至此,STM32的启动接力棒才真正交到了开发者手中。
扫码申领本地嵌入式教学实录全套视频及配套源码