ARM启动过程详解

时间:2021-09-01 01:04:18

前言:本人因工作需要,首次接触到了ARM单片机,但因无人指导,走了不少弯路。下面这些笔记是我在一个多月的学习过程中总结的一点心得(可能比较乱,工作忙,没时间整理,各位朋友莫怪!),现在发到网上,与各位网友共享,希望对大家有点小小的帮助。本人购买的是上海勤研电子提供的ARM实验板,使用三星的S3C44B0X芯片,我在学习过程写的一些程序也参考了他们随板提供的一些源代码,特此致谢!

关于ARM和嵌入式我仍是个新手,下面的东东有些可能是错的。因此仅供参考!!并希望网友给予指正。也欢迎各位网友来信共同交流。

 

 

 

系统初始化流程如下:

禁止看门狗——》在中断控制器中屏蔽所有中断——》系统时钟设置——》初始化端口——》DMA设置——》cashe和总线设置——》存储器设置,初始化SDRAM——》初始化堆栈——》设置IRQ和FIQ的入口——》地址重映射

   

    通常系统初始化有两个阶段组成,分别为汇编和C写成。汇编应尽量简单一些,把更多的任务交给C来做,这样可增加整个程序的可读性和灵活性。必须由汇编来完成的任务有:异常中断向量表的设置、IRQ向量表(向量模式)或ISR初始化(非向量模式)、二级ISR地址表的定义、Flash和SDRAM的设置(否则系统无法加载代码)、堆栈设置和模式切换、拷贝RW和ZI代码、设置系统时钟等。而端口初始化、cashe和总线的设置、DMA配置以及其它控制器如LCD、UART、SIO、IP等可以在C中第二阶段初始化程序完成,另外也可以继续更改时钟或存储器配置等。下面是几个关键步骤配置的注意事项。

 

看门狗设置

    watch dog即可以作为普通的timer以产生周期性的中断,也可以周期性的产生reset信号(如果每隔一定时间不被清除的话),以防治程序跑飞。

 

系统时钟的初始化:

至少设置三个寄存器:LOCKTIME,PLLCON,LOCKCON。

LOCKTIME,地址0x01D8000C。用于指定PLL的初始化时间,在PLL初始化时,系统时钟为晶振输入或外部时钟直接提供,即MCLK=Fin;初始化完成后,切换,MCLK=Fout。初始值为0xfff=4095个输入时钟周期。一般将其设为初始值。

PLLCON,,地址0x01D80000。设置MDIV,PDIV,SDIV三个值,用于确定Fout和Fin的频率分配比值:Fout = (m * Fin) / (p * 2s),其中

m = (MDIV + 8), p = (PDIV + 2), s = SDIV

典型的几个值如下:

No.FinFoutMDIVPDIVSDIV

110Mhz40Mhz0x480x30x2

210500x2a0x30x1

310600x340x30x1

44600x340x00x1

53600x480x00x1

610750x3a0x30x1

 

 

CLKCON,地址0x01D80004。用于设置是否向外设提供时钟,一般设为默认值0x7ff8,即所有外设提供时钟。

 

存储器初始化(尤其是SDRAM):

ARM7TDMI的地址映射如下:

(在ARM*中,所有的各种内、外存储器,外设,寄存器,cashe,write buffer,通用IO口等全都采用统一编址)

注:0x10000000~0x100047f0为内部cashe/sram 及其Tag和LRU的地址。

 

BANK0~BANK5为ROM/SRAM/FLASH,

BANK6~BANK7为SDRAM/ROM/SRAM/FLASH

要设置的寄存器如下:

BWSCON:BANK0~BANK7的UB/LB使能、Wait信号使能、数据线宽度;

BANKCON0~BANKCON5:各bank(flash或Sram)的访问时序控制。flash或Sram主要参数如下所示:

Tacs [14:13] Address set-up before nGCSn

Tcos [12:11] Chip selection set-up nOE

Tacc [10:8] Access cycle

Toch [7:6] Chip selection hold on nOE

Tcah [5:4] Address holding time after nGCSn

Tpac [3:2] Page mode access cycle @ Page mode

PMC [1:0] Page mode configuration

不同厂家、性能、速度的器件设置有所不同。

BANKCON6~BANKCON7:主要用于SDRAM,当然也可以是Flash或SRAM。SDRAM的时序控制稍微复杂,还有:

Trcd [3:2] RAS to CAS delay

    SCAN [1:0] Column address number

当然也可用于DRAM。

 

REFRESH 地址: 0x01C80024,DRAM/SDRAM的更新控制寄存器;

