Bootstrap

简单讲解一下STM32的启动过程

启动模式

首先我们先看一下启动模式

第一个为内部 FLASH 启动方式
     当芯片上电后采样到 BOOT0 引脚为低电平时, 0x00000000 0x00000004 地址被映射到 内部 FLASH 的首地址 0x08000000 0x08000004 。因此,内核离开复位状态后,读取内部 FLASH 117 的 0x08000000 地址空间存储的内容,赋值给栈指针 MSP ,作为栈顶地址,再读取内部 FLASH 的 0x08000004 地址空间存储的内容,赋值给程序指针 PC ,作为将要执行的第一条指令所在的 地址。完成这两个操作后,内核就可以开始从 PC 指向的地址中读取指令执行了。
第二种为内部 SRAM 启动方式
       类似于内部 Flash ,当芯片上电后采样到 BOOT0 BOOT1 引脚均为高电平时,地址0x00000000 和 0x00000004 被映射到内部 SRAM 的首地址 0x20000000 0x20000004。 在实际应用中,由启动文件 决定0x00000000 和 0x00000004 地址存储什么内容,链接时,由分散加载文件 (sct) 决定这些内容的绝 对地址,即分配到内部 FLASH 还是内部 SRAM
第三种系统存储器启动方式用到的较少,用于更新和修复功能,这个我感觉初学者不用考虑。

启动流程

    RESET->获取MSP值,获取PC值->Reset_Handler

在cortex-M3权威指南中是这样讲的:

下面两个图片可以先不看

现在讲一下MSP和SP是什么

MSP是复位向量的第一个字(32位)包含堆栈指针(MSP - Main Stack Pointer)的初始值。处理器将该值加载到堆栈指针寄存器(SP)中。简单地说,就是一会要分配的栈的起始地址。因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加 1。 举例来说,如果你的栈区域在 0x200003880x20000787 之间,那么 MSP 的初始值就必须是0x20000788。

PC: 复位向量的第二个字包含程序计数器(PC - Program Counter)的初始值,即复位向量。处理器将该值加载到程序计数器寄存器(PC)中。 简单的说就是指向并调用Reset_Handler函数

Reset_Handler: 一个特殊的函数,由启动文件(通常是startup_stm32xxx.s)提供。Reset_Handler 会执行一些处理器的基本初始化,然后跳转到 main 函数。

启动文件解析

参考正点原子的STM32启动浅析,启动文件的关键步骤如下

现在来看一下startup_stm32f103xb.s

栈空间的开辟,大小为Stack_Size,这里就是0x400
堆空间的开辟,和栈一样,堆大小为0X200,
PRESERVE8 :指示编译器按照 8 字节对齐。
THUMB :指示编译器之后的指令为 THUMB 指令。

中断向量定义表

    前几段声明了__Vectors(向量起始地址)、__Vectors_End (向量结束地址)和 __Vectors_Size (向量大小)三个标号具有全局性(EXPORT),可被外部的文件使用。

  61行注释说了这一个是,栈顶指针,这里如果是在内部FLASH运行,就是0x08000000。DCD表示以四字节对齐分配内存,那么下个地址是 0x0800 0004,存放的是 Reset_Handler 中断函数入口地址。这里也说明函数其实就是一个地址。这里截图不完整,在ST公司的参考手册9.1.2中断和异常向量中有完整的向量表。

122-124行是计算范围

126行是定义一个段命为.text,只读的代码段,在 CODE 区。

129行之后就是程序的实现了

第一个就是Reset_Handler

首先利用 PROC ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。
首先用EXPORT将其设置为全局属性 这样外部文件就可以调用此复位中断服务。WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用外部定义的标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。 就是你可以用这个,如果你自己想自己定义也可以,定义后以你自己定义的为主。
131 行和 132 行 IMPORT 表示该标号来自外部文件。这里表示 SystemInit 和__main 这
两个函数均来自外部的文件。
133 行 LDR 表示从存储器中加载字到一个存储器中。SystemInit 是一个标准的库函数,
在 system_stm32f1xx.c 文件中定义,主要作用是配置系统时钟、还有就是初始化 FSMC/FMC
总线上外挂的 SRAM(可选),前面说配置外部 SRAM 作为数据存储器(可选)就是这个。
134 行 BLX 表示跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,
还要把跳转前的下条指令地址保存到 LR。
135 行把__main 的地址给 R0。__main 是一个标准的 C 库函数,主要作用是初始化用
户堆栈和变量等,最终调用 main 函数去到 C 的世界。这就是为什么我们写的程序都有一
个 main 函数的原因,如果不调用__main,那么程序最终就不会调用我们 C 文件里面的
main,也就无法正常运行。__main和main不是一个东西
136 行 BX 表示跳转到由寄存器/标号给出的地址,不用返回。这里表示切换到__main
地址,最终调用 main 函数,不返回,进入 C 的世界。
137 行 ENDP 表示子程序结束

上面就是中断服务程序的声明了B 指令是跳转到一个标号,这里跳转到一个‘.’,表示无限循环。

都有WEAK声明。所以真正的中断服务函数需要我们在外部实现。  

最后面就是用户堆栈的初始化,堆是从低到高生长,栈是从高到低生长,是两个互相独立的数据段,并且不能交叉使用。这里就不做详细分析了。
;