亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 3454 | 回復(fù): 0
打印 上一主題 下一主題

ARM下啟動代碼 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2011-12-22 08:51 |只看該作者 |倒序瀏覽
1、

IAR匯編指令SFB和SFE

SFB Segment begin 段開始

語法格式
SFB(segment [{+|-} offset])

參數(shù)
segment: 可重定位段的段名, 必須在SFB使用前已定義
offset : 從開始地址的偏移, 是一個可選參數(shù), 當偏移量省略時, 可以不添加小括號

描述
SFB 右邊可以接受一個操作數(shù), 而且這個操作數(shù)必須是一個可重位段的段名.
這個操作符計算段的首字節(jié)地址. 這個操作發(fā)生在連接時.

NAME demo
RSEG CODE
start: DC16 SFB(CODE)
即使上面的代碼和多個其他的模塊進行連接, start標號處仍被置為段的首字節(jié)地址

 

語法格式
SFE (segment [{+|-} offset])

參數(shù)
segment: 可重定位段的段名, 必須在SFB使用前已定義
offset : 從開始地址的偏移, 是一個可選參數(shù), 當偏移量省略時, 可以不添加小括號

描述
SFE在其右邊接收一個操作數(shù). 操作數(shù)必須是一個可重定位段的段名. SFE操作符將段起始地址和段大小相加. 這個操作在連接時發(fā)生.
SFE accepts a single operand to its right. The operand must be the name of a relocatable segment. The operator evaluates to the segment start address plus the segment size. This evaluation takes place at linking time.


NAME demo
RSEG CODE
end: DC16 SFE(CODE)
即使當上面的代碼被和多個模塊想連接時, end標號仍然會被置為段最后一個字節(jié)的地址. Even if the above code is linked with many other modules, end will still be set to the address of the last byte of the segment.

段MY_SEGMENT的大小可以通過以下方式計算而得:
SFE(MY_SEGMENT)-SFB(MY_SEGMENT)


arm匯編的跳轉(zhuǎn)指令無非是b和ldr。但是如果沒有足夠理解,別人靈活的用一下你就犯暈了。
首先我們要知道兩者的兩個本質(zhì)區(qū)別:
1、b是位置無關(guān)的,ldr不是位置無關(guān)的。
2、b的范圍只能是+—32MB,而ldr是4GB。
 
在arm的啟動匯編的中斷向量表是必然用跳轉(zhuǎn)指令的,但是就是這里也有很多實現(xiàn)形式:
方式1:
                B           InitReset           ; 0x00 Reset handler
undefvec:
                B           undefvec            ; 0x04 Undefined Instruction
swivec:
                B           swivec              ; 0x08 Software Interrupt
pabtvec:
                B           pabtvec             ; 0x0C Prefetch Abort
dabtvec:
                B           dabtvec             ; 0x10 Data Abort
rsvdvec:
                B           rsvdvec             ; 0x14 reserved
irqvec:
                B           IRQ_Handler_Entry   ; 0x18 IRQ