MRSRB6 ~MRSRB7:DRAM/SDRAM的模式控制寄存器,这个寄存器在系统初始时,即SDRAM使用前必须被有效地的设置。

    这几个寄存器的设置比较复杂,应仔细阅读Samsang(page168)的数据手册和相关存储器的资料。

一个典型的配置如下:

ldr r0, =SMRDATA

ldmia r0, {r1-r13}

ldr r0, =0x01c80000 ; BWSCON Address

stmia r0, {r1-r13}

SMRDATA DATA

DCD 0x11222220 ; BWSCON Bank0=OM[1:0],8bits宽 Bank1~Bank5=32bit,Bank6~Bank=16bit,不使用UB/LB信号,WAIT disable; 使用little Endian存储格式

DCD 0x000056A8; GCS0 :Tacs=2clk;Tcos=2clk;Tacc=10clk;

Toch=2clk;Tcah=2clk;Tpac=4clk;PMC=normal(1data)

DCD 0x00000700 ; GCS1 除了Access cycle为14个clk外,其它均为0clk

DCD 0x00000700 ; GCS2

DCD 0x00000700 ; GCS3

DCD 0x00000700 ; GCS4

DCD 0x00000700 ; GCS5

DCD 0x00018005 ; GCS6, SDRAM;RAS to CAS delay 2 clk;Column address number:9bits

DCD 0x0001002a ; GCS7, EDO DRAM(Trcd=3, Tcas="2", Tcp="1", CAN="10bit")

DCD 0x00870441 ; Refresh enable;Auto Refresh; Trp="3", Trc="5", Tchr="3";

刷新计数:1019

DCD 0x17 ; SCLK power down mode enable;Bank6&7 Size, 16MB/16MB

DCD 0x20 ; MRSR 6:CAS Latency="2clk";burst type为线性(不支持交织访问);

burst number:1bit(不支持促发读写)

DCD 0x20 ; MRSR 7(CL=2)

 

注:三星的实验板中在nGCS0外接Flash,型号为SST39VF160,其datasheet中有其读写时序的详细说明和各种时间值的最大或最小值,但均以ns为单位,且各时间值的名称也与寄存器的要求不完全相同。要使Flash达到最优设置,必须读懂其时序并按其推荐值设置寄存器。显然这并不是件容易的事情。在本次实验板的boot程序中,其各时序值均是最大值给出。参见memcfg.h文件。

三星的实验板中在nGCS6外接SDRAM,型号为IC42S16800-7T,(4096ROW*512COLUM*4Bank*16bits=128Mbits=16MB)

由上面的例子可以看出需要设置的参数为:1.Banksize,UB/LB,WAITenable/disable,large/little Endian;

2.RAS to CAS delay, Column address number;

3.Refresh enable/disable,Auto/self refresh, SDRAM RAS pre-charge Time, SDRAM RC minimum Time, Refresh Counter;

4.power down mode,banksize;

4.CAS Latency,burst type,burst number。

 

Refresh counter的设置:Refresh period = (211-refresh_count+1)/MCLK

Ex) If refresh period is 16 us and MCLK is 60 MHz,

the refresh count is as follows;

refresh_count = 211 + 1 - 60x16 = 1089

 

上面的例子只是对Flash和SDRAM的一个经验设置值,可能不是最优的。最优设置还必须参考器件的数据手册。尤其是对于SDRAM的RAS to CAS delay、SDRAM RAS pre-charge Time、SDRAM RC minimum Time三个时序值。

 

特别注意:在线调试阶段,在AXD软件中必须引入对SDRAM初始化的seesion文件或ini文件,或者在command interface中敲入所需的配置命令(在load image之前完成),并且最后注释掉reset汇编程序中的初始化SDRAM的命令。否则程序就可能跑飞。而生成要下载的flash程序代码时,则系统reset时就必须完成此功能。

 

端口初始化

ARM的大部分信号在同一端口是功能复用的。为此初始化时必须指定各PA~PG口的各管脚的功能。在实验板根据外围器件的选择对各端口做如下配置:

PA(10bits):全部用作高端地址线;PCONA=0x3ff

PB(11bits):全部用作存储器控制信号;PCONB=0x7ff

PC(16bits):4~7用于LCD的VD4~VD7;12,13用于UART的TXD1,RXD1(注意:这里的UART没有使用CTS、RTS信号,只用了TX、RX);其它全部用于通用IO口,GPC0~3用于IIS;GPC10和14用于NAND Flash;GPC15用于USB Device;GPC8、9用于LCD;GPC11暂时没用。PCONC=0x5f55ff55.(这里假定IO口全为output,实际应用时再确认一下是In或是out)

