深入解析STM32单片机启动文件

启动文件

启动文件由汇编语言写成,时单片机上电之后执行的第一个文件。也就是从上电到mian函数中间的一段过程。

bootloader

bootloader也可以叫启动文件,每种MCU都有对应的启动文件。但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。

启动文件是使用汇编指令写的,所以在看启动文件之前大家需要先了解一下汇编指令,常用的汇编指令如下:

常见指令

指令 含义
EQU 给数字常量取一个符号名,类似于define
AREA 汇编一个新的代码或者数据段
SPACE 分配内存空间
PRESERVE8 当前文件堆栈需要按8字节对齐
EXPORT 声明一个符号具有全局属性,可以被外部文件调用
DCD 以word为单位分配内存,要求4byte对齐,并且要求初始化这些内存
PROC 定义子程序,与ENDP一同使用表示程序结束
WEAK 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件也没有定义,也不会出错。这个是编译器指令,不属于arm的指令。
IMPORT 声明标号来自外部文件,类似于extern
B 跳转到一个标号
ALIGN 编译器对数据或者内存进行对齐,一般跟一个立即数。缺省表示4字节对齐,这个是编译器指令,不属于arm的指令。
END 到达文件末尾文件结束
IF,ELSE,ENDIF 条件语句类似于if else
LDR(load) LDR R0,[R1] 假设R1的值是x,读取地址x上的数据(4个字节)。保存到R0中。
STR(store) STR R0,[R1] 假设R1的值是x,把R0的值写到地址x(4个字节)。
MOV:移动 MOV R0, R1:把R1的值赋给R0. MOV R0, #0x100 R0=0x100

启动文件做了什么

1.设置堆栈指针 SP = _initial_sp
2.设置PC指针 = Reset_Handler
3.配置系统时钟
4.配置外部 SRAM 用于程序变量等数据存储(可选)
5.调用C库的 _main 函数,最终调用main函数

这边我们举个例子来具体说明启动文件具体的执行

开辟栈

Stack_Size      EQU     0x00001800	

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

EQU 是伪指令。伪指令的意思是指这个 “指令” 并不会生产二进制程序代码,也不会引起变量空间分配。
ARER :汇编一个新的代码或者数据段
STACK : 表示这个段的名字,可以任意命名。
NOINIT: 表示此数据段不需要填入初始数据。
READWRITE:表示此段可读可写。
ALIGN=3: 表示首地址按照2的3次方对齐,所以栈空间是8字节对齐的
SPACE 给 STACK 段分配 Stack_Size 的空间。

__initial_sp只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于C语言中的“地址”概念。地址仅仅表示存储空间的位置。此处的 __initial_sp 紧接着 SPACE 语句放置,表示了栈顶地址。

开辟堆空间

堆和栈的属性都是 READWRITE 可读写,可读写段保存于 SRAM区,即地址0x2000 0000 地址后

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000000

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

中断向量表部分

PRESERVE8:指定当前文件的堆栈按照8 字节对齐。
THUMB :表示后面指令兼容THUMB 指令。

                PRESERVE8
                THUMB


; Vector Table Mapped to Address 0 at Reset

                AREA    DEFVECT, DATA, READONLY
                EXPORT  __Vectors

__Vectors       DCD     __initial_sp              ; Top of Stack
                DCD     Reset_Handler             ; Reset Handler


上图中的 AREA 定义了一段名为 RESET 的 READONLY 只读数据段,只读属性保存在 Flash 区(如果STM32从Flash启动,则此中断向量表的地址为0x0800 0000)
EXPORT 指令,使得标号可以被外部文件调用,对应的有个 IMPORT 指令,指示后续符号是在外部文件定义的,外部文件的函数供汇编文件调用
标号__Vectors,表示中断向量表入口地址
标号 __Vectors_End,表示中断向量表的结束地址
标号__Vectors_Size,表示中断向量表的大号

开始建立中断向量表:


中间中断向量省略。。。。。。。

DCD指令作用是开辟一段空间,其意义等价于 C 语言中的地址符 “&” 。
中断向量表的建立类似于使用C语言定义了一个指针数组,其每一个成员都是一个函数指针,分别指向各个中断服务函数。

; Vector Table Mapped to Address 0 at Reset

                AREA    DEFVECT, DATA, READONLY
                EXPORT  __Vectors				

