一、概述
1、STM32的启动过程:硬件复位后,到执行用户main函数之前的这段时间。
MCU主要做的工作:
1.硬件设置SP和PC
2.根据BOOT引脚状态选择重映射区
3.设置系统时钟
4.转跳至__main函数执行
2、分析启动代码主要可以分为以下几个阶段
1.开辟栈空间
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
代码解析:
第1行:宏定义一个Stack_Size表示栈空间的大小,类似C中的#define。
第2行:表示开辟一段名为STACK,可读可写的8字节对齐的数据空间。AREA表示下面将开始定义定义一个代码段或者数据段,后面跟着这个段的属性。
第3行:SPACE伪指令通知汇编器分配0x400个字节大小的连续内存空间给STACK段。
第4行:这句伪指令紧跟着开辟栈空间伪指令,表示初始化栈顶地址,也就是这个标号表示栈顶的地址。
2.开辟堆空间
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
代码解析:
开辟堆空间代码与开辟栈空间代码类似,主要注意的是__heap_base表示堆空间的开始地址,__heap_limit表示堆空间的结束地址。
最后两行代码表示:指定当前文件的堆栈保持8字节对齐。因为Cortex-M3使用的是THUMB指令集,所以指定当前使用THUMB指令集。
3.开辟只读数据段
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
代码解析:
第1行:开辟一段名为RESET的只读数据段
后三行:声明3个可供外部文件调用的标号
4.建立中断向量表
__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 DMA2_Channel3_IRQHandler ; DMA2 Channel3
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
代码解析:
原代码太长就只贴出一点,其主要的作用就是建立中断向量表,并将所有中断服务函数的入口地址存入表中。MCU的中断系统会将这些入口地址对应赋给PC指针,然后跳转到对应的函数中执行。
5.定义中断服务函数
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
...
...
...
...
...
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
...
...
代码解析:
源代码太长就只贴出最重要的复位中断服务函数,其作用就是调用外部文件的SystemInit函数和__main函数。通过查找下项目文件,我们可以知道SystemInit函数主要做的就是系统时钟的初始化,各种寄存器的初始化,包括系统控制块的向量表重映射起始地址的初始化(注意区分,从内部SRAM启动或者内部FLASH启动的向量表偏移寄存器起始地址是不一样的)。
__main函数的代码没找到,个人猜测是因为这个是C库封装起来的函数。其作用就是软件设置SP指针,使之指向栈顶,然后转跳至用户main函数。
还有一点小知识:WEAK表示弱定义,声明这个标号如果外部文件有用到,则使用外部的中断服务函数。
6.用户堆栈的初始化
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
代码解析:由此可见,使用微库和不使用微库是不一样的。个人猜测,使用微库,我们不需要自行初始化堆,因为微库不需要用到。不使用微库,我们就需要自己写初始化堆栈函数供__main调用,应该是系统库需要用到堆。
小问题:那究竟是用微库好呢?还是不用微库好呢?
答:个人认为,使用微库节省了初始化堆这一步,启动过程步骤少一点。但是微库函数都是封装好的,里面的代码我 们不得而知,所以安全性不敢保证。
二、启动方式
1.STM32的三种启动方式
BOOT0引脚状态 BOOT1引脚状态 启动模式 0 x 从FLASH中启动 1 0 从系统存储器启动 1 1 从内嵌SRAM中启动
正是因为启动方式的不同,中断向量表的起始地址也会不同,所以通过存储器重映射,能够有效解决系统上电复位之后中断向量表的存放问题,这样系统上电复位后固定从地址0x00000000获取SP,再从0x00000004获取PC后开始执行代码。这就对应上了前面说的启动过程了。
后记
本人的底层知识比较欠缺,写得不好,有错的地方请各位大佬指出,谢谢!