EDN首页   博客首页

1

关于投票
关于ARM处理器Remap的理解(抄的)

关于ARM处理器Remap的理解 (抄的)
0.什么是Remap

我的理解是:在ROM从0x0用几句指令引导系统之后,把RAM映射到0x0就是Remap。

1.Remap的作用

当ARM处理器上电或者Reset之后,处理器从0x0取指。因此,必须保证系统上电时,0x0处有指令可以执行。所以,上电的时候,0x0地址处必定是ROM或者Flash(NOR)。 但是,为了加快启动的速度,也方便可以更改异常向量表,加快中断响应速度,往往把异常向量表映射到更快、更宽(32bit/16bit)的RAM中。但是异常向量表的开始地址是由ARM架构决定的,必须位于0x0处,因此,必须把RAM映射到0x0。

 2.Remap的配置

Remap的实现和ARM处理器的实现相关。 1)如果处理器有专门的寄存器可以完成Remap。那么Remap是通过Remap寄存器的相应bit置1完成的。如Atmel AT91xx 2)如果处理器没有专门的寄存器,但是memory的bank控制寄存器可以用来配置bank的起始地址,那么只要把RAM的起始地址编程为0x0,也可以完成remap。如samsung s3c4510 3)如果上面两种机制都没有,那么Remap就不要做了。因为处理器实现决定了SDRAM对应的bank地址是不能改变的。如Samsung S3c2410. 3.Remap配置前后要做的工作 Remap前后,不同之处就是RAM的位置变了。为了达到Remap的目的,就是加快启动的速度和异常处理速度,一定要初始化异常堆栈和建立异常向量表的。 4.如果象2410那样不能Remap的话怎么办? 2410不是不能Remap吗?为了加快启动速度,可以这样做 1)使用它的NAND boot模式。为什么NAND boot会比较快,那是因为2410里面有块小石头——“SteppingStone”,一块4KB SRAM,它是映射在0x0的。启动程序会自动被copy到这个石头里面。自然异常向量的入口放到这个地方,一样可以达到比NOR boot快的启动、异常响应速度。 2)如果你对NOR Boot情有独衷,那么你只好把你的异常向量的入口copy到SDRAM里面,实现所谓的High Vector 我个人认为,如果中断向量放在rom中的0x0位置,就用不着remap,但是无法再更改中断向量,如果放在ram或其他的存储器中,那么就应该用的remap,因为在系统复位后,对系统环境的初始化是从中断向量中的中断reset开始的,而系统运行必须是从rom的0x0开始的,因此应该利用remap把放中断向量的初始地址映射到rom的0x0处,以使程序进行正确的初始化.这样既可以初始化,有可以使程序运行的快一些,因为毕竟rom的速度是比较慢的.

以上是个人理解,如有错误请大家指正!!!

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(1803) | 回复(7)

2

关于投票
基于ARM的嵌入式系统Bootloader启动流程分析
基于ARM的嵌入式系统Bootloader启动流程分析
作者:万永波 张根宝 田泽 杨峰 来源:微计算机信息

 

摘要:讲述了基于ARM处理器的嵌入式系统在上电启动后应用程序或操作系统运行前,对处理器及其内部功能模块进行初始化的过程,并结合经过实际验证的代码详细的分析了S3C44B0 Bootloader的运行过程。
关键字:ARM 嵌入式系统 Bootloader

一. 引言:
       对于PC机,其开机后的初始化处理器配置、硬件初始化等操作是由BIOS(Basic Input /Output System)完成的,但对于嵌入式系统来说,出于经济性、价格方面的考虑一般不配置BIOS,因此我们必须自行编写完成这些工作的程序,这就是所需要的开机程序。而在嵌入式系统中,通常并没有像 BIOS 那样的固件程序,启动时用于完成初始化操作的这段代码被称为Bootloader程序,因此整个系统的加载启动任务就完全由Bootloader 来完成。简单地说,通过这段程序,可以初始化硬件设备、建立内存空间的映射图(有的CPU没有内存映射功能如S3C44B0),从而将系统的软硬件环境设定在一个合适的状态,以便为最终调用操作系统内核、运行用户应用程序准备好正确的环境。Bootloader依赖于实际的硬件和应用环境,因此要为嵌入式系统建立一个通用、标准的Bootloader是非常困难的。Bootloader也依赖于具体的嵌入式板级设备的配置,这也就是说,对于两块不同的嵌入式主板而言,即使它们是基于同一 CPU 而构建,要想让运行在一块板子上的 Bootloader 程序也能运行在另一块板子上,通常都需要修改 Bootloader 的源程序。

二. 启动流程
       系统加电复位后,几乎所有的 CPU都从由复位地址上取指令。比如,基于 ARM7TDMI内核的CPU在复位时通常都从地址 0x00000000处取它的第一条指令。而以微处理器为核心的嵌入式系统通常都有某种类型的固态存储设备(比如EEPROM、FLASH等)被映射到这个预先设置好的地址上。因此在系统加电复位后,处理器将首先执行存放在复位地址处的程序。通过集成开发环境可以将Bootloader定位在复位地址开始的存储空间内,因此Bootloader是系统加电后、操作系统内核或用户应用程序运行之前,首先必须运行的一段程序代码。对于嵌入式系统来说,有的使用操作系统,也有的不使用操作系统,比如功能简单仅包括应用程序的系统,但在系统启动时都必须执行Bootloader,为系统运行准备好软硬件运行环境。
       系统的启动通常有两种方式,一种是可以直接从Flash启动,另一种是可以将压缩的内存映像文件从Flash(为节省Flash资源、提高速度)中复制、解压到RAM,再从RAM启动。当电源打开时,一般的系统会去执行ROM(应用较多的是Flash)里面的启动代码。这些代码是用汇编语言编写的,其主要作用在于初始化CPU和板上的必备硬件如内存、中断控制器等。有时候用户还必须根据自己板子的硬件资源情况做适当的调整与修改。
       系统启动代码完成基本软硬件环境初始化后,对于有操作系统的情况下,启动操作系统、启动内存管理、任务调度、加载驱动程序等,最后执行应用程序或等待用户命令;对于没有操作系统的系统直接执行应用程序或等待用户命令。
       启动代码是用来初始化电路以及用来为高级语言写的软件做好运行前准备的一小段汇编语言,在商业实时操作系统中,启动代码部分一般被称为板级支持包,英文缩写为BSP。它的主要功能就是:电路初始化和为高级语言编写的软件运行做准备。系统启动流程如图1所示,主要的过程如下:

300)this.width=300" align="absMiddle" border="0">
    1. 启动代码的第一步是设置中断和异常向量。
    2. 完成系统启动所必须的最小配置,某些处理器芯片包含一个或几个全局寄存器,这些寄存器必须在系统启动的最初进行配置。
    3. 设置看门狗,用户设计的部分外围电路如果必须在系统启动时初始化,就可以放在这一步。
    4. 配置系统所使用的存储器,包括Flash,SRAM和DRAM等,并为他们分配地址空间。如果系统使用了DRAM或其它外设,就需要设置相关的寄存器,以确定其刷新频率,数据总线宽度等信息,初始化存储器系统。有些芯片可通过寄存器编程初始化存储器系统,而对于较复杂系统通常集成有MMU来管理内存空间。
    5. 为处理器的每个工作模式设置栈指针,ARM处理器有多种工作模式,每种工作模式都需要设置单独的栈空间。
    6. 变量初始化,这里的变量指的是在软件中定义的已经赋好初值的全局变量,启动过程中需要将这部分变量从只读区域,也就是Flash拷贝到读写区域中,因为这部分变量的值在软件运行时有可能重新赋值。还有一种变量不需要处理,就是已经赋好初值的静态全局变量,这部分变量在软件运行过程中不会改变,因此可以直接固化在只读的Flash或EEPROM中。
    7. 数据区准备,对于软件中所有未赋初值的全局变量,启动过程中需要将这部分变量所在区域全部清零。
    8. 最后一步是调用高级语言入口函数,比如main函数等。

三. 程序分析
    下面根据实际经过测试的代码详细讲述系统的启动过程。
    .text      /*将此操作符开始的代码编译到代码段或代码段子段中*/
    /* 集成开发环境(IDE)可以通过链接脚本文件将下面的语句定位在零起始地址,系统上电后CPU从此处开始执行*/
    ENTRY:
        b ResetHandler   /*跳至ResetHandler,此句被定位在零起始地址*/
/*除用户模式外的其他6种模式称为特权模式。特权操作模式主要处理异常和监控调用(有时称为软件中断),它们可以自由的访问系统资源和改变模式。特权模式中除系统模式以外的5种模式又称为异常模式,下面的代码用于出现异常时CPU就会根据以下的语句自动跳转到对应的异常处理程序处*/


    b HandlerUndef        /* handlerUndef         */
    b HandlerSWI         /* SWI interrupt handler  */
    b HandlerPabort       /* handlerPAbort        */
    b HandlerDabort       /* handlerDAbort       */
    b .                   /* handlerReserved      */
    b HandlerIRQ
    b HandlerFIQ
