hehu8的笔记 https://bbs.21ic.com/?560500 [收藏] [复制] [RSS] 追求卓越,成功就会在不经意间追上你。。。

日志

LPC2000系列ARM处理器重映射原理的分析与实现

已有 987 次阅读2012-1-13 02:43 |系统分类:ARM| arm, 重映射, 分析


LPC2000系列ARM处理器重映射原理的分析与实现


 原文地址:http://blog.sina.com.cn/s/blog_4e0fe0510100iesw.html


摘要: ARM嵌入式软件开发过程中,经常会遇到一些重要的概念,比如重映射、分散加载等。本文结合具体的实例并以源代码的形式对这些概念进行了详细的分析,并给出了LPC2210处理器片外Flash启动和重映射的实现方法。


关键字:启动代码 重映射 ARM


 

 

多数嵌入式系统中,在进入主程序main之前,需要执行初始化序列对应用程序的运行环境进行配置。启动代码一般用汇编语言来编写,它主要对关键的设备进行初始化和配置,为应用程序的运行创造条件。在整个启动过程中,重映射是其中重要一环,正确的理解和实现重映射对于理解系统的工作原理极为重要。


 


本文的实验平台是EasyARM2210开发板,处理器为Philips LPC2210,它的片内只集成了一块位于0x400000000x40004000,大小为16KSRAM,同时还扩展了一块位于0x80000000~0x801FFFFF,大小为8M字节的Flash。虽然本文的内容都是以LPC2100处理器为例来进行说明的,但对其它ARM系列的处理器来说也是适用的。


 


1 复位和初始化


ARM处理器在复位后处于SVC模式,中断是禁止的,并且处于ARM状态。必须设置好各个异常模式堆栈的位置和大小,应用程序运行时所在的处理器模式和状态,并为它分配合适的堆栈和堆空间,以及使能中断和启用缓存(如果有的话)的时机。一般来说,你总是需要按照一个合理的顺序来初始化你的系统,图1从总体上描述了一个适用于ARM嵌入式系统的可能的初始化序列。


 


 


可以看出,整个初始化序列可以分为两个大的部分,用户代码和C库。C库初始化代码是由集成开发环境(这里使用的是ADS1.2)自动生成的,在完成任务之后,它最终将控制权转交给用户代码。__scatterload部分的作用相当于通常所说的bootloader,它会根据应用程序的设置(比如,我们可以通过分散加载文件精确的指定代码和数据在加载时和运行时的位置)自动生成代码将程序装载到用户希望的地址。__rt_entry部分的作用是执行运行时库的初始化工作。我们需要编码的是用户代码部分:首先设置各个模式的堆栈指针(由于LPC2210中没有MMU/MPU、缓存和TCM,因此这里只需要设置各个模式的堆栈指针即可,程序如list 1所示);其次,如果你使用了分散加载描述文件(由于实际系统中存储器类型的多样性,这几乎总是事实),那么必须实现函数__user_initial_stackheap( );最后是启用中断。


 


//list 1: 设置各种模式的堆栈指针值


Reset_Handler


              IMPORT stack_base             //stack_base的值在分散加载文件中设置


              LDR   r0, =stack_base
             


              MSR   CPSR_c, #Mode_FIQ|I_Bit|F_Bit


              SUB   sp, r0, #offset_FIQ_Stack


              MSR   CPSR_c, #Mode_IRQ|I_Bit|F_Bit


              SUB   sp, r0, #offset_IRQ_Stack


              //…设置其它模式的堆栈指针


 


设置完各个模式的堆栈指针之后,要对关键的I/O设备进行初始化,对处理器时钟、存储器加速模块以及外部存储器模块进行设置。然后跳转到C库的__main,首先由C库代码按照分散加载文件的描述将代码和数据从加载地址拷贝到运行地址并对未初始化数据进行清零,然后跳转到__rt_entryC运行时库进行初始化,最后将控制权转交给用户代码(即主程序main)。


 


2 存储器重映射


2.1 内存布局


       嵌入式系统中经常同时存在多种不同类型的存储介质,从访问速度比较快的SRAMDRAM到比较慢的FLASH ROME2PROM等。嵌入式系统开发者必须仔细的安排内存布局,将访问相对频繁的程序代码放到访问速度相对较快的存储器中。不同的集成开发环境提供了不同的方法帮助开发者来处理这些问题。在ADS1.2中,是通过编写分散加载文件进行描述的。


       分散加载文件能够准确的将某个源文件编译后生成的目标文件加载到一个确定的内存位置,它同时指定了目标文件的加载地址和执行地址。每个目标文件都有一个唯一的加载地址和一个唯一的运行地址。如果一个目标文件的加载地址和执行地址不同,那么C库的__scatterload部分负责将它复制到执行地址处。


2.2 LPC2210处理器的重映射机制


ARM是一个采用RISC体系结构的处理器内核,ARM公司本身并不生成芯片,它将ARM内核授权给生成和销售半导体的合作伙伴。虽然各个ARM芯片生产厂商所采用的内核是相同的,但是在一些细节上也是各有各的特色。本文所说的重映射都是针对LPC2000系列处理器来说的,其它公司比如AtmelIntel等所采用的机制会有所不同。


所有的ARM系统都有一个异常向量表,它是跳转到各种异常处理程序的跳转指令列表。由于普通的跳转指令B label的跳转范围只有32M,为了实现4G范围内的任意跳转,可以采用指令LDR PC, target_addr,它将target_addr的值看作是一个地址值装载到PC中,于是跳转到那个地址处继续执行。这样向量表通常包括32字节的相对PC的跳转指令和32字节的异常处理程序地址。