PD(8bits):全部用于LCD的控制信号。PCOND=0xaaaa。

PE(9bits):8用于Endian,确定存储器格式;1,2分别为TXD0,RXD0;0,3~7用于通用IO口(暂定output),PE3用于蜂鸣器;PE4~7用于LED显示。PCONE=0x05569。

PF(9bits):0、1用于IIC总线的SCK、SDA信号;其它均为通用IO口(暂定output),其中GPF2~4用于IDE,GPF5~8用于触摸屏。PCONF=0x09255a。

PG(8bits):全部用于外部中断EXINT0~7。PCONG=0xffff。

注意:上述端口分配是S3C44b0x测试板(勤研电子)的分配情况。

 

另外还有下面几个寄存器需要设置:

上拉电阻寄存器,包括一些端口和数据线;

EXTIN:设置8个外中断的触发方式,low,high,rising or falling edge等。

EXTINTPND:中断待处理寄存器,用于解决EXTINT4~7共享一个中断源的问题。

 

 

Cache&Bus设置

通过内部寄存器可以设置cashe mode(cashe和sram),write buffer,non-cashable area,以及bus的优先级等。

通常,在一般的用户程序中不使用cashe(即全部用作Sram),禁用write buffer,bus优先级选择默认就可以了,即1. DRAM refresh controller-2. LCD_DMA-3. ZDMA0,1-4. BDMA0,1-5. External bus master-6. Write buffer-7. Cache & CPU。

只配置一下SYSCFG:0x0。

 

 

DMA配置

ZDMA在系统总线上,完成系统总线上器件的数据传送,如存储器。BDMA也有2个,桥接系统总线和外设总线,即可以完成分别位于两条总线上的器件的数据传送,也可完成外设总线上器件如SIO、UART、TIMER等之间数据传送。

DMA的四种数据传送方式。

DMA的触发选择:XDREQ/XDACK、S/W、H/W等。

一般地,DMA的初始化只需完成BDMA的目标地址寄存器的初始化:BDIDES0,1=0x40000000,即传送方向为内部存储器到外设,初始目的地址:0x0。

 

 

 

中断寄存器的配置和中断向量表的设计

中断有两种IRQ和FIQ,后者优先级高于前者。另外,ARM系统还做了些特殊安排以使FIQ有更快的响应速度,如FIQ的ISR可以直接放在0x1c(紧跟FIQ)开始的地址单元中,免去了跳转;属于FIQ的中断向量表可常驻cashe;FIQ较IRQ有更多的物理寄存器等。通常在简单的用户程序中,可以不使用FIQ,所有中断都设为IRQ(默认情况)。

ARM7有30个中断源,实际使用25个。其优先级如下所示:

 

一些重要的中断设置寄存器如下:

INTCON  0x01E00000 :中断控制。指定IRQ是否采用向量模式(一般采用非向量模式,这也是默认值)。指定CPU是否响应IRQ和FIQ。

INTPND:只读。指定中断源是否有中断请求,可以同时有多个中断请求。当对应的ISR结束时,通过向I_ISPC和F-ISPC写1来清除INTPND中对应的比特位,否则该中断将连续执行。

INTMSK:各中断源是否屏蔽。初始值时屏蔽。

INTMOD:指定各中断源是IRQ或FIQ,默认值全为IRQ。

I_PSLV:  0x01E00010   R/W    IRQ priority of slave register  0x1b1b1b1b

I_PMST:  0x01E00014   R/W    IRQ priority of master register  0x00001f1b

I_CSLV:  0x01E00018   R       Current IRQ priority of slave register  0x1b1b1b1b

I_CMST:  0x01E0001C  R       Current IRQ priority of master register  0x0000xx1b

I_ISPR:   0x01E00020   R      IRQ interrupt service pending register

I_ISPC:   0x01E00024   W      IRQ interrupt service clear register

F_ISPC:   0x01E00024   W      FIQ interrupt service clear register

优先级取默认值就可以了。

ISPR只读,指示当前被响应的中断源,没有或只有一个被响应,尽管此时INTPND中可能有几个中断请求。ISR结束时,通过向ISPC对应位写1来清除ISPR中的对应位。

在ARM7TDMI中,中断向量表的设置有两种模式:向量模式和非向量模式。前者只适于全IRQ的设置。采用非向量模式时,通过分析ISPR(发生中断时,其中只有一个位为1,其它全为0)找到要执行的ISR的入口地址。在向量模式中,当发生IRQ时,CPU自动产生跳转地址,如同异常中断的使用。各IRQ的一级ISR的跳转地址如下:

