STM32启动文件详解:逐行解析代码,轻松掌握启动流程

MCU上电后,怎么跑进程序里了呢? 想必这是大部分人想搞明白的事。毕竟任何事情,想清楚来龙去脉还是挺好的。长此以往,对技术的理解也会比较通透。

由于.s启动文件是用汇编写的,我们先熟习一下汇编的一些基本指令:

1:分配堆栈空间

EQU:给数字常量取一个符号名,相当于C语言的Define

Stack_Size      EQU     0x00000400     //等价:#define    Stack_Size      0x00000400    

AREA: 汇编一个新的代码段或者数据段  

NOINIT:这个属性指示链接器在将程序加载到内存时,不要初始化这个区域的内容

READWRITE:这个属性指定该区域是可读写的

ALIGN:这个属性指定了该区域的起始地址应该按照 2 的 3 次方(即 8 字节)对齐

AREA    STACK, NOINIT, READWRITE, ALIGN=3

SPACE:分配内存空间(指定大小,连续的字节空间)

Stack_Mem       SPACE   Stack_Size

注意:在实际应用时,经常会碰到Stack空间溢出的情况,会导致莫名重启。此时可以把Stack_Size改大。

Stack_Size      EQU     0x00000400  

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


1:定义栈的大小为:0x00000400  (这个大小可以根据实际情况改动)
2:分配栈空间
3:__initial_sp:占位符,代表栈顶(栈结束地址),后面会用到这个符号
4:Stack_Mem:label,可以理解为栈开始地址,或者Stack空间标号     
Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit


1:__heap_base:占位符:堆起始位置
2:__heap_limit:占位符:堆结束位置 __heap_limit = __heap_base + Heap_Size

2:对齐方式和指令集选择

PRESERVE8
THUMB

1:PRESERVE8:是一个汇编器指令或伪指令,用于指示汇编器在生成代码时保持8字节对齐
2:THUMB:表示后面的指令兼容THUMB指令,THUMB是ARM以前的指令集,16位的,而现在Cortex-M系列都使用THUMB-2指令集,是32位的

3:初始化中断向量表

EXPORT声明一个标号具有全局属性,可被外部文件使用

AREA    RESET, DATA, READONLY
EXPORT  __Vectors
EXPORT  __Vectors_End
EXPORT  __Vectors_Size

1:AREA RESET, DATA, READONLY 汇编一个只读数据段,数据段名称为RESET
2:EXPORT __Vectors 表示在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用,中断向量表的入口地址。
3:EXPORT __Vectors_End 表示在程序中声明一个全局的标号__Vectors_End,中断向量表的结束地址
4:EXPORT __Vectors_Size 在程序中声明一个全局的标号__Vectors_Size,中断向量表的大小

DCD:以字节为单位分配内存,要求4字节对齐,并初始化这些内存

__initail_sp:对应的是栈顶的地址,所以它一定是Ram所在的地址范围

建立中断向量表:

DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。中断向量表被放置在代码段的最前面。例如:当我们的程序在 FLASH 运行时,那么向量表的起始地址是:0x0800 0000。地址 0x0800 0000 存放的是栈顶地址。
DCD:以四字节对齐分配内存,也就是下个地址是0x0800 0004,存放的是Reset_Handler中断函数入口地址。
从代码上看,向量表中存放的都是中断服务函数的函数名, C 语言中的函数名对芯片来说实际上就是一个地址。

4: AREA    |.text|, CODE, READONLY

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

利用 PROC、ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。

AREA |.text|, CODE, READONLY 这行代码的作用是定义了一个名为.text的内存区域,用于存放程序的执行代码,这个区域是只读的,以确保代码在执行过程中不会被意外修改。这是嵌入式系统和许多其他类型程序开发中常见的内存管理实践。

5:定义字程序

129 行:reset_Handler子程序开始
130 行:声明Reset_Handler 为全局属性,外部文件就可以调用此复位中断服务。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,也就无法正常运行。
136 行: BX 表示跳转到由寄存器/标号给出的地址,不用返回。这里表示切换到__main地址,最终调用 main 函数,不返回,进入 C 的世界。
137 行:  ENDP 表示子程序结束

6: 中断服务函数

中断服务函数都被[WEAK]声明为弱定义函数,如果外部文件声明了一个标号,则优先使用外部文件定义的标号。
分为系统异常中断和外部中断,外部中断根据不同芯片有所变化。B 指令是跳转到一个标号,这里跳转到一个‘.’,表示无限循环。
在启动文件代码中,已经把我们所有中断的中断服务函数写好了,但都是声明为弱定义,所以真正的中断服务函数需要我们在外部实现。
如果我们开启了某个中断,但是忘记写对应的中断服务程序函数又或者把中断服务函数名写错,那么中断发生时,程序就会跳转到启动文件预先写好的弱定义的中断服务程序中,并且在 B 指令作用下跳转到一个‘.’中,无限循环。

7: 用户堆栈初始化

IF, ELSE, ENDIF 是汇编的条件分支语句。
282行:判断是否定义了__MICROLIB。关于__MICROLIB 这个宏定义

如果没有勾选__MICROLIB,相当没有定义__MICROLIB,所以使用默认的 C 库运行。堆栈的初始化由 C 库函数__main 来完成。

__user_initial_stackheap,表示用户堆栈初始化程序入口。
接下来进行堆栈空间初始化,堆是从低到高生长,栈是从高到低生长,是两个互相独立的数据段,并且不能交叉使用

作者:DevinLGT

物联沃分享整理
物联沃-IOTWORD物联网 » STM32启动文件详解:逐行解析代码,轻松掌握启动流程

发表回复