启动文件由汇编语言写成,时单片机上电之后执行的第一个文件。
也就是从上电到mian函数中间的一段过程。
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 中断服务函数使用了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文件中实现使用到的中断服务函数:
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的代码段