這個我很容易理解,實現(xiàn)的標號油InitReset和IRQ_Handler_Entry,其他沒有實現(xiàn)的在原地跳轉(zhuǎn)。
方式二:
        LDR     pc, =resetHandler        ; Reset
        LDR     pc, Undefined_Addr       ; Undefined instructions
        LDR     pc, SWI_Addr             ; Software interrupt (SWI/SYS)
        LDR     pc, Prefetch_Addr        ; Prefetch abort
        LDR     pc, Abort_Addr           ; Data abort
        B       .                        ; RESERVED
        LDR     pc, =irqHandler          ; IRQ
        LDR     pc, FIQ_Addr             ; FIQ
  LDR PC,[PC,#0x18]
Undefined_Addr: DCD   Undefined_Handler
SWI_Addr:       DCD   SWI_Handler
Prefetch_Addr:  DCD   Prefetch_Handler
Abort_Addr:     DCD   Abort_Handler
FIQ_Addr:       DCD   FIQ_Handler
我們注意到兩種ldr
一種LDR PC,=label,這時把LDR當做偽指令,他要被翻譯成:
LDR PC,[PC,offset_to_label2]
label2:DCD resetHandler
這種label是在程序中標記的了,如resetHandler和irqHandler
還有一種LDR PC,label,這時直接把label地址內(nèi)存內(nèi)容copy到PC中
這種label都是 label:DCD label2 ,這些label2可以在任何地方實現(xiàn)
 
我們來理解b和ldr兩者的不同
1,b是位置無關(guān),因為他的跳轉(zhuǎn)都是相對PC來的,而ldr不是位置無關(guān)的,因為他的跳轉(zhuǎn)是根據(jù)DCD里面的值,這個值是連接的時候確定的,他是跟連接地址有關(guān)的
2,b的范圍只能是32M,是因為指令里面給偏移的空間只有24bit,而ldr是一個32bit的DCD,所以他是32bit的
 
注意:像上面方式二
LDR     pc, =resetHandler        ; Reset
。
。
resetHandler:
 ....
 ....
如果運行地址是0,那么LDR     pc, =resetHandler還在以基址為0的空間運行
但是執(zhí)行完了,假設(shè)裝載地址是0x3000000,所以resetHandler的基址不是0,而是0x30000000
所以resetHandler標號以后的指令應(yīng)該存在以0x3000000為基址的空間,pc跳過去了
這種技巧在at91的remap模式經(jīng)常用到


理解啟動代碼(ADS)
  所謂啟動代碼,就是處理器在啟動的時候執(zhí)行的一段代碼,主要任務(wù)是初始化處理器模式,設(shè)置堆棧,初始化變量等等.由于以上的操作均與處理器體系結(jié)構(gòu)和系統(tǒng)配置密切相關(guān),所以一般由匯編來編寫.
  具體到S64,啟動代碼分成兩部分,一是與ARM7TDMI內(nèi)核相關(guān)的部分,包括處理器各異常向量的配置,各處理器模式的堆棧設(shè)置,如有必要,復(fù)制向量到RAM,以便remap之后處理器正確處理異常,初始化數(shù)據(jù)(包括RW與ZI),最后跳轉(zhuǎn)到Main.二是與處理器外部設(shè)備相關(guān)的部分,這和廠商的聯(lián)系比較大.雖然都采用了ARM7TDMI的內(nèi)核,但是不同的廠家整合了不同的片上外設(shè),需要不同的初始化,其中比較重要的是初始化WDT,初始化各子系統(tǒng)時鐘,有必要的話,進行remap.這一部分與一般控制器的初始化類似,因此,本文不作重點描述.
  在進行分析之前,請確認如下相關(guān)概念:
S64片上FLASH起始于0x100000,共64kB,片上RAM起始于0x200000,共16kB.
S64復(fù)位之后,程序會從0開始執(zhí)行,此時FLASH被映射到0地址,因此,S64可以取得指令并執(zhí)行.顯然,此時還是駐留在0x100000地址.如果使用remap命令,將會把RAM映射到0地址,同樣的這時0地址的內(nèi)容也只是RAM的鏡像.
S64的FLASH可以保證在最差情況時以30MHz進行單周期訪問,而RAM可以保證在最大速度時的單周期訪問.
OK,以下開始分析啟動代碼.

一,處理器異常
     S64將異常向量至于0地址開始的幾個直接,這些是必需要處理的.由于復(fù)位向量位于0,也需要一條跳轉(zhuǎn)指令.具體代碼如下:
     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指令經(jīng)匯編后會替換為當前PC值加上一個修正值(+/-),所以這條指令是代碼位置無關(guān)的,也就是不管這條指令是在0地址還是在0x100000執(zhí)行,都能跳轉(zhuǎn)到指定的位置,而LDR PC,=???將向PC直接裝載一個標號的值,請注意,標號在編譯過后將被替換為一個與RO相對應(yīng)的值,也就是說,這樣的指令無論在哪里執(zhí)行,都只會跳轉(zhuǎn)到一個指定的位置.下面舉一個具體的例子來說明兩者的區(qū)別:
      假定有如下程序:
       RESET
                   B       INIT        或者   LDR      PC,=INIT
                   …

       INIT
                   …
      其中RESET為起始時的代碼,也就是這條代碼的偏移為0,設(shè)INIT的偏移量為offset.如果將這段程序按照RO=0x1000000編譯, 那么B INIT可理解為ADD   PC, PC, #offset,而LDR     PC,=INIT可被理解為 MOV PC,#(RO+offset) .顯然當系統(tǒng)復(fù)位時,程序從0開始運行,而0地址有FLASH的副本,執(zhí)行B     INIT將把PC指向位于0地址處的鏡像代碼位置,也即INIT;如果執(zhí)行LDR    PC,=INIT將會將PC直接指向位于FLASH中的原始代碼.因此以上兩者都能正確運行.下面將RO設(shè)置為0x200000,編譯后生成代碼,還是得燒寫到FLASH中,也就是還是0x100000,系統(tǒng)復(fù)位后從0地址執(zhí)行,還是FLASH的副本,此時執(zhí)行B     INIT,將跳到副本中的INIT位置執(zhí)行,此處有對應(yīng)的代碼;但是如果執(zhí)行LDR     PC,=INIT,將向PC加載0x200000+offset,這將使得PC跳到RAM中,而此時由于代碼沒有復(fù)制,RAM中的指定位置并沒有代碼,程序無法運行.

二,處理器模式
         ARM的處理器可工作于多種模式,不同模式有不同的堆棧 ,以下設(shè)置各模式及其堆棧.
          預(yù)定義一些參數(shù):
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

修改這些值即可修改相應(yīng)模式堆棧的尺寸.
以下為各模式代碼:
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    

四,復(fù)制異常向量
         由于代碼于RAM運行時,有明顯的速度優(yōu)勢,而且變量可以動態(tài)配置,因此可以通過remap將RAM映射到0,使得出現(xiàn)異常時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 

這段程序?qū)YSINIT之前的代碼,也就是異常處理函數(shù),全部復(fù)制到RAM中, 這就意味著不能將RW設(shè)置為0x200000,這樣會使得向量被沖掉.

