STM32启动过程
STM32的启动过程中包括
- 堆栈指针SP的初始化
- PC指针的初始化(指向复位程序)
- 初始化中断向量表
- 配置系统时钟
- 调用C库函数_main,开始执行main函数
启动文件
STM32从地址0x00000000开始启动,它是怎么到main函数的呢?
这里以F103为例,查看启动文件
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
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 WWDG_IRQHandler ; Window Watchdog
...(省略)
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
...(省略)
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
...
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
WWDG_IRQHandler
...(省略)
DMA2_Channel4_5_IRQHandler
B .
ENDP
ALIGN
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
栈空间配置
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
- EQU宏定义的伪指令,相当于
#define Stack_Size 0x00000400 - AREA告诉汇编编译器一个新的代码段或者数据段
- STACK表示定义的是一个栈区
- NOINIT表示不将初始值写入内存区域,也就是初始化为03
- READWRITE标志区域可读可写
- ALIGN=3,表示2^3=8字节对齐(相当于ALIGN 8)
- SPACE用于分配一定大小的内存空间,单位为字节,相当于分配了一个Stack_Size大小的内存空间,这段内存空间的起始地址是Stack_Mem
- __initial_sp紧挨着SAPCE语句放置,表示栈的结束地址,也即栈顶地址(栈是由高向低生长的)
堆空间配置
-
用于动态内存的分配,malloc函数分配内存用的就是这里的内存 Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
-
同上,这段代码的函数是:分配一个大小为Heap_Size的堆区,可读写,8字节对齐,堆区的起始地址是Heap_Mem -
__heap_limit是汇编代码的地址标号,这里用来表示堆区的结束地址
ARM指令集
PRESERVE8
THUMB
- PRESERVE8表示指定当前文件的堆栈按照8字节对齐
- THUMB表示后面的指令为THUMB指令。THUMB是ARM以前的指令集,16bit,现在的Cortex-M系列用的是THUMB-2指令集,32bit的,也兼容16bit的,绝大多数 16 位 Thumb 指令只能访问 R0‐R7,而 32 位 Thumb‐2 指令可以访问所有寄存器。
向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
- 定义一个新的数据段RESET,只读
- 声明三个全局标号
__Vectors DCD __initial_sp ; Top of Stack
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 DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
- 通过DCD分配一片连续的字单元的存储空间,
__Vectors 标号的存储的值是__initial_sp 所表示的地址值,也就是栈顶地址 - 向量表的第二个表项Reset_Handler是中断复位函数Reset_Handler的入口地址,以此类推
- __Vectors_End表示中断向量表结束
- __Vectors_Size的大小就是
__Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
- 定义一个新的代码段,段名为
.text 只读 - 如果段名是以数字或者标点开头则需用‘|’括起来
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
- PROC定义一个子程序Reset_Handler
- Reset_Handler作为一个全局标号
__main 和SystemInit 来自外部文件,C库的main函数和库函数的系统时钟的配置函数LDR R0, =SystemInit 将SystemInit 函数的指针的值传入R0寄存器中,然后跳转到SystemInit执行,跳转之前把下条指令保存到LR连接寄存器中,当SystemInit执行完成之后,再继续执行下条指令,也就是LDR R0, =__main LDR R0, =__main 将C库的main函数的地址值传入R0寄存器中,然后跳转到main函数中执行,BX不用返回,然后就开始执行用户的APP程序了- ENDP表示子程序结束
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
...(省略)
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
...省略
EXPORT DMA2_Channel3_IRQHandler [WEAK]
EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
...(省略)
DMA2_Channel3_IRQHandler
DMA2_Channel4_5_IRQHandler
B .
ENDP
ALIGN
- 定义各个中断服务程序,启动文件为我们写好了全部的中断服务程序,函数的名称必须与向量表里面初始化的名称一样。
- 这些程序都是空的,需要我们在C文件里面重新实现。如果我们写的中断服务程序的函数名写错了,程序也不报错,而是会进入一个死循环
- [WEAK]选项声明其他的同名标号优先于该标号被引用
- Default_Handler前面的和后面的分别属于系统异常中断服务程序和外部中断服务程序
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
-
判断是否使能了MicroLib -
使能,则将__initial_sp __heap_base __heap_limit 赋予全局属性 -
未使能
- 将堆区首地址保存到R0
- 将栈区大小保存到R1
- 将堆区大小保存到R2
- 将栈区首地址保存到R3
- 跳转到LR寄存器保存的地址中执行,相当于
MOV PC, LR -
R14 是连接寄存器( LR)。在一个汇编程序中,你可以把它写作 both LR 和 R14。 LR 用于在调用子程序时存储返回地址。例如,当你在使用 BL(分支并连接, Branch and Link)指令时,就自动填充 LR 的值 -
在ARM体系结构中LR的特殊用途有两种:一是用来保存子程序返回地址;二是当异常发生时,LR中保存的值等于异常发生时PC的值减4(或者减2),因此在各种异常模式下可以根据LR的值返回到异常发生前的相应位置继续执行。在这里是第一种功能,那么结合BX的用法,就是回到之前保存的返回地址处。
汇编指令
-
EXPORT声明一个标号具有全局属性,可被外部的文件使用。 -
DCD分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中, DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们 -
WEAK表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。 -
IMPORT表示该标号来自外部文件,跟 C 语言中的EXTERN 关键字类似。 -
LDR从存储器中加载字到一个寄存器中 -
BL跳转到有寄存器或者标号给出的地址,并把跳转前的下条指令地址保存到LR(ARM内核的连接寄存器) -
BLX跳转到寄存器给出的地址,并根据寄存器的LSE确定处理器的状态,还要把跳转前的下条指令保存到LR -
BX跳转到寄存器或者标号给出的地址,不用返回 -
B跳转到下一个标号 -
IF,ELSE,ELDIF汇编的条件分支语句 -
END文件结束 -
ALIGN对指令或者数据存放的地址进行对齐,默认是4字节对齐 -
PROC定义子程序,与ENDP成对使用,表示子程序结束
|