STM32 启动文件

1 什么是启动文件

启动文件是嵌入式系统中必不可少的文件,通常以汇编语言编写,负责初始化硬件和准备运行环境,以便用户的 C 程序能够正常执行。C 语言的程序通常从 main 函数开始执行,但 CPU 在上电后并不知道从哪里开始执行,因此需要启动文件来引导执行过程。

2 汇编指令

伪指令的意思是指这个 “指令” 并不会生产二进制程序代码,也不会引起变量空间分配。

3 启动文件分析

3.1 主要功能

  • 初始化堆栈指针 SP=_initial_sp
  • 初始化 PC 指针 =Reset_Handler
  • 初始化中断向量表
  • 配置系统时钟
  • 调用C库的 _main 函数,最终调用main函数

3.2 开辟栈空间

  • Stack_Size EQU 0x00000400
    • 定义栈空间大小为1kb
    • 相当于#define Stack_Size 0x00000400
    • EQU 是宏定义的伪指令
  • AREA STACK, NOINIT, READWRITE, ALIGN=3
    • 开辟名为STACK
    • 不初始化
    • 可读可写
    • 8字节对齐的地址空间
  • Stack_Mem SPACE Stack_Size
    • 告诉汇编器给栈分配Stack_Size 大小的连续内存空间
  • __initial_sp
    • 一个标号,标号主要用于表示一片内存空间的某个位置
    • 在这里,紧接着 SPACE 语句放置,表示了栈顶地址

3.3 开辟堆空间

  • Heap_Size EQU 0x00000200
    • 定义堆空间大小为0.5kb
    • 相当于#define Heap_Size 0x00000400
    • EQU 是宏定义的伪指令
  • AREA HEAP, NOINIT, READWRITE, ALIGN=3
    • 开辟名为HEAP
    • 不初始化
    • 可读可写
    • 8字节对齐的地址空间
  • __heap_base
    • 标识堆的开始地址
  • Heap_Mem SPACE Heap_Size
    • 告诉汇编器给堆分配Heap_Size 大小的连续内存空间
  • __heap_limit
    • 标识堆的结束地址
  • PRESERVE8
    • 指定当前文件的堆栈按照 8 字节对齐
  • THUMB:
    • 表示后面指令兼容 THUMB 指令。THUBM 是 ARM 以前的指令集,16bit,现在 Cortex-M系列的都使用 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB的超级。

3.4 中断向量表

  • AREA RESET, DATA, READONLY
    • 定义一个名为RESET 的只读数据段
  • EXPORT
    • 声明 __Vectors、__Vectors_End 和 __Vectors_Size 这三个标号具有全局属性,可供外部的文件调用
  • __Vectors
    • 表示中断向量表入口地址
  • __Vectors_End
    • 表示中断向量表的结束地址
  • __Vectors_Size
    • 表示中断向量表的大号

………………..

  • DCD
    • 表示分配一个4字节的空间
  • __Vectors DCD __initial_sp
    • 开始建立中断向量表,首地址一定是栈顶指针
  • 73行 ~ 87行
    • 系统的中断
  • ; External Interrupts
    • 表示下面90行 ~ 180行是外设的中断

3.5 复位程序

  • AREA |.text|, CODE, READONLY
    • 定义一个名为.text 的可读代码段
  • PROC … ENDP
    • 表示程序的开始和结束
  • EXPORT Reset_Handler [WEAK]
    • 声明带全局属性的WEAK 中断服务程序
  • IMPORT
    • 告知编译器要是用的标号在其他源文件中定义
  • LDR R0, =SystemInit
    • 跳转到SystemInit 执行
  • LDR R0, =__main
    • 跳转到__main 执行

3.6 中断服务程序

……

  • B:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环。
  • 在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。
  • 如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。

3.7 初始化堆栈

  • __user_initial_stackheap
    • 标号,表示用户堆栈初始化程序入口
  • LDR
    • 分别保存栈顶指针和栈大小,堆始地址和堆大小至R0,R1,R2,R3寄存器

4 系统启动流程

下面这段话引用自《CM3 权威指南 CnR2》3.8—复位序列,CM4 的复位序列跟 CM3 一样。
在离开复位状态后,CM3 做的第一件事就是读取下列两个 32 位整数的值:

  • 从地址 0x0000,0000 处取出 MSP 的初始值。
  • 从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量,LSB 必须是 1。然后从这个值所对应的地址处取指。

请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是我们刚刚分析的 Reset_Handler
这个函数。

因为 CM3 使用的是向下生长的满栈,所以 MSP(主堆栈指针) 的初始值必须是堆栈内存的末地址加 1。举例来说,如果我们的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值就必须是 0x20008000。向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在 Thumb 下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因,图中使用 0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行(即去到 C 的世界)。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了NMI 或是其它 fault。MSP 初始化好后就已经为它们的服务例程准备好了堆栈。现在,程序就进入了我们熟悉的 C 世界,现在我们也应该明白 main 并不是系统执行的第一个程序了。

发表评论