… ...
… ...
ResetHandler:  /*上电后跳转到此处开始执行*/
    Ldr r0,=WTCON  /*禁止看门狗*/
    ldr     r1,=0x0
    str     r1,[r0]
    ldr     r0,=INTMSK    /*屏蔽所有中断请求 */
    ldr     r1,=0x07ffffff
    str     r1,[r0]


 /*设置时钟控制寄存器*/
    ldr  r0,=LOCKTIME
    ldr  r1,=0xfff
    str  r1,[r0]
.if PLLONSTART
 ldr  r0,=PLLCON   /* 设置PLL */
 ldr  r1,=((M_DIV<<12)+(P_DIV<<4)+S_DIV) /*Fin=8MHz,Fout=64MHz*/
 str  r1,[r0]
.endif
    ldr     r0,=CLKCON 
    ldr     r1,=0x7ff8      /*所有单元时钟允许*/
    str     r1,[r0]
/*为BDMA设置复位值*/
    ldr     r0,=BDIDES0
    ldr     r1,=0x40000000     /* BDIDESn 复位值应为 0x40000000 */
    str     r1,[r0]
    ldr     r0,=BDIDES1
    ldr     r1,=0x40000000     /* BDIDESn 复位值应为 0x40000000 */
str     r1,[r0]


        /*设置存储器控制寄存器,存储器的配置数据都存储在SMRDATA为起始地址的数据表中,下面的代码可以一次将预先配置好的初始化数据存入与存储器控制器相关的13个寄存器,这些寄存器则是以0x01c80000为起始地址的13个连续的32位寄存器*/


    ldr     r0,=SMRDATA
    ldmia   r0,{r1-r13}
    ldr     r0,=0x01c80000    /* BWSCON存储控制寄存器地址 */
    stmia   r0,{r1-r13}
/*初始化堆栈*/
/* CPU复位后是处于管理模式下的,所以首先要初始化管理模式下的堆栈寄存器*/
    ldr     sp, =SVCStack


       /*由于处理器的每种运行模式都要有自己独立的物理堆栈寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间,这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行*/
       bl     InitStacks   /*跳转至其它堆栈初始化程序并返回*/


 /*设置IRQ中断处理*/
       /*44B0有两种中断模式:一种是没有中断向量表;一种是使用了中断向量表,使用中断向量表只能是IRQ方式。当使用中断向量表的时候,中断发生时由S3C44B0的中断控制器根据中断向量表,利用硬件方式自动跳转到相应的中断处理服务程序所在的位置;不使用中断向量表时按下面的代码,利用软件方式跳转而进行中断处理,因为S3C44B0有30个中断源,所以需要程序判断以确定调用那个中断服务程序*/


    ldr     r0,=HandleIRQ /*如果在0x18和0x1c地址处无“subs pc,lr,#4”*/
    ldr     r1,=IsrIRQ  /*为了中断正常返回这些语句是必须的 */
    str     r1,[r0]


        /*拷贝读写区域数据/数据区准备,将系统需要读写的数据和变量从ROM拷贝到RAM里。 Image_RO_Limit、Image_RW_Base、Image_ZI_Base等这些符号还会在另外的链接脚本文件中出现,这些符号是用来定位程序各个段的参考信息。集成开发环境在编译链接的时候会根据我们编写的程序,把它们转换成用来对各个段定位的地址信息*/


    LDR     r0, =Image_RO_Limit /*取只读数据区域地址指针*/
    LDR     r1, =Image_RW_Base /*准备执行拷贝操作*/
    LDR     r3, =Image_ZI_Base
     CMP     r0, r1       /*检查是否相同*/
    BEQ     F1        /*相同则跳过拷贝操作*/
F0:
    CMP     r1, r3    /*执行拷贝操作*/
    LDRCC   r2, [r0], #4
    STRCC   r2, [r1], #4
    BCC     F0
F1:
    LDR     r1, =Image_ZI_Base /*零数据准备区起始地址*/
    MOV     r2, #0
F2:
    CMP     r3, r1       /*执行数据区清零*/
    STRCC   r2, [r3], #4
    BCC     F2

 MRS r0, CPSR
 BIC r0, r0, #NOINT     /*中断请求允许*/
 MSR CPSR_cxsf, r0
/* 跳转到C入口程序 */
    BL Main
    B.

四. 总结:
       启动过程中的初始化程序就是初始化CPU内部各个关键的寄存器、配置外围硬件电路相关寄存器、建立中断向量表等,然后跳转到一般由高级语言编写的主函数的应用程序代码去执行,这样就可以利用高级语言来编写完成系统设计所要求的各种功能。初始化的过程对大多数初学者来说,比较难理解的是中断的处理和一些少见的操作符号,这些符号多是一些宏定义或系统用于在内存空间中对各个段的定位标识符号。掌握了S3C44B0的启动代码之后,对系统功能程序设计会起到很大的帮助,是进行下一步程序设计的基础。

五. 参考文献:
1. 田泽.嵌入式系统开发与应用.北京.北京航空航天大学出版社.2005
2. 田泽.嵌入式系统开发与应用实验教程.北京.北京航空航天大学出版社.2004
3. 深圳英蓓特信息技术有限公司.Embest ARM实验教学系统用户手册.Version 2.01.2003
4. SAMSUNG公司.S3C44B0_datasheet.pdf.

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(1240) | 回复(1)

1

关于投票
ARM启动代码设计参考
(转贴)ARM启动代码设计参考

基于ARM的芯片多数为复杂的片上系统,这种复杂系统里的多数硬件模块都是可配置的,需要由软件来设置其需要的工作状态。因此在用户的应用程序之前,需要由专门的一段代码来完成对系统的初始化。由于这类代码直接面对处理器内核和硬件控制器进行编程,一般都是用汇编语言。一般通用的内容包括:
中断向量表
初始化存储器系统
初始化堆栈
初始化有特殊要求的断口,设备
初始化用户程序执行环境
改变处理器模式
呼叫主应用程序
1. 中断向量表
ARM要求中断向量表必须放置在从0地址开始,连续8X4字节的空间内。
每当一个中断发生以后,ARM处理器便强制把PC指针置为向量表中对应中断类型的地址值。因为每个中断只占据向量表中1个字的存储空间,只能放置一条ARM指令,使程序跳转到存储器的其他地方,再执行中断处理。
中断向量表的程序实现通常如下表示:
AREA Boot ,CODE, READONLY
ENTRY
B    ResetHandler
B    UndefHandler
B    SWIHandler
B    PreAbortHandler
B    DataAbortHandler
B
B    IRQHandler
B    FIQHandler
其中关键字ENTRY是指定编译器保留这段代码,因为编译器可能会认为这是一段亢余代码而加以优化。链接的时候要确保这段代码被链接在0地址处,并且作为整个程序的入口。
2. 初始化存储器系统
(1)存储器类型和时序配置
通常Flash和SRAM同属于静态存储器类型,可以合用同一个存储器端口;而DRAM因为有动态刷新和地址线复用等特性,通常配有专用的存储器端口。
存储器端口的接口时序优化是非常重要的,这会影响到整个系统的性能。因为一般系统运行的速度瓶颈都存在于存储器访问,所以存储器访问时序应尽可能的快;而同时又要考虑到由此带来的稳定性问题。
(2)存储器地址分布
一种典型的情况是启动ROM的地址重映射。
3. 初始化堆栈
因为ARM有7种执行状态,每一种状态的堆栈指针寄存器(SP)都是独立的。因此,对程序中需要用到的每一种模式都要给SP定义一个堆栈地址。方法是改变状态寄存器内的状态位,使处理器切换到不同的状态,让后给SP赋值。注意:不要切换到User模式进行User模式的堆栈设置,因为进入User模式后就不能再操作CPSR回到别的模式了,可能会对接下去的程序执行造成影响。
这是一段堆栈初始化的代码示例,其中只定义了三种模式的SP指针:
MRS   R0,CPSR
BIC    R0,R0,#MODEMASK  安全起见,屏蔽模式位以外的其他位
ORR   R1,R0,#IRQMODE
MSR   CPSR_cxfs,R1
LDR   SP,=UndefStack

ORR   R1,R0,#FIQMODE
MSR   CPSR_cxsf,R1
LDR   SP,=FIQStack

ORR   R1,R0,#SVCMODE
MSR   CPSR_cxsf,R1
LDR   SP,=SVCStack
4. 初始化有特殊要求的端口,设备
5. 初始化应用程序执行环境
映像一开始总是存储在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面执行,也可以转移到速度更快的RAM中执行;而RW和ZI这两部分是必须转移到可写的RAM里去。所谓应用程序执行环境的初始化,就是完成必要的从ROM到RAM的数据传输和内容清零。
下面是在ADS下,一种常用存储器模型的直接实现:
LDR    r0,=|Image$$RO$$Limit|      得到RW数据源的起始地址
LDR    r1,=|Image$$RW$$Base|      RW区在RAM里的执行区起始地址
LDR    r2,=|Image$$ZI$$Base|        ZI区在RAM里面的起始地址
CMP    r0,r1                      比较它们是否相等
      BEQ    %F1
