CM3/4启动流程
- 1. 启动模式
- 2. 启动流程
1. 启动模式
复位方式有三种:
上电复位
,硬件复位
和软件复位
。
当产生复位,并且离开复位状态后,CM3/4 内核做的第一件事就是读取下列两个 32 位整数的值:
- 从地址 0x0000 0000 处取出堆栈指针 MSP 的初始值,该值就是栈顶地址。
- 从地址 0x0000 0004 处取出程序计数器指针 PC 的初始值,该值指向复位后执行的第一条指令(复位向量)。
上述过程中,内核是从 0x0000 0000 和 0x0000 0004 两个的地址获取堆栈指针 SP 和程序计数器指针 PC。事实上,0x0000 0000 和 0x0000 0004 两个的地址可以被重映射到其他的地址空间。例如:我们将 0x0800 0000 映射到 0x0000 0000,即从内部 FLASH 启动,那么内核会从地址 0x0800 0000 处取出堆栈指针 MSP 的初始值,从地址 0x0800 0004 处取出程序计数器指针PC 的初始值。CPU 会从 PC 寄存器指向的地址空间取出的第 1 条指令开始执行程序,就是开始执行复位中断服务程序 Reset_Handler。将 0x0000 0000 和 0x0000 0004 两个的地址重映射到其他地址空间,就是启动模式选择。
启动模式(也称自举模式)有三种,如下图所示:
根据 BOOT 引脚的电平[0:低电平;1:高电平;x:任意电平,即高低电平均可]
选择启动模式,这两个 BOOT 引脚根据外部施加的电平来决定芯片的启动地址。(0 和 1 的准确电平范围可以查看 F4 系列数据手册 I/O 特性表,但我们最好是设置成 GND 和 VDD 的电平值)
复位后,在SYSCLK
的第四个上升沿锁存 BOOT 引脚的值。复位后,用户可以通过设置 BOOT0[专用引脚]
和 BOOT1[与 PB2 引脚共用]
引脚来选择需要的自举模式。一旦完成对 BOOT1 的采样,相应 GPIO 引脚即进入空闲状态,可用于其它用途。
器件退出待机模式时,还会对 BOOT 引脚重新采样。因此,当器件处于待机模式时,这些引 脚必须保持所需的自举模式配置。这样的启动延迟结束后,CPU 将从地址 0x0000 0000 获 取栈顶值,然后从始于 0x0000 0004 的自举存储器开始执行代码。
1. 内部FLASH启动方式:
当芯片上电后采样到 BOOT0 引脚为低电平时,0x00000000 和 0x00000004 地址被映射到内部 FLASH 的首地址 0x08000000 和 0x08000004。因此,内核离开复位状态后,读取内部 FLASH的 0x08000000 地址空间存储的内容,赋值给栈指针 MSP,作为栈顶地址,再读取内部 FLASH的 0x08000004 地址空间存储的内容,赋值给程序指针 PC,作为将要执行的第一条指令所在的地址。完成这两个操作后,内核就可以开始从 PC 指向的地址中读取指令执行了。2. 内部 SRAM 启动方式:
类似于内部 Flash,当芯片上电后采样到 BOOT0 和 BOOT1 引脚均为高电平时,地址0x00000000 和 0x00000004 被映射到内部 SRAM 的首地址 0x20000000 和 0x20000004,内核从SRAM 空间获取内容进行自举。在实际应用中,由启动文件 starttup_stm32f407xx.s 决定了0x00000000 和 0x00000004 地址存储什么内容,链接时,由分散加载文件(sct)决定这些内容的绝对地址,即分配到内部 FLASH 还是内部 SRAM。3. 系统存储器启动方式:也称为 ISP 启动方式
当芯片上电后采样到 BOOT0=1,BOOT1=0 的组合时,内核将从系统存储器的 0x1FFFF000及 0x1FFFF004 获取 MSP 及 PC 值进行自举。系统存储器是一段特殊的空间,用户不能访问,ST 公司在芯片出厂前就在系统存储器中固化了一段代码。因而使用系统存储器启动方式时,内核会执行该代码,该代码运行时,会为 ISP(In System Program)提供支持,在 STM32F4 上最常见的是检测 USART1 传输过来的信息,并根据这些信息更新自己内部 FLASH 的内容,达到升级产品应用程序的目的,因此这种启动方式也称为 ISP 启动方式。
2. 启动流程
以前 ARM7/ARM9 内核的控制器在复位后,CPU 会从存储空间的绝对地址0x00000000 取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为 0x00000000(PC =0x00000000),同时中断向量表的位置也是固定的。而 CM3/4内核复位后的起始地址和中断向量表的位置可以被重映射,重映射的方法是通过3 种启动模式来选择的
(如上)
。
启动流程:【以代码下载到内部 FLASH 的情况举例】
- 当产生复位
[上电复位、硬件复位、软件复位]
,并且离开复位状态后,从地址 0x0800 0000 处取出堆栈指针 MSP 的初始值,该值就是栈顶地址。 [小端:MSP=0x2000 0788]- 从地址 0x0800 0004 处取出程序计数器指针 PC 的初始值,该值指向复位后执行的第一条指令。 [小端:PC=0x0800 01CD]
因为 CM3/4 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加 1。举例来说,如果你的栈区域在 0x2000 0388‐0x2000 0787(1KB 大小)之间,那么 MSP 的初始值就必须是 0x2000 0788。
向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。
R15 是程序计数器,在汇编代码中,可以使用名字“PC”来访问它。ARM 规定:PC 最低两位并不表示真实地址,最低位 LSB 用于表示是 ARM 指令(0)还是 Thumb 指令(1),因为 CM3 主要执行 Thumb 指令,所以这些指令的最低位都是 1(都是奇数)。因为 CM3 内部使用了指令流水线,读 PC 时返回的值是当前指令的地址+4。比如说:【0x1000: MOV R0, PC ; R0 = 0x1004】
如果向 PC 写数据,就会引起一次程序的分支(但是不更新 LR 寄存器)。CM3/4 中的指令至少是半字对齐的,所以 PC 的 LSB 总是读回 0。然而,在分支时,无论是直接写 PC 的值还是使用分支指令,都必须保证加载到 PC 的数值是奇数(即 LSB=1),表明是在 Thumb 状态下执行。倘若写了 0,则视为转入 ARM 模式,CM3 将产生一个 fault 异常。
正因为上述原因,使用 0x0800 01CD 来表达地址 0x0800 01CC。当 0x0800 01CD 处的指令得到执行后,就正式开始了程序的执行(即去到 C 的世界)。所以在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没执行就会被 NMI 或是其它 fault 打断。MSP 初始化好后就已经为它们的服务例程准备好了堆栈。
详细启动流程:
1. 硬件复位
- 初始化内核寄存器。
- 设置程序计数器(PC)为 0x08000000(Flash的起始地址)。
2. 读取初始栈指针(SP)
- 从Flash的起始地址(0x08000000)读取初始栈指针(SP)的值。
- 栈指针用于初始化栈,栈通常位于RAM的顶部。
3. 读取复位向量
- 从Flash的第二个字(0x08000004)读取复位处理程序(Reset_Handler)的地址。
- 复位向量是复位处理程序的入口地址。
4. 执行复位处理程序
- 设置堆和栈的起始地址。
- 调用 SystemInit 函数(如果存在),配置外部 SRAM 作为数据存储器(可选)和 配置重映射的中断向量表地址(可选)。
- 跳转到 __main 函数。
1. 初始化 .data 段
data 段存储已初始化的全局变量和静态变量。这些变量的初始值存储在Flash中,将这些值复制到RAM中。
步骤1:获取 .data 段在Flash中的起始地址和大小。
步骤2:将数据从Flash复制到RAM中的目标地址。
2. 初始化 .bss 段
.bss 段存储未初始化的全局变量和静态变量。将 .bss 段对应的RAM区域清零。清零步骤如下:
步骤1:获取 .bss 段的起始地址和大小。
步骤2:将对应内存区域清零。
3. 设置堆和栈
栈(Stack)用于存储局部变量和函数调用信息。栈的大小在启动文件中定义,通常位于RAM的顶部。
堆(Heap)用于动态内存分配(如 malloc 和 free)。堆的大小也在启动文件中定义。
__main 函数根据启动文件中的定义,初始化堆和栈的起始地址和大小。
4. 调用用户的 main 函数
在完成上述初始化工作后,__main 函数会跳转到用户的 main 函数,开始执行用户代码。