启动文件:
startup_stm32f40_41xxx.s
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
SPACE和DCD的区别在于:
SPACE和DCD的功能类似,SPACE申请一片内存空间,DCD申请一个字(32bit)的内存空间。
SPACE和DCD的区别在于,SPACE申请空间但不赋初值,DCD申请一个字的空间,并赋初值。
AREA
语法格式:
AREA 段名 属性1 ,属性2 ,……
AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用“|”括起来,如:|1_test| 。
属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下:
— CODE 属性:用于定义代码段,默认为READONLY 。
— DATA 属性:用于定义数据段,默认为READWRITE 。
— READONLY 属性:指定本段为只读,代码段默认为READONLY 。
— READWRITE 属性:指定本段为可读可写,数据段的默认属性为READWRITE 。
— ALIGN 属性:使用方式为ALIGN表达式。在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐的,表达式的取值范围为0~31,相应的对齐方式为2表达式次方。
— COMMON 属性:该属性定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元。
一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。
使用示例:
AREA Init ,CODE ,READONLY ; 该伪指令定义了一个代码段,段名为Init ,属性为只读。
NOINIT
指示数据节未初始化,或初始化为零。 它只包含空间保留指令 SPACE 或初始化值为零的DCB、DCD、DCDU、DCQ、DCQU、DCW 或 DCWU。 您可以在链接时决定某区域是未初始化还是初始化为零(请参阅《RealView 编译工具链接器和实用程序指南》中的第 3 章 使用基本链接器功能)
设置初始SP
设置初始PC=Reset_Handler
设置向量表入口地址,并初始化向量表
调用SystemInit,把系统时钟配制成72M,SystemInit在库文件system_stm32f10.c定义
调转到标号_main,最终来到C程序文件
; Reset handler
Reset_Handler PROC // reset_hander子程序
EXPORT Reset_Handler [WEAK] //供其他模块调用
IMPORT SystemInit //其他模块供自己调用
IMPORT __main //同上
LDR R0, =SystemInit //把SystemInit 的地址加载到寄存器R0。
BLX R0 //程序跳转到R0 中的地址执行程序,之后系统的时钟就被设置成72MHZ
LDR R0, =__main //把_main 的地址加载到寄存器R0
BX R0 //程序跳转到R0 中的地址执行程序,执行完毕之后就去到我们熟知的C 世界
ENDP //表示子程序的结束
PROC 是子程序定义伪指令。一般用法为:
1 子程序名PROC NEAR ( 或FAR )
2 ……
3 ret
4子程序名ENDP
其中NEAR 和FAR 是属性词。 NEAR属性(段内近调用):
调用程序和子程序在同一代码段中,只能被相同代码段的其他程序调用。
FAR属性(段间远调用):调用程序和子程序不在同一代码段中,可以被相同或不同代码段的程序调用。
arm跳转指令详细请参考:https://blog.csdn.net/bytxl/article/details/49883103
上面是对基本的arm指令找到一些解释 可能并不能具体说明启动过程下面在总结下
.S 文件中基本定义了各个段的定义 但是没有具体的实际地址,汇编中各个标号的偏移地址跟编译链接后的地址是两个不同的概念 但是经过编译器的重定向后才编程我们需要的地址
Stack_Size EQU 0x00000400
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 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
; 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
据上图可以看到,_initial_sp栈顶指针和向量表起始地址均未设置。
默认情况下,cortex-m0+内核认为该表位于零地址处,且各向量占用4 字节,因此每个表项占用4 字节
重定向后:
中断向量表里的中断跳转地址在编译后就定下来了,SCB->VTOR向量
可动态调整就是让我们的程序运行后还能改变向量的跳转地址。方法就是:在RAM
重建一个中断向量表,在想改变的位置重新赋值新的跳转地址
要想实现Flash程序跳转和SRAM程序跳转,第一步先设置SCB->VTOR向量,重置中断向量表的起始地址(不是默认的0x00000000了),其中_DSB()为数据同步隔离、_ISB()为指令同步隔离(确保接下来的所有指令都使用新配置)。
以上为改变中断向量表起始地址,经过_DSB()和_ISB()指令后接下来要给这些地址指针重新赋值,也就是在FlashLoader_ASM()和SRAMLoader_ASM()被调函数中重新配置MSP和PC的值。
以SJ_FLASH_BASE=0x10000000,SJ_SRAM_BASE=0x20000000为例:
以上两个函数均是将SJ_FLASH_BASE或SJ_SRAM_BASE地址处存储的值赋给SP
,再将地址值偏移4字节后得到的地址里存储的值给PC
上面过程类似启动文件中中断向量表里提到的Reset_Handler,也是先给_initial_sp
栈顶指针赋值中断向量表起始地址,然后地址偏移4
字节程序跳转到启动文件后面定义的Reset_Handler函数执行如下:
区别只不过是_main和SystemInit代表的是入口地址常量,而前者是直接操作地址值