提示:若转载请备注来源,谢谢
文章目录
- 启动文件
-
- 1. 什么是启动代码
- 2. 启动代码主要干了什么
- 启动文件分析
-
- 一、设置堆栈
- 二、定义中断向量表
- 三、初始化系统时钟
- 总结
启动文件
1. 什么是启动代码
启动代码是系统上电或者复位后运行的第一段代码,是进入C 语言的main 函数之前需要执行的那段汇编代码。或者说用户程序运行之前对系统硬件及软件环境进行必要的初始化并在最后使程序跳转到用户程序。
将启动文件理解为一种描述性的代码,不要拘泥于它的实现机制,我之前一直在想启动代码中汇编代码的执行顺序,后来没明白,估计和keil工具有关系,只需要记住一个道理,程序上电执行的第一条代码(我们可以控制的代码),就是启动文件中Reset Handler函数,Reset Handler函数 的地址存放在flash的0x00000004处。
2. 启动代码主要干了什么
对于 Cortex-M系列的芯片而言,启动代码大同小,明白一个几乎所有的也就都明白了。
- 设置堆栈
- 定义中断向量表
- 初始化系统时钟(Reset_Handler中做)
- 初始化堆栈,有的启动代码不一定做(Reset_Handler中做)
- 执行_main,准备c语言的运行环境,初始化程序计数器指针PC指向main,进而来到c语言的世界
启动文件分析
一、设置堆栈
; <h> Stack Configuration
; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Stack_Size EQU 0x00000800
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 0x00000100
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
以上仅仅定义了堆栈的大小,堆栈的起始地址在keil中设置方式有两种,
第一种:
第二种,分散加载文件(将keil的Use Memory Layout from Target Dialog去掉勾选就会有该文件),在实际项目中,我通常使用该文件,可以理解问链接文件
在上图(第一幅图片中),启动文件的最后几行,有关于Use MicoLIB的描述,还有关于堆栈的设置,代码为,主要是初始化堆栈的地址范围:
;*******************************************************************************
; 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
二、定义中断向量表
1. 当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了―向量表查表机制‖(是ARM的实现机制)。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。
2. 向量表在地址空间中的位置默认存放在0x00000000处,但是还可以设置,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。
代码为:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_IRQHandler ; NMI Handler
DCD HardFault_IRQHandler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
......
......
AREA |.text|, CODE, READONLY
附加:常常在涉及到升级loader中使用
- 向量表重定位 + 跳转代码如下:
__asm void SoftReset(u32 address)
{
LDR R1, =0xE000ED08 // 0xE000ED08 重定位地址
STR R0, [R1, #0] // R0为形参address
LDR r1, [r0] // address内前4个直接存放的 Top of Stack
msr MSP,r1 // 给MSP赋值
ADDS r0, r0, #4 // Reset_Handler 的地址
LDR r1, [r0]
NOP
BX R1 // 执行 Reset_Handler
NOP
NOP
NOP
}
- 若仅仅想实现向量表重定位,例如将向量表定位到0x00000200,代码如下:
// SCB->VTOR就等于 0xE000ED08
//SCB->VTOR = 0x00000000; // 等价于0xE000ED08 = 0x00000000;
SCB->VTOR = 0x00000200;
三、初始化系统时钟
还是要在重申一下,复位子程序Reset_Handler是系统上电后第一个执行的程序。这里分析两个Reset handler,一个是STM32 demo上的,一个是我们使用的thk88芯片的
stm32代码为:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit // 系统初始化,里面包含了初始化时钟
BLX R0
LDR R0, =__main // 最后跳转到main()
BX R0
ENDP
thk88代码为
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT main
; 开启内部时钟30M
LDR R3, =SCUBASEADDR
LDR R0, =0x00000000
STR R0, [R3,#SCUSCK]
LDR R0, =0x00000000
STR R0, [R3,#SCUINCKD]
; 关闭USB PHY,禁止DP上拉电阻
LDR R0, =0x00000100
STR R0, [R3, #SCUCM3] ;OPEN USB CLK
LDR R2, =USBBASEADDR
LDR R0, =0x00000001
STR R0, [R2,#USBPCON] ; 关闭USB PHY
LDR R0, =0x00000000
LDR R2, =USBPHYCON
STR R0, [R2] ;禁止DP上拉
;STR R0, [R2,#USBPHYCON] ;禁止DP上拉
; 屏蔽 NMI 中断
LDR R0, =0xFFFFFFFF
LDR R2, =SCUNMI
STR R0, [R3,R2]
; DMA使用event方式
LDR R2, = 0x40000100 ;SCUEVTSET1 = 0x02;DMA event mode
LDR R0, = 0x00000002
STR R0, [R2]
;初始化用到的RAM区,4字节对齐,包括RW和ZI区域
BL CLEAR_ZI
IMPORT __main
LDR R0,= __main
BX R0
ENDP
每款芯片的启动代码大同小异,熟悉一个其他的都可以看懂。
总结
具体启动流程如图,两种方式
- 启动流程1(使用标准库,不使用Microlib)如下图:
- 启动流程2(使用Microlib)如下图:
假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x00000000(映射到0x8000000,两个是一个东西),则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处(复位地址在栈顶地址4字节后)。当STM32上电后,遇到复位信号,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。
重申:
- 绝大部分ARM-M协议的芯片,复位之后先进入厂商boot,此时所有的用户均无法接入处理器;厂商boot主要负责芯片最初级的初始化,对MCU进行一些差异性设置等,BOOT完成后,会把主动权交给用户,也就是启动代码;
- 启动代码(执行汇编语言不需要此启动代码),在启动文件中,会设置MSP(主堆栈指针)和PC(程序计数器)的值,MSP的地址默认是0x00000000,PC的地址默认是0x00000004,这两个地址可以通过CORTEX-M中的VTOR寄存器来进行重映射,修改。