四,在RAM中運行
         如果有必要,且代碼足夠小,可以將代碼置于RAM中運行,由于RAM中本身沒有代碼,就需要將代碼復(fù)制到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起始地址比較,如果小于,那么就跳過代碼復(fù)制.
在復(fù)制代碼的時候需要注意,在這段程序結(jié)束之前的代碼沒有必要復(fù)制,因為這些代碼都已經(jīng)執(zhí)行過了,所以,先取得COPY_END,作為復(fù)制起始地址,然后計算其相對RESET的偏移,然后以RO的值加上這個偏移,就是復(fù)制目的地的起始地址,然后開始復(fù)制.

五,開始主程序
         以上步驟完成,就可以跳轉(zhuǎn)到main運行
         IMPORT Main

         LDR     PC,=Main
         B       .

 
六,器件初始化
         主程序首先要進行器件的初始化,對S64而言,應(yīng)該先初始化WDT,因為默認情況下,WDT是打開的,然后是各設(shè)備的時鐘分配,最后應(yīng)該remap

RemapSRAM:
MOV R0, #0x40000000 //RAM 區(qū)首地址
LDR R1, =Vectors //向量表首地址
#下面一段程序是把從0x00000000 開始的64 個字節(jié)(FLASH 中的中斷向量表和地址表)搬移到以
#0x40000000 為首地址的RAM 區(qū)中
LDMIA R1!, {R2-R9} //把以[R1]為首地址的32 個字節(jié)數(shù)據(jù)裝載到R2-R9 中
STMIA R0!, {R2-R9} //把R2-R9 中的數(shù)據(jù)存入以[R0]為首地址的單元中
LDMIA R1!, {R2-R9} //把以[R1]為首地址的32 個字節(jié)數(shù)據(jù)裝載到R2-R9 中
STMIA R0!, {R2-R9} ////把R2-R9 中的數(shù)據(jù)存入以[R0]為首地址的單元中

ARM的啟動代碼分析

相信使用過MDK的朋友都會發(fā)現(xiàn),在新建任意一個ARM芯片的工程時,MDK開發(fā)環(huán)境都會自動的在代碼中加入一個Startup.s的文件。
大家不能小看這個代碼的功能了,這個代碼中就是對應(yīng)芯片的啟動代碼了。
       本文就以在MDK開發(fā)環(huán)境下對于LPC2103的啟動代碼分析了。
       在MDK開發(fā)環(huán)境下,系統(tǒng)復(fù)位后,都是會將ARM芯片切換到系統(tǒng)模式下工作。啟動代碼會在系統(tǒng)復(fù)位后立即執(zhí)行,其功能如下:
1、定義中斷和異常向量。
2、配置CPU時鐘源(對于一些設(shè)備來說)。
3、使用內(nèi)存重映射命令拷貝ROM中的異常向量到RAM中(例如:Atmel)。
4、初始化外部總線控制器和執(zhí)行內(nèi)存重映射(如果有必要)。
5、如果有必要,初始化其它外設(shè)。
6、為所有的模式預(yù)留和初始化堆棧。
7、清零數(shù)據(jù)初始化(僅針對GNU)。
8、跳轉(zhuǎn)到C語言函數(shù)執(zhí)行。
本文只是在探討第一個步驟(定義中斷和異常向量)。因為這個將會在之后我們要講到的在uC/OS-II中進行中斷函數(shù)的編寫有關(guān)系,其他步驟,如果大家有興趣,可以到網(wǎng)上去查閱相關(guān)資料。
下面代碼就是我在MDK中,拷貝出來的一段初始化異常向量的代碼了,詳細代碼如下:

AREA
RESET, CODE, READONLY

ARM

Vectors

LDRPC, Reset_Addr

LDRPC, Undef_Addr


LDRPC, SWI_Addr

