第三课主要是分析启动代码和中断处理过程
之前有分析过44b0下的这个启动代码,差别不是非常大,今天再重新看了一遍。启动代码与Bootloader不同,主要是指进入C语言之前的汇编代码,网上都称为是bootloader的stage1,一般通用的内容包括: 1. 定义程序入口点 2. 设置异常向量表 3. 初始化存储系统 4. 初始化用户程序的执行环境 5. 初始化堆栈指针寄存器,改变处理器的模式 6. 设置FIQ/IRQ中断处理程序入口 7. 进入C程序
1)编译器选择 GBLL THUMBCODE [ {CONFIG} = 16 THUMBCODE SETL {TRUE} CODE32 | THUMBCODE SETL {FALSE} ] 因为处理器分为16位 32位两种工作状态,程序的编译器也是分16位和32两种编译方式,所以这段程序用于根据处理器工作状态确定编译器编译方式,程序不难理解,主要解释一下符号“[ | ]”的意思,上面的程序是指: if({CONFIG} = 16 ) { THUMBCODE SETL {TRUE} CODE32 } else THUMBCODE SETL {FALSE} 还是没有不明白CONFIG怎么区分是16位还是32位?哪里决定它的取值?应该是编译器指定的这个值。
2)宏定义 MACRO $HandlerLabel HANDLER $HandleLabel $HandlerLabel sub sp,sp,#4 stmfd sp!,{r0} ldr r0,=$HandleLabel ldr r0,[r0] str r0,[sp,#4] ldmfd sp!,{r0,pc} MEND $HandlerLabel是宏的地址标号,HANDLER是宏名,$HandleLabel是宏的参数,$标号在宏指令展开时,标号会替换为用户定义的符号。在此后,所有遇到$HandlerLabel HANDLER $HandleLabel这种形式的表达式都会被展开成$HandlerLabel到MEND之间的函数。 例如:ADC_IRQ HANDLER HandleADC即代表如下函数,语句ldr pc,=ADC_IRQ的作用也就是跳转到这个函数: ADC_IRQ sub sp,sp,#4 stmfd sp!,{r0} ldr r0,=HandleADC ldr r0,[r0] str r0,[sp,#4] ldmfd sp!,{r0,pc} 这段程序用于把ADC中断服务程序的首地址装载到pc中,跳转到中断处理函数,称之为“加载程序”。HandleADC是一个地址标号,它的内容就是ADC中断服务程序的地址标号,即文件最后的那个表HandleADC # 4所示,将HandleADC # 4中的4换成中断服务程序的地址标号即是,程序在这里定义了一个数据区,存放各种中断服务程序的首地址。每个字空间都有一个标号,以Handle***命名。
3)寄存器及堆栈设置 按照上面的顺序,可以比较容易读懂启动代码的作用,主要就是通过设置特殊功能寄存器来达到对系统参数的设定。依次禁止看门狗,中断,设定时钟控制寄存器,存储器控制寄存器等等。 由于各个工作模式下的堆栈指针是相互独立的,所以要分别进入各个模式下设置其堆栈指针,基本上都差不多,比如未定义指令模式下的设置: mrs r0,cpsr bic r0,r0,#MODEMASK orr r1,r0,#UNDEFMODE|NOINT msr cpsr_cxsf,r1 ldr sp,=UndefStack UnderStack是在程序后面用UnderStack # 256建立的一个堆栈空间的首地址,这部分空间建立在RAM中,256字节空间的堆栈大小。
4)初始化用户程序的执行环境 之前在44B0里的启动代码里还有包括把ROM里的程序拷贝到RAM中并跳转到RAM运行程序,也就是把加载状态下的程序按照编译连接时的设置重新排布成运行时的程序状态,以达到符号能够正确连接的目的,这里是涉及到了所谓的映像文件,但是2410这里没有这一段,即程序的加载态就是它的运行态,所以要求烧写程序时必须要把它烧写在设置的RO地址上,否则程序将不能正确执行。下面这段程序实现RW数据初始化,只是把数据段复制到高地址,如果没有设置RW的话这段代码也不会执行。 ;Copy and paste RW data/zero initialized data ldr r0, =|Image$$RO$$Limit| ; Get pointer to ROM data ldr r1, =|Image$$RW$$Base| ; and RAM copy ldr r3, =|Image$$ZI$$Base| ;Zero init base => top of initialised data cmp r0, r1 ; Check that they are different beq %F2 1 cmp r1, r3 ldrcc r2, [r0], #4 strcc r2, [r1], #4 bcc %B1 2 ldr r1, =|Image$$ZI$$Limit| mov r2, #0 3 cmp r3, r1 ; Zero init strcc r2, [r3], #4 bcc %B3 b %F1(B1)的意思是在临近的地址标号跳转,F是向后寻找,B是向前寻找。
5)说说映象文件 用ADS编译产生的映像文件有.axf、.bin、.hex等等格式,就拿要直接烧进Flash里的.bin文件来说,在其他书上看到有这么句话“映像文件一般由域组成,域由最多三个输出段(RO,RW,ZI)组成,输出段又由输入段组成。” 对于这段话,前两句是比较好理解的,域就是整个映像文件,对于大部分程序来说就只有一个域,也就是烧进Flash里的那部分东东,称作加载域;输出段就是用AREA定义的RO,Rw,一般就这两个,拿前面的bootloader来说,整体框架是这样的: AREA Init,CODE,READONLY ;<--RO段 ENTRY Entry ;<--CODE部分
… …
SMRDATA DATA ;<--DATA部分
… …
AREA RamData, DATA, READWRITE ;<--RW段
… …
然而对于输出段又由输入段组成却着实糊涂了好一阵,输入段是指源程序的代码(CODE)部分和数据(DATA)部分。上面这个框架中,在RO输出段的Entry下开始一系列的汇编指令操作,这个应该是CODE输入段,而SMRDATA DATA引领DCD用于开辟一片数据存储空间,这部分应该是DATA输入段,它与RW里的数据不同之处在于这部分数据不能被修改。 在ADS编译前在ARM-Linker里的Ro_Base和Rw_Base两个地址值,就是指两个输出段的起始地址,即程序是按照你设置的这种方式排布在内存中的,各个地址标号根据这两个值确定。然而,用Ultraedit打开bin文件却发现其实Rw是跟在Ro后面的,也就是说,这两个段并没有按照你设置的地址起始,由此引出映像文件的加载域和运行时域两个概念。 加载域就是用Ultraedit打开看到的程序最原始的状态,而运行时域则是程序在执行时按照你设定的方式排布的状态,显然,上面设置的两个地址是针对运行时域来设置的,程序要满足上面的设置才能正确连接。也就是程序开始阶段(加载域状态)是不能正确连接的,不过开始时不需要用到Rw里的数据,程序是可以运行的,因此必须在需要用到Rw数据之前把它拷贝到上面设置的位置上,这就是bootloader里初始化用户程序的执行环境部分的作用,把数据移动到正确的位置! 拷贝完Rw里的数据之后,所有的符号都可以正确连接,这时跳转到main函数里去执行程序就可以了。2410的这段启动代码没有进行Ro的拷贝,所以如果你把程序烧在0x0地址,那么Ro就必须设置成0x0,如果你设置成0x30000000,那么Ro就必须设置成0x30000000,如果Rw不设置,它将默认跟在Ro后面,否则就执行上面的搬迁代码,挪到正确的位置上。由于本系统是采用NandFlash启动的,最初的启动代码必须要在0x0处的SRAM里执行,所以,如果要把这段启动代码当作NandFlash的启动代码的话,Ro就必须设成0x0。 |
|