0     CMP    r1,r3
      LDRCC  r2,[r0],#4
      STRCC  r2,[r1],#4
      BCC    %B0
1     LDR    r1,=|Image$$ZI$$Limit|
      MOV   r2,#0
2     CMP    r3,r1
      STRCC  r2,[r3],#4
      BCC    %B2
程序实现了RW数据的拷贝和ZI区域的清零功能。其中引用到的4个符号是由链接器第一输出的。
|Image$$RO$$Limit|:表示RO区末地址后面的地址,即RW数据源的起始地址
|Image$$RW$$Base|:RW区在RAM里的执行区起始地址,也就是编译器选项RW_Base指定的地址
|Image$$ZI$$Base|:ZI区在RAM里面的起始地址
|Image$$ZI$$Limit|:ZI区在RAM里面的结束地址后面的一个地址
程序先把ROM里|Image$$RO$$Limt|开始的RW初始数据拷贝到RAM里面|Image$$RW$$Base|开始的地址,当RAM这边的目标地址到达|Image$$ZI$$Base|后就表示RW区的结束和ZI区的开始,接下去就对这片ZI区进行清零操作,直到遇到结束地址|Image$$ZI$$Limit|
6. 改变处理器模式
因为在初始化过程中,许多操作需要在特权模式下才能进行(比如对CPSR的修改),所以要特别注意不能过早的进入用户模式。
内核级的中断使能也可以考虑在这一步进行。如果系统中另外存在一个专门的中断控制器,这么做总是安全的。
7. 呼叫主应用程序
当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序。最简单的一种情况是:
IMPORT main
B      main
直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。
IMPORT __main
B     __main
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()函数。
系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(749) | 回复(0)

0

关于投票
AT91RMM9200的PIO使用与设置
发贴心情 [原创]AT91RMM9200的PIO使用与设置

AT91RM9200管理PA,PB,PC,PD四个PIO,每个PIO都可以被设置工作在GPIO或者外设I/O状态下,但是要注意,要使能某个引脚的GPIO功能时,不能和该引脚的外设I/O功能发生冲突,只能使其工作在一个特定的状态下。


1。配置PIO工作在GPIO模式


以PB0为例,如果要使其工作在GPIO模式,则首先要使能PIO控制器,设置:


*(AT91C_PIOB_PER)=0x1;


然后或者把PB0作为输出,或者作为输入。如果作为输出口,设置:


*(AT91C_PIOB_OER)=0x1;


向使能为输出口的PB0写入一个数,设置:


*(AT91C_PIOB_SODR)=0x1;//写入1


*(AT91C_PIOB_CODR)=0x1;//写入 0


如果要把PB0作为输入口,则配置:


*(AT91C_PMC_PCER)|=0x1 << AT91C_ID_PIOB;//Enable PIOB MCK


*(AT91C_PIOB_ODR)=0x1; //Enable PB0 Input
很多用户忽略了上面的设置,结果读不到口的状态,如果使能了外设时钟,则读端口


状态设置:


int status;


status=*(AT91C_PIOB_PDSR)&0x01;


status反映了当前PB0口的电平,即status=0或1;


以上的过程就是如何配置一个I/O口作为通用I/O来使用。


2。配置PIO工作在外设I/O模式


仍以PB0为例,如果要使PB0工作在外设A模式或者B模式下,首先要禁止PIO控制器,设置:


*(AT91C_PIOB_PDR)=0x1;


如果要设置PB0为A功能,即TF0,则设置:


*(AT91C_PIOB_ASR)=0x1;


如果要设置PB0为B功能,即RTS3,则设置:


*(AT91C_PIOB_BSR)=0x1;


这时,PB0就不再受PIO控制器的管理而工作在外设引脚模式下了。


必须指出:AT91RM9200的所有外设引脚在使用之前必须进行步骤2的设置,否则该引脚无法使用。

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(816) | 回复(0)

0

关于投票
AT91RM9200的启动过程
9200的启动过程
AT91RM9200的启动过程


系统上电,检测BMS,选择系统的启动方式,如果BMS为高电平,则系统从片内ROM启动。AT91RM9200的ROM上电后被映射到了0x0和0x100000处,在这两个地址处都可以访问到ROM。由于9200的ROM中固化了一个BOOTLOAER程序。所以PC从0X0处开始执行这个BOOTLOAER(准确的说应该是一级BOOTLOADER)。这个BOOTLOER依次完成以下步骤:


1.              PLL SETUP


设置PLLB产生48M时钟频率提供给USB DEVICE。同时DEBUG USART也被初始化为48M的时钟频率。


2.              相应模式下的堆栈设置


3.              检测主时钟源(Main oscillator)


4.              中断控制器(AIC)的设置


5.              C 变量的初始化


6.              跳到主函数


完成以上步骤后,我们可以认为BOOT过程结束,接下来的就是LOADER的过程,或者也可以认为是装载二级BOOTLOER。9200按照DATAFLASH、EEPROM、连接在外部总线上的8位并行FLASH的顺序依次来找合法的BOOT程序。所谓合法的指的是在这些存储设备的开始地址处连续的存放的32个字节,也就是8条指令必须是跳转指令或者装载PC的指令,其实这样规定就是把这8条指令当作是异常向量表来处理。必须注意的是第6条指令要包含将要装载的映像的大小。关于如何计算和写这条指令可以参考用户手册。一旦合法的映像找到之后,则BOOT程序会把找到的映像搬到SRAM中去,所以映像的大小是非常有限的,不能超过16K-3K的大小。当BOOT程序完成了把合法的映像搬到SRAM的任务以后,接下来就进行存储器的REMAP,经过REMAP之后,SRAM从映设前的0X200000地址处被映设到了0X0地址并且程序从0X0处开始执行。而ROM这时只能在0X100000这个地址处看到了。至此9200就算完成了一种形式的启动过程。如果BOOT程序在以上所列的几种存储设备中未找到合法的映像,则自动初始化DEBUG USART口和USB DEVICE口以准备从外部载入映像。对DEBUG口的初始化包括设置参数115200 8 N 1以及运行XMODEM协议。对USB DEVICE进行初始化以及运行DFU协议。现在用户可以从外部(假定为PC平台)载入你的映像了。在PC平台下,以WIN2000为例,你可以用超级终端来完成这个功能,但是还是要注意你的映像的大小不能超过13K。一旦正确从外部装载了映像,接下来的过程就是和前面一样重映设然后执行映像了。我们上面讲了BMS为高电平,9200选择从片内的ROM启动的一个过程。如果BMS为低电平,则9200会从片外的FLASH启动,这时片外的FLASH的起始地址就是0X0了,接下来的过程和片内启动的过程是一样的,只不过这时就需要自己写启动代码了,至于怎么写,大致的内容和ROM的BOOT差不多,不同的硬件设计可能有不一样的地方,但基本的都是一样的。由于片外FLASH可以设计的大,所以这里编写的BOOTLOADER可以一步到位,也就是说不用像片内启动可能需要BOOT好几级了,目前9200上使用较多的bootloer是u-boot,这是一个开放源代码的软件,用户可以自由下载并根据自己的应用配置。


总的说来,笔者以为9200的启动过程比较简单,ATMEL的服务也不错,不但提供了片内启动的功能,还提供了UBOOT可供下载。笔者写了一个BOOTLODER从片外的FLASHA启动,效果还可以。

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(1933) | 回复(0)

0

关于投票
arm指令集
rar
系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(600) | 回复(0)

0

关于投票
学习arm的网站

ouravr的ourarm : http://www.ouravr.net/bbs/bbs_list.jsp?bbs_id=1032&bbs_page_no=6 

超前科技:http://www.mcu123.net/bbs/index.asp?boardid=28 

ARM开发网:http://www.okarm.com/index.asp?boardid=2 

IC开发网:http://www.icdev.com.cn/bbs/board.aspx?boardid=5 

IAR EWARM:http://www.iar.com 

H-JTAG的官方网站:http://twentyone.blogchina.com/ H-JTAG属于调试代理软件,软件做的那是相当好,我个人特别佩服。在这个网站上能学到不少东西。

新加坡的一家卖板网站:http://www.embeddedhub.com/cn/(中文的只是主页)描述:The main mission of EmbeddedHub is to supply a wide range of low cost embedded system development Hardware and Software tools to those who are willing to learn the arts of embedded system while keeping the cost under control.

Keil的官方更新下载网站:www.keil.com/update

www.mcuzone.com的书籍整理得不错,推荐下载。

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(950) | 回复(0)

0

