本文简要介绍一下国民技术N32的启动代码。
启动文件主要做以下几件事情:
初始化堆栈指针SP(__initial_sp)
初始化PC指针(Reset_Handler)
初始化中断向量表(__Vectors)
配置系统时钟(SystemInit)
配置外部SRAM用于程序变量等数据存储(可选)
调用C库函数_main初始化用户堆栈,从而最终调用main函数去到C的世界
通过MAP文件查看SP数值
通过MAP文件查看复位中断入口地址
Keil工程实际生成的HEX文件可以看出,前两个字(4个字节)分别就是SP和PC的值
下面这段代码用于开辟栈(stack)空间,用于局部变量,函数调用,函数的参数等
34行:EQU是代表宏定义的伪指令,类似于C语言中的#define,
伪指令的意思是指这个“指令”,并不会生成二进制代码,也不会引起变量空间的分配。
0x00001500表示栈大小,注意这里是字节为单位
36行:开辟一段数据空间可读可写,段名STACK,按照8字节对齐
ARER伪指令表示下面将开始定义一个代码段或者数据段。此处定义数据段,ARER后面的关键字表示这个段的属性。
- STACK:表示这个段的名字,可以任意命名
- NOINIT:表示此数据段不需要植入初始数据
- READWRITE:表示此段可读可写
- ALIGN=3:表示首地址按照2的3次方对齐,此处表示按照8字节对齐
37行:SPACE这行指令告诉汇编器,给STACK段分配 0x00001500字节连续内存空间
38行:__initial_sp紧接着 SPACE 语句放置,表示了栈顶地址。__initial_sp 只是一个标号,标号主要用于表示片内存空间的某个位置,等价于C语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从C语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。
下面这段代码用于开辟堆(heap)空间,用于动态内存申请,如malloc等函数分配的变量空间是在堆上。
__heap_base 表示堆的开始地址
__heap_limit 表示堆的结束地址
51行:PRESERVE8指定当前文件保持堆栈8字节对齐
52行:THUMB表示当前文件使用thumb指令集 ,CM4采用的是thumb-2指令集
56行:AREA定义一块代码段,只读,段名字是RESET。READONLY表示段只读
57-59行:3行EXPORT语句将3个标号申明为可被外部引用,主要提供给连接器用于连接库文件或其他文件
下面这段代码是建立中断向表,中断向量表定位在代码段的最前面,具体的物理地址配置参数(IROM1的地址)决定。如果程序在FLASH中运行,则中断向量表的起始地址是0x0800 0000
DCD表示分配1个4字节的空间,每行DCD都会生成一个4字节的二进制代码,中断向量表存放的实际是中断服务程序的入口地址。当异常(中断事件)发生时,CPU的中断系统会将相应的入口地址赋值给PC程序记数器,之后就开始执行中断服务程序。
169行:AREA 定义一块代码段,只读,段名字是.text。READONLY 表示只读
172行:利用 PROC、ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰
173行:WEAK 声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。 这个申明很重要,它让我们可以在 C 文件中任意地方放置中断服务程序,只要保证C 函数的名字和向量表中的名字一致即可。
175行:IMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
176行:SystemInit 函数在文件 system_XXX.c里面。
178行:_main 标号表示 C/C++标准实时库函数里的一个初始化子程序_main 的入口地址,该程序的一个主要作用是初始化堆栈(跳转_user_initial_stackheap 标号进行初始化堆栈的,下面会讲到这个标号),并初始化腴像文件,最后跳转到C程序中的 main函数。这就解释了为何所有的C程序必须有一个 main 函数作为程序的起点。因为这是由C/C++标准实时库所规,并且不能更改。
补充一个点:
为什么__initial_sp的值是 0x20001508。Stack_Size大小是0x00001500,栈是向下生长的,初始栈指针指向栈区域的最高地址(即栈顶)。那这个0x20001508的0x00000008是怎么来的呢?
可以在map文件中找到答案,PAD是编译器为了对齐(alignment)插入的填充段,以保证后续段对齐到 8 字节。
从0x0200 0000开始的放置的是SystemCoreClock。
SystemCoreClock 被放在 SRAM 最前面,是因为它属于 .data 段,链接器默认会优先把 .data 放在 RAM 开始处,而栈(STACK)段通常排在后面或最后
下面做一个简单的验证:
在main.c中添加:
重新编译后,查看map文件:
可以看到__initial_sp的地址也有了相应的变化