__Vectors       DCD     __initial_sp              ; Top of Stack	//分配4字节的空间
                DCD     Reset_Handler             ; Reset Handler
                DCD     NMI_Handler               ; NMI Handler
                DCD     HardFault_Handler         ; Hard Fault Handler
                DCD     MemManage_Handler         ; MPU Fault Handler
                DCD     BusFault_Handler          ; Bus Fault Handler
                DCD     UsageFault_Handler        ; Usage Fault Handler
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     0                         ; Reserved
                DCD     SVC_Handler               ; SVCall Handler
                DCD     DebugMon_Handler          ; Debug Monitor Handler
                DCD     0                         ; Reserved
                DCD     PendSV_Handler            ; PendSV Handler
                DCD     SysTick_Handler           ; SysTick Handler

                ; External Interrupts				//外部中断
                DCD     DMACERR0_IRQHandler       ; 0,   DMAC Error Count Interrupt
                DCD     DMACTC0_IRQHandler        ; 1,   DMAC Terminal Count Interrupt
                DCD     DMACERR1_IRQHandler       ; 2,   DMAC Error Count Interrupt
                DCD     DMACTC1_IRQHandler        ; 3,   DMAC Terminal Count Interrupt
                DCD     DMACERR2_IRQHandler       ; 4,   DMAC Error Count Interrupt
                DCD  

Reset_Handler 系统启动

系统上电或者复位后首先执行的代码就是复位中断服务函数 Reset_Handler:也是中断向量表的第一个函数。

图中的 Reset_Handler 中断服务函数使用了WEAK申明,说明我们在外部可以自定义 Reset_Handler 函数
PROC、ENDP这一对伪指令把程序分为若干个过程,是程序结构更加清晰
LDR R0,[R1]:假设R1的值是x,读取地址x上的数据(4个字节)。保存到R0中。这里是读取SystemInit函数地址,存入R0.

调用SystemInit函数。这个函数里面开启了外部晶振设置了PLL除能了所有中断设置了时钟

_main 标号表示 C/C++标准实时库函数里的一个初始化子程序 _main的入口地址。该程序的一个主要作用是初始化堆栈(跳转_user_initial_stackheap标号进行初始化堆栈),并初始化映像文件,最后跳转到C程序中的main函数。这也正解释了为什么所有的C程序必须有一个main函数作为程序的起点,因为这是由C/C++标准实时库所规定的。

; Reset Handler

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  boot_init
                IMPORT  boot_entry
                LDR     R0, =boot_init
                BLX     R0
				LDR     R0, =0x1008DFFC		;ブートプログラム破損検知状態格納アドレスに"0"を設定
				LDR     R1, =0x00000000
				STR     R1, [R0]
                LDR     R0, =boot_entry
                BX      R0
                ENDP

LDR R0, =0x1008DFFC:仍然是LDR,但是里面有个等号,这是一条伪指令,伪指令就是并不存在这么一条指令,它最终会被拆分成几条真正的RAM指令。执行结果是R0=0x1008DFFC

STR R1, [R0]:假设R1的值是x,把R0的值写到地址x(4个字节)。这里是把R0, =0x1008DFFC的地址写到R1 (R1, =0x00000000)

中断服务程序

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC
                EXPORT  NMI_Handler               [WEAK]
                B       .
                ENDP

中间中断服务程序省略。。。。。。。

中间中断服务程序省略。。。。。。。

当CPU检查到某个中断产生时,硬件会根据我们提供的中断号自动跳转到向量表中与这个中断号对应的这个中断服务函数的入口地址。

上面的这些不管是系统的中断服务程序还是外设的中断服务程序,都是_WEAK申明,其实我们写中断服务函数的时候,都会自己实现,比如F1中,我们在stm32f1xx_it.c文件中实现使用到的中断服务函数:

在工作中遇到一个第二次Reset_Handler2的例子
                ALIGN

                AREA    boot_program_2, CODE, READONLY

; Reset Handler2

Reset_Handler2  PROC
                EXPORT  Reset_Handler2             [WEAK]
                IMPORT  boot_init
                IMPORT  boot_entry
                LDR     R0, =boot_init
                BLX     R0				
				LDR     R0, =0x1008DFFC		;ブートプログラム破損検知状態格納アドレスに"1"を設定
				LDR     R1, =0x00000001
				STR     R1, [R0]
                LDR     R0, =boot_entry
                BX      R0
                ENDP

AREA boot_program_2, CODE, READONLY开辟一段数据空间名叫boot_program_2的readonly的代码段

初始化堆栈

文件最后就是堆栈的初始化工作:

物联沃分享整理
物联沃-IOTWORD物联网 » 深入解析STM32单片机启动文件

发表评论