上一节,咱们在建立工程的时候,默认的是使用MDK自带的启动代码,这些启动代码到底做了什么工作呢?在这里我想探究一下,探究不全没什么事,能看懂个大概就行了。
我先申明一下,其实我并不是头一次学ARM裸机,我先前已经按照韦东山的使用arm-linux-gcc在linux下编译裸机程序的方法走了一遍了,用那个方法的话对以后的uboot移植非常有帮助,但是有一个不方便的就是,使用Linux系统下编译裸机程序,很多自带的库函数不能用,比如print()函数我都不能用,主要是我的水平不行,不会用,其实可以用的。所以现在想认真的再用编译器学一下ARM裸机,裸机学好了,对驱动开发很有帮助的。
所以,很可能我讲的可能细节上照顾不到没接触过ARM裸机的,在此深感抱歉。我也只是想把自己的学习弄成笔记而已。
言归正传,下面进行S3C2440.s的分析,初学者这一节可以略过。
1、首先,了解一下这个文件都要完成那些功能。
①看门狗初始化(可以选择是否初始化)
②时钟初始化(可以选择是否初始化)
③存储控制器初始化(可以选择是否初始化)
④GPIO口初始化(可以选择是否初始化)
⑤堆栈初始化(没有选择性,必须初始化)
⑥跳转到C文件的main函数执行
2、其实知道上面文件做了哪些工作就行了,下面具体分析一下
;/*****************************************************************************/
;/* S3C2440.S: Startup file for Samsung S3C2440 */
;/*****************************************************************************/
;/*
;*启动代码S3C2440.S是在CPU复位后执行的。这个文件会根据以下的
;*SET标志来进行翻译执行。
;*NO_CLOCK_SETUP:启动代码不初始化时钟(这种情况大多出现在时钟已经在script.ini文件中初始化时)
;*NO_MC_SETUP:启动代码不初始化寄存器控制器。
;*NO_GP_SETUP:启动代码不初始化GPIO口
;* RAM_INTVEC:启动代码将异常向量表从执行地址处复制到RAM中去
CPSR中的低8位。I:IRQ中断禁止位,置位禁止。F:FIQ中断禁止位,置位禁止。 T:CPU状态位(ARM或者THUMB)。M4-M0:工作模式选择位
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
I | F | T | M4 | M3 | M2 | M1 | M0 |
M[4:0] | 工作模式 |
10000 | 用户模式 |
10001 | 快中断模式 |
10010 | 中断模式 |
10011 | 管理模式 |
10111 | 数据访问中止模式 |
11011 | 未定义指令中止模式 |
11111 | 系统模式 |
;状态寄存器CPSR中的标准的模式位和中断位的宏定义
Mode_USR EQU 0x10 ;用户模式
Mode_FIQ EQU 0x11 ;快中断模式
Mode_IRQ EQU 0x12 ;中断模式
Mode_SVC EQU 0x13 ;管理模式
Mode_ABT EQU 0x17 ;数据访问中止模式
Mode_UND EQU 0x1B ;未定义指令中止模式
Mode_SYS EQU 0x1F ;系统模式
I_Bit EQU 0x80 ; when I bit is set, IRQ is disabled
F_Bit EQU 0x40 ; when F bit is set, FIQ is disabled
;栈(Stack)设置。不同工作模式的堆栈寄存器sp不一样。
;设置栈空间
UND_Stack_Size EQU 0x00000000 ;未定义模式
SVC_Stack_Size EQU 0x00000008 ;管理模式栈长度
ABT_Stack_Size EQU 0x00000000 ;数据访问中止模式栈长度
FIQ_Stack_Size EQU 0x00000000 ;快中断模式栈长度
IRQ_Stack_Size EQU 0x00000080 ;中断模式栈长度
USR_Stack_Size EQU 0x00000400 ;用户模式栈长度
ISR_Stack_Size EQU (UND_Stack_Size + SVC_Stack_Size + ABT_Stack_Size + \
FIQ_Stack_Size + IRQ_Stack_Size) ;所有的堆栈大小进行相加,得到总堆栈大小
;/*******************************************************************************************************
;arm的汇编程序由段组成,段是相对独立的指令或数据单位,每个段由AREA伪指令定义,并定义段的属性
;READWRITE(读写)READONLY(只读)NOINIT(不初始化内存单元或将内存写0)
;*********************************************************************************************************/
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义栈段,段名为STACK,字节对齐方式
Stack_Mem SPACE USR_Stack_Size ;SPACE指令用于分配一块内存单元。这里分别为这两个栈分配对应
__initial_sp SPACE ISR_Stack_Size ;长度的内存空间
Stack_Top EQU Stack_Mem + ISR_Stack_Size ;//定义栈开始地址(最大地址,堆栈向下访问)
;堆(heap)配置
;堆大小(单位:字节)
Heap_Size EQU 0x00000000 ;系统的堆空间设定//定义堆空间大小(配合最后的动态内存申请使用)
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;定义堆段,段名:HEAP,不初始化内存,可读写,字节对齐
__heap_base
Heap_Mem SPACE Heap_Size ;申请堆的内存空间
__heap_limit
;-----------------------存储器设定 ------------------------------------
IRAM_BASE EQU 0x40000000 ;内存基地址
;-----------------------看门狗定义 ----------------------------
WT_BASE EQU 0x53000000 ; 看门狗寄存器基地址
WTCON_OFS EQU 0x00 ; 看门狗控制寄存器 对应基地址的偏移值
WTDAT_OFS EQU 0x04 ; 看门狗数据寄存器 对应基地址的偏移值
WTCNT_OFS EQU 0x08 ; 看门狗计数寄存器 对应基地址的偏移值
;看门狗时钟设置
WT_SETUP EQU 1 ;看门狗设置
WTCON_Val EQU 0x00000000 ;看门狗控制寄存器相关
WTDAT_Val EQU 0x00008000 ;看门狗数据寄存器相关
;----------------------- 时钟管理定义 ----------------
CLOCK_BASE EQU 0x4C000000 ; 时钟寄存器基地址
LOCKTIME_OFS EQU 0x00 ;PLL锁定时间计数器对应基地址的偏移值
MPLLCON_OFS EQU 0x04 ; MPLL配置寄存器对应基地址的偏移值
UPLLCON_OFS EQU 0x08 ; UPLL配置寄存器对应基地址的偏移值
CLKCON_OFS EQU 0x0C ; 时钟控制器对应基地址的偏移值
CLKSLOW_OFS EQU 0x10 ; 慢时钟控制寄存器对应基地址的偏移值
CLKDIVN_OFS EQU 0x14 ; 时钟分频控制器对应基地址的偏移值
CAMDIVN_OFS EQU 0x18 ; 摄像时钟分频控制器对应基地址的偏移值
;时钟相关寄存器设置值的宏定义
CLOCK_SETUP EQU 0 ;时钟设置 ;在以后的实验里,我会将它设置为1,这样做是为了让系统在运行时就对时钟进行初始化
LOCKTIME_Val EQU 0x0FFF0FFF ;PLL锁定时间计数器 值
MPLLCON_Val EQU 0x00043011 ;MPLL控制寄存器 值 ;设置FCLK=300MHz
UPLLCON_Val EQU 0x00038021 ;UPLL控制寄存器 值
CLKCON_Val EQU 0x001FFFF0 ;时钟控制器 值
CLKSLOW_Val EQU 0x00000004 ;慢时钟控制器 值
CLKDIVN_Val EQU 0x0000000F ; 时钟分频控制器 值 ;设置分频比为1:3:6,UCLK=48MHz
CAMDIVN_Val EQU 0x00000000 ;摄像时钟分频器 值
;----------------------- 存储器控制设定 -------------------------
MC_BASE EQU 0x48000000 ; 存储器控制器基地址
BWSCON_OFS EQU 0x00 ; 位宽和等待控制寄存器 偏移值
BANKCON0_OFS EQU 0x04 ;BANK0控制寄存器 偏移值
BANKCON1_OFS EQU 0x08 ; BANK1控制寄存器 偏移值
BANKCON2_OFS EQU 0x0C ; BANK2控制寄存器 偏移值
BANKCON3_OFS EQU 0x10 ; BANK3控制寄存器 偏移值
BANKCON4_OFS EQU 0x14 ; BANK4控制寄存器 偏移值
BANKCON5_OFS EQU 0x18 ; BANK5控制寄存器 偏移值
BANKCON6_OFS EQU 0x1C ; BANK6控制寄存器 偏移值
BANKCON7_OFS EQU 0x20 ;BANK7控制寄存器 偏移值
REFRESH_OFS EQU 0x24 ; SDRAM刷新控制寄存器 偏移值
BANKSIZE_OFS EQU 0x28 ; BANKSIZE寄存器 偏移值
MRSRB6_OFS EQU 0x2C ; SDRAM控制寄存器 偏移值
MRSRB7_OFS EQU 0x30 ; SDRAM控制寄存器 偏移值
;存储控制器的相应的设置值
MC_SETUP EQU 0 ;存储控制器设定
BWSCON_Val EQU 0x22000000 ;总线宽度和等待控制器 值
BANKCON0_Val EQU 0x00000700 ;Boor ROM控制器 值
BANKCON1_Val EQU 0x00000700 ;BANK1控制 值
BANKCON2_Val EQU 0x00000700 ;BANK2控制 值
BANKCON3_Val EQU 0x00000700 ;BANK3控制 值
BANKCON4_Val EQU 0x00000700 ;BANK4控制 值
BANKCON5_Val EQU 0x00000700 ;BANK5控制 值
BANKCON6_Val EQU 0x00018005 ;BANK6控制 值
BANKCON7_Val EQU 0x00018005 ;BANK7控制 值
REFRESH_Val EQU 0x008404F3 ;DRAM/SDRAM刷新控制
BANKSIZE_Val EQU 0x00000032 ;存储器大小控制
MRSRB6_Val EQU 0x00000020 ;SDRAM的模式设置寄存器 控制
MRSRB7_Val EQU 0x00000020 ;SDRAM的模式设置寄存器 控制
;----------------------- I/O 口设定----------------------------------
GPA_BASE EQU 0x56000000 ; GPA Base Address
GPB_BASE EQU 0x56000010 ; GPB Base Address
GPC_BASE EQU 0x56000020 ; GPC Base Address
GPD_BASE EQU 0x56000030 ; GPD Base Address
GPE_BASE EQU 0x56000040 ; GPE Base Address
GPF_BASE EQU 0x56000050 ; GPF Base Address
GPG_BASE EQU 0x56000060 ; GPG Base Address
GPH_BASE EQU 0x56000070 ; GPH Base Address
GPJ_BASE EQU 0x560000D0 ; GPJ Base Address
GPCON_OFS EQU 0x00 ; 控制寄存器相对应于上面(A-J)基地址的偏移值
GPDAT_OFS EQU 0x04 ; 数据寄存器相对应于上面(A-J)基地址的偏移值
GPUP_OFS EQU 0x08 ; 上拉控制寄存器相对应于上面(B-J)基地址的偏移值
; I/O端口 设定
GP_SETUP EQU 0
;端口A
GPA_SETUP EQU 0
GPACON_Val EQU 0x000003FF
;端口B
GPB_SETUP EQU 0
GPBCON_Val EQU 0x00000000
GPBUP_Val EQU 0x00000000 ;端口B上拉开启
;端口C
GPC_SETUP EQU 0
GPCCON_Val EQU 0x00000000
GPCUP_Val EQU 0x00000000 ;端口C上拉开启
;端口D
GPD_SETUP EQU 0
GPDCON_Val EQU 0x00000000
GPDUP_Val EQU 0x00000000 ;端口D上拉开启
;端口E
GPE_SETUP EQU 0
GPECON_Val EQU 0x00000000
GPEUP_Val EQU 0x00000000 ;端口E上拉开启
;端口F
GPF_SETUP EQU 0
GPFCON_Val EQU 0x00000000
GPFUP_Val EQU 0x00000000 ;端口F上拉开启
;端口G
GPG_SETUP EQU 0
GPGCON_Val EQU 0x00000000
GPGUP_Val EQU 0x00000000 ;端口G上拉开启
;端口H
GPH_SETUP EQU 0
GPHCON_Val EQU 0x00000000
GPHUP_Val EQU 0x00000000 ;端口H上拉开启
;端口J
GPJ_SETUP EQU 0
GPJCON_Val EQU 0x00000000
GPJUP_Val EQU 0x00000000 ;端口J上拉开启
;----------------------- 这才是真正的程序开始的地方,前边都是一些宏定义和准备工作--------------------------------------------------
;汇编程序数据8字节对齐
PRESERVE8 ;C和汇编有8位对齐的要求,这个伪指令可以满足此要求
; 段定义和程序入口点
; 启动代码必须连接到第一个地址才能运行。在MDK配置选项里边默认的连接就是从这段代码开始的,别轻易改啊
AREA RESET, CODE, READONLY ;定义程序段,段名为RESET,只读
ARM ;ARM模式运行程序
IF :LNOT::DEF:__EVAL ;这是要引用咱们的编译器MDK自动生成的两个符号
IMPORT ||Image$$ER_ROM1$$RO$$Length|| ;RO段地址,长度,可以再MDK配置选项Linker看到这两个的定义
IMPORT ||Image$$RW_RAM1$$RW$$Length|| ;RW段地址,长度
ENDIF
; 异常向量表
Vectors LDR PC, Reset_Addr ;复位异常, 地址0x0000 0000
LDR PC, Undef_Addr ;未定义异常, 地址0x0000 0004//关于地址,我不确定,因为前面说的是8字节对齐,是不是0x0000 0008呢
LDR PC, SWI_Addr ;软件中断, 地址0x0000 0008
LDR PC, PAbt_Addr ;指令预取中断,地址0x0000 000C
LDR PC, DAbt_Addr ;数据异常中断,地址0x0000 0010
IF :DEF:__EVAL ;如果定义了__EVAL变量
DCD 0x4000 ;DCD常用于分配一块连续的内存单元,分配2K空间
ELSE
DCD ||Image$$ER_ROM1$$RO$$Length||+\ ;否则分配空间大小为RO输出区的字节长度与RW输出区的字节长度之和
||Image$$RW_RAM1$$RW$$Length||
ENDIF
LDR PC, IRQ_Addr ;普通中断
LDR PC, FIQ_Addr ;快中断
IF :DEF:__RTX
IMPORT SWI_Handler
IMPORT IRQ_Handler_RTX
ENDIF
Reset_Addr DCD Reset_Handler ;Label DCD expr是分配一块连续的内存单元,并用expr初始化,Label是一个标号,代表的是
Undef_Addr DCD Undef_Handler ;所分配的内存单元的首地址。这里实际上是将相应的异常中断的处理程序的入口地址赋值给前边的标号
SWI_Addr DCD SWI_Handler
PAbt_Addr DCD PAbt_Handler
DAbt_Addr DCD DAbt_Handler
DCD 0 ; Reserved Address
IF :DEF:__RTX
IRQ_Addr DCD IRQ_Handler_RTX
ELSE
IRQ_Addr DCD IRQ_Handler
ENDIF
FIQ_Addr DCD FIQ_Handler ;将快中断处理函数FIQ_Handler的地址赋值给FIQ_Addr
Undef_Handler B Undef_Handler
IF :DEF:__RTX
ELSE
SWI_Handler B SWI_Handler
ENDIF
PAbt_Handler B PAbt_Handler
DAbt_Handler B DAbt_Handler
IRQ_Handler PROC
EXPORT IRQ_Handler [WEAK]
B .
ENDP
FIQ_Handler B FIQ_Handler
; 复位异常处理程序,每次复位后程序都要从此处开始执行
EXPORT Reset_Handler ;定义一个全局函数变量
Reset_Handler
;----------------------------------------------------------------------------------------------------
;看门狗配置
;若WT_SETUP不等于0,执行此语句,否则执行下一个语句
;前面已经定义WT_SETUP=1,所以会执行这一段
; ---------------------------------------------------------------------------------------------------
IF WT_SETUP != 0
LDR R0, =WT_BASE ;加载看门狗地址
LDR R1, =WTCON_Val ;加载看门狗控制寄存器数据
LDR R2, =WTDAT_Val ;加载看门狗数据寄存器数据
STR R2, [R0, #WTCNT_OFS] ;将WTDAT_Val配置给看门狗计数寄存器
STR R2, [R0, #WTDAT_OFS] ;将WTDAT_Val配置给看门狗数据寄存器
STR R1, [R0, #WTCON_OFS] ;将WTCON_Val配置给看门狗控制寄存器
ENDIF
;----------------------------------------------------------------------------------------------------
;时钟设置
;如果逻辑上没有定义NO_CLOCK_SETUP并且CLOCK_SETUP!=0,则执行下面程序
;前面定义CLOCK_SETUP=0,则会跳过这段程序。
;所以咱们以后可以自己设置时钟频率,我是倾向于自己设置的,因为这也是一个知识点,
;并且S3C2440的时钟配置是一个很重要的知识。在这里我猜测,既然没有时钟初始化,那么咱们下载到
;SDRAM后,应该是以晶振12MHz来工作的,当然只是猜测,错了的时候再改
;后面几个实验后,我返回来再说一句,我的猜测是对的,系统默认的是不对系统时钟进行初始化,为了方便,
;在以后的实验中,我都将CLOCK_SETUP修改为1,这样子我就不用重写代码了。
;系统初始化之后的结果就是FCLK=300MHz,HCLK=100MHz,PCLK=50MHz
;-----------------------------------------------------------------------------------------------------
IF (:LNOT:(:DEF:NO_CLOCK_SETUP)):LAND:(CLOCK_SETUP != 0)
LDR R0, =CLOCK_BASE ;加载时钟基地址
LDR R1, =LOCKTIME_Val ;加载PLL锁定时间计数值
STR R1, [R0, #LOCKTIME_OFS] ;将PLL锁定时间值配置到PLL锁定时间计数器
MOV R1, #CLKDIVN_Val
STR R1, [R0, #CLKDIVN_OFS] ;配置时钟分频器
LDR R1, =CAMDIVN_Val
STR R1, [R0, #CAMDIVN_OFS] ;配置摄像头分频控制寄存器
LDR R1, =MPLLCON_Val
STR R1, [R0, #MPLLCON_OFS] ;配置MPLL配置寄存器
LDR R1, =UPLLCON_Val
STR R1, [R0, #UPLLCON_OFS] ;配置UPLL配置寄存器
MOV R1, #CLKSLOW_Val
STR R1, [R0, #CLKSLOW_OFS] ;配置慢时钟配置寄存器
LDR R1, =CLKCON_Val
STR R1, [R0, #CLKCON_OFS] ;配置时钟控制寄存器
ENDIF
;-----------------------------------------------------------------------------------------------------
;存储控制器设置
;如果没有定义NO_MC_SETUP且CLOCK_SETUP!=0,则执行下面的程序。
;此处前面定义CLOCK_SETUP=0,也就是说这一段又不执行,真假的,这是要闹哪样?
;----------------------------------------------------------------------------------------------------
IF (:LNOT:(:DEF:NO_MC_SETUP)):LAND:(CLOCK_SETUP != 0)
LDR R0, =MC_BASE ;加载存储控制器基地址
LDR R1, =BWSCON_Val
STR R1, [R0, #BWSCON_OFS] ;配置总线宽度和等待控制寄存器
LDR R1, =BANKCON0_Val
STR R1, [R0, #BANKCON0_OFS] ;配置BANK0控制寄存器
LDR R1, =BANKCON1_Val
STR R1, [R0, #BANKCON1_OFS] ;配置BANK1控制寄存器
LDR R1, =BANKCON2_Val
STR R1, [R0, #BANKCON2_OFS] ;配置BANK2控制寄存器
LDR R1, =BANKCON3_Val
STR R1, [R0, #BANKCON3_OFS] ;配置BANK3控制寄存器
LDR R1, =BANKCON4_Val
STR R1, [R0, #BANKCON4_OFS] ;配置BANK4控制寄存器
LDR R1, =BANKCON5_Val
STR R1, [R0, #BANKCON5_OFS] ;配置BANK5控制寄存器
LDR R1, =BANKCON6_Val
STR R1, [R0, #BANKCON6_OFS] ;配置BANK6控制寄存器
LDR R1, =BANKCON7_Val
STR R1, [R0, #BANKCON7_OFS] ;配置BANK7控制寄存器
LDR R1, =REFRESH_Val
STR R1, [R0, #REFRESH_OFS] ;配置DRAM/SDRAM刷新控制寄存器
MOV R1, #BANKSIZE_Val
STR R1, [R0, #BANKSIZE_OFS] ;配置可调的Bank大小控制寄存器
MOV R1, #MRSRB6_Val
STR R1, [R0, #MRSRB6_OFS] ;配置SDRAM模式控制寄存器
MOV R1, #MRSRB7_Val
STR R1, [R0, #MRSRB7_OFS] ;配置SDRAM模式控制寄存器
ENDIF
;-----------------------------------------------------------------------------------------------------------
;I/O端口配置
;如果没有定义NO_GP_SETUP且GP_SETUP!=0,则执行下面的程序
;好吧,前边定义了GP_SETUP=0,我现在想知道,他到底执行什么??代码是我直接复制过来的,
;为什么我看到有的人的代码都定义为1呢?淡定,接着分析
;--------------------------------------------------------------------------------------------------------------
IF (:LNOT:(:DEF:NO_GP_SETUP)):LAND:(GP_SETUP != 0)
IF GPA_SETUP != 0
LDR R0, =GPA_BASE ;配置端口A
LDR R1, =GPACON_Val ;A口有25个口,做I/O时只能做输出口,不能做输入口
STR R1, [R0, #GPCON_OFS]
ENDIF
IF GPB_SETUP != 0
LDR R0, =GPB_BASE ;配置端口B
LDR R1, =GPBCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPBUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPC_SETUP != 0
LDR R0, =GPC_BASE ;配置端口C
LDR R1, =GPCCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPCUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPD_SETUP != 0
LDR R0, =GPD_BASE ;配置端口D功能
LDR R1, =GPDCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPDUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPE_SETUP != 0
LDR R0, =GPE_BASE ;配置端口E
LDR R1, =GPECON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPEUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPF_SETUP != 0
LDR R0, =GPF_BASE ;配置端口F
LDR R1, =GPFCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPFUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPG_SETUP != 0
LDR R0, =GPG_BASE ;配置端口G
LDR R1, =GPGCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPGUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPH_SETUP != 0
LDR R0, =GPH_BASE ;配置端口H
LDR R1, =GPHCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPHUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
IF GPJ_SETUP != 0
LDR R0, =GPJ_BASE ;配置端口J
LDR R1, =GPJCON_Val
STR R1, [R0, #GPCON_OFS]
LDR R1, =GPJUP_Val
STR R1, [R0, #GPUP_OFS]
ENDIF
ENDIF
;----------------------------------------------------------------------------------------------------
;将异常向量表复制到内部RAM
;如果定义了RAM_INTVEC就执行下一条程序,我突然看到在咱们S3C2440.s代码的开头给介绍了4个SET标志,其中一个就是
;RAM_INTVEC,反正在这段程序中没有定义。要么就是需要咱们人为定义,要么就是编译器自己定义的
;本文件代码中没有定义,所以不复制
;---------------------------------------------------------------------------------------------------
IF :DEF:RAM_INTVEC
ADR R8, Vectors ; 读取向量源地址,Vectors是一个标号,在程序的前面可以看到
LDR R9, =IRAM_BASE ; 读取片上SRAM的基地址
LDMIA R8!, {R0-R7} ; 批量加载异常向量
STMIA R9!, {R0-R7} ; 批量存储异常向量
LDMIA R8!, {R0-R7} ;批量加载程序入口地址
STMIA R9!, {R0-R7} ; 批量存储程序入口地址
ENDIF
;---------------------------------------------------------------------------------------------------------
;配置相应模式栈的大小
;下面这一段代码主要是设置各个异常模式的堆栈,注意在设置的时候需要禁止IRQ和FIQ
;这段代码也是系统复位后执行的第一段代码。执行完这段代码后系统处于系统模式,
;并且IRQ和FIQ都是禁止的。
;---------------------------------------------------------------------------------------------------------
LDR R0, =Stack_Top ;加载栈顶指针地址,在前边已经定义
;进入未定义模式,并设定其栈指针,将(Mode_UND | I_Bit | F_Bit)赋值给CPSR_c即CPSR_c即CPSR[7:0]
MSR CPSR_c, #Mode_UND:OR:I_Bit:OR:F_Bit
MOV SP, R0 ;栈顶指针赋值给SP指针
SUB R0, R0, #UND_Stack_Size ;栈顶指针减去未定义栈的大小就等于下一个模式的SP指针位置
;进入数据异常中断模式,并设定其栈指针
MSR CPSR_c, #Mode_ABT:OR:I_Bit:OR:F_Bit
MOV SP, R0
SUB R0, R0, #ABT_Stack_Size
;进入FIQ中断模式,并设定其栈指针
MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit
MOV SP, R0
SUB R0, R0, #FIQ_Stack_Size
;进入IRQ中断模式,并设定其栈指针
MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit
MOV SP, R0
SUB R0, R0, #IRQ_Stack_Size
;进入管理模式,并设定其栈指针
MSR CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit
MOV SP, R0
SUB R0, R0, #SVC_Stack_Size
;进入用户模式,并设定其栈指针
MSR CPSR_c, #Mode_USR
MOV SP, R0
SUB SL, SP, #USR_Stack_Size
; 进入用户模式
MSR CPSR_c, #Mode_USR
IF :DEF:__MICROLIB ;如果定义了__MICROLIB
EXPORT __initial_sp ;那么就声明__initial_sp
ELSE
MOV SP, R0 ;否则就设定用户模式栈指针
SUB SL, SP, #USR_Stack_Size
ENDIF
;----------------------------------------------------------------------------------------------
;开始正式进入C代码区
;反汇编以后C程序中的main函数名就变成了__main
;-----------------------------------------------------------------------------------------------
IMPORT __main ;声明__main函数
LDR R0, =__main ;加载__main函数入口地址
BX R0 ;跳转到__main处
IF :DEF:__MICROLIB ;如果定义了__MICROLIB
EXPORT __heap_base ;则声明__heap_base
EXPORT __heap_limit ;声明__heap_limit
ELSE
;--------------------------------------------------------------------------------------------------------
;用户初始化堆与栈,用于动态申请内存使用
;__use_two_region_memory是MDK的库函数
;__user_initial_stackheap也是一个Z库函数,它的返回值有
; *堆基址(heap base) -----> R0
; *栈基址(stack base) -----> R1 一般为栈的最高地址
; *堆顶(heap limit) ----> R2
; *栈顶(stack limit) ----> R3
;---------------------------------------------------------------------------------------------------------
AREA |.text|, CODE, READONLY
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem ;堆内存起始地址----->R0
LDR R1, =(Stack_Mem + USR_Stack_Size) ;栈起始地址------> R1
LDR R2, = (Heap_Mem + Heap_Size) ;栈顶地址 -----> R3
LDR R3, = Stack_Mem ;栈顶地址 ----->R3
BX LR ;子程序返回
ENDIF
END
至此,将S3C2440.s已经分析完了,也知道他做什么功能了,挺好的!