关于投票
arm开发经验(连载)
arm开发经验(连载)
发表于 2005-11-27 21:51:36

 前一段时间做了arm的一些开发,主要是编写了arm的启动软件和移植了uCOS-II到arm7。我做事情喜欢深入简出,及从最简单,最原理的方面先做一个框架,然后在这个框架里面进行补充。我还是一个很喜欢和别人讨论的人,希望有人可以给我提出意见和建议。我的这个心得很初级,都是一些基本的东西。现在拿出来和大家分享,希望在我毕业之前能给大家留一些纪念。^_^
    由于这些东西发paper实在是没有价值,但是我感觉可以作为arm开发的入门。由于我的水平和经验有限,错误也是难免的。但是如果不拿出来和大家分享,就算有错误我也发现不了,是么?呵呵。我现试试发连载的第一篇,看看有多少价值,如果大家觉得有价值,我会继续连载的。
连载一:
前言
这个文档是我学习ARM编程的总结和心得。阅读这个文档的人应当首先阅读ADS1.2的帮助文档及相关内容。这个文档不会对编译器及连接器做出详细的说明,在需要的时候会指出具体内容在相关资料的章节。同时阅读这个文档的人需要了解ARM指令集和一些ARM汇编的基本内容以及C和C++的相关编程内容。同时还需要了解ARM的流水线结构及一些基本的编程知识。同时为了方便查阅英文文档,所有的相关术语都使用英文原文

第一章 STARTUP
1 ARM的启动
一般的嵌入式系统在主程序执行之前都需要执行一些初始化的过程以创造嵌入式程序运行的环境,尤其是一些高级的嵌入式系统,由于核心芯片使用内存映射、内存保护等机制以及编程使用高级语言C,C++甚至JAVA语言,都需要先创建一个适合程序运行的硬件环境,然后初始化或者配置或者剪裁run-time library, 这些工作都必须在主程序运行前完成,所以一个startup程序或者程序组对于一个嵌入式系统来说是非常重要的。要编写startup程序,需要对编译器、链接器和汇编器的细节有一定的了解,同时对ARM芯片硬件本身的地址分配以及memory mapping机制也需要有一些了解。
2 ARM 程序的工作过程
首先由各种source file经过编译产生object文件,然后object文件经过链接生成Image文件,然后通过ICE的方法,根据描述文件的指定下载到目标板上的固态存储器指定地址当中,比如flash,EEPROM, ROM等等。在程序执行之前,根据某些描述文件,将需要读写数据的部分读出放入动态存储器比如RAM当中,然后程序从ROM开始执行。或者有时为了提高程序的运行速度,也可以将所有的程序(有一些root的部分除外,以后会提及)通过一个描述文件放入指定的RAM当中,然后程序从RAM开始执行,但是这样会耗费大量的动态存储器,所以大部分程序会取折中的方法,将需要快速运行的部分和要读写的部分放入RAM中(一般读固态存储器的过程和动态存储器的过程是一样的,但是写就不同了,所以读写的部分一定要放到RAM中),而只读的部分和对速度要求不是那么高的部分放入固态存储器。同时ARM结构的异常向量表规定放在地址为0x00000000开始的地址空间上,而一般的CPU为了提高异常相应速度,会将这个向量段remap到其他的RAM当中,所以在描述文件当中必须精确指定异常向量跳转程序的地址到remap的地方。在application程序执行前,还需要由一些文件描述application程序执行的环境。比如系统工作时钟,总线频率。现在一般嵌入式编程语言为C,C++等。如果在使用它们的时候使用的runtime-library,那么在程序执行前还需要为这些库函数初始化heap。然后ARM可能工作在不同的模式,还需要为不同的工作模式设置stack。这样,描述链接地址的文件,以及在application运行前所有的初始化程序就是startup程序组
3 STARTUP分类
这样,将startup程序所完成的功能分类。一类是链接地址描述,一类是各种初始化的程序。根据不同的应用,描述文件和初始化程序的内容以及结构和复杂程度都会不同。但是基本上,它们都必须实现以下功能。
3.1 描述文件实现功能
描述文件可以是链接命令行上简单的几个字符,也可以是一个非常复杂的文件,但是它必须完成如下功能:
; 指定程序下载的地址
; 指定程序执行的地址
3.2 初始化程序实现的功能
初始化程序根据不同的应用,其结构和复杂度也不同,但是它必须完成如下基本功能:
; 异常向量初始化
; 内存环境初始化
; 其他硬件环境初始化

ARM开发经验(二)
发表于 2005-11-27 21:53:44

注:这个连载的版权属于自控所158所有。转载的时候请注明。转载需要通过作者本人同意。

/*
*********************************************************************************************************
*                                               Programming Arm
*
*
*                             (c) Copyright 1992-2008, 西安交通大学
*                                          All Rights Reserved
*
*                                              自控研究所158
*
* 文件      : 连载二
* 版本   : V1.00
* 作者      : 潘自强
*
* 对象      : ARM7
* 模式      : ARM
* 工具      : ADS1.20
*********************************************************************************************************
*/


4 描述文件
要编写描述文件,必须知道ARM Image文件的组成及ARM Image文件执行的机理。
4.1 ARM Image的结构
一个ARM Image structure由linker在以下几个方面定义:
 组成它的regions 和 output sections
 当Image 下载的时候这些regions 和 sections 在内存中的位置
 当Image 执行时这些regions和sections在内存中的位置
4.1.1 ARM Image的组成
一个ARM Image被保存在可执行文件当中,它的层次结构可以包括Image,regions,output sections和input sections。
 一个Image由一个或多个regions组成,每个region包括一个或多个output sections
 每个output section由一个或多个input sections组成
 Input sections是一个object file中的code和data信息。
Image的结构如
下图:
1 附图: tu1.JPG (24684 字节)
NOTE Input section,output section和region的定义见ADS_LinkerGuide 3-3页。
同时Input section 有几种属性,分别为readonly,read-write,zero-initialized。分别称为RO,RW和ZI。属性来源于AREA后的attr属性。
比如CODE是RO,DATA是RW,NOINT默认为ZI,即用0值初始化,但是可以选择不进行0值初始化。ZI属性仅仅来源于SPACE, DCB, DCD, DCDU, DCQ, DCQU, DCW, 或者DCWU。由以上定义,ZI属性的包含于RW属性,它是有初始值的RW数据。又例如在C语言中,代码为RO,静态变量和全局变量是RW,ZI的。

 点击看大图

ARM开发经验(三)
发表于 2005-11-28 9:14:09
注:这个连载的版权属于自控所158所有。转载的时候请注明。转载需要通过作者本人同意。 

/*
*********************************************************************************************************
*                                               Programming Arm
*
*
*                             (c) Copyright 1992-2008, 西安交通大学
*                                          All Rights Reserved
*
*                                              自控研究所158
*
* 文件      : 连载三
* 版本   : V1.00
* 作者      : 潘自强
*
* 对象      : ARM7
* 模式      : ARM
* 工具      : ADS1.20
*********************************************************************************************************
*/

4.1.2 Image 的Load view 和 execution view
在下载的时候Image regions被放置在memory map当中,而在执行Image前,或许你需要将一些regions放置在它们执行时的地址上,并建立起ZI regions。例如,你初始化的RW数据需要从它在下载时的在ROM中的地址处移动到执行时RAM的地址处。
1 附图: tu2.jpg (640566 字节)
点击看大图
NOTE Load view 和execution view的详细定义见ADS_LinkerGuide 3-4
以上的描述包括二个内容,一是要指定各个section在load view和execution view时的地址即memory map,二是要在执行前根据这些地址进行section的初始化。
4.1.3 制定Memory map
制定memory map的方法基本上有二种,一是在link时使用命令行选项,并在程序执行前利用linker pre-define symbol使用汇编语言制定section的段初始化,二是使用scatter file。以上二种方法依应用程序的复杂度而定,一针对简单的情况,二针对复杂的情况。
ARM开发经验(四)精华帖
注:这个连载的版权属于自控所158所有。转载的时候请注明。转载需要通过作者本人同 
意。

/*
******************************************************************************
***************************
*                                               Programming Arm
*
*
*                             (c) Copyright 1992-2008, 西安交通大学
*                                          All Rights Reserved
*
*                                              自控研究所158
*
* 文件      : 连载四
* 版本   : V1.00
* 作者      : 潘自强
*
* 对象      : ARM7
* 模式      : ARM
* 工具      : ADS1.20
******************************************************************************
***************************
*/

4.1.1.1 利用linker pre-define symbol使用汇编程序
这是简单的方法,针对简单的memory map。在link时使用选项-ro, -rw, 等等指定memory map的地址。详细说明参看ADS_LinkerGuide中命令行选项说明。然后利用汇编使用pre-define symbol,来进行各种段的定位。Linker pre-define定义如下:
1 附图: tu1.jpg (22811 字节)
点击看大图
由前面对ZI的说明,Image$$RW$$Limit = Image$$ZI$$Limit。
2 附图: tu2.jpg (30577 字节)
点击看大图
这些都是linker预先定义的外部变量,在使用的时候可以用IMPORT引入。下面给出一个例子。
假设linker 选项为:-ro-base 0x40000000 -rw-base 0x40003000。程序和只读变量(const 变量)大小为0x84,这样RO section的大小为0x84 bytes。Data的大小为0x04 bytes,并且data被初始化,则RW section的大小为0x04,ZI section的大小为0x04。这样程序
在load view,地址是这样的:
0x40000000开始到地址0x40000080,是RO section部分(程序从0x40000000开始),Image$$RO$$Limit = 0x40000084.
0x40000084地址开始到地址0x40000084,是RW section部分。

