stm32f407启动代码分析
官方资料
有关Cortex 内核的指令我们可以参考《CM3 权威指南 CnR2》第四章:指令集。剩下的 ARM 的汇编指令我们可以在 MDK->Help->Uvision Help 中搜索到,检索出来的结果会有很多,我们只需要看 Assembler User Guide 这部分即可。
指令名称 | 作用 |
---|---|
EQU | 给数字常量取一个符号名,相当于 C 语言中的 define |
AREA | 汇编一个新的代码段或者数据段 |
SPACE | 分配内存空间 |
PRESERVE8 | 当前文件堆栈需按照 8 字节对齐 |
EXPORT | 声明一个标号具有全局属性,可被外部的文件使用 |
DCD | 以字为单位分配内存,要求 4 字节对齐,并要求初始化这些内存 |
PROC | 定义子程序,与 ENDP 成对使用,表示子程序结束 |
WEAK | 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号 |
IMPORT | 声明标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似 |
B | 跳转到一个标号 |
ALIGN | 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示 4 字节对齐。 |
END | 到达文件的末尾,文件结束 |
IF,ELSE,ENDIF | 汇编条件分支语句,跟 C 语言的 if else 类似 |
Cortex-M3内核怎么开始执行一个程序
程序要执行得有栈用于局部变量,函数调用,函数形参等的开销,得有一个指针指向要执行的程序,所以Cortex-M3内核复位后做以下三件事:
1、从0x00000000读出栈顶(MSP)
2、从0x00000004读出程序计数器(PC)
3、跳转执行PC指向的代码
启动文件做什么
我们知道了Cortex-M3内核要读出栈顶和程序计数器,那么栈顶在哪,栈有多大,PC指针指向哪里,中断怎么处理,怎么开始执行C函数?这些就是启动文件里通过汇编语句要处理的事情
1、初始化堆栈大小
设置栈大小
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
EQU相当于#define
AREA用于定义一个新的数据段或者代码段,后面参数表示这个段名字为 STACK,NOINIT 即不初始化,可读可写,8(2^3)字节对齐。
SPACE指令用于分配一段连续的内存空间,Stack_Mem 是标签也是首地址,__initial_sp是末地址(即栈顶)
设置堆大小
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
开辟堆的大小为 0X00000200(512 字节),名字为 HEAP,NOINIT 即不初始化,可读可写,8(2^3)字节对齐。__heap_base 表示对的起始地址,__heap_limit 表示堆的结束地址。堆是由低向高生长的,跟栈的生长方向相反。堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆上面。
2、初始化中断向量表
Cortex-M3内核拥有11个系统异常和最多240个外部中断,当有中断或异常发生,会返回一个编号给内核,内核通过编号查找中断向量表找到对应中断服务函数的地址去执行,所以中断向量表存放的就是中断服务函数的地址
3、初始化堆栈指针 SP=_initial_sp、PC 指针=Reset_Handler
; 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 PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through
;为减小篇幅中间部分省略
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
DCD指令表示在存储器上分配一片连续的字存储单元,并把 DCD 后面跟的值赋值到刚分配的存储单元内。因为是第一次使用DCD所以分配的地址从0开始,每次分配4字节所以0x00000000放__initial_sp,0x00000004放Reset_Handler,实现了初始化MSP地址和PC指向的地址
4、配置系统时钟
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
WEAK表示此声明是弱声明
EXPORT 是声明外部全局标签
IMPORT 引入外部全局标签
①“LDR R0, =SystemInit” 是将函数 SystemInit 的地址放到寄存器 R0中保存。
② “BLX R0” 是跳转到 R0 寄存器存储的地址处运行。B是直接跳转指令,B后加L表示保存PC的值到寄存器 R14,可以在执行完跳转全部指令后返回跳转前的位置。B后加X是表示根据跳转的地址改变当前的状态,地址的最低位 add[0] 若是1,则将存储器状态更改为 Thumb 状态,反之地址的最低位 add[0] 若是0,则将存储器状态更改为 ARM 状态。( 注意Cortex-M3内核不允许进入ARM 状态,否则将会产生一个硬件异常中断 HardFault_Handler )。
在SystemInit中完成时钟配置等的系统配置,__main() 函数执行C语言环境初始化的操作,包括堆栈、寄存器等的设置,初始化完成后,将会跳转到main()
5、初始化用户堆栈,从而最终调用 main 函数
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 (在 KEIL 里面配置),如果定义了这个宏则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用,然后堆栈的初始化就由 C 库函数_main 来完成如果没有定 义 __MICROLIB ,需要编写一个初始化堆栈的函数,标签为 __user_initial_stackheap ,并将其声明为外部全局标签,以供 __main() 函数在初始化C语言环境时使用。
IF,ELSE,ENDIF:汇编的条件分支语句,跟 C 语言的 if ,else 类似
END:文件结束
参考博客: https://blog.csdn.net/qq_36300069/article/details/124319349