功能:作为C程序启动代码,配置内存、中断向量、栈,初始化C语言环境变量
大概流程:
1.定义处理器模式及各模式的堆栈指针常量用于初始化堆栈sp(R13)
2.设置处理器模式
3.宏定义MOV_PC_LR,作用:子程序返回
4.第一次查表实现中断向量的重定向
5.代码入口
6.大小端判断与转换
7.中断的第二次查表
8.禁止看门狗,屏蔽所有中断
9.设置系统时钟频率
10.初始化内存控制器
;================================================================ ; NAME: 2440INIT.S ; DESC: C start up codes ; Configure memory, ISR ,stacks ; Initialize C-variables ; HISTORY: ; 2002.02.25:kwtark: ver 0.0 ; 2002.03.20:purnnamu: Add some functions for testing STOP,Sleep mode ; 2003.03.14:DonGo: Modified for 2440. ; 2009 06.24:Tinko Modified ;================================================================ ;================================================================ ;GET和INCLUDE的功能是相同的,功能都是引进一些编译过的文件 ;汇编不能使用include包含头文件,所以用Get ;汇编也不认识*.h 文件,所有只能用*.inc ;================================================================ GET option.inc ;定义芯片相关的配置,包含选项说明,如pll等 GET memcfg.inc ;定义存储器配置,包含MMU配置说明 GET 2440addr.inc ;定义了寄存器符号,包含各寄存器地址说明 ;================================================================ ;定义SDRAM工作在Reflesh模式下,SDRAM有两种刷新模式:selfreflesh,autoreflesh。 ;后者是在其使用过程当中设置的。 ;REFRESH寄存器[22]bit : 0- auto refresh; 1 - self refresh ; MMU自刷新 ; EQU相当于C语言中的#define ;================================================================ BIT_SELFREFRESH EQU (1<<22) ;用于节电模式中,SDRAM自动刷新 ;处理器模式常量: CPSR寄存器的后5位决定目前处理器模式 M[4:0] ;Pre-defined constants ;系统的工作模式设定 USERMODE EQU 0x10 ;用户模式 FIQMODE EQU 0x11 ;快速中断模式 IRQMODE EQU 0x12 ;中断模式 SVCMODE EQU 0x13 ;监管模式 ABORTMODE EQU 0x17 ;异常中断模式 UNDEFMODE EQU 0x1b ;未定义模式 MODEMASK EQU 0x1f ;模式掩码 NOINT EQU 0xc0 ;取消中断 ;================================================================ ;定义各模式的堆栈指针常量用于初始化堆栈sp(R13) ;用于下面对各个模式中堆栈的设置 ;如何确保堆栈不会溢出? ;在ADS环境下ARM堆栈为只能为递减堆栈(SP向低地址方向伸张) ;堆栈参考:http://apps.hi.baidu.com/share/detail/5538633 ;The location of stacks ;定义处理器各模式下堆栈地址常量 ;在option.inc中定义了_STACK_BASEADDRESS EQU 0x33ff8000 ;================================================================ UserStack EQU (_STACK_BASEADDRESS-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 ~ ;================================================================ ;检查在tasm.exe里是否设置了采用THUMB(16位)代码(armasm -16 ...@ADS 1.0) ;判断是不是thumb指令。 ;Check if tasm.exe(armasm -16 ...@ADS 1.0) is used. ;{CONFIG}编译器内建变量具体参考手册 ;================================================================ GBLL THUMBCODE ;定义THUMBCODE全局变量(逻辑型) [ {CONFIG} = 16 ;如果发现是用16位代码的话 THUMBCODE SETL {TRUE} ;把THUMBCODE设置为TURE CODE32 | ;否则是ARM模式 THUMBCODE SETL {FALSE} ] ;宏定义MOV_PC_LR,作用:子程序返回 ;寄存器R14又被称为连接寄存器(LR),在ARM中有下面两种特殊用途。 ;每一种处理器模式在自己的物理R14中存放当前子程序的返回地址。 ;当通过BL或者BLX指令调用子程序时,R14被设置成该子程序的返回地址。 ;在子程序中,当把R14的值复制到程序计数器PC中时,就实现了子程序返回 ;详见http://blog.csdn.net/liufei_learning/archive/2010/08/22/5830374.aspx MACRO ;宏定义 MOV_PC_LR [ THUMBCODE ;在目标地址是THUMB指令,在ARM模式中 bx lr ;要用BX指令转THUMB 使跳到THUMB指令,并转换模式,BX会根据PC的最后一位判断是否进入thumb模式 | mov pc,lr ;否则,就是目标地址是ARM模式,就直接把函数返回地址赋给PC ] MEND ;宏定义MOVEQ_PC_LR,作用:带相等条件判断的子程序返回 。与宏定义 ;MOV_PC_LR类似 MACRO MOVEQ_PC_LR [ THUMBCODE bxeq lr | moveq pc,lr ] MEND ;宏定义结束 ;=============================================================== ;下面这个宏是用于第一次查表过程的实现中断向量的重定向,你会发现 ;在_ISR_STARTADDRESS=0x33FF_FF00里定义的第一级中断向量表 ;是采用型如Handle***的方式的. 而在程序的ENTRY处(程序开始处)采用的是 ;b Handler***的方式. ;在这里Handler***就是通过HANDLER这个宏和Handle***进立联系的. ;这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处 ;的ROM(FLASH)空间里, 这样,我们就可以在程序里灵活的改动向量的数据了. ;其中HANDLER是一个宏,用于查找中断处理程序的入口地址。这些地址存放在 ;由HandleXXX指向的表项中,该表定位在RAM高端,基地址为_ISR_STARTADDRESS。 ;假如_ISR_STARTADDRESS为 0x800000000,当IRQ中断时,根据b HandlerFIQ,先跳转 ;再根据^ _ISR_STARTADDRESS基地址+HandleIRQ 的偏移地址(4*6)得到的中断地址 ;0x80000000+0x00000024=0x80000024 ;========================================================================================== ;=============================================================================================================== ;注意下面这段程序是个宏定义 ;下面包含的HandlerXXX HANDLER HandleXXX将都被下面这段程序展开 ;这段程序用于把中断服务程序的首地址装载到pc中,有人称之为“加载程序”。 ;本初始化程序定义了一个数据区(在文件最后),34个字空间,存放相应中断服务程序的首地址。每个字,即4字节 ;空间都有一个标号,以Handle***命名。 ;在向量中断模式下使用“加载程序”来执行中断服务程序。 ;这里就必须讲一下向量中断模式和非向量中断模式的概念 ;向量中断模式是当cpu读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的指令取代0x18处的指令, ;通过跳转指令系统就直接跳转到对应地址 ;函数中 节省了中断处理时间提高了中断处理速度标 例如ADC中断的向量地址为0xC0,则在0xC0处放如下代码: ;ldr PC,=HandlerADC 当ADC中断产生的时候系统会自动跳转到HandlerADC函数中 ;非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将interrupt pending寄存器中对应标志位置位 ;然后跳转到位于0x18处的统一中断;函数中 ;该函数通过读取interrupt pending寄存器中对应标志位来判断中断源,并根据优先级关系再跳到;对应中断源的处理代码中 ;大致作用是把宏的第一个参数$HandlerLabel转变为一个标号,然后让程序跳转到第二个参数$HandleLabel(为一个地址) ;对应的值的地址去。 ;================================================================================================================== ;SP 堆栈指针,指向当前堆栈地址 ;MACRO ;下面一句一句分析一下,为了便于分析,假设sp = 0x33ff8000,$HandleLabel = 0x33ffff00, ;[0x33ffff00]=0x10000000,r0 =0x56001234: ;$HandlerLabel HANDLER $HandleLabel ;宏的名字叫HANDLER ,有两个参数 ;$HandlerLabel 定义一个标号 ; sub sp,sp,#4 ;decrement sp(to store jump address) ;把栈顶指针减4,留出一个字的空间(用于保存跳转地址的值),sp=0x33ff7ffc ; stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original address) ;首先把sp减4 (sp=0x33ff7ff8),然后把将要使用的r0寄存器入栈,此时[0x33ff7ff8]=0x56001234 ; ldr r0,=$HandleLabel;load the address of HandleXXX to r0 ;给寄存器r0赋值,r0=0x33ffff00 ; ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX ;给寄存器r0赋值,r0=0x10000000 ; str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack ;把寄存器r0保存到0x33ff7ffc (0x33ff7ff8+4),sp没有改变sp=0x33ff7ff8,如果 ;str r0,[sp,#4]!sp 改变 ;此时 ;[0x33ff7ffc] = 0x10000000 ; ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR) ;把栈顶的两个字弹出,分别保存到r0、pc,此时sp=0x33ff8000,r0=0x56001234,pc=0x10000000, ;通过比较不难发现,sp和r0在执行前后都没有变化,程序就跳转到0x10000000处执行 ; MEND ;所以,通过上面的分析可以看出,$HandlerLabel HANDLER $HandleLabel是让PC跳转到$HandleLabel中存放的地址执行。 ;================================================================================================================== MACRO $HandlerLabel HANDLER $HandleLabel $HandlerLabel sub sp,sp,#4 ;decrement sp(to store jump address) ;减少sp(用于存放转跳地址)实质上是在计算返回地址 stmfd sp!,{r0} ;PUSH the work register to stack(lr does not push because it return to original address) ldr r0,=$HandleLabel;load the address of HandleXXX to r0 ;将HandleXXX的址址放入r0 ldr r0,[r0] ;load the contents(service routine start address) of HandleXXX ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0 str r0,[sp,#4] ;store the contents(ISR) of HandleXXX to stack ;把中断服务程序(ISR)压入栈. ldmfd sp!,{r0,pc} ;POP the work register and pc(jump to ISR) ;用出栈的方式恢复r0的原值和为pc设定新值(完成了到ISR的转跳) MEND ;========================================================================================== ;一个ARM由RO,RW,ZI三个段组成,其中RO(只读)为代码段,RW(读写)是已经初始化的全局变量, ;ZI是未初始化的全局变量 ;对应于GNU工具的概念是TEXT,DATA,BSS ;bootloader要将RW段复制到RAM中,并将ZI段清零 编译器使用下列方式来记录各段的起始和结束地址 ;在这里用IMPORT伪指令(和c语言的extren一样)引入|Image$RO$Base|, ;|Image$RO$Limit|...等比较古怪的变量是编译器生成的。 ;RO, RW, ZI这三个段都保存在Flash中,但RW,ZI在Flash中 ;的地址肯定不是程序运行时变量所存储的位置,因此我们的程序在初始化时应该 ;把Flash中的RW,ZI拷贝到RAM的对应位置。这些变量是通过ADS的工程设置里面 ;设定的RO Base和RW Base设定的, 最终由编译脚本和连接程序导入程序. ;实际上RW,ZI在Flash中的位置就紧接着RO存储。我们知道Image$RO$Base, ;Image$RO$Limit, 那么Image$RO$Limit就是RW(ROM data)的开始。 ;============================================================================================ IMPORT |Image$RO$Base| ; Base of ROM code ;ROM code(也就是代码)的开始地址 IMPORT |Image$RO$Limit| ; End of ROM code (=start of ROM data) ;ROM code的结束地址(=ROM data的开始地址) IMPORT |Image$RW$Base| ; Base of RAM to initialise ;RAM 的起始地址 IMPORT |Image$ZI$Base| ; Base and limit of area ;0初始化的起始地址 IMPORT |Image$ZI$Limit| ; to zero initialise ;0初始化的结束地址 ;=============================================================== ;在这里用IMPORT伪指令(和c语言的extren一样)引入外部变量MMU的快速总线 ;模式和异步总线模式两个变量 ;=============================================================== IMPORT MMU_SetAsyncBusMode ;MMU的快速总线模式 IMPORT MMU_SetFastBusMode ;异步总线模式 ;这里引入一些在其它文件中实现在函数,包括为我们所熟知的main函数 IMPORT Main ; The main entry of mon program IMPORT RdNF2SDRAM ; Copy Image from Nand Flash to SDRAM ;把镜像从Nandflash拷贝到SDRAM的函数 ;============================================================================================== ;初始化程序中必须指明入口地址,因为处理器复位(仿真时,装载image)后PC 要找到入口开始执行代码, ;当各种异常或是中断产生的时候也要找到各个异常的入口开始执行代码。 ;从这里开始就是真正的代码入口了! ;AREA 伪指令用于定义一个代码段或数据段.ARM 汇编程序设计采用分段式设计,一 ;个 ARM 源程序至少需要一个代码段,大的程序可以包含多少个代码段及数据段. ;CODE 为定义代码段.默认属性为 READONLY; ;READONLY 指定本段为只读,代码段的默认属性为READONLY; ;板子上电和复位后程序开始从位于0x0处开始执行,硬件刚刚上电复位后程序从这里开始执行跳转到标为ResetHandler处执行 ;DCD用于分配一段字内存单片,并用后面的伪指令初始化,分配字节由expr 个数决定 ;============================================================================================ AREA Init,CODE,READONLY ENTRY ;定义程序的入口(调试用) ;ENTRY只是定义一个普通的入口点,且在程序中可以多处定义,如果要使用它作为整 ;个映像文件的唯一入口点,还需要设置链接器中的相关选项。 EXPORT __ENTRY ;导出__ENTRY,即导出代码段入口地址 __ENTRY ResetEntry ;复位后入口 ;1)The code, which converts to Big-endian, should be in little endian code. ;2)The following little endian code will be compiled in Big-Endian mode. ; The code byte order should be changed as the memory bus width. ;3)The pseudo instruction,DCD can not be used here because the linker generates error. ;*********************************************************** ;1、ASSERT :DEF:ENDIAN_CHANGE ;ASSERT 是断言伪指令,语法是:ASSERT +逻辑表达式 ;def 是逻辑伪操作符,格式为: :DEF:label,作用是:判断label是否定义过 ;2、下面的四句指令能且只能执行一句,并且前三句若执行跳转后处理程序的最后一句也是 ;b ResetHandler ;3、" [ " 相当于 if ;" | "相当于else ;" ] " 相当于endif ;*************************************************************** ;================================================================================================================= ;在0x0处的异常中断是复位异常中断,是上电后执行的第一条指令 ;变量ENDIAN_CHANGE用于标记是否要从小端模式改变为大端模式,因为编译器初始模式是小端模式, ;如果要用大端模式,就要事先把该变量设置为TRUE,否则为FLASE ;变量ENTRY_BUS_WIDTH用于设置总线的宽度,因为用16位和8位宽度来表示32位数据时,在大端模式下,数据的含义是不同的 ;由于要考虑到大端和小端模式,以及总线的宽度,因此该处看似较复杂,其实只是一条跳转指令: ;当为大端模式时,跳转到ChangeBigEndian函数处,否则跳转到ResetHandler函数处 ;================================================================================================================= ASSERT :DEF:ENDIAN_CHANGE ;判断是否定义了ENDIAN_CHANGE,如果没有定义,则报告该处错误信息 [ ENDIAN_CHANGE ;if ENDIAN_CHANGE ==TRUE ASSERT :DEF:ENTRY_BUS_WIDTH ;判断是否定义了ENTRY_BUS_WIDTH,如果没有定义,则报告该处错误信息 [ ENTRY_BUS_WIDTH=32 if ENTRY_BUS_WIDTH ==32 ;跳转到ChangeBigEndian(ChangeBigEndian在0x24),因此该条指令的机器码为0xea000007 ;所以该语句与在该处(即0x0处)直接放入0xea000007数据(即DCD 0xea000007)作用相同 b ChangeBigEndian ;DCD 0xea000007 ] ;跳转到ChangeBigEndian,执行DCD 0xea000007 改变大小端数据模式 [ ENTRY_BUS_WIDTH=16 ;if ENTRY_BUS_WIDTH ==16 ;在小端模式下,用16位或8位数据总线宽度表示32位数据,与用32位总线宽度表示32位数据,格式完全一致。 ;但在大端模式下,格式就会发生变化 ;在复位时,系统默认的是小端模式,所以就要人为地改变数据格式,使得用16位大端数据表示的32位数据 ;也能被小端模式的系统识别 ;该语句的目的也是跳转到ChangeBigEndian,即机器码也应该是0xea000007,但为了让小端模式系统识别, ;就要把机器码的顺序做一下调整,改为0x0007ea00,那么我们就可以用DCD 0x0007ea00把机器码装载进去了 ;,但由于该处不能使用DCD伪指令,因此我们就要用一条真实的指令来代替DCD 0x0007ea00, ;即该指令编译后的机器码也为0x0007ea00,而andeq r14,r7,r0,lsl #20就是一条编译后机器码 ;为0x0007ea00的指令,所以我们在该处写上该条指令总线的宽度,因此该处看似较复杂,其实只是 ;一条跳转指令:当为大端模式时,跳转到ChangeBigEndian函数处,否则跳转到ResetHandler函数处 andeq r14,r7,r0,lsl #20 ;DCD 0x0007ea00 ] ;当标志状态寄存器CPSR的Z位=1时,r14=r7+r0逻辑左移20位,执行DCD 0x0007ea00改变大小端模式 [ ENTRY_BUS_WIDTH=8 ;if ENTRY_BUS_WIDTH ==8 ;该语句的分析与上一段代码的分析相似; ;streq r0,[r0,-r10,ror #1]编译后的机器码为0x070000ea streq r0,[r0,-r10,ror #1] ;DCD 0x070000ea ] ; 当标志状态寄存器CPSR的Z位=1时…,执行DCD 0x070000ea 改变大小端模式 | ;ELSE 即如果没定义ENDIAN_CHANGE b ResetHandler ;复位异常,开发板上电或复位时进入0x00 ] b HandlerUndef ;handler for Undefined mode 未定义异常,遇到无法识别的指令时0x04 b HandlerSWI ;handler for SWI interrupt 软中断异常0x08 b HandlerPabort ;handler for PAbort 指令预取错误时进入0x0c 处理终止程序访问终止模式 b HandlerDabort ;handler for DAbort 数据访问不能完成时进入0x10 处理数据访问终止模式 b . ;reserved ; 保留 0x14 "."代表指令的地址 ,即表示进行死循环 b HandlerIRQ ;handler for IRQ interrupt 发生IRQ 中断时进入0x18 b HandlerFIQ ;handler for FIQ interrupt 发生FIQ 中断时进入0x1c ;@0x20 "@" 存储区位置计数器的当前值 b EnterPWDN ; Must be @0x20. ;=============================================================== ;DCD用于分配一段字内存单元,并用伪指令中的expr初始化.DCD伪指令分配的内存 ;需要字对齐,一般可用来定义数据表格或其它常数.&与DCD 同义. ;下面是改变大小端的程序,这里采用直接定义机器码的方式,至说为什么这么做 ;就得问三星了反正我们程序里这段代码也不会去执行,不用去管它 ;=============================================================== ;由0x0跳转至此,目的是把小端模式改为大端模式,即把CP15中的寄存器C1中的第7位置1 ChangeBigEndian ;改变大小端数据模式 ;@0x24 [ ENTRY_BUS_WIDTH=32 ;if ENTRY_BUS_WIDTH == 32 ;执行mrc p15,0,r0,c1,c0,0,得到CP15中的寄存器C1,放入r0中 ;由于mrc p15,0,r0,c1,c0,0的机器码为0xee110f10 ;因此DCD 0xee110f10的意思就是mrc p15,0,r0,c1,c0,0。下同 DCD 0xee110f10 ;0xee110f10 => mrc p15,0,r0,c1,c0,0 ;执行orr r0,r0,#0x80,置r0中的第7位为1,表示选择大端模式 DCD 0xe3800080 ;0xe3800080 => orr r0,r0,#0x80; //Big-endian ;执行mcr p15,0,r0,c1,c0,0,把r0写入CP15中的寄存器C1 DCD 0xee010f10 ;0xee010f10 => mcr p15,0,r0,c1,c0,0 ] [ ENTRY_BUS_WIDTH=16 ;if ENTRY_BUS_WIDTH == 16 ;由于此时系统还不能识别16位或8位大端模式下表示的32为数据 ;因此还需人为地进行数据调整,即把0xee110f10变为0x0f10ee11 ;然后用DCD指令存入该数据。下同 DCD 0x0f10ee11 DCD 0x0080e380 ;DCD伪指令定义一个32位存储单元,并初始化 DCD 0x0f10ee01 ] [ ENTRY_BUS_WIDTH=8 ;if ENTRY_BUS_WIDTH == 8 DCD 0x100f11ee DCD 0x800080e3 DCD 0x100f01ee ] ;相当于NOP指令 ;作用是等待系统从小端模式向大端模式转换 ;此后系统就能够自动识别出不同总线宽度下的大端模式,因此以后就无需再人为调整指令了 DCD 0xffffffff ;swinv 0xffffff is similar with NOP and run well in both endian mode. DCD 0xffffffff DCD 0xffffffff DCD 0xffffffff DCD 0xffffffff b ResetHandler ;当系统进入异常中断后,由存放在0x0~0x1C处的中断向量地址中的跳转指令, ;跳转到此处相应的位置,并由事先定义好的宏定义再次跳转到相应的中断服务程序中 ;如第70行所说,这里采用HANDLER宏去建立Hander***和Handle***之间的联系 HandlerFIQ HANDLER HandleFIQ HandlerIRQ HANDLER HandleIRQ HandlerUndef HANDLER HandleUndef HandlerSWI HANDLER HandleSWI HandlerDabort HANDLER HandleDabort HandlerPabort HANDLER HandlePabort ;========================================================================================== ;这一段程序就是用来进行第二次查表的过程了.如果说第一次查表是由硬件来 ;完成的,那这一次查表就是由软件来实现的了. 为什么要查两次表?? ;没有办法,ARM把所有的中断都归纳成一个IRQ中断异常和一个FIRQ中断异常,第一 ;次查表主要是查出是什么异常,可我们总要知道是这个中断异常中的什么中断呀! ;没办法了,再查一次表呗! ;========================================================================================== ;下面这段代码是用于处理非向量中断,即由软件程序来判断到底发生了哪种中断, ;然后跳转到相应地中断服务程序中 ;具体地说就是,当发生中断时,会置INTOFFSET寄存器相应的位为1,然后通过查表 ;(见该程序末端部分的中断向量表),找到相对应的中断入口地址 ;观察中断向量表,会发现它与INTOFFSET寄存器中的中断源正好相对应,即向量表的 ;顺序与INTOFFSET寄存器中的中断源的由小到大的顺序一致,因此我们可以用基址加 ;变址的方式很容易找到相对应的中断入口地址。其中基址为向量表的首个中断源地址, ;变址为INTOFFSET寄存器的值乘以4(因为系统是用4个字节单元来存放一个中断向量 ;========================================================================================== IsrIRQ sub sp,sp,#4 ; 给PC寄存器保留;reserved for PC stmfd sp!,{r8-r9} ; 把r8-r9压入栈 ;INTOFFSET的值在2440addr.inc中定义为0x4a000014 ldr r9,=INTOFFSET ; 把中断偏移INTOFFSET的地址装入r9 ldr r9,[r9] ; 把中断偏移INTOFFSET的值装入r9 ldr r8,=HandleEINT0 ; 这就是向量表的入口HandleEINT0装入r8 add r8,r8,r9,lsl #2 ;R8=R8+(R9<<2) ldr r8,[r8] ; 装入中断服务程序的入口 str r8,[sp,#8] ;把入口压入堆栈 ldmfd sp!,{r8-r9,pc} ;出栈 ;LTORG用于声明一个文字池,在使用LDR伪指令时,要在适当的地址加入LTORG声明 ;文字池,这样就会把要加载的数据保存在文字池内,再用 ARM 的加载指令读出数据.(若 ;没有使用 LTORG 声明文字池,则汇编器会在程序末尾自动声明.) ;定义一个数据缓冲池,供ldr伪指令使用 LTORG ;声明文字池 ;======= ; ENTRY ;1.禁止看门狗 屏蔽所有中断 ;======= ResetHandler ldr r0,=WTCON ;watch dog disable ;关闭看门狗 ldr r1,=0x0 str r1,[r0] ;r1->[r0] 禁止看门狗 ;WTCON 为看门狗控制寄存器,此处将其写入0x0,就是禁止它的所有功能,包括定时器定时,溢出中断及溢出复位。 ;INTMSK 为中断屏蔽寄存器,写入0xffffffff,就是禁止所有的中断产生,因为中断 ;向量表还未初始化,如果此时产生中断会使程序进入未知的状态而跑飞。因为外设的中断太 ;多,INTMSK 不够用,还需要INTSUBMSK 来将剩余的中断源也禁止掉。 ldr r0,=INTMSK ldr r1,=0xffffffff ;all interrupt disable str r1,[r0] ldr r0,=INTSUBMSK ldr r1,=0x7fff ;all sub interrupt disable str r1,[r0] ;由于启动文件是无法仿真的,因此为了判断该文件中语句的正确与否,往往在需要观察的地方加上一段点亮LED的程序, ;这样就可以知道程序是否已经执行到此处 ;下面方括号内的程序就是点亮LED的小程序 [ {FALSE} ;亮灯用的,可以用来调试用 ; GPBDAT = (rGPFDAT & ~(0xf<<4)) | ((~data & 0xf)<<4); ; Led_Display ldr r0,=GPBCON ldr r1,=0x00555555 str r1,[r0] ldr r0,=GPBDAT ldr r1,=0x07fe str r1,[r0] ] ;================================================================================================================== ;下列程序是用于设置系统时钟频率 ;设置PLL的锁定时间常数,以得到一定时间的延时 ;2.根据工作频率设置pll ;这里介绍一下计算公式 ;Fpllo=(m*Fin)/(p*2^s) ;m=MDIV+8,p=PDIV+2,s=SDIV ;Fpllo必须大于20Mhz小于66Mhz ;Fpllo*2^s必须小于170Mhz ;如下面的PLLCON设定中的M_DIV P_DIV S_DIV是取自option.h中 ;#elif (MCLK==40000000) ;#define PLL_M (0x48) ;#define PLL_P (0x3) ;#define PLL_S (0x2) ;所以m=MDIV+8=80,p=PDIV+2=5,s=SDIV=2 ;硬件使用晶振为10Mhz,即Fin=10Mhz ;Fpllo=80*10/5*2^2=40Mhz ;To reduce PLL lock time, adjust the LOCKTIME register. ;================================================================================================================== ;LOCKTIME 为PLL 锁定时间计数寄存器,重新设定分频值时,PLL 进入锁定,输出稳 ;定频率的时钟需要一定的时间。这里设置成默认的值,以满足锁定的要求。 ldr r0,=LOCKTIME ;设置pll锁定时间 ldr r1,=0xffffff ;Pll锁定寄存器S3C2410默认为0XFFFFFF str r1,[r0] ;S3C2440默认为0XFFFFFFFF [ PLL_ON_START ;在option.inc中定义,初始化为真 ; Added for confirm clock divide. for 2440. ; Setting value Fclk:Hclk:Pclk ldr r0,=CLKDIVN ;用于设定FCLK,HCLK和PCLK的比例 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.,在option.inc中有定义 str r1,[r0] ; MMU_SetAsyncBusMode and MMU_SetFastBusMode over 4K, so do not call here ; call it after copy ; [ CLKDIV_VAL>1 ; means Fclk:Hclk is not 1:1. ; bl MMU_SetAsyncBusMode ; | ; bl MMU_SetFastBusMode ; default value. ; ] ;=============================================================== ;三星手册里提供的MMU_SetAsyncBusMode 和 MMU_SetFastBusMode 函数都在4K代码 ;以上(三星2440芯片就提供4K的内部SRAM),如果你想你编译出来的程序能在NAND ;上运行的话,就不能在这调用这两函数了.如果你不要求的话,你就可以直接调用. ;下面的代码就是实现和上面两函数一样的功能. 利用的协处理器的命令实现了对 ;总线模式的设置 ;=============================================================== ;program has not been copied, so use these directly [ CLKDIV_VAL>1 ; means Fclk:Hclk is not 1:1. ;if 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 ] ;======================================================================================================== ;配置UPLL ;按照手册中的计算公式,确定MDIV、PDIV和SDIV ;得到当系统输入时钟频率为12MHz的情况下,UCLK输出频率为48MHz ;S3C2440有两个PLL(phase locked loop)一个是MPLL,一个是UPLL。 ;MPLL用于CPU及其他外围器件,UPLL用于USB。专门用于驱动USB host/Device。 ;并且驱动USB host/Device的频率必须为48MHz ;======================================================================================================== ldr r0,=UPLLCON ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV) ;Fin = 12.0MHz, UCLK = 48MHz str r1,[r0] ;等待至少7个时钟周期,以保证系统的正确配置 nop ; Caution: After UPLL setting, at least 7-clocks delay must be inserted for setting hardware be completed. nop nop nop nop nop nop ;Configure MPLL ldr r0,=MPLLCON ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin = 12.0MHz, FCLK = 400MHz str r1,[r0] ] ;======================================================================================= ;查看是否是由睡眠状态启动,如果是则跳转到WAKEUP_SLEEP状态 ;从SLEEP模式下被唤醒,类似于RESET引脚被触发,因此它也要从0x0处开始执行 ;在此处要判断是否是由SLEEP模式唤醒引起的复位 ;Check if the boot is caused by the wake-up from SLEEP mode. ldr r1,=GSTATUS2 ldr r0,[r1] tst r0,#0x2 ;是从休眠模式唤醒,跳转到SLEEP_WAKEUP handler. ;In case of the wake-up from SLEEP mode, go to SLEEP_WAKEUP handler. bne WAKEUP_SLEEP ;======================================================================================= ;初始化内存控制器其实就是对S3C2440 的memory bank 进行设置,使其扩展的存储器 ;或外部设备能够被处理器通过内存控制器正确读写。由于S3C2440 的最终应用程序是在 ;SDRAM(bank6)中运行,并与C 语言变量等的用户数据,各种模式的堆栈,中断向量表, ;都被定位在SDRAM 的空间,所以它必须在涉及这些处理之前完成初始化工作。 ;============================================================================================ ;设置存储器控制寄存器,此段代码把13个存储控制器的内容批量的读取到了对应的特殊功能寄存器中 ;首先是有一个数据区SMRDATA,在程序的后面有定义,这个数据区给13个寄存器分配52字节的地址空间。 ;在下面的代码中,r0是这个数据区的起始地址,r2是数据区的结束地址,r1是寄存器的起始地址。 ;这样,用一个判断语句 cmp r2, r0 ; bne %B0,就可以把内存中的数据赋给这13个存储控制寄存器了。 ;============================================================================================ ;设置一个被唤醒复位后的起始点地址标号,可以把它保存到GSTATUS3中 ;导出该地址标号,以便在C语言程序中使用 EXPORT StartPointAfterSleepWakeUp StartPointAfterSleepWakeUp ;设置内存控制寄存器 ;关于内存控制寄存器一共有以BWSCON为开始的连续放置的13个寄存器,我们要一次性批量完成这13个寄存器的配置 ;因此开辟一段以SMRDATA为地址起始点的13个字单元空间,按顺序放入要写入的13个寄存器内容 ;Set memory control registers ;adr装载相对地址(l后缀:long(大范围寻址)),ldr装载绝对地址 adrl r0,SMRDATA ;canot use ldr r0,=SMRDATA; 得到SMRDATA空间的首地址 起始位置 依次为BANKCON0 BANKCON1 BANKCON2 ........ ldr r1,=BWSCON ;BWSCON Address 在2440addr.inc中定义 ;得到BWSCON的地址 add r2, r0, #52 ;End address of SMRDATA ;得到SMRDATA空间的末地址 0 ldr r3, [r0], #4 ;读取R0地址的数据到R3 R0=R0+4 读取SMRDATA 数组(BWSCON BANKCON0 ....)的值 str r3, [r1], #4 ;读取R3的数据到R1地址内存中R1=R1+4 赋给BWSON BANKCON0 ....寄存器 cmp r2, r0 bne %B0 ;当不相等时跳到上面那个标号 0 , B表示backward(往后—上) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;; When EINT0 is pressed, Clear SDRAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; check if EIN0 button is pressed ;这一段检测EINT0是否被按下,假如EINT0被按下,则清空SDRAM ldr r0,=GPFCON ;加载地址,在2440addr.inc中定义 ldr r1,=0x0 str r1,[r0] ;置F(0:15)input ldr r0,=GPFUP ldr r1,=0xff str r1,[r0] ;不使用上拉电阻 ;配置GPF ldr r1,=GPFDAT ; GPFDAT=0xff ldr r0,[r1] bic r0,r0,#(0x1e<<1) ; bit clear ;仅保留第1位数据,其他清0 tst r0,#0x1 ;判断第1位 bne %F1 ;不为0表示按钮没有被按下,则向前跳转,不执行清空SDRAM ; Clear SDRAM Start ldr r0,=GPFCON ldr r1,=0x55aa str r1,[r0] ;GPF7~4为输出,GPF3~0为中断 ldr r0,=GPFDAT ldr r1,=0x0 str r1,[r0] ;控制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 ;64MB的SDRAM,0x30000000----0x34000000 ldr r0,=0x30000000 0 stmia r0!,{r1-r8} subs r9,r9,#32 bne %B0 ;Clear SDRAM End 1 ;Initialize stacks bl InitStacks ;初始化堆栈 ;(调试时在SDRAM中取指运行,否则在norflash中取指执行,或者nandflash启动模式下在4kb的SRAM中取指执行) ;====================================================================== ;这段程序能在nor nand flash 运行,也可以在内存中运行。在nor nand flash运行,需要拷贝数据; ;====================================================================== ;====================================================================== ;检测引脚OM[1:0],如果OM[1:0]!=0,选择从NOR Flash启动,如果OM[1:0]==0,则表示从nand flash启动 ldr r0, =BWSCON ldr r0, [r0] ands r0, r0, #6 ;OM[1:0] != 0, NOR FLash boot ;OM[1:0] != 0, NOR FLash 或者内存启动 bne copy_proc_beg ;do not read nand flash ;不用读取 nand flash; adr r0, ResetEntry ;OM[1:0] == 0, NAND FLash boot ;OM[1:0] == 0, NAND FLash 启动 cmp r0, #0 ;if use Multi-ice, ;再比较入口是否为0地址处,如果不是则用了仿真器 ;测试是不是处于调试模式 bne copy_proc_beg ;do not read nand flash for boot ;用仿真器的情况也不要用 nand flash启动 ;此时处于调试状态,程序在SDRAM中运行,不需要从nandflash中拷贝数据 ;nop ;不是处于调试模式,而是从板从nandflash启动 ;此时4KB的片上SRAM(0x0000_0000--0x0000_0fff)存储nandfllash的前4KB程序,所以此时是从SRAM中取指令执行 nand_boot_beg ;这一段代码完成从NAND读代码到RAM [ {TRUE} bl RdNF2SDRAM ;执行从nandflash拷贝数据子程序 ;此时nandflash代码已经被拷贝到了SDRAM中 ] ;nand flash启动,拷贝程序到steppingstone区域 ldr pc, =copy_proc_beg ;拷贝完成后进入SDRAM中执行程序(在运行域中执行代码),pc指向SDRAM中代码 ;=========================================================== ;若是在板从norflash启动,先把用来’拷贝装载域RO段到运行域的程序‘拷贝到SDRAM中, ;并在SDRAM中执行此程序,以便快速完成装载域RO段程序到运行域的拷贝 ;若是处于调试状态,或者nandfalsh启动模式的拷贝代码已经完成,则不执行’拷贝装载域RO段到运行域‘的拷 ;贝操作,直接跳到初始化ZI段的子程序处执行 copy_proc_beg ;若是norflash启动模式,r0指向的是装载域RO段起始地址,否则r0指向的是运行域的RO段起始地址 adr r0, ResetEntry ;装载地址,ResetEntry值->r0 ;r1指向运行域RO段起始地址 ;|Image$RO$Base| ldr r2, BaseOfROM ;BaseOfROM值(后面有定义)->r2 cmp r0, r2 ;比较RO,R2 ;如果相等的话(在内存运行 --- ice -- 无需复制code区中的ro段,但需要复制code区中的rw段),TopOfROM->r0 ldreq r0, TopOfROM ;如果相等的话(说明在内存中运行,或是从NOR Flash启动),TopOfROM->r0 beq InitRam ;同时跳到InitRam ;========================================================= ;下面这个是针对代码在NOR FLASH时的拷贝方法 ;功能为把从ResetEntry起,TopOfROM-BaseOfROM大小的数据拷到BaseOfROM ;TopOfROM和BaseOfROM为|Image$RO$Limit|和|Image$RO$Base| ;|Image$RO$Limit|和|Image$RO$Base|由连接器生成 ;为生成的代码的代码段运行时的起启和终止地址 ;BaseOfBSS和BaseOfZero为|Image$RW$Base|和|Image$ZI$Base| ;|Image$RW$Base|和|Image$ZI$Base|也是由连接器生成 ;两者之间就是初始化数据的存放地放 ; --在加载阶段,不存在ZI区域-- ;======================================================= ldr r3, TopOfROM 0 ;ldm批量数据加载指令,ldm后的ia表示每次传送后地址+1 ldmia r0!, {r4-r7} ;运行一次后R0=(R4到R7寄存器个数)*4 开始时,r0 = ResetEntry --- source stmia r2!, {r4-r7} ;stm批量数据存储指令,!的作用,当数据传输完毕,将最后的地址写入R2 开始时,r2 = BaseOfROM --- destination cmp r2, r3 ;终止条件:复制了TopOfROM-BaseOfROM大小 ; b跳转 条件:CC/LO C=0 无符号数小于 bcc %B0 ;C标志置位/大于或小于时向后跳转到0标号处 ;最终实现拷贝数据到ram ;修正非字对齐的情况 ; 下面2行目的是为了计算正确的r0(必须使之指向code区中的rw域开始处) sub r2, r2, r3 ;r2=BaseOfROM-TopOfROM=(-)代码长度 sub r0, r0, r2 ;r0=ResetEntry-(-)代码长度=ResetEntry+代码长度 InitRam ;复制代码加载位置中的RM区到|Image$RW$Base| ldr r2, BaseOfBSS ;装载数值RWBase ;BaseOfBSS->r2 , BaseOfBSS = |Image$RW$Base| ldr r3, BaseOfZero ;装载数值ZIBase ;BaseOfZero->r3 , BaseOfZero = |Image$ZI$Base| ;下面循环体为复制已初始化的全局变量 0 cmp r2, r3 ;如果R2