IAR下的匯編/單片機啟動代碼匯編
1、
IAR匯編指令SFB和SFE
SFB Segment begin 段開始
語法格式
SFB(segment [{+|-} offset])
參數
segment: 可重定位段的段名, 必須在SFB使用前已定義
offset : 從開始地址的偏移, 是一個可選參數, 當偏移量省略時, 可以不添加小括號
描述
SFB 右邊可以接受一個操作數, 而且這個操作數必須是一個可重位段的段名.
這個操作符計算段的首字節地址. 這個操作發生在連接時.
例
NAME demo
RSEG CODE
start: DC16 SFB(CODE)
即使上面的代碼和多個其他的模塊進行連接, start標號處仍被置為段的首字節地址
語法格式
SFE (segment [{+|-} offset])
參數
segment: 可重定位段的段名, 必須在SFB使用前已定義
offset : 從開始地址的偏移, 是一個可選參數, 當偏移量省略時, 可以不添加小括號
描述
SFE在其右邊接收一個操作數. 操作數必須是一個可重定位段的段名. SFE操作符將段起始地址和段大小相加. 這個操作在連接時發生.
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標號仍然會被置為段最后一個字節的地址. 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中的幾種跳轉
arm匯編的跳轉指令無非是b和ldr。但是如果沒有足夠理解,別人靈活的用一下你就犯暈了。
首先我們要知道兩者的兩個本質區別:
1、b是位置無關的,ldr不是位置無關的。
2、b的范圍只能是+—32MB,而ldr是4GB。
在arm的啟動匯編的中斷向量表是必然用跳轉指令的,但是就是這里也有很多實現形式:
方式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
這個我很容易理解,實現的標號油InitReset和IRQ_Handler_Entry,其他沒有實現的在原地跳轉。
方式二:
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地址內存內容copy到PC中
這種label都是 label:DCD label2 ,這些label2可以在任何地方實現
我們來理解b和ldr兩者的不同
1,b是位置無關,因為他的跳轉都是相對PC來的,而ldr不是位置無關的,因為他的跳轉是根據DCD里面的值,這個值是連接的時候確定的,他是跟連接地址有關的
2,b的范圍只能是32M,是因為指令里面給偏移的空間只有24bit,而ldr是一個32bit的DCD,所以他是32bit的
注意:像上面方式二
LDR pc, =resetHandler ; Reset
。
。
。
resetHandler:
....
....
如果運行地址是0,那么LDR pc, =resetHandler還在以基址為0的空間運行
但是執行完了,假設裝載地址是0x3000000,所以resetHandler的基址不是0,而是0x30000000
所以resetHandler標號以后的指令應該存在以0x3000000為基址的空間,pc跳過去了
這種技巧在at91的remap模式經常用到
--------------------------------------------------------------------------------
理解啟動代碼(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 ? MOV R3,#0 LDR R1,=|Image$$ZI$$Limit| 2 CMP R2,R1 STRLO R3,[R2],#4 BLO ? 四,復制異常向量 由于代碼于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 ? 這段程序將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 ? LDR PC,=COPY_END COPY_END 程序首先取得RESET的連接地址,判斷程序是否時是在RAM中運行,方法是與RAM起始地址比較,如果小于,那么就跳過代碼復制. 在復制代碼的時候需要注意,在這段程序結束之前的代碼沒有必要復制,因為這些代碼都已經執行過了,所以,先取得COPY_END,作為復制起始地址,然后計算其相對RESET的偏移,然后以RO的值加上這個偏移,就是復制目的地的起始地址,然后開始復制. 五,開始主程序 以上步驟完成,就可以跳轉到main運行 IMPORT Main LDR PC,=Main B . 六,器件初始化 主程序首先要進行器件的初始化,對S64而言,應該先初始化WDT,因為默認情況下,WDT是打開的,然后是各設備的時鐘分配,最后應該remap
--------------------------------------------------------------------------------
RemapSRAM:
MOV R0, #0x40000000 //RAM 區首地址
LDR R1, =Vectors //向量表首地址
#下面一段程序是把從0x00000000 開始的64 個字節(FLASH 中的中斷向量表和地址表)搬移到以
#0x40000000 為首地址的RAM 區中
LDMIA R1!, {R2-R9} //把以[R1]為首地址的32 個字節數據裝載到R2-R9 中
STMIA R0!, {R2-R9} //把R2-R9 中的數據存入以[R0]為首地址的單元中
LDMIA R1!, {R2-R9} //把以[R1]為首地址的32 個字節數據裝載到R2-R9 中
STMIA R0!, {R2-R9} ////把R2-R9 中的數據存入以[R0]為首地址的單元中
--------------------------------------------------------------------------------
ARM的啟動代碼分析
相信使用過MDK的朋友都會發現,在新建任意一個ARM芯片的工程時,MDK開發環境都會自動的在代碼中加入一個Startup.s的文件。
大家不能小看這個代碼的功能了,這個代碼中就是對應芯片的啟動代碼了。
本文就以在MDK開發環境下對于LPC2103的啟動代碼分析了。
在MDK開發環境下,系統復位后,都是會將ARM芯片切換到系統模式下工作。啟動代碼會在系統復位后立即執行,其功能如下:
1、定義中斷和異常向量。
2、配置CPU時鐘源(對于一些設備來說)。
3、使用內存重映射命令拷貝ROM中的異常向量到RAM中(例如:Atmel)。
4、初始化外部總線控制器和執行內存重映射(如果有必要)。
5、如果有必要,初始化其它外設。
6、為所有的模式預留和初始化堆棧。
7、清零數據初始化(僅針對GNU)。
8、跳轉到C語言函數執行。
本文只是在探討第一個步驟(定義中斷和異常向量)。因為這個將會在之后我們要講到的在uC/OS-II中進行中斷函數的編寫有關系,其他步驟,如果大家有興趣,可以到網上去查閱相關資料。
下面代碼就是我在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內核定義的異常像量表
異常類型
異常向量地址
該地址中的內容
復位
0x00000000(或0xFFFF0000)
LDR PC, Reset_Addr
未定義異常
0x00000004(或0xFFFF0004)
LDR PC, Undef_Addr
軟件中斷
0x00000008(或0xFFFF0008)
LDR PC, SWI_Addr
指令預取異常
0x0000000C(或0xFFFF000C)
LDR PC, PAbt_Addr
數據預取異常
0x00000010(或0xFFFF0010)
LDR PC, DAbt_Addr
預留
0x00000014(或0xFFFF0014)
NOP
IRQ中斷
0x00000018(或0xFFFF0018)
LDR PC, [PC, #-0x0FF0]
FIQ中斷
0x0000001C(或0xFFFF001C)
LDR PC, FIQ_Addr
大家注意一下,在中斷向量地址中必須保存一條ARM指令集。在對應的異常向量地址中將跳轉到對應的異常處理程序。
當然大家在這里也可以使用B指令,但是大家要注意一下,使用LDR指令可以實現±4GB地址空間實現。但是使用B執行跳轉的話,就只能在±32M地址空間。所以建議大家使用LDR指令實現跳轉。
關鍵詞:
IAR匯編單片機啟動代
評論