目录
概述
1 启动文件介绍
1.1 启动文件功能
1.2 汇编语言指令
2 启动代码细节
2.1 分配栈空间
2.2 分配堆空间
2.3 中断向量表
2.4 复位程序
2.5 中断服务程序
2.5.1 CPU内部中断程序
2.5.2 CPU内部扩展中断程序
2.6 用户堆栈初始化
3 总结
概述
本文以startup_stm32h750xx.s为例,介绍stm32h750的启动代码的内容,本文对启动的每一个环节实现的功能做了简要分析,了解ARM CM7内核是如何从启动代码中跳到用户程序中,执行用户程序。
1 启动文件介绍
1.1 启动文件功能
启动文件由汇编编写,是系统上电复位后第一个执行的程序。主要做了以下工作:
1)初始化堆栈指针SP=_initial_sp
2)初始化PC指针=Reset_Handler
3)初始化中断向量表
4)配置系统时钟
5)调用C库函数_main初始化用户堆栈,最终跳到C语言代码段
1.2 汇编语言指令
指令名称 | 作用 |
---|---|
EQU | 给数字常量取一个符号名,相当于C语言中的define |
AREA | 汇编一个新的代码段或者数据段 |
SPACE | 分配内存空间 |
PRESERVE8 | 当前文件堆栈需按照8字节对齐 |
EXPORT | 声明一个标号具有全局属性,可被外部的文件使用 |
DCD | 以字为单位分配内存,要求4字节对齐,并要求初始化这些内存 |
PROC | 定义子程序,与ENDP成对使用,表示子程序结束 |
WEAK | 弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号, 如果外部文件没有定义也不出错。要注意的是:这个不是ARM的指令,是 编译器的,这里放在一起只是为了方便。 |
IMPORT | 声明标号来自外部文件,跟C语言中的EXTERN关键字类似 |
B | 跳转到一个标号 |
ALIGN | 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省 表示4字节对齐。要注意的是:这个不是ARM的指令,是编译器的,这里 放在一起只是为了方便 |
END | 到达文件的末尾,文件结束 |
IF,ELSE,ENDIF | 汇编条件分支语句,类似C语言的if else |
2 启动代码细节
2.1 分配栈空间
代码第32~34行:
开辟栈的大小为0x400(1KB),名字为STACK,NOINIT即不初始化,可读可写,8(2^3)字节对齐。栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬fault的时候,这时你就要考虑下是不是栈不够大,溢出了。
2.2 分配堆空间
代码第43~51行:
开辟堆的大小为0x200(512字节),名字为HEAP,NOINIT即不初始化,可读可写,8(2^3)字节对齐。
__heap_base: 表示对的起始地址
__heap_limit: 表示堆的结束地址, 堆是由低向高生长的,跟栈的生长方向相反
2.3 中断向量表
代码第54~58行:
定义一个数据段,名字为RESET,可读。并声明 __Vectors、__Vectors_End和__Vectors_Size这三个标号具有全局属性,可供外部的文件调用。
代码第230行:
__Vectors为向量表起始地址,__Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。
2.4 复位程序
代码第234~245行:
复位子程序是系统上电后第一个执行的程序,调用SystemInit()函数初始化系统时钟,然后调用C库函数_mian(),最终调用main函数去到C的世界。
WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。 这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT:表示该标号来自外部文件,和C语言中的EXTERN关键字类似。这里表示SystemInit和__main这两个函数均来自外部的文件。
SystemInit()是一个标准的库函数,在system_stm32f7xx.c这个库文件中定义。主要作用是配置系统时钟,这里调用这个函数之后,配置系统时钟。
__main是一个标准的C库函数,主要作用是初始化用户堆栈,最终调用main函数调到C语言中。这就是为什么写的程序都有一个main函数的原因。 如果在这里不调用__main,那么程序最终就不会调用我们C文件里面的main,如果是调皮的用户就可以修改主函数的名称, 然后在这里面IMPORT你写的主函数名称即可。
2.5 中断服务程序
2.5.1 CPU内部中断程序
2.5.2 CPU内部扩展中断程序
在启动文件里定义中断服务函数,其是一个weak函数,真正的中断复服务程序需要在外部的C文件里面重新实现,这里只是提前占了一个位置而已。
代码292~434行: 定义所有的外部中断函数名
2.6 用户堆栈初始化
代码587~596行:判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的C库,然后初始化用户堆栈大小,这部分有C库函数__main来完成,当初始化完堆栈之后,就调用main函数去到C的世界。
代码606行:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示4字节对齐
代码610行:文件结束
3 总结
ARM Cortex-M7(CM7)内核的启动流程如下:
-
复位向量表:当系统复位时,程序计数器(PC)被设置为复位向量表的起始地址。复位向量表包含一系列指令地址,用于初始化处理器和引导操作系统。
-
复位处理:在复位向量表中,第一个指令的地址是复位处理程序的入口点。复位处理程序执行一系列初始化操作,包括初始化处理器和外设。
-
初始化堆栈指针:在复位处理程序中,堆栈指针(SP)被初始化为堆栈的起始地址。堆栈用于存储函数调用时的局部变量和返回地址。
-
初始化全局指针:在复位处理程序中,全局指针(GP)被初始化为全局变量的起始地址。全局变量存储在数据段中,可以被程序的任何部分访问。
-
初始化中断向量表:中断向量表包含一系列中断处理程序的入口点。在复位处理程序中,中断向量表被初始化为默认的中断处理程序入口点。后续可以通过修改中断向量表的内容来注册新的中断处理程序。
-
启用中断:在复位处理程序中,中断控制器(例如NVIC)被初始化并使能中断。使能中断后,当发生中断时,处理器会跳转到相应的中断处理程序并执行。
-
进入主程序:完成上述初始化操作后,处理器会跳转到主程序的入口点,开始执行应用程序的代码。