中断有异常中断(如:Dabort、Pabort、Undef等)和IRQ或FIQ两种。下面以向量模式下的IRQ为例介绍一下中断设置。

过程如下:IRQ中断向量表设置——>写一级ISR——>分配二级ISR的入口地址表——>写二级ISR——>把二级ISR的入口地址放到二级ISR的入口地址表中。这样在开中断的情况下,一个ISR就可以正常执行了。一个ISR的执行过程如下。

首先要在系统初始化时开中断:INTMSK各中断位清零且INTCON的IRQ位清零(使能)且CPSR的I比特清零(使能),缺一不可。中断发生时,首先由模式SYS或User切换至IRQ,同时完成现场保护(工作指针入栈、保存CPSR、PC->LR),然后PC直接跳到IRQ中断向量表的相应地址(一级ISR的入口),紧接着跳到一级ISR并执行;一级ISR通常由汇编写成,仅完成一个跳转任务(有时也看一下寄存器ISPR,判断该中断是否被错误触发,如果错误将直接返回),即从二级ISR的入口地址表中找到相应中断的入口地址,其间工作现场没有变化。二级ISR通常由c语言写成,中断的真正的响应程序就在此处。ISR结束时,要对INTMSK中的pending比特清零(通过置位ISPR中相应比特),否则将连续响应该中断。然后CPU自动切换至中断前的工作模式,并恢复现场。

 

在C语言中关键字”__irq”的作用:当ISR定义时有此关键字,则ISR结束后CPU自动从栈中恢复中断前模式的LR,并把它赋值给PC,完成ISR的正常返回。如果无此关键字,则CPU只能返回到二级ISR前的中断状态,此时仍为IRQ工作模式。当然也能够继续执行用户程序,只是工作模式不对,此模式下再不能响应其它IRQ中断。

事实上,CPU响应中断并执行ISR相当于一个程序调用过程。用户程序不必干预CPU的模式切换、现场保护、程序返回。

 

中断向量表的设置。一级中断向量表紧跟异常中断向量表,位于0x20~0xc0。只读。由于S3C44b0x没有MMU和地址映射功能,该中断向量表必须和异常中断向量表一起固化到系统地址空间的0x0处,即Flash的起始处。在线调试阶段也必须保证该表存在于Flash中。二级ISR的地址表一般位于RAM空间的最后256个字节处,紧跟在堆栈后,在汇编语言中由MAP语句创建(8个异常中断和25个IRQ,共33×4=132Byte),可读写。同时在c中定义一组指向相同地址空间的无符号型指针,当然指针名称必须和汇编中的定义相同。这样在C中的ISR初始化程序中,可直接把二级ISR的入口放到地址表中。如:pISR_EINT0=(unsigned)Isr_Eint0; pISR_EINT0为地址表中的指针,而Isr_Eint0为ISR的名称,也是其入口地址。二级ISR地址表和一级表不同的是,其各中断的顺序可任定,但必须保证汇编和C中的定义一致。

对于非向量模式,不使用IRQ中断向量表,但二级ISR地址表的设置是相同的。在本测试程序中boot.s同时包含了两种格式的设置,只要设置好INTCON中的mode比特,两种模式都可以用。注意非向量模式,在汇编中要设置IRQ和FIQ的入口地址。因为在非向量模式要靠IsrIRQ和IsrFIQ来定位响应的中断源位置。

    另外,为了保证开中断后,程序不至于跑飞,最好编写所有的IRQ 的ISR,该ISR可以是个空函数,确保能正常返回就行了。

 

堆栈初始化和工作模式的切换

ARM7TDMI有7种工作模式,要用到6个stack,其中SYS和User共用一个Stack。堆栈设置采用流行的FD模式(full decresment)。通常放在RAM空间的次最高段(最高的256B为ISR的地址表),在16M的SDRAM中,各stack设置如下:

0x0cff_f000~0x0cff_fa00: Uers and SYS stack,2560B,够大了。

0x0cff_fa01~0x0cff_fb00: SVC stack, 256B;

0x0cff_fb01~0x0cff_fc00: Undef stack, 256B;

0x0cff_fc01~0x0cff_fd00: Abort stack, 256B;

0x0cff_fd01~0x0cff_fe00: IRQ stack, 256B;

0x0cff_fe01~0x0cff_ff00: FIQ stack, 256B;

0x0cff_ff01~0x0cff_ffff: ISR地址表, 256B;

 