在execution view,由linker的选项,各个section的地址是这样的:
RO section的地址不变。
RW section的起始地址应当为0x40003000,则Image$$RW$$Base = 0x40003000。
因为全部的0x04 bytes data被初始化,所以Image$$RW$$Limit = Image$$ZI$$Limt = 0x40003004。
现在要做的就是将RW section移到以0x40003000开始的地方,并且创造一个ZI section。
一个更通用的做法是:
首先比较Image$$RO$$Limit和mage$$RW$$Base,如果相等,说明execution view下RW section的地址和load view 下RW section的地址相同,这样,不需要移动RW section;如果不等,说明需要移动RW section 到它在execution view中的地方。然后将Image$$ZI$$Base地址到Image$$ZI$$Limt地址的内容清零。
示例代码如下:
;读入linker pre-define symbols

IMPORT |Image$$RO$$Limit|
IMPORT |Image$$RW$$Base|
IMPORT |Image$$ZI$$Base|
IMPORT |Image$$ZI$$Limit|

; .......一些其他的代码或伪指令

;R0读入section load address
LDR R0,=|Image$$RO$$Limit|
;R1读入section execution address
LDR R1,=|Image$$RW$$Base|
;R2读入execution section 后的紧跟的word address
LDR R2,=|Image$$ZI$$Base|
;检查RW section的地址在load view和execution view下
;是否相等,如果相等,就不移动RW section,直接建立
;ZI scetion
CMP R0,R1
BEQ do_zi_init

;否则就copy RW section到execution view下指定的地址
BL copy

; ......
; ......

;copy 是一个用于copy的子函数,它把从R0中的地址开始的
;section copy到R1中的地址开始的section,这个section的
;上限地址后紧跟的word address保存在R2中
copy
CMP R1,R2
LDRCC R3,[R0],#4
STRCC R3,[R1],#4
BCC copy
MOV PC,LR

; ......
; ......
;do_zi_int子函数是为创建ZI section做一些准备工作
do_zi_int
;将ZI section开始的地址装入R1
LDR R1,=|Image$$ZI$$Base|
;将ZI section结束后紧跟的word address装入R2
LDR R2,=|Image$$ZI$$Limit|
;将ZI section 需要的初始化量装入R3
MOV R3,#0
BL zi_int


; ......
; ......
;zi_int子函数用于建立并初始化ZI section,ZI section的
;开始地址储存在R1,ZI section结束后紧跟的word address
;地址储存在R2

zi_int
CMP R1,R2
STRCC R3,[R1],#4
BCC zi_int
MOV PC,LR

; ......
; ......
这个方法针对比较简单的应用,如果需要进行一个比较复杂的memory map,如下图,那么这个方法就不适用了。为了解决复杂memory map的问题
需要用到scatter load 机制。
3 附图: tu3.jpg (32473 字节)
点击看大图 

 
系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(585) | 回复(0)

0

关于投票
实现一个最简单的嵌入式操作系统

实现一个最简单的嵌入式操作系统(一)精华帖

实现一个什么都不能做的嵌入式操作系统

1.首先确定CPU,在这里为了简单,就选用嵌入式的CPU,比如ARM系列,之所以用RISC(简单指令集)
类型的CPU,其方便之处是没有实模式与保护模式之分,采用线性的统一寻址,也就是不需要进行段
页式内存管理,还有就是芯片内部集成了一些常用外设控制器,比如以太网卡,串口等等,不需要像
在PC机的主板上那么多外设芯片
2.确定要实现的模块和功能,为了简单,只实现多任务调度(但有限制,比如最多不超过10),实
现中断处理(不支持中断优先级),不进行动态SHELL交互,不实现动态模块加载,不实现fork之类
的动态进程派生和加载(也就是说要想在你的操作系统上加入用户程序,只能静态编译进内核中;不
支持文件系统,不支持网络,不支持PCI,USB,磁盘等外设(除了支持串口,呵呵,串口最简单嘛),
不支持虚拟内存管理(也就是说多任务中的每个进程都可以访问到任何地址,这样做的话,一个程序
死了,那么这个操作系统也就玩完了)
3.确定要使用的编译器,这里采用GCC,文件采用ELF格式,当然,最终的文件就是BIN格式,GCC和
LINUX有着紧密的联系,自己的操作系统,需要C库支持和系统调用支持,所以需要自己去裁剪C库,
自己去实现系统调用
4.实现步骤:首先是CPU选型,交叉编译环境的建立,然后就是写BOOTLOADER,写操作系统

实现一个最简单的嵌入式操作系统(二)精华帖

如何实现BOOTLOADER

1.之所以要实现一个专用的BOOTLOADER,一是为了更好的移植和自身的升级,二是为了方便操作系统的调试,当然,你完全可以将这部分所要实现的与操作系统相关的功能集成到操作系统中去
2.确定一个简单的BOOTLOADER所要完成的功能:我们这里只需要完成两个主要功能,一是将操作系统加载到内存中去运行,二是将自己和操作系统内核固化到ROM存储区(这里的ROM可以是很多设备,比如嵌入式芯片中的FLASH,PC机上的软盘,U盘,硬盘等)

3.BOOTLOADER的编写:
第一步:要进行相关硬件的初使化,比如在at91rm9200这块嵌入式板子上(以后都使用这一款芯片,主要是我对这款芯片比较熟悉,嘿嘿),大概要做接下来的几方面的工作,其一:将CPU模式切换进系统模式,关闭系统中断,关闭看门狗,根据具体情况进行内存区域映射,初始化内存控制区,包括所使用的内存条的相关参数,刷新频率等,其二:设定系统运行频率,包括使用外部晶振,设置
CPU频率,设置总线频率,设置外部设备所采用的频率等。其三:设置系统中断相关,包括定时器中断,是否使用FIQ中断,外部中断等,还有就是中断优先级设置,这里只实现两个优先级,只有时钟中断高一级,其它都一样,而中断向量初始化时都将这些中断向量指向0x18处,并关闭这里的所有中断,如果板子还接有诸如FLASH设备的话,还需要设置诸如FLASH相关操制寄存器,其四:需要关闭CACHE,到此为止,芯片相关内容就完成初始化了

第二步:中断向量表,ARM的中断与PC机芯片的中断向量表有一点差异,嵌入式设备为了简单,当发生中断时,由CPU直接跳入由0x0开始的一部分区域(ARM芯片自身决定了它中断时就会跳入0x0开始的一片区域内,具体跳到哪个地址是由中断的模式决定的,一般用到的就是复位中断,FIQ,IRQ中断,SWI中断,指令异常中断,数据异常中断,预取指令异常中断),而当CPU进入相应的由0x0开始的向量表中时,这就需要用户自己编程接管中断处理程序了,这就是需要用户自己编写中断向量表,中断向量表里存放的就是一些跳转指令,比如当CPU发生一个IRQ中断时,就会自动跳入到0x18处,这里就是用户自己编写的一个跳转指令,假如用户在此编写了一条跳转到0x20010000处的指令,那么这个地址就是一个总的IRQ中断处理入口,一个CPU可能有多个IRQ中断,在这个总的入口处如何区分不同的中断呢?就由用户编程来决定了,具体实现请参见以后相关部分,中断向量表的一般用一个vector.S文件,当然,如何命名那是你自己的喜爱,但有一点需要声明,那就是在链接时一定要将它定位在0x0处


第三步:设置堆栈,一般使用三个栈,一个是IRQ栈,一个是系统模式下的栈(系统模式下和用户模式共享寄存器和内存空间,这主要是为了简单),设置栈的目的主要是为了进行函数调用和局部变量的存放,不可能全用汇编,也不可能不用局部变量


第四步:将自己以后的代码段和数据段全部拷贝至内存,并将BSS段清零


第五步:进行串口的初始化(主要是为了与用户交互,进行与PC机的文件传输),FLASH的初始化这里在FLASH中存放BOOT和内核),FLASH驱动的编写(这里的驱动有别于平常所说的驱动,由于FLASH不像SDRAM,只要设定了相关控制器之后就可以直接读写指定地址的数据,对FLASH的写操作是一块一块数据进行,而不是一个字节一个字节地写,具体请查阅相关资料)
第六步:等待一定的秒数,来接收用户进行输入,如果在指定的秒数内用户未输入任何字符,那么
BOOT就开始在FLASH中的指定位置(可以由自己指定,这么做主要是为了简单)读取内核的所有数据到内存中(具体是内存中的什么位置由自己指定,也可以采用LINUX之类的做法,就是在内存的起始位置加上一个0x8000处),将跳转到内核的第一条代码处);如果用户在指定的秒数内键入了字符(这主要是为了方便开发,如果开发定型之后完全可以不要这段代码),那么就在串口与用户进行交互,接受用户在串口输入的命令,比如用户要求下载文件在FLASH中指定的位置等,具体内容可参考U-BOOT之类的开源项目到这里为止,BOOT部分已完成,这个BOOT非常简单,仅仅只是将PC机上传下来的文件固化到FLASH中,然后再将FLASH中的操作系统内核部分加载进内存中,并将CPU的控制权交给操作系统,下一页开始讲解如何写一个最简单的操作系统,呵,到现在才开始切入正题呢!!!!

