赞
踩
简述:
1、初始化堆栈指针SP=_initial_sp
2、初始化PC指针=Reset_Handler
3、初始化中断向量表
4、配置系统时钟
5、调用C库函数_main初始化用户堆栈,从而最终调用main函数;
一、堆栈设置
1、栈空间设置
一般默认为Stack_Size EQU 0x00000400(1024Byte);
注:一个函数中定义的所有局部变量,加起来不能大于工程的栈大小,否则程序必然会出现内存溢出,导致复位;一个函数中定义的所有局部变量+全局变量不能大于工程的栈。
2、分配内存空间,初始化内存单元值
AREA STACK, NOINIT, READWRITE, ALIGN
伪指令AREA,表示开辟一段内存空间,段名是SUN(叠加);
NOINIT:表示不初始化,此数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或将各个内存单元值初始化为0;
READWRITE:可读可写
ALIGN=3: 2 的3次方表示以8字节对齐;
Stack_Mem SPACE Stack_Size
__initial_sp:设置栈指针
3、堆设置
Heap_Size EQU 0x0000200
AREA HEAP, NOINIT, READWRITE, ALIGN= 3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
与栈类似
4、堆栈对齐
REQUIRE8
指令指定当前文件要求堆栈八字节对齐。 它设置 REQ8 编译属性以通知链接器。
PRESERVE8
指令指定当前文件保持堆栈八字节对齐。 它设置 PRES8 编译属性以通知链接器。
链接器检查要求堆栈八字节对齐的任何代码是否仅由保持堆栈八字节对齐的代码直接或间接地调用。
THUMB:表示后面指令兼容THUMB指令。THUBM是ARM以前的指令集,16bit,现在Cortex-M系列的都使用THUMB-2指令集,THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超级。
5、向量表
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
定义一个数据段,名字为RESET,可读。
并声明__Vectors、__Vectors_End和__Vectors_Size这三个标号具有全局属性,可供外部的文件调用。
EXPORT:声明一个标号可被外部的文件使用,使标号具有全局属性。如果是IAR编译器,则使用的是GLOBAL这个指令。
当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定ESR 的入口地址,内核使用了"向量表查表机制"。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为0。因此,在地址0 (即FLASH 地址0)处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类:0 号类型并不是什么入口地址,而是给出了复位后MSP 的初值。
__Vectors
DCD __initial_sp ; Top of Stack
71 00000004 00000000 DCD Reset_Handler ; Reset Handler
72 00000008 00000000 DCD NMI_Handler ; NMI Handler
73 0000000C 00000000 DCD HardFault_Handler ; Hard Fault
Handler
74 00000010 00000000 DCD MemManage_Handler
; MPU Fault Handler
75 00000014 00000000 DCD BusFault_Handler
; Bus Fault Handler
76 00000018 00000000 DCD UsageFault_Handler ; Usage Faul
t Handler
77 0000001C 00000000 DCD 0 ; Reserved
78 00000020 00000000 DCD 0 ; Reserved
79 00000024 00000000 DCD 0 ; Reserved
80 00000028 00000000 DCD 0 ; Reserved
81 0000002C 00000000 DCD SVC_Handler ; SVCall Handler
82 00000030 00000000 DCD DebugMon_Handler ; Debug Monito
r Handler
... ...中间省略部分代码
175 0000019C 00000000 DCD 0 ; 103:Reserved
176 000001A0 00000000 DCD TLI_IRQHandler ; 104:TLI
177 000001A4 00000000 DCD TLI_ER_IRQHandler
; 105:TLI error
178 000001A8 __Vectors_End
179 000001A8
180 000001A8 000001A8
__Vectors_Size
EQU __Vectors_End - __Vectors
181 000001A8
182 000001A8 AREA |.text|, CODE, READONLY
1 __Vectors_Size EQU __Vectors_End - __Vectors
__Vectors为向量表起始地址,__Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。
向量表从FLASH的0地址开始放置,以4个字节为一个单位,地址0存放的是栈顶地址,0X04存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,C语言中的函数名就是一个地址。
DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中,DCD分配了一堆内存,并且以ESR的入口地址初始化它们。
6、复位程序
184 00000000 ;/* reset Handler */
185 00000000 Reset_Handler
PROC
186 00000000 EXPORT Reset_Handler
[WEAK]
187 00000000 IMPORT __main
188 00000000 IMPORT SystemInit
189 00000000 4809 LDR R0, =SystemInit
190 00000002 4780 BLX R0
191 00000004 4809 LDR R0, =__main
192 00000006 4700 BX R0
193 00000008 ENDP
复位子程序是系统上电后第一个执行的程序,调用SystemInit函数初始化系统时钟,然后调用C库函数_mian,最终调用main函数。
WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
IMPORT:表示该标号来自外部文件,跟C语言中的EXTERN关键字类似。这里表示SystemInit和__main这两个函数均来自外部的文件。
SystemInit()是一个标准的库函数,主要作用是配置系统时钟。
__main是一个标准的C库函数,主要作用是初始化用户堆栈,最终调用main函数。这就是为什么我们写的程序都有一个main函数的原因。如果我们在这里不调用__main,那么程序最终就不会调用我们C文件里面的main,如果是调皮的用户就可以修改主函数的名称,然后在这里面IMPORT你写的主函数名称即可。
7、中断
在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的C文件里面重新实现。
如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。
195 00000008 ;/* dummy Exception Handlers */
196 00000008 NMI_Handler
PROC
197 00000008 EXPORT NMI_Handler
... ...中间省略部分代码
[WEAK]
238 00000018 E7FE B .
239 0000001A ENDP
240 0000001A
241 0000001A Default_Handler
PROC
402 0000001A UART7_IRQHandler
403 0000001A TLI_IRQHandler
404 0000001A TLI_ER_IRQHandler
405 0000001A
406 0000001A E7FE B .
407 0000001C ENDP
8、堆栈初始化
ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示4字节对齐。
用户堆和栈初始化:
411 0000001C ; user Initial Stack & Heap
413 0000001C IF :DEF:__MICROLIB
420 0000001C
421 0000001C IMPORT __use_two_region_memory
422 0000001C EXPORT __user_initial_stackheap
423 0000001C
424 0000001C __user_initial_stackheap
PROC
425 0000001C 4804 LDR R0, = Heap_Mem
426 0000001E 4905 LDR R1, =(Stack_Mem + Stack_Size)
427 00000020 4A05 LDR R2, = (Heap_Mem + Heap_Size)
428 00000022 4B06 LDR R3, = Stack_Mem
ARM Macro Assembler Page 12
429 00000024 4770 BX LR
430 00000026 ENDP
431 00000026
432 00000026 00 00 ALIGN
433 00000028
434 00000028 ENDIF
435 00000028
436 00000028 END
IF :DEF:__MICROLIB 判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的C库,然后初始化用户堆栈大小,这部分有C库函数__main来完成,当初始化完堆栈之后,就调用main函数去到C的世界。
IF,ELSE,ENDIF:汇编的条件分支语句,跟C语言的if ,else类似
END:文件结束
9、系统启动
在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:
1、从地址 0x0000,0000 处取出 MSP 的初始值。
2、从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量, LSB 必须是 1。 然后从这个值所对应的地址处取指。
图 142 复位序列
请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是我们刚刚分析的Reset_Handler这个函数。
因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加 1。举例来说,如果我们的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值就必须是 0x20008000。
向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在 Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因,图 143中使用0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行(即去到C的世界)。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。
附录、汇编指令
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。