通常系统对异常向量表的访问最为频繁,为了尽可能快的响应各种异常,必须考虑将异常向量表装载到何种类型的存储器中。片内RAM的访问速度最快,而且可以运行时对向量表进行修改,它无疑是向量表的理想选择。但是考虑到RAM不属于非易失性存储器,在掉电后不能保存它的内容,因此只能在系统启动后,将向量表复制到片内RAM中,并利用存储器重映射机制使向量表从片内RAM重新映射。在LPC2210中,重映射是通过寄存器MEMMAP来控制的,其用法如图2所示:


 
















MEMMAP 功能 描述
1:0 MAP1:0

00Boot装载程序模式。异常向量从bootblock重新映射。01:保留。该选项不使用。、


10:用户RAM模式。异常向量从片内RAM重新映射。


11:用户外部存储器模式。异常向量从外部存储器重新映射。

7:2 保留 保留,不要向其写入1

2 MEMAP寄存器


 


LPC2000中,存储器重映射的部分包括异常向量区(32字节)和额外的32字节,一共是64字节,重新映射的代码位置与地址0x0~0x0000003F重叠。也就是说,当处于用户RAM模式时,如果访问0x0~0x3F的数据,实际上是在对0x40000000~0x4000003F进行访问。同样如果切换到外部存储器模式,并且同样对0x0~0x3F进行访问,就变成访问0x80000000~0x8000003F中的数据/指令了。


 


     实验结果表明:当在MEMMAP设置为0x02时,向0x0中写入数据0xAA,在ADS中所看到的0x40000000和0x00地址上的数据均为0xAA,同理,在0x40000000中写入0xAA,在ADS中所看到的0x40000000和0x00地址上的数据均为0xAA。但是一旦超过了0x0~0x3F这个范围,如在0x40上写入数据,将无法写入(因为其为FLASH)。


 


2.3 存储器重映射的实现


    为了实现对系统启动过程的精确控制,最好把与异常向量和启动代码相关的程序分别放在vectors.sinit.s这两个文件中,其中vectors.s包含了异常向量表,init.s包含了复位处理程序Reset_Handler,这样可以使用分散加载机制精确的控制异常向量表和复位处理程序在内存中的布局。


     LPC2000系列处理器中异常向量表可以从片内RAM或者从片外存储器中重新映射,下面分别讨论一下这两种方式。


 


2.3.1 RAM中重映射


   将复位处理程序放在片外Flash存储器最低地址处,将异常向量表放在片内RAM的最低地址处,使用的分散加载文件如list 2所示。在系统复位后,外部存储器底部64个字节重新映射到地址0x0,并从中取出第一条指令LDR  pc, =Instruct_2,于是控制流跳转到真实的ROM中继续执行。紧接着访问存储器控制寄存器使MEMMAP[1:0]=10,这样片内RAM中的异常向量表就映射到地址0x0。整个过程如图3所示。



//list 2:分散加载文件――异常向量从片内RAM重新映射
ROM_LOAD 0x80000000
{
    ROM_EXEC 0x80000000
     
        init.o(Init, +First)
        * (+RO)
    }
    IRAM 0x40000000
    {
        vectors.o (Vect, +First)
        * (+RW,+ZI)
    }
    //...
}

2.3.2 为ROM编写代码



    上面的方法虽然能够正确的实现从RAM中重新映射异常向量,但是由于在系统复位或掉电重启后,RAM中的内容会丢失,导致程序不能运行,于是为了使程序能够正常运行,必须将异常向量表加载到非易失性存储器如Flash中。由于LPC2210处理器没有片内Flash存储器,必须正确的配置引脚13和16使系统从外部存储器启动。启动代码如list 3所示,系统启动后首先从标号Instruct_2开始执行,然后修改MEMMAP寄存器的内容(其中CM_ctl_reg等于MEMMAP寄存器的地址),于是异常向量表从片内RAM重新映射,最后将异常向量表从片外Flash拷贝到片内Flash。此后在发生异常后,处理器就自动从片内RAM取出异常处理程序的地址,从而能够显著提高程序的性能。


//list 3:部分启动代码
CM_ctl_reg      EQU     0xE01FC040
RAM             EQU     0x2
Reset_Handler
    LDR     pc,     =Instruct_2        
Instruct_2
    LDR     r1,   =CM_ctl_reg
    LDR     r0,   [r1]
    AND     r0,   r0, #RemapMask
    ORR     r0,   r0, #RAM
    STR     r0,   [r1]
    MOV     r9  , #0x80000000
    MOV     r10 , #0x40000000
    LDMIA   r9! , {r0-r7}
    STMIA   r10!, {r0-r7}
    LDMIA   r9! , {r0-r7}
    STMIA   r10!, {r0-r7}


在上面的复制完成之后,异常向量表就会同时出现在地址0x0、0x40000000以及0x80000000。描述映像布局的分散加载文件如列表list 4所示。注意执行区IRAM的基址不是0x40000000,而是0x40000040,因为起始的64字节是为异常向量表预留的。整个过程如果图4所示。


//list 4:分散加载文件―将异常向量
//从ROM拷贝到RAM中,并从片内RAM重新映射
ROM_LOAD 0x80000000
{
    ROM_EXEC 0x80000000
     
        vectors.o (Vect, +First)  
        * (+RO)
    }
    IRAM 0x40000040
    {
        * (+RW,+ZI)
    }
    HEAP +0 UNINIT
    {
        heap.o(+ZI)
    }
    STACKS 0x40004000 UNINIT
    {
        stack.o(+ZI)
    }
}


3 结论


      本文详细的介绍了基于LPC2000系列处理器的系统的初始化过程,并结合具体代码(在附带的程序中)对重映射作了细致的阐述,这些对于正确的理解和开发基于ARM的嵌入式系统具有重要的意义。


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)