实现一个最简单的嵌入式操作系统(三)

如何实现一个最简单的操作系统

这里为了简单,就不考虑可移植性开求,不从BOOT部分来接收参数,也不对硬件进行检测,
也不需要进行DATA段,代码段的重定位。我只是读了LINUX内核相关部分,并未自己去实现
一个操作系统,所以我以下所说的只是概念性的东西:


1.接管系统的中断处理,由于BOOT部分的代码决定了那个中断向量表,从而决定了系统中断
之后进入的内存位置,但BOOT并不知道操作系统的中断处理函数位置所在啊,怎么办呢?
有几种方法,其一是:如果你的板子可以重映射地址,也就是可以将内存条所在的位置
重映射成0x0开始,那么在链接内核的时候,就将操作系统自己的中断向量表定位在0x0处
并且在BOOTLOADER引导结束时就完成映射操作,并让CPU跳转到0x0处执行;如果没有重映
射功能,我就不晓得怎么办了,不过我想到一个折衷的办法,就是在BOOTLOADER启动完成
时(也就是将CPU控制权交给操作系统内核时),重新改写FLASH的0x0区域,就是将操作
系统的内核的中断向量表写入FLASH区的0x0处,比如,当一个IRQ发生时,CPU决定了会
跳入0x18(假设这里FLASH占用地址总线0x0至0x0fffffff,内存占用0x20000000至0x2fffffff)
,而BOOTLOADER在最后将0x18处的代码修改成了0x20000000加上0x18的地址处的代码,而这个
地址就是内核的中断向量表中的相关跳转指令,就相当于跳转进了内核所关联的IRQ处理函数
的地址上去执行中断处理函数了,而这样的不好之处在于:当系统重新上电之后,BOOT的
中断向量表已经被修改,除非BOOT本身不使用中断,呵,在这样简单的系统中,BOOT是不
需要中断功能的

2.这里为了简单,所以没有使用分页内存管理,就不需要建立页表等操作,直接进行操作
系统的堆栈设置,同BOOT一样的设置过程一样,接着就进行BSS段清零操作,这里的BSS段
是指操作系统自身的BSS段,与BOOT的BSS段是同一个含义只是用在了不同的地方了,接着
就跳入了MAIN函数

3.为了最大可能的简单,采用静态建立任务结构数组,比如只建立十个任务,那么首先要
为这十个任务结构分配段内存,可以在堆上分配(这个分配的内存直到操作系统结束才会
被释放,当然也可以指定一片操作系统的其它地方都用不到的内存区域,不过这样写的话
就有点外行的味道了,而符务结构数组的指针却是全局变量,存放在BSS段或者DATA段),
由于在上一步中已经分配了一个系统堆栈,那么我们这十个任务就分享这总体的堆栈区域
这里的重点就是如果定义每个任务结构数组里面的结构,可以参照LINUX的相关部分设计

4.中断处理:在第一步中已经确定了CPU进行相关的几类型的中断跳转地址,而相同类型
的中断却只有一个入口地址,这里的中断处理就会完成以几个动作:
其一:入栈操作,包括所有寄存器入栈,至于这个栈,就是在第二步中所设置的IRQ栈,
其二:屏掉所有中断,呵,这里为了简单起见,所以在处理中断时不允许再次发生中断
其三:读取中断相关的寄存器,判别是发生了什么中断,以至于跳进相关的中断处理函
数中去执行(在这里只包括两种中断,一是时钟中断,另一个是SWI中断,也就是所谓
的系统调用时需要用到的)
其四:等待中断处理完成,然后就开启中断并出栈,恢复现场,将CPU控制权交给被中断
的代码处
注意:
其一:在MIAN中必须首先确定整个系统有哪些需要处理的中断,也就是有哪些中断处理
函数,然后才编写这里的中断处理函数
其二:本操作系统不处理虚拟内存,其至连CPU异常都不处理(一切都为了简单),一旦
发生异常,系统就死机

5.对TIMER的实现,首先确定时间片,为了让系统更稳定,而且我们不需要实时功能,尽
可能让时间片设置长一点,比如我们让一个任务运行20个时钟滴答数,然后应根据系统
频率来确定每个系统滴答所占用的毫秒,这里使用5毫秒让系统定时器中断一次,那么就
需要写时钟寄存器,具体参阅芯片资料,计算下来,一个任务最大可能连续运行100毫秒
,注意:我们的操作系统不支持内核抢占,同时只支持两级中断优先级,就是只有时钟
中断的优先级高一点,其它的优先级都低一级,但是在中断处理一节中却屏掉了这个功能
因为一进入中断处理,就禁止中断,所以不管其它中断优先级有多高都没有用的,这样做
优点是简单了,但不好之处显而易见,特别在相关中断处理函数如果进入了死循环,那么
整个系统就死了,而且时间片也变得不准确了,反正都不用实时,也不需要实时钟支持嘛
至于中断优先级设置请参阅芯片资料


6.进程调度的实现,也就是do_timer函数(时钟中断处理函数),有一个全局变量指针,
指向的就是当前任务结构数组(或者链表),当时钟中断时,就进入此函数中,首先判断
任务结构体中的时间片是否用完,如未用完,就减一,然后退出中断,让CPU继续运行当
前的任结构,若用完了时间片,就重置时间片,并重新寻找任何结构数组中的下一个等待
运行的任务,若找到了,就切换至新的任务,至于如何切换,请见下一页描述,如果未找
到就切换到IDLE任务(类似于LINUX,呵呵,所有的处理就是模仿LINUX,由于本人水平太
差,所就不能自创一招),注意:为了简单,所以没有实现任务优先级,也未实现任务
休眠等,也就是说只要静态地决定了有十个任务,这十个任务就按先后顺序一个一个执行
而且每个任务都不允许结束,就是说在每个进程中的最后一句代码都必须用死循环,不然
的话系统就跑飞了),还有一点,进程不支持信号,没有休眠与唤醒操作,这个CPU就是
不停地在运行,呵呵,反正CPU又不是人,所以不需要人权的哈!!!这种调度是不是简
单得不能再简单了?????!!!!

7.串口不使用中断,这就是最大可能的降低难度,串口使用论询的方式来实现读写(当
然是阻塞的方式了哦,而且只有写,不允许读,因为读的时候需要涉及到采用中断方式,
因为轮询方式有个不好的地方,那就是正在读的时候,这里有可能当前进程的时间片用
完了,系统切换到另一个进程,这里你在PC机的串口输入的数据就丢弃了,唉,又是为
了简单嘛)

8,最后一步就是MIAN函数的最后一部分,将本进程当作IDLE进程(相当于修改任务结构
数组中的数据),开启中断,将当前进程加入一段死循环,以免它退出去。

9.编译你的BOOTLOADER,KERNEL,并烧写至FLASH,反复调试

10.至此将你的at91rm9200(或者是其它相类似的芯片)的串口接上PC机,打开超级终端,
打开板子电源,说不定你的操作系统就打印出了"hello,world"了!!!一个最简单的操作
系统就出来了

下一页是具体的功能模块实现