CPU的模式切换通常由异常中断产生,或者在SVC或SYS模式下完成。User模式中用户程序不能改变工作模式(除了应用异常中断,如SWI),当然也不能改变CPSR的值(也就不能开关中断了!)。通常如果不用嵌入式OS,单任务的用户程序工作在SYS或SVC模式下更好一些,这样可以更方便的使用硬件资源。如果使用SVC模式,甚至可以不设置SYS and User stack。

系统加电重起时,首先进入SVC模式,完成初始化,在调用C的main函数之前再切换到SYS或User模式。因此可以把堆栈初始化放到最后执行,并最后设置SYS stack,这样进入main之后可以直接工作在SYS模式下。本测试程序就是如此设置的。

 

 

 

分布式加载

ADS1.2中的ARM linker支持分布式加载,即加载域(load)和执行域(image)的各个输出段(RO、RW、ZI)可以有不同的地址。可以很方便的生成供在线调试和下载的elf格式的文件。通常总线调试只需设置RO base=0x0c000000;而生成下载代码则要设置RO base=0x0,RW base=0x0c000000,并且一定要把boot.o设成first section,否则程序入口不在0x0则无法完成异常中断和普通中断,包括reset。至于ropi、rwpi、split的应用参见linker的有关资料。

链接器同时产生一组符号,给出各个域或者各个输出段的区间的长度,装载地址和执行地址。由于链接器和C库都没有将代码从它的装载区间拷贝到执行区间,或创建一个零初始化区域的功能,所以要由应用程序员利用这组符号产生的信息完成这项工作,这是在呼叫C程序之前必须完成的,举例如下:

LDR r0, = |Load$$DRAM$$Base|

LDR r1, = |Image$$DRAM$$Base|

CMP r0, r1  检查装载地址和执行地址是否相同

BEQ do_zi_init  相同,则不拷贝该区间,初始化零数据区

MOV r2, r1 ; 不相同,将装载区拷贝到执行区

LDR r4, = |Image$$DRAM$$length|

ADD r2, r2, r4

BL copy

do_zi_init

LDR r1, = |Image$$DRAM$$ZI$$Base|

MOV r2, r1

LDR r4, = |Image$$DRAM$$ZI$$length|

ADD r2, r2, r4

MOV r3, #0

BL zi_init  调用零初始化子程序

 

上例中使用了ARM Linker产生的与域有关的几个符号

<script type="text/javascript"><!-- google_ad_client = "pub-9447242586152886"; google_ad_output = "textlink"; google_ad_format = "ref_text"; google_cpa_choice = "CAEQ9cm1_wEQABoIrVSJdtyXBh0o6bzhhwEo6bzhhwEwAA"; //--> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script> <script language="JavaScript1.1" src="http://pagead2.googlesyndication.com/cpa/ads?client=ca-pub-9447242586152886&amp;cpa_choice=CAEQ9cm1_wEQABoIrVSJdtyXBh0o6bzhhwEo6bzhhwEwAA&amp;oe=gb2312&amp;dt=1188974542218&amp;lmt=1188974542&amp;format=ref_text&amp;output=textlink&amp;correlator=1188974542171&amp;url=http%3A%2F%2Fwww.icwin.net%2Fbbs%2Fdispbbs.asp%3FboardID%3D61%26ID%3D4803%26keywords%3Darm%25E7%25AB%25AF%25E5%258F%25A3%25E8%25AE%25BE%25E7%25BD%25AE%25E7%25AD%2589....html&amp;region=_google_cpa_region_&amp;ref=http%3A%2F%2Fwww.google.cn%2Fsearch%3Fcomplete%3D1%26hl%3Dzh-CN%26inlang%3Dzh-CN%26newwindow%3D1%26q%3DUB%252FLB%25E4%25BF%25A1%25E5%258F%25B7%25E7%259A%2584%25E4%25BD%259C%25E7%2594%25A8%26meta%3D%26aq%3Dnull&amp;cc=4&amp;ga_vid=1143094070.1188974542&amp;ga_sid=1188974542&amp;ga_hid=495889518&amp;flash=9&amp;u_h=768&amp;u_w=1024&amp;u_ah=738&amp;u_aw=1024&amp;u_cd=32&amp;u_tz=480&amp;u_java=true" type="text/javascript"></script> <script type="text/javascript"><!-- google_ad_client = "pub-9447242586152886"; google_ad_width = 728; google_ad_height = 90; google_ad_format = "728x90_as"; google_ad_type = "text"; //2007-06-20: wantSo728 x 90X4Begining google_ad_channel = "6457988613"; //--> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script>