板子上电后就会从这里开始执行,主要完成基本初始化,还有判断是从nor还是nand启动,再实现把程序搬到
当中,在搬运成功后再跳到main函数里面执行。 我们现在开始来看看它的具体代码吧! GET和INCLUDE的功能是相同的,功能都是引进一些编译过的文件。 GET option.inc GET memcfg.inc GET 2440addr.inc 定义S
工作在Reflesh模式下,SDRAM有两种刷新模式:selfreflesh,autoreflesh。后者是在其使用过程当中设置的。 BIT_SELFREFRESH EQU (1<<22) 下面是对arm处理器模式寄存器对应的常数进行赋值,arm处理器有一个CPSR寄存器,它的后五位决定了处理器处于哪个模式下。可以看出常数的定义都不会超过后5位的。 USERMODE EQU 0x10 FIQMODE EQU 0x11 IRQMODE EQU 0x12 SVCMODE EQU 0x13 ABORTMODE EQU 0x17 UNDEFMODE EQU 0x1b MODEMASK EQU 0x1f NOINT EQU 0xc0 各个异常模式的堆栈 UserStack EQU (_STACK_BASEA
ESS-0x3800) ;0x33ff4800 ~ SVCStack EQU (_STACK_BASEADDRESS-0x2800) ;0x33ff5800 ~ UndefStack EQU (_STACK_BASEADDRESS-0x2400) ;0x33ff5c00 ~ AbortStack EQU (_STACK_BASEADDRESS-0x2000) ;0x33ff6000 ~ IRQStack EQU (_STACK_BASEADDRESS-0x1000) ;0x33ff7000 ~ FIQStack EQU (_STACK_BASEADDRESS-0x0) ;0x33ff8000 ~ 这一段是统一arm的工作状态和对应的软件编译方式(16位编译环境使用tasm.exe编译)。arm处理器的工作状态分为两种:32位,arm执行字对齐的arm指令集;16位,arm执行半字对齐的Thumb指令集。不同的工作状态,编译方式也不一样。所以下面的程序就是判断arm的工作方式来确定它的编译方式。 GBLL THUMBCODE//定义THUMBCODE 这个变量GBLL 声明一个全局逻辑变量并初始化为{FALSE} [ {CONFIG} = 16//"["表示"if","|"表示"else","]"表示"endif",对于CONFIG是在ADS编译中定义的内部变量。 THUMBCODE SETL {TRUE} CODE32 | THUMBCODE SETL {FALSE} ]//如果
是在16位的工作状态的话,就使全局变量THUMBCODE设置为ture。 MACRO//这个是宏定义的关键字 MOV_PC_LR//作用是子程序返回 [ THUMBCODE bx lr//当目标程序是Thumb时,就要使用BX跳转返回,并转换模式。 | mov pc,lr//目标程序是ARM指令集,直接把lr赋给pc就可以了。 ] MEND//宏定义的结束标志。 MACRO MOVEQ_PC_LR//这个是带“相等”条件的子程序返回。和上面说的类似。 [ THUMBCODE bxeq lr | moveq pc,lr ] MEND 在宏定义下面的handlexxx HANDLER handlexxx都会展成以下的程序段,这段程序主要把中断服务程序的入口地址传送给pc,在程序的用34字空间来存放中断服务程序的入口地址,每个字空间都会有一个标号,以handlerxxx开头的。 MACRO $HandlerLabel HANDLER $HandleLabel $HandlerLabel sub sp,sp,#4 //先预留空间,为了存储跳转地址。 stmfd sp!,{r0} //把工作寄存器按入堆栈。 ldr r0,=$HandleLabel ldr r0,[r0] //这两句的功能是把中断程序的入口地址先放在中间变量r0处。 str r0,[sp,#4]//把中断服务程序的入口地址按入堆栈。 ldmfd sp!,{r0,pc}//最后把堆栈中的中断程序入口地址弹给pc寄存器,这样就可以执行相应的中断服务程序了。 MEND S3C2440有两种中断模式:一种有中断向量表的,一种则没有。有表的话实时性比较好。当一个外部中断0发生后,程序自动跳转到地址0x20处,0x20地址单元的指令为“ldr pc, = HandlerEINT0”,因此程序跳转到HandlerEINT0处执行这个宏操作,就是把外部中断地址赋给PC。 一个arm程序是由R0,RW,ZI三个段组成。其中R0为代码段,RW是已经初始化的全局变量,ZI是未初始化的全局变量,BOOTLOADER要将RW段复制到RAM中并将ZI段清零。 编译器使用下列段来记录各段的起始地址和结束地址 |Image$$RO$$Base| ; RO 段起始地址|Image$$RO$$Limit| ; RO 段结束地址加1|Image$$RW$$Base| ; RW 段起始地址 |Image$$RW$$Limit| ; RW 段结束地址加1|Image$$ZI$$Base| ; ZI 段起始地址|Image$$ZI$$Limit| ; ZI 段结束地址加1 这些标号的值是通过编译器的设定来确定的如编译软件中对ro-base 和rw-base 的设定,例如ro-base=0xc000000 rw-base=0xc5f0000,在这里用
ORT 伪指令( 和c 语言的extren 一样) 引入|Image$$RO$$Base|,|Image$$RO$$Limit|...等比较古怪的变量是编译器生成的。RO, RW, ZI 这三个段都保存在Flash 中,但RW,ZI 在Flash 中的地址肯定不是程序运行时变量所存储的位置,因此我们的程序在初始化时应该把Flash 中的RW,ZI 拷贝到RAM 的对应位置。这些变量是通过ADS 的工程设置里面设定的RO Base 和RW Base 设定的,最终由编译脚本和连接程序导入程序. IMPORT |Image$$RO$$Base| IMPORT |Image$$RO$$Limit| IMPORT |Image$$RW$$Base| IMPORT |Image$$ZI$$Base| IMPORT |Image$$ZI$$Limit| 引入外部变量mmu的快速总线模式和同步总线模式两个变量 IMPORT MMU_SetAsyncBusMode IMPORT MMU_SetFastBusMode 我们所熟知的main函数 IMPORT Main 把镜像从Nandflash拷贝到SDRAM的函数 IMPORT RdNF2SDRAM 定义arm汇编程序段,段名叫init段,为只读段 AREA Init,CODE,READONLY ENTRY EXPORT __ENTRY//导出__ENTRY标号 __ENTRY ResetEntry ASSERT :DEF:ENDIAN_
E//判断模式改变是否定义过(ASSERT是伪指令,:DEF:lable判断lable是否定义过了) [ ENDIAN_CHANGE ASSERT :DEF:ENTRY_BUS_W
H//判断是否定义了总线宽度 [ ENTRY_BUS_WIDTH=32//如果存储器是32位的总线宽度 b ChangeBigEndian ;DCD 0xea000007 ] [ ENTRY_BUS_WIDTH=16//如果存储器是16位的总线宽度 andeq r14,r7,r0,lsl #20 ;DCD 0x0007ea00 ] [ ENTRY_BUS_WIDTH=8//如果是存储器是8位总线宽度 streq r0,[r0,-r10,ror #1] ;DCD 0x070000ea ] |//如果总线宽度没有定义的话,就直接跳转到复位中断 b ResetHandler//程序执行的地跳跳转指令 ] b HandlerUndef ;handler for Undefined mode b HandlerSWI ;handler for SWI interrupt b HandlerPabort ;handler for PAbort b HandlerDabort ;handler for DAbort b . ;reserved b HandlerIRQ ;handler for IRQ interrupt b HandlerFIQ ;handler for FIQ interrupt ;@0x20 b EnterPWDN ; Must be @0x20.//进入powerdown模式 以上8条跳转指令,是8个异常中断处理向量,一定要按照顺序排好,据我了解,每次出现异常的话,是由硬件自行查表的。 HandlerFIQ HANDLER HandleFIQ HandlerIRQ HANDLER HandleIRQ HandlerUndef HANDLER HandleUndef HandlerSWI HANDLER HandleSWI HandlerDabort HANDLER HandleDabort HandlerPabort HANDLER HandlePabort 下面这段程序很重要,他是实现第二次查表的程序。arm把所有中断都归为一个IRQ和一个FIRQ中断异常,我们为了要知道具体的中断,从而才可以跳到中断对应的中断服务程序。 IsrIRQ sub sp,sp,#4 //保留pc寄存器的值 stmfd sp!,{r8-r9}//把r8 r9按入堆栈 ldr r9,=INTOFFSET//把中断偏移INTOFFSET的地址装入r9里面 ldr r9,[r9]//取出INTOFFSET单元里面的值给r9 ldr r8,=HandleEINT0//向量表的入口地址赋给r8 add r8,r8,r9,lsl #2//求出具体中断向量的地址 ldr r8,[r8]//中断向量里面存储的中断服务程序的入口地址赋给r8 str r8,[sp,#8]//按入堆栈 ldmfd sp!,{r8-r9,pc}//堆栈弹出,跳转到相应的中断服务程序 LTORG//声明文字池 板子上电后就,程序就执行0x00处的b ResetHandler ResetHandler ldr r0,=WTCON //关闭看门狗 ldr r1,=0x0 str r1,[r0] ldr r0,=INTMSK ldr r1,=0xffffffff //关闭所有中断 str r1,[r0] ldr r0,=INTSUBMSK ldr r1,=0x7fff //关闭所有子中断 str r1,[r0] [ {FALSE} ;rGPFDAT = (rGPFDAT & ~(0xf<<4)) | ((~data & 0xf)<<4); ; Led_Display ldr r0,=GPBCON ldr r1,=0x155500 str r1,[r0]//使GPB10~GPB4为输出口,GPB3~GPB0为输入口 ldr r0,=GPBDAT ldr r1,=0x0 str r1,[r0]//使GPB10~GPB4输出为低电平,GPB3~GPB0输入为低电平 ] 通过数据手册可以发现,当输出为1时,LED灭,反之亦然。 LOCKTIME是pll的lock time
。为了减少pll的lock time,调整LOCKTIME寄存器。 ldr r0,=LOCKTIME ldr r1,=0xffffff//赋给这个值后,UPLL和MPLL的locktime的值都会设定好了。具体为什么是设定这个值,你就去问问三星公司吧,我也不太懂。 str r1,[r0] 说到这里,大家可能不太懂。我就在这里细说一下吧。这个涉及到arm9的时钟模块的知识。arm9有个时钟控制逻辑,它可以产生cpu的FCLK时钟、AHB总线外围接口器件的HCLK时钟以及APB总线外围接口器件的PCLK时钟。arm9有两个锁相环PLL,一个用于FCLK、HCLK、HCLK。一个用于USB模块。这两个PLL我们分别称之为MPLL和UPLL。在系统复位之后,PLL按照默认的配置进行操作,由于认为它这时是一个不稳定的状态,所以这时用外部时钟作为FCLK时钟的输出。只有当向PLLCON寄存器设置相应的值后,PLL就会按照软件设置的频率运行了。这时就换成使用PLL的输出作为FCLK了。对于FCLK先后不是有两次不同时钟作为输入,这样就余姚一个适应的时间,这个时间的设定就是我们这里在LOCKTIME寄存器里面设置的常数啦。 [ PLL_ON_START//设置CLKDIVN的值在PLL锁存时间之后有效。 ldr r0,=CLKDIVN ldr r1,=CLKDIV_VAL ; 0=1:1:1, 1=1:1:2, 2=1:2:2, 3=1:2:4, 4=1:4:4, 5=1:4:8, 6=1:3:3, 7=1:3:6. str r1,[r0] 可以看出是对FCLK、PCLK以及HCLK三者的比率设置。只要通过对CLKDIVN执行操作就可以得到相应需要的比率了。 [ CLKDIV_VAL>1 //如果 Fclk:Hclk不是1:1的话执行下面 mrc p15,0,r0,c1,c0,0 orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA mcr p15,0,r0,c1,c0,0 | mrc p15,0,r0,c1,c0,0 bic r0,r0,#0xc0000000;R1_iA:OR:R1_nF mcr p15,0,r0,c1,c0,0 ] 这里可以看出,如果FCLK:HCLK不是1:1的关系的话,就要转成异步总线模式。反之,如果是这个比例关系的话,就转成快速总线模式。 ldr r0,=UPLLCON//对UPLL进行配置 ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)//这里就是非常熟悉的PMS啦,Fin = 12.0MHz, UCLK = 48MHz str r1,[r0] nop ; Caution: After UPLL setting, at least 7-clocks delay must be inserted for setting hardware be completed. nop nop nop nop nop nop ldr r0,=MPLLCON//对MPLL进行配置 ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin = 12.0MHz, FCLK = 400MHz str r1,[r0] ] ldr r1,=G
2 ldr r0,[r1] tst r0,#0x2 判断是否是从休眠模式唤醒的,对GSTATUS2[2]的检测就可以判断出是否从休眠模式唤醒的。 bne WAKEUP_SLEEP//如果是的话就跳转。 EXPORT StartPointAfterSleepWakeUp//定义一个外部的StartPointAfterSleepWakeUp StartPointAfterSleepWakeUp adrl r0, SMRDATA ldr r1,=BWSCON add r2, r0, #52 0 ldr r3, [r0], #4 str r3, [r1], #4 cmp r2, r0 bne %B0 这段代码的作用就是设置存储控制器。在代码的后面有一个SMRDATA的数据区,用r0来定义它的起始地址,用r2来定义它的结束地址。r3是代表那13个存储控制器.代码很明显,就是把内存的数据赋给这13个存储控制器里面的。 ldr r0,=GPFCON ldr r1,=0x0 str r1,[r0]//对GPF设置为输入的功能 ldr r0,=GPFUP ldr r1,=0xff str r1,[r0]//禁止上拉电阻 ldr r1,=GPFDAT ldr r0,[r1] bic r0,r0,#(0x1e<<1)//bic是r0与#(0x1e<<1)的反码按位相与。 tst r0,#0x1//这里就是测试最后一位是否为0,为0时说明是有按键按下了。 bne %F1//当按键0没有被按下的时候,就跳转啦。 这段代码是检测EINT0是否被按下了。 ldr r0,=GPFCON ldr r1,=0x55aa str r1,[r0]//GPF7~GPF4设置为输出,GPF3~GPF0设置为EINT0~EINT3 ldr r0,=GPFDAT ldr r1,=0x0 str r1,[r0] //很明显,GPF7~GPF4设置为LED灯的控制,低电平全部亮了。起到指示的用途。 mov r1,#0 mov r2,#0 mov r3,#0 mov r4,#0 mov r5,#0 mov r6,#0 mov r7,#0 mov r8,#0 ldr r9,=0x4000000 ;64MB ldr r0,=0x30000000 0 stmia r0!,{r1-r8} subs r9,r9,#32 bne %B0 很明显可以看出,程序利用r1~r8这几个寄存器把0x30000000到0x34000000的内存全部清零了。 1 bl InitStacks//初始化堆栈 ldr r0, =BWSCON ldr r0, [r0] ands r0, r0, #6//OM[1:0] != 0, 从NOR FLash或者内存启动,不用读取NAND FL
bne copy_proc_beg//不需要从NAND FLASH启动就在这里跳转啦 adr r0, ResetEntry//OM[1:0] == 0,就从NAND FLash启动 cmp r0, #0//在进行比较,是否入口地址是在0处,如果不是则是用仿真器 bne copy_proc_beg//仿真器也不需要在NAND FLASH启动 nand_boot_beg [ {TRUE} bl RdNF2SDRAM ] ldr pc, =copy_proc_beg 我们来看下RdNF2SDRAM具体是怎么工作的,这段代码的作用就是把NAND的程序读到RAM里面。 void RdNF2SDRAM( ) { U32 i; U32 start_addr = 0x0; u