任务结构数组(或链表)的实现  
   
  我们的任务结构就采用链表形式吧,但其长度是限定了的,头指针是一个全局指针变量(  
  指针变量是一个无符号整型指针,其指针本身所在的地址是在BSS段,但其指向的内容是分  
  配在堆上的一片内存),分配内核内存的函数就用kmalloc吧,kmalloc函数需要自己编写  
  呵,为了简单,这个函数只接受一个参数,就是所需分配大小,这个函数做得很简单,首先  
  有一个全局针指,它在初始化时指向了整个堆的起始位置,并且固定大小,就是所谓的内核  
  堆栈,在内核堆栈之后就是用户堆栈,由于总共有十个任务,当然不包括内核本身的任务,  
  所以整个堆栈就平均分成十一部分,注意:在所有任务初始化完成之后,还有一个步骤就是  
  将内核这个任务移到用户态,相当于要将自己的任务结构的堆栈指针修改一下就行了),  
  判断大小是否超出了内核堆的可分配范围,还有一点,需要维护内核堆和其它任务的堆,  
  需要进行分块,并且有一个全局的内存使用标识,就用数组吧,简单,0表示相应的内存  
  部分未占用,1就表示占用,对应的kfree就相当于把标志置0),  
  对于内存的维护,比较复杂,为了简单,就定为4K,并且不能进行大于四K的内存申请,因为  
  大于4K之后,由于没有虚拟地址的概念,就不能实现堆上的连续分配地址,当然在栈上分配  
  是可以大于4K的,栈是由编译器和CPU所决定了的  
   
  任务结构包括:  
  1.所剩的时间片  
  2.本任务所指向的代码段内存地址,这里也就是函数入口地址  
  3.本任务所指向的数据段地址,这里的数据段被包含进了整个内核中,所以并没有用,作为保留  
  4.本任务的函数体是否存在,也就是否会被调度  
  5.本任务所使用的栈指针  
  6.本任务所使用的堆指针  
  7.本任务的标识,用0代表是IDLE,1代表是其它进程  
  8.所有寄存器的值  
  9.当前PC值,初始化时被置成了函数入口地址  
   
  首先讲解一下任务数组结构的初始化:  
  将先定义一个全局指针,然后将此指针强制转换为一个任务结构指针,并通过kmalloc函在内核所  
  占用的堆(前而讲过内核的堆的起始就是整个堆的起始)上去分配十个任务结构所占的内存,这里  
  是绝不会超过4K的  
  并且为这十个任务结构赋值,将第一个任务置为IDLE,时间片为20,代码段内存地址为main函数的  
  的地址,数据段地址忽略,函数体存在,可以被调度,栈指针指向的位置根据以下来计算:  
  假定每个给每个任务可使用的堆栈设定为64K,而整个堆的起始位置是0x20030000,那么第一个堆指  
  针所指向的就是0x20030000,栈就是0x20030000+64K的位置,第二个以后就以此类推  
  注意:在初始化任务结构之前,不允许系统使用堆,但可以使用栈,那么内核任务栈部分就分成了  
  两个,在未进行调度之前,栈就是上一页中第二步中所设的栈,那么上一页设置堆栈的时候就得注  
  意必须将堆栈空间设成十个64K再加上在本步骤使用以前的最大可能所需的栈空间  
   
   
  再讲解一下任务切换时所要做的事情:  
  进入整个中断处理入口时,会将所有寄存器推入IRQ栈之中,并把值拷贝到当前任务结构相应的字段  
  当中,并取出被中断的进程的当前PC值存入当前任务结构中的相应字段中,接下就判别中断类型,  
  以进入相应的中断处理函数,这里就会进入do_timer函数中,以下就是进入此函数之后的流程:  
  内核中还有一个全局指针,就是当前任务指针,它本身也是在系统BSS段中,它的定义如上一步中的  
  那个全局指针一样,当由系统时钟中断之后,就取出这个全局指针,上一步初始化完成之后,还会  
  把这个指针指向第一个任务结构所在位置,也就是0x20030000处,那么就取出这个任务结构中的时  
  间片字段,判断其是否为0,若为0,就进行以下的操作:  
   
  保存用户态下的栈指针至当前任务结构,保存堆指针,并将搜索一下可以被调度的任务结构,并将  
  此任务结构赋给当前任务指针,置需要进行任务切换标识,此标识同样是一个全局变量,但它是被  
  赋了初值,会放在整个系统的DATA段中,返回do_timer函数。  
   
  若不为0,就进行以下操作:  
  将时间片减一,返回do_timer函数  
   
   
  接下来判断任务切换标识,若为0,则进行以下操作:  
  不需要进行任务切换,所有寄存器出栈(这里的栈指的是IRQ栈),重新开启中断,切换到用户模式,  
  加载当前任务结构中的当前PC值字段,以退出中断处理程序  
   
  若此标识为1,则执行以下操作:  
  就需要进行任务切换,让所有寄存器出栈(这里的栈指的是IRQ栈),将当前任务结构中的所有寄  
  存器的值恢复到相应寄存器中,将用户态下的栈指针恢复至当前任务结构栈指针,将堆指针恢复至  
  当前任务结构堆指针,并把需要进行任务切换标识恢复为0,重新开启中断,切换到用户模式,任务  
  切换是通过加载PC值来实现的,也就是通过加载当前任务结构中的当前PC值字段,以退出中断处理程序  
   
  系统调用的实现  
   
  本系统是完全可以不实现系统调用的,因为没有实现内核态和用户态的保护,完全可以不实现  
  自己的C库,所有的函数都像kmalloc之类的实现一样,在内核中直接写函数原型,但为了以后  
  扩展,还是说一下系统调用,这里以malloc系统调用来实现  
   
  首先说明还有一个堆指针(前面在kmalloc时有一个堆指针,不过那个堆指针是为内核任务,中  
  断处理所提供),这里这个堆指针是用于用户态的,它在系统初始化完成之前会赋上初值,其初  
  值就是第一个任务结构所使用的堆的起始位置,也就是在内核所使用的堆加上64K的位置  
  函数库中的malloc函数实现步骤如下:  
  1.首先检测申请大小是否超出了4K,若超出4K,就返回错误  
  2.进行系统调用(这里用_syscall1,并只传递一个参数(所需分配大小)  
  系统调用函数_syscall1的实现:  
  1.将寄存器压入堆栈(这里的栈指向就是当前任务的栈)  
  2.将系统调用号1放至R0,参数放入R1  
  3.发出SWI指令以产生SWI中断(就是所说的软中断,陷阱)  
  此时系统发生中断,会进入SWI中断处理入口,下面说一下SWI入口函数的实现  
  1.取出R0的值,判断其值,进入相应的分支处理代码段  
  2.在此进入_malloc处理代码段,取出R1的值,然后再得到前面所说的当前堆指针,并申请对应数  
  据块大小,置用于内存占用标识的相应字段,将当前堆指针放入R0,移动当前堆指针,改变当前任  
  务结构的堆指针,切换到用户态,返回SWI中断系统调用_syscall1的返回处理:  
  为了简单,在从内核态返回用户态时,不再进行任务的重新调度,所以上面的步骤就相对简单  
  1.当从SWI中断返回后,系统就运行在了用户态,此时取出R0的值,并赋值给需要申请内存的指针  
  2.在用户态弹出寄存器,返回到上一层函数  
  malloc函数的返回,此时malloc函数直接返回指针就行了,整个malloc的流程就结束了,其它的系  
  统调用同这个过程类似  
   
   
   
  到此为止,这个操作系统初步实现了,但好像什么事情都不能做,如果让它支持串口中断的话,或许  
  可以做那么一点点事情,比如像单片机那样的功能,整个系统的难点就是中断处理和任务切换,在本  
  例中,由于ARM不支持像0x86那样的CPU级的保护模式,所以进行任务切换的时候,就得自己通过加载  
  PC值的方法来实现,呵,因为我想不到更好的办法,但这个办法有一个不好解决的地方,就是寄存器  
  入栈和出栈的保护,在进入中断时,必须保护寄存器,但如果需要进行重新调度,就得从中断上下文  
  切换到进程上下文中,如何从中断上下文切换到进程上下文呢??我在这里所采用的方法很笨拙:  
  1.首先让寄存器入栈  
  2.让寄存器保存至当前任务结构数组,被中断掉的进程的PC值保存至任务结构  
  3.处理timer中断  
  4.如果进行任务切换,寻找下一个可调度的进程,然后把当前任务结构指下刚搜索到  
  的任务结构,让寄存器出栈,恢复当前任务结构里的值到寄存器,恢复堆栈指针,切换到用户态,通  
  过加载当前任务结构的PC值来恢复被挂起的进程  
   
  这里在中断上下文中使用了任务结构,这在LINUX上好像是不这样用的,中断上下文和进程上下文是两  
  个不同的概念,中断上下文中不能访问进程上下文里的任务结构,我实在想不出有什么办法来实现进程  
  调度了,所以请看到我这则文章的人提出好一点的方法  
   
  欢迎对LINUX有兴趣的人加入群讨论,群号:10074203  

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(537) | 回复(0)

0

关于投票
浅谈ARM启动代码分析

理解启动代码(ADS)
   所谓启动代码,就是处理器在启动的时候执行的一段代码,主要任务是初始化处理器模式,设置堆栈,初始化变量等等.由于以上的操作均与处理器体系结构和系统配置密切相关,所以一般由汇编来编写.
   具体到S64,启动代码分成两部分,一是与ARM7TDMI内核相关的部分,包括处理器各异常向量的配置,各处理器模式的堆栈设置,如有必要,复制向量到RAM,以便remap之后处理器正确处理异常,初始化数据(包括RW与ZI),最后跳转到Main.二是与处理器外部设备相关的部分,这和厂商的联系比较大.虽然都采用了ARM7TDMI的内核,但是不同的厂家整合了不同的片上外设,需要不同的初始化,其中比较重要的是初始化WDT,初始化各子系统时钟,有必要的话,进行remap.这一部分与一般控制器的初始化类似,因此,本文不作重点描述.
   在进行分析之前,请确认如下相关概念:
S64片上FLASH起始于0x100000,共64kB,片上RAM起始于0x200000,共16kB.
S64复位之后,程序会从0开始执行,此时FLASH被映射到0地址,因此,S64可以取得指令并执行.显然,此时还是驻留在0x100000地址.如果使用remap命令,将会把RAM映射到0地址,同样的这时0地址的内容也只是RAM的镜像.
S64的FLASH可以保证在最差情况时以30MHz进行单周期访问,而RAM可以保证在最大速度时的单周期访问.
OK,以下开始分析启动代码.

一,处理器异常
    S64将异常向量至于0地址开始的几个直接,这些是必需要处理的.由于复位向量位于0,也需要一条跳转指令.具体代码如下:
    RESET
         B      SYSINIT                       ; Reset
         B      UDFHANDLER           ; UNDEFINED
         B      SWIHANDLER            ; SWI
         B      PABTHANDLER         ; PREFETCH ABORT
         B      DABTHANDLER         ; DATA ABORT
         B      .                                      ; RESERVED
         B      VECTORED_IRQ_HANDLER
         B      .                                      ; ADD FIQ CODE HERE
 
 UDFHANDLER
         B      .
 
 SWIHANDLER
         B      .
 
 PABTHANDLER
         B      .
 
 DABTHANDLER
         B      .
 
 请注意,B指令经汇编后会替换为当前PC值加上一个修正值(+/-),所以这条指令是代码位置无关的,也就是不管这条指令是在0地址还是在0x100000执行,都能跳转到指定的位置,而LDR PC,=???将向PC直接装载一个标号的值,请注意,标号在编译过后将被替换为一个与RO相对应的值,也就是说,这样的指令无论在哪里执行,都只会跳转到一个指定的位置.下面举一个具体的例子来说明两者的区别:
     假定有如下程序:
      RESET
                  B      INIT       或者  LDR     PC,=INIT
                  …
 
      INIT
                  …
     其中RESET为起始时的代码,也就是这条代码的偏移为0,设INIT的偏移量为offset.如果将这段程序按照RO=0x1000000编译, 那么B INIT可理解为ADD  PC, PC, #offset,而LDR    PC,=INIT可被理解为 MOV PC,#(RO+offset) .显然当系统复位时,程序从0开始运行,而0地址有FLASH的副本,执行B    INIT将把PC指向位于0地址处的镜像代码位置,也即INIT;如果执行LDR   PC,=INIT将会将PC直接指向位于FLASH中的原始代码.因此以上两者都能正确运行.下面将RO设置为0x200000,编译后生成代码,还是得烧写到FLASH中,也就是还是0x100000,系统复位后从0地址执行,还是FLASH的副本,此时执行B    INIT,将跳到副本中的INIT位置执行,此处有对应的代码;但是如果执行LDR    PC,=INIT,将向PC加载0x200000+offset,这将使得PC跳到RAM中,而此时由于代码没有复制,RAM中的指定位置并没有代码,程序无法运行.

二,处理器模式
        ARM的处理器可工作于多种模式,不同模式有不同的堆栈 ,以下设置各模式及其堆栈.
         预定义一些参数:
MODUSR    EQU    0x10
MODSYS    EQU    0x1F
MODSVC    EQU    0x13
MODABT    EQU    0x17
MODUDF    EQU    0x1B
MODIRQ    EQU    0x12
MODFIQ    EQU    0x11

IRQBIT    EQU    0x80
FIQBIT    EQU    0x40

RAMEND    EQU    0x00204000  ; S64 : 16KB RAM

VECTSIZE  EQU    0x100       ;

UsrStkSz    EQU   8          ; size of  USR  stack
SysStkSz    EQU   128        ; size of  SYS  stack
SvcStkSz    EQU   8          ; size of  SVC  stack
UdfStkSz    EQU   8          ; size of  UDF  stack
AbtStkSz    EQU   8          ; size of  ABT  stack
IrqStkSz    EQU   128        ; size of  IRQ  stack
FiqStkSz    EQU   16         ; size of  FIQ  stack

修改这些值即可修改相应模式堆栈的尺寸.
以下为各模式代码:
SYSINIT
                                ;
        MRS    R0,CPSR
        BIC    R0,R0,#0x1F
  
        MOV    R2,#RAMEND
        ORR    R1,R0,#(MODSVC :OR: IRQBIT :OR: FIQBIT)
        MSR    cpsr_cxsf,R1     ; ENTER SVC MODE
        MOV    sp,R2
        SUB    R2,R2,#SvcStkSz
  
        ORR    R1,R0,#(MODFIQ :OR: IRQBIT :OR: FIQBIT)
        MSR    CPSR_cxsf,R1     ; ENTER FIQ MODE
        MOV    sp,R2
        SUB    R2,R2,#FiqStkSz

        ORR    R1,R0,#(MODIRQ :OR: IRQBIT :OR: FIQBIT)
        MSR    CPSR_cxsf,R1     ; ENTER IRQ MODE
        MOV    sp,R2
        SUB    R2,R2,#IrqStkSz

        ORR    R1,R0,#(MODUDF :OR: IRQBIT :OR: FIQBIT)
        MSR    CPSR_cxsf,R1     ; ENTER UDF MODE
        MOV    sp,R2
        SUB    R2,R2,#UdfStkSz

        ORR    R1,R0,#(MODABT :OR: IRQBIT :OR: FIQBIT)
        MSR    CPSR_cxsf,R1     ; ENTER ABT MODE
        MOV    sp,R2
        SUB    R2,R2,#AbtStkSz

        ;ORR    R1,R0,#(MODUSR :OR: IRQBIT :OR: FIQBIT)
        ;MSR    CPSR_cxsf,R1    ; ENTER USR MODE
        ;MOV    sp,R2
        ;SUB    R2,R2,#UsrStkSz

        ORR    R1,R0,#(MODSYS :OR: IRQBIT :OR: FIQBIT)
        MSR    CPSR_cxsf,R1     ; ENTER SYS MODE
        MOV    sp,R2            ;

三,初始化变量     
       编译完成之后,连接器会生成三个基本的段,分别是RO,RW,ZI,并会在image中顺序摆放.显然,RW,ZI在运行开始时并不位于指定的RW位置,因此必须初始化
        LDR    R0,=|Image$$RO$$Limit|
        LDR    R1,=|Image$$RW$$Base|
        LDR    R2,=|Image$$ZI$$Base|
1      
        CMP    R1,R2
        LDRLO  R3,[R0],#4
        STRLO  R3,[R1],#4
        BLO    %B1 

        MOV    R3,#0
        LDR    R1,=|Image$$ZI$$Limit|
2
        CMP    R2,R1
        STRLO  R3,[R2],#4
        BLO    %B2   

四,复制异常向量
        由于代码于RAM运行时,有明显的速度优势,而且变量可以动态配置,因此可以通过remap将RAM映射到0,使得出现异常时ARM从RAM中取得向量.
        IMPORT |Image$$RO$$Base|
        IMPORT |Image$$RO$$Limit|
        IMPORT |Image$$RW$$Base|
        IMPORT |Image$$RW$$Limit|
        IMPORT |Image$$ZI$$Base|
        IMPORT |Image$$ZI$$Limit|

                                         
COPY_VECT_TO_RAM
                        LDR    R0,=|Image$$RO$$Base|
  LDR    R1,=SYSINIT
  LDR    R2,=0x200000     ; RAM START 
0  
                        CMP    R0,R1
  LDRLO  R3,[R0],#4
  STRLO  R3,[R2],#4
  BLO    %B0 

这段程序将SYSINIT之前的代码,也就是异常处理函数,全部复制到RAM中, 这就意味着不能将RW设置为0x200000,这样会使得向量被冲掉.

四,在RAM中运行
        如果有必要,且代码足够小,可以将代码置于RAM中运行,由于RAM中本身没有代码,就需要将代码复制到RAM中:
        COPY_BEGIN
                        LDR    R0,=0x200000
  LDR    R1,=RESET        ; =|Image$$RO$$Base|
  CMP    R1,R0            ;
  BLO    COPY_END         ;
  
  ADR    R0,RESET
  ADR    R2,COPY_END
  SUB    R0,R2,R0
  ADD    R1,R1,R0
  
  LDR    R3,=|Image$$RO$$Limit|
3  
                        CMP    R1,R3
  LDRLO  R4,[R2],#4
  STRLO  R4,[R1],#4
  BLO    %B3
  
  LDR    PC,=COPY_END
  
COPY_END
 程序首先取得RESET的连接地址,判断程序是否时是在RAM中运行,方法是与RAM起始地址比较,如果小于,那么就跳过代码复制.
 在复制代码的时候需要注意,在这段程序结束之前的代码没有必要复制,因为这些代码都已经执行过了,所以,先取得COPY_END,作为复制起始地址,然后计算其相对RESET的偏移,然后以RO的值加上这个偏移,就是复制目的地的起始地址,然后开始复制.

五,开始主程序
        以上步骤完成,就可以跳转到main运行
        IMPORT Main

        LDR    PC,=Main
        B      .

六,器件初始化
        主程序首先要进行器件的初始化,对S64而言,应该先初始化WDT,因为默认情况下,WDT是打开的,然后是各设备的时钟分配,最后应该remap

 

 

EDN China技术论坛嵌入式系统 → 淺談 ARM启动代码分析

系统分类: ARM
用户分类: ARM
标签: 无标签
来源: 转贴
发表评论 阅读全文(878) | 回复(0)
Total , Page /