LDRPC, PAbt_Addr

LDRPC, DAbt_Addr

NOP
;LDRPC, IRQ_Addr

LDRPC, [PC, #-0x0FF0]

LDRPC, FIQ_Addr

Reset_Addr DCD Reset_Handler
Undef_Addr DCD Undef_Handler
SWI_Addr DCD SWI_Handler
PAbt_Addr DCD PAbt_Handler
DAbt_Addr DCD DAbt_Handler

DCD 0
IRQ_Addr DCD IRQ_Handler
FIQ_Addr DCD FIQ_Handler

Undef_Handler B Undef_Handler
SWI_Handler B SWI_Handler
PAbt_Handler B PAbt_Handler
DAbt_Handler B DAbt_Handler
IRQ_Handler B IRQ_Handler
FIQ_Handler B FIQ_Handler
下面就進行一步一步代碼分析:
1、
AREA
RESET, CODE, READONLY

ARM
這兩段話表達的意思是:在代碼段中定義一個段名為RESET的只讀段,該代碼段中的代碼都在ARM指令集下進行。
2、Vectors

LDR PC, Reset_Addr

LDR PC, Undef_Addr

LDR PC, SWI_Addr


LDR PC, PAbt_Addr

LDR PC, DAbt_Addr

NOP
;LDR PC, IRQ_Addr

LDR PC, [PC, #-0x0FF0]

LDR PC, FIQ_Addr
上面的代碼是ARM內(nèi)核定義的異常像量表

異常類型
異常向量地址
該地址中的內(nèi)容

復(fù)位
0x00000000(或0xFFFF0000)
LDR PC, Reset_Addr

未定義異常
0x00000004(或0xFFFF0004)
LDR PC, Undef_Addr

軟件中斷
0x00000008(或0xFFFF0008)
LDR PC, SWI_Addr

指令預(yù)取異常
0x0000000C(或0xFFFF000C)
LDR PC, PAbt_Addr

數(shù)據(jù)預(yù)取異常
0x00000010(或0xFFFF0010)
LDR PC, DAbt_Addr

預(yù)留
0x00000014(或0xFFFF0014)
NOP

IRQ中斷
0x00000018(或0xFFFF0018)
LDR PC, [PC, #-0x0FF0]

FIQ中斷
0x0000001C(或0xFFFF001C)
LDR PC, FIQ_Addr

大家注意一下,在中斷向量地址中必須保存一條ARM指令集。在對應(yīng)的異常向量地址中將跳轉(zhuǎn)到對應(yīng)的異常處理程序。
當然大家在這里也可以使用B指令,但是大家要注意一下,使用LDR指令可以實現(xiàn)±4GB地址空間實現(xiàn)。但是使用B執(zhí)行跳轉(zhuǎn)的話,就只能在±32M地址空間。所以建議大家使用LDR指令實現(xiàn)跳轉(zhuǎn)。


可能大家看到了在IRQ中斷向量地址中裝載的是LDR PC, [PC, #-0x0FF0]指令,這里要和大家解釋一下這條語句,在和大家解釋這條語句時,希望大家去看看LPC2103的datasheet。
當處理器開始執(zhí)行LDR PC, [PC, #-0x0FF0]
指令,PC寄存器的值就已經(jīng)變成PC+8 = 0x20(這個不清楚的朋友必須去查詢一下《ARM+Architecture+Reference+Manual(2nd+Edition)》中的Prefetch and self-modifying code),
Result = 0x20 – 0xff0 = 0xFFFFF030(實在不知道怎么算的就用電腦自帶的計算器算算);
在查詢LPC2103數(shù)據(jù)手冊后,發(fā)現(xiàn)該地址對應(yīng)的寄存器為VICVectAddr。
在這條語句執(zhí)行完畢后,處理器就會跳轉(zhuǎn)到導致產(chǎn)生IRQ中斷,并跳轉(zhuǎn)到VICVectAddr寄存器所指向的IRQ異常處理函數(shù)中進行操作。

實際運用中,我們可以更改一下IRQ異常向量地址的語句,讓大家更加好看。更改如下:
LDR PC, [PC, #0xFFFFF020]
這條語句和剛才的MDK自帶的語句實現(xiàn)的效果是一樣的!

實例
軟件實現(xiàn)中斷處理過程

實驗芯片:LPC2103


開發(fā)環(huán)境:MDK3.50


實現(xiàn)功能:在不使用__irq關(guān)鍵詞時,如果編寫中斷服務(wù)程序。

知識要點:

1、在MDK開發(fā)環(huán)境下,對于LPC2000系列處理器,MDK默認的啟動模式位系統(tǒng)模式。

2、在不使用__irq關(guān)鍵字聲明時,只將中斷處理函數(shù)當成普通函數(shù)進行處理,而不會在進入中斷時對通用寄存器的內(nèi)容進行保存,也不會再退出中斷時對通用寄存器的內(nèi)容進行恢復(fù),因此這部分功能是必須手動添加的。

下面進行代碼講述,下面就是當進入IRQ中斷處理函數(shù)時的處理程序,代碼全部用匯編語句完成詳細代碼如下:

       EXPORT IRQ_Uart0

IMPORT DuleUart0


REQUIRE8

PRESERVE8

AREA CODE, CODE, READONLY

CODE32


IRQ_Uart0

STMFD SP!,{R0-R12,LR}


;保存當前處理器的所有寄存器


MRS R0,SPSR
;保存當前處理器的SPSR

STMFD SP!,{R0}

MRS R0,CPSR ;保存當前處理器的CPSR

STMFD SP!,{R0}

MOV R12, SP ;保存當前的SP寄存器


MRS R0,CPSR ;重新打開FIQ或IRQ中斷


BIC R0,R0,#0xC0

MSR CPSR_cxsf,R0

BL DuleUart0 ;執(zhí)行串口0的實際處理程序


MOV SP,R12 ;恢復(fù)SP寄存器的值


LDMFD SP!,{R0} ;恢復(fù)中斷時的CPSR寄存器值


MSR CPSR_cxsf,R0


LDMFD SP!,{R0};恢復(fù)中斷前的SPSR寄存器狀態(tài)


MSR SPSR_cxsf,R0


LDMFD SP!,{R0-R12};恢復(fù)中斷時的所有寄存器值


LDMFD SP!,{LR};恢復(fù)進入中斷時的PC返回地址


SUBS PC,LR,#4;返回中斷服務(wù)程序


END


大家可能存在的問題如下:

1、為什么我們在進入這段中斷處理程序中還在使用LDM和STM語句呢,不是說在用戶或系統(tǒng)模式中使用該語句會產(chǎn)生不可預(yù)料的結(jié)果么?

答:當系統(tǒng)進入IRQ異常時,就會將處理器模式切換到IRQ異常模式。

2、為什么會在最后一句執(zhí)行SUBS PC,LR,#4(這一句就不在解釋了),不清楚的就去看看《ARM體系結(jié)構(gòu)與編程》

好了,我們現(xiàn)在對照著在uC/OS-II中的中斷編寫方法:

Void ISP_Function( void )


{


         保存全部的CPU寄存器;

         調(diào)用OSIntEnter()或OSIntNesting++;


If(OSIntNesting == 1 )

{

OSTCBCur->OSTCBStkPtr= SP ;

}


         清中斷源;

         重新打開中斷;

         執(zhí)行用戶代碼做中斷服務(wù);

         調(diào)用OSIntExit();

         恢復(fù)所有CPU寄存器;

         執(zhí)行中斷返回指令;

}


而在我們的處理代碼中,我們的執(zhí)行過程如下偽代碼所示:

Void ISRFunction( void )


{


         保存全部寄存器的內(nèi)容;

         清中斷源;

         執(zhí)行用戶代碼;

         恢復(fù)所有CPU寄存器;

         執(zhí)行中斷返回;

}


對于沒有的部分,大家可以試著去編寫一下!

詳細代碼可以下載,也可以移植到你現(xiàn)有的開發(fā)板上去試試。


基于ARM的芯片多數(shù)為復(fù)雜的片上系統(tǒng),這種復(fù)雜系統(tǒng)里的多數(shù)硬件模塊都是可配置的,需要由軟件來設(shè)置

 

其需要的工作狀態(tài)。因此在用戶的應(yīng)用程序之前,需要由專門的一段代碼來完成對系統(tǒng)的初始化。由于這

 

類代碼直接面對處理器內(nèi)核和硬件控制器進行編程,一般都是用匯編語言。一般通用的內(nèi)容包括:

 

中斷向量表

初始化存儲器系統(tǒng)

初始化堆棧

初始化有特殊要求的斷口,設(shè)備

初始化用戶程序執(zhí)行環(huán)境

改變處理器模式

呼叫主應(yīng)用程序

 

中斷向量表

      ARM要求中斷向量表必須放置在從0地址開始,連續(xù)8X4字節(jié)的空間內(nèi)。

      每當一個中斷發(fā)生以后,ARM處理器便強制把PC指針置為向量表中對應(yīng)中斷類型的地址值。因為每

 

個中斷只占據(jù)向量表中1個字的存儲空間,只能放置一條ARM指令,使程序跳轉(zhuǎn)到存儲器的其他地方,再執(zhí)行

 

中斷處理。

中斷向量表的程序?qū)崿F(xiàn)通常如下表示:

AREA Boot ,CODE, READONLY

ENTRY

B  ResetHandler

B  UndefHandler

B  SWIHandler

B  PreAbortHandler

B  DataAbortHandler

B

B  IRQHandler

B  FIQHandler

其中關(guān)鍵字ENTRY是指定編譯器保留這段代碼,因為編譯器可能會認為這是一段亢余代碼而加以優(yōu)化。鏈

 

接的時候要確保這段代碼被鏈接在0地址處,并且作為整個程序的入口。

 

初始化存儲器系統(tǒng)

 

存儲器類型和時序配置

 

      通常FlashSRAM同屬于靜態(tài)存儲器類型,可以合用同一個存儲器端口;DRAM因為有動態(tài)刷新和地

 

址線復(fù)用等特性,通常配有專用的存儲器端口。

 

      存儲器端口的接口時序優(yōu)化是非常重要的,這會影響到整個系統(tǒng)的性能。因為一般系統(tǒng)運行的速度

 

瓶頸都存在于存儲器訪問,所以存儲器訪問時序應(yīng)盡可能的快;而同時又要考慮到由此帶來的穩(wěn)定性問題

 

。

存儲器地址分布

 

一種典型的情況是啟動ROM的地址重映射。

 

初始化堆棧

因為ARM7種執(zhí)

行狀態(tài),每一種狀態(tài)的堆棧指針寄存器(SP)都是獨立的。因此,對程序中需要用到的每一種模式都要給

 

SP定義一個堆棧地址。方法是改變狀態(tài)寄存器內(nèi)的狀態(tài)位,使處理器切換到不同的狀態(tài),讓后給SP賦值。

 

注意:不要切換到User模式進行User模式的堆棧設(shè)置,因為進入User模式后就不能再操作CPSR 回到別的

 

模式了,可能會對接下去的程序執(zhí)行造成影響。

 

這是一段堆棧初始化的代碼示例,其中只定義了三種模式的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

初始化有特殊要求的端口,設(shè)備初始化應(yīng)用程序執(zhí)行環(huán)境
映像一開始總是存儲在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面執(zhí)行,也可以轉(zhuǎn)移到速度更快的RAM中執(zhí)行;而RW和ZI這兩部分是必須轉(zhuǎn)移到可寫的RAM里去。所謂應(yīng)用程序執(zhí)行環(huán)境的初始化,就是完成必要的從ROM到RAM的數(shù)據(jù)傳輸和內(nèi)容清零。
下面是在ADS下,一種常用存儲器模型的直接實現(xiàn):
LDR  r0,=|Image$$RO$$Limit| 得到RW數(shù)據(jù)源的起始地址
LDR  r1,=|Image$$RW$$Base| RW區(qū)在RAM里的執(zhí)行區(qū)起始地址
LDR  r2,=|Image$$ZI$$Base| ZI區(qū)在RAM里面的起始地址
CMP  r0,r1         比較它們是否相等
   BEQ  %F1
0   CMP  r1,r3
   LDRCC r2,[r0],#4STRCC r2,[r1],#4
   BCC  %B0
1   LDR  r1,=|Image$$ZI$$Limit|
   MOV  r2,#0
2   CMP  r3,r1
   STRCC r2,[r3],#4
   BCC  %B2
程序?qū)崿F(xiàn)了RW數(shù)據(jù)的拷貝和ZI區(qū)域的清零功能。其中引用到的4個符號是由鏈接器第一輸出的。
|Image$$RO$$Limit|:表示RO區(qū)末地址后面的地址,即RW數(shù)據(jù)源的起始地址
|Image$$RW$$Base|:RW區(qū)在RAM里的執(zhí)行區(qū)起始地址,也就是編譯器選項RW_Base指定的地址
|Image$$ZI$$Base|:ZI區(qū)在RAM里面的起始地址
|Image$$ZI$$Limit|:ZI區(qū)在RAM里面的結(jié)束地址后面的一個地址
程序先把ROM里|Image$$RO$$Limt|開始的RW初始數(shù)據(jù)拷貝到RAM里面|Image$$RW$$Base|開始的地址,當RAM這邊的目標地址到達|Image$$ZI$$Base|后就表示RW區(qū)的結(jié)束和ZI區(qū)的開始,接下去就對這片ZI區(qū)進行清零操作,直到遇到結(jié)束地址|Image$$ZI$$Limit|

改變處理器模式
因為在初始化過程中,許多操作需要在特權(quán)模式下才能進行(比如對CPSR的修改),所以要特別注意不能過早的進入用戶模式。
內(nèi)核級的中斷使能也可以考慮在這一步進行。如果系統(tǒng)中另外存在一個專門的中斷控制器,這么做總是安全的。
呼叫主應(yīng)用程序
當所有的系統(tǒng)初始化工作完成之后,就需要把程序流程轉(zhuǎn)入主應(yīng)用程序。最簡單的一種情況是:
IMPORT main


B   main
直接從啟動代碼跳轉(zhuǎn)到應(yīng)用程序的主函數(shù)入口,當然主函數(shù)名字可以由用戶隨便定義。
在ARM ADS環(huán)境中,還另外提供了一套系統(tǒng)級的呼叫機制。
IMPORT __main

B   __main
__main()是編譯系統(tǒng)提供的一個函數(shù),負責完成庫函數(shù)的初始化和初始化應(yīng)用程序執(zhí)行環(huán)境,最后自動跳轉(zhuǎn)到main()函數(shù)。(

 


本文主要以philips公司ARM7TDMI核的LPC2119為例來分析如何編寫ARM7的啟動代碼。
  1、啟動代碼
  在嵌入式系統(tǒng)軟件的開發(fā)中,應(yīng)用程序通常是在嵌入式操作系統(tǒng)的開發(fā)平臺上采用C語言編寫的。然而,在ARM系統(tǒng)上電復(fù)位后,需要設(shè)置中斷向量表、初始化各模式堆棧、設(shè)置系統(tǒng)時鐘頻率等,而這些過程都是針對ARM內(nèi)部寄存器結(jié)構(gòu)的操作,用C語言編程是很難實現(xiàn)的。因此在轉(zhuǎn)到應(yīng)用程序的c/c++編寫之前,需要用ARM的匯編語言編寫啟動代碼,由啟動代碼完成系統(tǒng)初始化以及跳轉(zhuǎn)到用戶C程序。在ARM設(shè)計開發(fā)中,啟動代碼的編寫是一個極重要的過程。然而啟動代碼隨具體的目標系統(tǒng)和開發(fā)系統(tǒng)有所區(qū)別,但通常包含以下部分:
  ·向量表定義
  ·地址重映射及中斷向量表的轉(zhuǎn)移
  ·堆棧初始化
  ·設(shè)置系統(tǒng)時鐘頻率
  ·中斷寄存器的初始化
  ·進入C應(yīng)用程序
  下面就結(jié)合PHILIPS的LPC2119的啟動代碼來分析與說明ARM7處理器的啟動代碼的編寫。
  1.1向量表定義
  ARM芯片上電或復(fù)位后,系統(tǒng)進入管理模式、ARM狀態(tài)、PC(R15)指向0x00000000地址處。中斷向量表為每一個中斷設(shè)置1個字的存儲空間,存放一條跳轉(zhuǎn)指令,通過這條指令使PC指針指向相應(yīng)的中斷服務(wù)程序入口,繼而執(zhí)行相應(yīng)的中斷處理程序。LPC2219的中斷向量表和其它基于ARM核的芯片中斷向量表較類似,只要注意LPC2219要使向量表所有數(shù)據(jù)32位累加和為零(0x00000000-0x0000001C的8個字的機器碼累加), 才能使用戶的程序脫機運行。
  1.2 地址重映射及中斷向量表的轉(zhuǎn)移
  ARM7處理器在復(fù)位后從地址0讀取第一條指令并執(zhí)行,因此系統(tǒng)上電后地址0必須是非易失的ROM/FLASH,這樣才能保證處理器有正確可用的指令。為了加快對中斷的處理以及實現(xiàn)在不同操作系統(tǒng)模式下對中斷的處理,這就需要重新映射中斷向量表、Bootblock和SRAM空間的一小部分。ARM具有非常靈活的存儲器地址分配特性。ARM處理器的地址重映射機制有兩種情況:
  ①由專門的寄存器完成重映射(Remap),只需對相應(yīng)的Remap寄存器相應(yīng)位設(shè)置即可。
  ②沒有專門的Remap控制寄存器需要重新改寫用于控制存儲器起始地址的塊(Bank)寄存器來實現(xiàn)Remap。在LPC2119上的重映射,可以通過存儲器映射控制器來實現(xiàn)。實現(xiàn)REMAP操作的程序?qū)崿F(xiàn)如下:
  MOV R8,#0x40000000;            /設(shè)置新向量表起始地址/
  LDR R9,=Interrupt_Vector_Table;             /讀原向量表源地址/
  LDMIA R9!,(R0-R7);               /復(fù)制中斷向量表及中斷處理程序的入口地址到RAM中(64字節(jié))/
  STMIA R8!,(R0-R7)
  LDMIA R9!,(R0-R7)
  STMIA R8!,(R0-R7)
  LDR R8,=MEMMAP ;           /REMMAP操作/
  MOV R9,#0x02
  STR R9, [R8]
  1.3 堆棧初始化
  啟動代碼中各模式堆?臻g的設(shè)置是為中斷處理和程序跳轉(zhuǎn)時服務(wù)的。當系統(tǒng)響應(yīng)中斷或程序跳轉(zhuǎn)時,需要將當前處理器的狀態(tài)和部分重要參數(shù)保存在一段存儲空間中,所以對每個模式都要進行堆棧初始化工作,給每個模式的SP定義一個堆;刂泛投褩5娜萘。堆棧的初始化有兩種方法:第一種方法是結(jié)合ADS開發(fā)套件中的分散加載文件來定義堆棧。第二種方法是最簡單也是最常用的一種就是直接進入對應(yīng)的處理器模式,為SP寄存器指定相應(yīng)的值。下面給出了用第二種方法初始化管理模式和中斷模式堆棧的程序:
  MSR CPSR_c, #0xD3 ;           /切換到管理模式,并初始化管理模式的堆棧/
  LDR SP, Stack_Svc
  MSR CPSR_c, #0xD2 ;            /切換到IRQ模式,并初始化IRQ模式的堆棧/
  LDR SP, Stack_Irq
  …
  1.4 系統(tǒng)部分時鐘初始化
  時鐘是芯片各部分正常工作的基礎(chǔ),應(yīng)該在進入main()函數(shù)前設(shè)置。部分ARM7片子內(nèi)部集成有PLL(鎖相環(huán))電路,用戶可以用低頻率的晶振通過PLL電路獲得一個較高頻率的時鐘。LPC2119內(nèi)部的PLL電路接受的輸入時鐘頻率范圍為10~25MHz,輸入頻率通過一個電流控制振蕩器(CCO)倍增到范圍10~60MHz。同時為了使高速的ARM處理器與低速的外設(shè)正常通訊和降低功耗(降低外設(shè)運行速度使功耗降低),LPC2119又集成了一個額外的分頻器。PLL的激活是由PLLCON寄存器控制。PLL倍頻器和分頻器的值由PLLCFG寄存器控制。對PLLCON或PLLCFG寄存器的更改必須遵循嚴格的順序,否則所作更改是無法生效的(在連續(xù)的VPB周期內(nèi)向PLLFEED寄存器寫入0xAA、0x55,在此期間中斷必須是被禁止的。)
  1.5 中斷初始化
  ARM7的向量中斷控制器(Vectored Interrupt Controller)可以將中斷編程為3類:FIQ、向量IRQ、非向量IRQ。FIQ中斷請求的優(yōu)先級最高,其次是IRQ中斷請求,非向量IRQ的優(yōu)先級最低。VIC具有32個中斷請求輸入,但在LPC2219中只占用了17個中斷輸入。對于這17個中斷源的IRQ/FIQ選擇,由VICIntSelect寄存器控制,當對應(yīng)位設(shè)置位1時,則此中斷為FIQ中斷,否則為IRQ中斷。若再將IRQ中斷設(shè)置到向量控制寄存器(VICVectCntIn)中,則此中斷為向量IRQ中斷,否則為非向量IRQ中斷。FIQ中斷是專門用來處理那些需要及時響應(yīng)的特殊事件,盡可能地只給FIQ分配一個中斷源。
  1.6 進入C應(yīng)用程序
  至此,系統(tǒng)各部分的初始化基本完成,可以直接從啟動代碼轉(zhuǎn)入到應(yīng)用程序的main()函數(shù)入口。從啟動代碼轉(zhuǎn)入到應(yīng)用程序的實例代碼如下:
  IMPORT main
  LDR R0,=main
  BX R0
  2、總結(jié)
  一個優(yōu)秀的啟動代碼將給應(yīng)用程序的開發(fā)提供一個良好的開發(fā)平臺。本文中較詳細的討論了啟動代碼的編寫及難點。其中在堆棧初始化過程中要特別的注意兩點:
  ①要盡量給堆棧分配快速和高帶寬的存儲器。
 、盡量避免過早將處理器切換到用戶模式,一般在系統(tǒng)初始化的最后階段才切換到用戶模式(用戶模式?jīng)]有權(quán)限通過修改CPSR來進行模式切換)。
  嵌入式系統(tǒng)的迅猛發(fā)展,使啟動代碼的編寫成為嵌入式系統(tǒng)開發(fā)人員應(yīng)該具備的能力。本文有助于正在從事嵌入式ARM開發(fā)的讀者理解啟動代碼的內(nèi)涵與編寫出適合自己的啟動代碼。
 
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP