AT91系列微处理器启动过程的分析与实现
摘 要:本文讨论了ARM启动的基本过程,详细分析了其中的Remap过程和从加载时域到运行时域的转换这两个技术难点,给出了AT91系列微处理器启动过程的具体实现方法。
%A
%A 关键词:AT91;Remap;加载时域;运行时域;启动
%A
%A 引言
%A 在ARM程序设计中,启动代码的编写是一个及其重要的过程,由于ARM处理器的启动过程相对51单片机复杂,不仅要设置中断、初始化堆栈,还涉及到存储器的地址重映射以及重映射后中断地址的搬移、初始化主程序用到的存储区等问题,导致许多人对ARM处理器的初始化以及启动代码的编写感到较为困难。本文详细分析了At91系列微处理器的启动过程。
%A
%A
%A
%A 图1 Remap过程
%A
%A
%A
%A 图2 系统初始化过程
%A
%A 系统存储介质和地址映射
%A 开发板的Flash存储器采用ATMEL公司的AT49BV1614A,板上采用的SRAM是ISSI公司的IS61LV51216,AT91M55800A片内还集成了8K字节的片内SRAM。Flash存储器连接在AT91M55800A的片选0引脚上,SRAM连接在片选1引脚上。上电时,Flash映射地址为0x01000000,片内RAM映射地址为0x00300000,片外SRAM不可见。
%A 系统上电时,从地址0开始读取上电复位中断处理程序,进行系统初始化。此时,地址0x00000000指向的是连接在片选0的存储器器件,所以要在片选0上连接Flash。由于Flash的运行速度相对RAM要慢,且中断处理程序在Flash中无法改动,所以,人们希望程序在RAM中运行。为此,ARM的CPU提供了Remap命令来解决这个问题,当置位M55800A的重映射控制寄存器的重映射命令位(EBI_RCR的RCB位)后,内部RAM就映射到地址0,配置EBI寄存器后,Flash的地址是0x01000000,SRAM的地址是0x02000000。这就是系统的重映射过程。
%A
%A 启动过程的分析与实现
%A 一般而言,一个ARM的启动代码必须要完成以下部分的初始化:定义入口点、设置中断/异常向量、初始化存储系统(如果需要,进行代码的搬移)、初始化堆栈、初始化I/O设备、初始化中断中用到的变量、开中断、必要时改变处理器的模式和处理器的状态、初始化C程序中用到的存储区、引导处理器进入C程序。其中的难点在于内存系统的重映射和对C程序用到的存储区进行初始化。
%A (1)设置入口指针:启动程序首先必须定义入口指针,而且整个应用程序只有一个入口指针。
%A (2)设置中断向量:ARM7要求中断向量表必须设置在从0地址开始,连续8×4字节的空间,分别是复位、未定义指令错误、软件中断、预取指令错误、数据存取错误、IRQ、FIQ和一个保留的中断向量。由于Flash在系统Remap之前指向地址0,所以要在Flash中安排中断向量。此时,真正有用的只有位于地址0的复位向量,在此安排一条 B Initreset指令,转向引导程序,其余中断未用。接下来在Flash中构建一个新的中断向量表,用于在Remap之后复制到片内RAM中,采取相对寻址的方式。
%A 系统复位时,外部总线并没有被配置,此时片选0的总线等待周期是8,为了加速启动过程,可以在这时把片选0的总线等待周期设为与Flash ROM相应的数值。并且配置AT91M55800的PLL,使系统从慢晶振(32768Hz)转到32MHz。
%A 由于AT91M55800A的IRQ和FIQ中断是由先进中断控制器(AIC)管理的,所以,要对AIC进行配置。
%A 将刚才构建的中断向量表复制到位于0x300000的片内RAM中,这样在Remap后,重新映射到地址0的片内RAM中就有了中断向量表。
%A (3)进行Remap。
%A (4)初始化堆栈和寄存器:堆栈设置在AT91M55800A的片内RAM中,可以提高运行速度。系统堆栈初始化取决于用户使用了哪些中断,以及系统需要处理哪些错误类型。一般来说管理者堆栈必须设置,如果使用了IRQ中断,则IRQ堆栈也必须设置。进入相应的处理器模式,直接设置堆栈指针即可。
%A (5)改变处理器模式、状态:对于不带操作系统的用户程序,系统可以处在user模式下;对于mC/OS-II,系统应当工作在svc模式下,否则无法完成任务的切换。
%A (6)初始化C语言所需的存储器空间:拷贝程序到片外RAM中,完成加载时域到运行时域的转换,初始化RW和ZI段。
%A (7)呼叫C程序:ARM有16位THUMB和32位ARM两种指令集。使用16位的存储器可以降低成本, 在这种情况下,THUMB指令集的整体执行速度比ARM 32位指令集快,而且代码密度高,所以一般用THUMB编译器将C语言程序编译成16位的代码。 处理器一开始总在ARM状态,可使用BX指令转换到THUMB状态呼叫C程序。
%A
%A 难点分析
%A Remap
%A 在Remap的过程中,存储器的地址改变时,PC指针并不会自动改变,因此,要保证Remap以后程序能够继续运行下去,必须采取非正常跳转的手段,使PC指针指向Remap后的程序地址,以下是实现Remap过程的具体指令序列:
%A ldr r12, PtInitRemap
%A ;得到Remap以后的跳转地址,存入r12
%A ldmia r10!,{r0-r9,r11}
%A ;将EBI各个寄存器的设置值存入r0到r9和r11,其中r11指向EBI寄存器的基地址
%A stmia r11!, {r0-r9}
%A ; 将r0到r9寄存器中的数值存入r11指向的EBI各个寄存器,进行Remap
%A mov pc, r12
%A ;跳转到位于Flash rom中的地址继续运行
%A PtInitRemap
%A DCD InitRemap
%A ; 定义Remap后的跳转目的地址。
%A InitRemap
%A ;这里是跳转目的地址
%A ldr r0, =AT91C_SRAM_AFTER _REMAP_SIZE;这里是Remap后的第一条指令
%A 对上述指令的执行过程详细分析就会发现,由于ARM采用三级流水线的体系结构,在执行stmia r11!, {r0-r9}指令进行Remap时,mov pc,r12指令已经被读取。当Remap指令结束后,pc指针开始从重映射到地址0x00的片内RAM中预取指令,但此时片内RAM并没有有效的指令。接下来执行的mov pc,r12指令将标号InitRemap的地址赋值给了pc指针,实际上是使程序转移到位于Remap以后的Flash ROM中去继续执行,同时这条指令断开了流水线,使流水线从InitRemap后的指令开始重新取指。这样,就保证了Remap以后,程序能够在Flash ROM中继续执行下去,做到了“无缝连接”。Remap过程如图1所示。
%A 从加载时域到运行时域的转换
%A 一般而言,一个程序包括只读的代码段和可读写的数据段。在ARM的集成开发环境中,只读的代码段和常量被称作RO段(ReadOnly);可读写的全局变量和静态变量被称作RW段(ReadWrite);RW段中要被初始化为零的变量被称为ZI段(ZeroInit)。对于嵌入式系统而言,程序映象都是存储在Flash存储器等一些非易失性器件中的,而在运行时,程序中的RW段必须重新装载到可读写的RAM中。这就涉及到程序的加载时域和运行时域。简单来说,程序的加载时域就是指程序烧入Flash中的状态,运行时域是指程序执行时的状态。对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项中指定RO BASE和RW BASE,告知连接器RO和RW的连接基地址。对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为“分布装载描述文件”的文本文件,通知连接器把程序的某一部分连接在存储器的某个地址空间。需要指出的是,分布装载描述文件中的定义要按照系统重定向后的存储器分布情况进行。
%A 在引导程序完成初始化的任务后,应该把主程序转移到RAM中去运行,以加快系统的运行速度。
%A 根据以上的设想和系统的存储器分布,本系统的分布装载文件应该如下设置。
%A FLASH 0x01000000
%A ; 一个称作FLASH的加载时域,起始地址0x01000000
%A {
%A FLASH 0x01000000
%A ; 一个称作FLASH的运行时域,起始地址0x01000000
%A {
%A cstartup.o (+RO,+FIRST)
%A ; 域中包括cstartup.o的RO段,first关键字指出本部分要放在首地址
%A * (+RO)
%A ; 其余RO段也放在FLASH中。
%A }
%A SRAM 0x02000000
%A ; 一个称作SRAM的运行时域,起始地址0x02000000
%A { *(+RW,+ZI)
%A ;其余的RW和ZI段放在SRAM中运行。
%A main.o (+RO,+RW,+ZI)
%A ; 域中包括主程序main.o的RO、RW、ZI段。告诉连接器:主程序在RAM中运行。
%A }
%A }
%A 连接器只是根据装载文件的描述将程序的各部分分段连接,最后生成一个可以烧入Flash的加载时域文件映象。从加载时域到运行时域的转换由系统引导程序完成,所以,启动代码必须进行数据和代码搬移工作,以完成该转换。在ADS中,系统提供了缺省的系统函数为用户进行程序和数据的搬移,不需要再由用户在自己的程序中添加数据搬移代码。这一部分是由系统函数__main完成的,__main根据分布装载文件完成代码和数据的复制,并把ZI数据区清零。接着_main跳进_rt_entry,进行STACK和HEAP等的初始化。最后_rt_entry才调用应用程序的入口main(),进入真正的用户程序中去。当应用程序执行完时,_rt_entry又将控制权交还给调试器。整个系统的初始化过程如图2所示。
%A 当用户使用分布装载功能时,必须重调用系统函数_user_initial_stackheap(),否则连接器会报错。图2中C库函数部分由系统在连接时自动加入,用户代码部分的_user_initial_stackheap()和$sub$$main()在需要时可由用户改变。这时,系统的控制权已经交给用户编写的应用程序,启动过程结束。
%A
%A 结语
%A 本启动代码已经在AT91M55800A上调试通过。随着32位ARM处理器的广泛应用,越来越多的工程技术人员开始进行ARM的开发,本文介绍的一些方法和注意点有助于开发人员理解ARM的启动过程,结合自己的开发板硬件设置写出自己的启动代码。■
%A
%A 参考文献
%A 1 马忠梅等. AT91系列ARM核微控制器结构与开发. 北京航空航天大学出版社,2003
%A 2 AT91 C Library v2.14, 2002
%A 3 ARM Developer Suite Linker and Utilities Guide, issue A 2001
%A 4 AT91 Assembler Code Startup Sequence for C Code Application Software, 2002
%A%A
%A
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。