如果搞懂STM32的上电启动过程,那么IAP就可以信手拈来了。下面我们一起来研究研究。

先说启动文件

我们正常在操作一款单片机的时候,都是从main函数开始进行编程的,但是单片机上电是从main函数开始执行的吗?答案当然是否定的,在main函数之前单片机最先执行的是硬件设置SP、PC然后是“启动文件”,一般主要是项目文件里面的startup_xxxxx.s文件。其实这个就是我们常说的Bootloader。

其实不光STM32系列单片机是这样,我们接触的NXP的微控制器、TI的MSP430以及51单片机等等其实都是有上述的启动文件的。启动文件负责的就是从单片机复位开始到main函数之前这段时间所需要进行的工作。我们一般很少接触启动文件的主要原因是开发环境往往给开发者自动的提供了这个启动文件,不需要我们再去操心,直接从main函数开始进行设计就可以了。


STM32三种启动方式

接触过STM32系列单片机的朋友应该知道STM32有三种启动模式,用户可以通过设置BOOT0和BOOT1的引脚电平状态,来选择复位后的启动模式。

需要注意的是STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。

1)从Flash启动,将Flash地址0x08000000映射到0x00000000,这样启动以后就相当于从0x08000000开始的,这是我们最常用的模式;

2)从SRAM启动,将SRAM地址0x20000000映射到0x00000000,这样启动以后就相当于从0x20000000开始的,用于调试,笔者基本没用过;

3)从系统存储器启动(可以看上篇文章里的内存映射图,System memory),将系统存储器地址0x1FFFF000映射到0x00000000,这样启动以后就相当于从0x1FFFF000开始执行的,值得注意的是这个系统存储器里面存储的其实是STM32自带的Bootloader代码,这其实是一个官方的IAP,它提供了可以通过UART1接口将用户的代码下载到Flash中的功能,下载完以后再切换到从Flash中启动就可以正常运行了。打个比方这个官方的Bootloader就相当于我们玩路由器时的“不死breed”。笔者之前在调STM32低功耗的时候将下载口给复用了其他功能导致“变砖”,就是通过这种方式恢复的


切回正题

下面我们来具体看一下从用户的Flash启动STM32,从上电到main函数之间的这段时间都做了什么。

1)第一步是硬件设置SP、PC

我们参考《Cortex-M3权威指南》向量表章节表7.6,如下图所示:


前两段地址主要是用来指定SP和PC的初值,上一节我们已经知道了映射关系,所以这时已自动从0x08000000位置处读取数据赋值给了栈指针SP,从0x08000004位置处读取数据赋值给了PC。需要注意的是这个复位向量初始值并不是固定的,可以通过一个叫“向量表偏移量寄存器”来修改定位。


下图是我们那个开源OLED时钟项目的HEX文件,用J-Flash打开就可以看到设置完的SP=0x20005B88,PC=0x0800282D。


2)第二步是设置系统时钟

我们接着来追踪系统的运行轨迹,上面我们已经知道了PC的地址为0x0800282D,但是这没有遵循4字节对齐,我们将其对齐为0x0800282C,这时我们打开项目文件里面的.map文件,找到这个地址,如下图示:


我们发现来到了第一节说的startup_xxxxx.s文件,我们打开startup文件找到:


我们发现运行到了SystemInit,C的世界我们就不陌生了,在项目文件的system_stm32f10x.c里面可以找到SystemInit函数,也就是初始化系统时钟了。

3)第三步是___main

到这里大家可能会以为已经到了main函数了,其实不是这样的。___main和main是不一样的,我们寻找这个___main会发现找不到,startup文件里面没有,map文件里面也没有。其实它是在MDK自带的库里面了,主要的功能是软件设置SP、加载.data\.bss并初始化栈区。由于需要在线跟踪才能看到,我在这里就不给大家列出来了,感兴趣的朋友可以深入研究一下。

4)最后来到C的世界

在执行到___main的最后就跳转到了C文件的main函数了。

最后用一张图来整体看一下流程:


总 结

到这里STM32的存储器以及上电启动过程就完整的总结完了,希望对大家有所帮助,大家如果感兴趣可以在调试STM32的时候一步一步的来跟踪一下看看,每一款单片机的启动文件其实都是很值得玩味的,对我们系统的来体会控制器的架构、指令集、中断向量等内容是很有帮助的。