前期準備——2.基本彙編語法
在做裸機開發前,我們要掌握一些基礎的ARM彙編語法,因為即使後面我們用C去寫驅動,也要用匯編去執行配置指標、中斷、清除session等操作。我們使用的晶片是I.MX6UL,這是款Cortex-A7的核心晶片,所以使用的就是Cortex-A的彙編指令,這裡有兩份資料可以參考點選下載(提取碼:l1rg)。還好我們主要目的就是進行系統的初始化,用到的都是比較簡單的指令,也不會涉及到複雜的程式碼結構。
GNU彙編語法
語法結構
GNU彙編語法是適用於所有的架構,並不是ARM獨享的,GNU彙編由一系列的語句組成,每條語句結構如下:
label: instruction @ comment
label:標號,用來表示地址位置,有些指令前面可能會有標號,這樣可以通過標號來找到指令的地址注意標號後面有個冒號,在彙報中,任何以冒號結尾的表示符都會被識別為標號
instruction:指令,包括彙編指令或偽指令
@:註釋符,也可以用/**/來包裹註釋內容
comment:註釋內容
比如我們在第一個用匯編點亮LED的試驗中的一段彙編程式碼
第二行的_start就是就是標號,第6行的
ldr r0,=0x020c4068
就是指令。注意指令中的指令、偽指令、暫存器、暫存器名可以全部使用大寫,也可以全部使用小寫,但是切記不能大小寫混用!
後面的@CCGR0就是表明註釋內容。
常用的偽操作
在定義標號時,要知道有些標號是常用的偽操作
- .byte 定義單位元組資料
- .short 定義雙位元組資料
- .long 定義4位元組資料
- .equ 賦值語句 比如.equ num,0x12,表示num=0x12
- .align 位元組對齊,比如.align 4 表示4位元組對齊,這個在後面定義session會經常用到
- .end 表示原始檔結束
- .global 定義全域性符號,比如上面的.global _start。
並且在在使用.session偽操作定義段的時候,彙編已經預定義了一些段名:
- .text——程式碼段
- .data——初始化的資料段
- .bss——未初始化的資料段
- .rodata——只讀資料段
函式
GNU也是支援函式的,函式的格式如下:
函式名: 函式體 返回語句
可以發現,函式宣告的方法和前面的基礎語法一樣,就是把label換成了函式名。返回值在函式裡不是必要的。比如下面這段程式碼就是我們在講中斷時定義的函式
/*復位中斷服務函式 */ Reset_Handler: ldr r0,=Reset_Handler bx r0 /*未定義指令中斷服務函式 */ Undefined_Handler: ldr r0,=Undefined_Handler bx r0 /*SVC中斷服務函式 */ SVC_Handler: ldr r0,=SVC_Handler bx r0
常用匯編語句
下面看看我們常用的彙編語句
處理器內部資料傳輸指令
處理器做的最多的事情就是在處理器內部來回傳遞資料,這些資料操作基本為
- 通用暫存器之間資料相互傳遞
- 通用暫存器與特殊暫存器(如CPSR、SPSR等)之間資料相互傳遞
- 暫存器存入立即數
資料傳輸常用的指令有三個:MOV、MRS和MSR,這三個指令的用法如下:
指令 | 目的 | 資料來源 | 說明 |
MOV | R0 | R1 | 將暫存器R1內資料複製到暫存器R0中 |
MRS | R0 | CPSR | 將特殊暫存器CPSR內的資料複製到R0中 |
MSR | CPSR | R0 | 將R0內資料複製到特殊暫存器CPSR內 |
下面分別介紹下3個指令的具體用法
MOV指令
MOV指令用來將一個暫存器裡的資料拷貝到另一個暫存器內,也可以將一個立即數拷貝到暫存器內,用法如下
MOV R0,R1 @將R1內的資料傳遞給R0 MOV R0,#0xFF @將立即數0xFFC傳遞給R0
MRS指令
MRS是將資料從特殊暫存器傳遞個通用暫存器,也就是用於讀取特殊暫存器的值,用法如下:
MRS R0,CPSR @將CPSR的資料傳遞給R0
MSR指令
MSR指令是將資料從通用暫存器傳遞給特殊暫存器,也就是寫通用暫存器
MSR CPSR,R0 @將R0的資料傳遞給CPSR
MSR和MRS兩個指令非常容易混淆,有個簡單的方法記憶:因為指令後面兩個暫存器方向是固定的,前面的是目標,後面的是源,R可以記成通用暫存器,S剛好和特殊的頭文字相符,就記成特殊暫存器,MRS就是從特殊到通用,MSR就是從通用到特殊。
儲存器訪問指令
ARM處理器是不能直接訪問儲存器的,這裡的儲存器不是SD卡或者NANDFlash,而是類似RAM中的資料,它必須藉助核心暫存器組,也就是R0~R15來實現功能,我們需要用匯編來配置I.MX6UL暫存器的時候需要藉助儲存器訪問指令,一般就是將要配置的引數寫入到通用暫存器(R0~R12)中,然後藉助儲存器訪問指令將R儲存器中的資料寫入到I.MX6UL暫存器中,讀取暫存器也是一樣的,只是過程相反。常用的訪問儲存器的指令有兩種:LDR和STR:用法如下表
指令 | 說明 |
LDR Rd,[Rn,#offset] |
從儲存器Rn+offset的位置讀取資料放到Rd中 |
STR Rd,[Rn,#offset] | 將Rd裡的資料寫入到Rn+offset的位置 |
下面來看看這兩條指令是怎麼用的:
LDR指令
LDR用於從RAM裡讀取暫存器的值,比如我們要讀取地址為0x0209C004這個暫存器(GPIO1_GDIR,控制GPIO1輸入輸出功能的暫存器,點燈時候會用到)的值,就要這麼做
LDR R1,=0x0209C004
LDR R0,[R1]
就是現在R1中儲存要讀取暫存器的地址,再把R1地址中的資料傳給R0。注意這裡傳遞立即數的時候和MOV指令有區別,一個用的是#,一個用的是=。
STR指令
STR指令和LDR相反,用來設定暫存器的值,比如我們要講GPIO1_GDIR的值設定為0x00000001,程式碼如下:
LDR R1,=0x0209C004 LDR R0,=0x00000001 STR R0,[R1]
LDR和STR是按照位元組進行讀取和寫入到,也就是操作得都是32位的資料,如果要按照位元組或板子姐進行操作得話可以來指令後加上B(Byte)或者H(Half)。同樣我們找個方便記憶的方法:LDR可以記憶成LosdR,也就是讀取暫存器,STR是setR,設定,也就是寫入暫存器。
壓棧和出棧
我們通常在中斷等操作中需要在A函式中呼叫B函式,當B函式執行完成後繼續執行A函式,要想在跳回A函式時程式碼能夠正常執行,就需要在跳轉B函式前講當前處理器狀態儲存(也就是儲存R0~R15的值),在B函式執行完畢後將儲存的值恢復值R0~R15即可。所以儲存R0~R15的過程就叫現場保護,恢復的過程就叫恢復現場。在現場保護的時候要進行壓棧操作,恢復現場就是進行出棧操作。我們有兩個指令來進行該擦操作:PUSH和POP
指令 | 說明 |
PUSH <reg list> |
將暫存器列表存入棧中 |
POP <reg list> |
從棧中恢復到暫存器列表 |
假如我們現在要把R0~R3和R12這5個暫存器壓棧,當前的SP指標指向0x80000000,處理器的堆疊為向下增長,使用的程式碼就是這樣的
PUSH {R0~R3, R12} @將 R0~R3 和 R12 壓棧
一定要注意堆疊指標的增長方向,入棧以後的堆疊如下圖所示
現在的指標就指向0x7FFFFFEC,假如此刻我們需要對LR暫存器入棧,那麼執行完下面程式碼
PUSH {LR} @將LR壓棧
堆疊模型如下:
在這種情況下我們如果要出棧,就要執行下面的程式碼
POP {LR} @先恢復 LR POP {R0~R3,R12} @在恢復 R0~R3,R12
出入棧的另一種方法
出棧就是從棧盯,也就是SP當前執行的開始位置,地址依次減小來提取堆疊中的資料到要恢復的暫存器列表中,PUSH和POP的另一種方法就是 STMFD SP!和LDMFD SP!所以上面的程式碼也可以寫成這樣:
STMFD SP!,{R0~R3, R12} @R0~R3,R12 入棧
STMFD SP!,{LR} @LR 入棧
LDMFD SP!, {LR} @先恢復 LR
LDMFD SP!, {R0~R3, R12} @再恢復 R0~R3, R12
STMFD可以被分解為兩部分:STM和FD,同樣,LDMFD也可以被分解成LDM和FD這個STM和LDM就跟前面我們講過LDR和STR,但是這兩個指令只能讀取儲存器中一個數據,而這兩個STM和LDM可以多儲存和多載入,可以操作儲存器內多個連續資料。FD是Full Descending的縮寫,即滿遞減的意思,根據ATPCS規則,ARM使用的FD型別的堆疊,SP指向最後一個入棧的數值,堆疊是由高地址向下增長的,所以用的是STMFD的指令。STM和LDM的指令暫存器列表中標號小的對應低地址,編號高度對應小地址。
跳轉指令
在程式碼中我們經常需要程式跳轉至別的程式碼段,這就是跳轉操作。有兩種方法我們比較常用:
- 使用跳轉指令:B、BL、BX
- 直接向PC暫存器寫入資料
第一種方法我們是最常用的,指令用法如下:
指令 | 說明 |
B <Label> |
跳轉到label處,如果跳轉範圍超過了±2KB,可以指定B.W來使用32位版本的跳轉指令。 |
BX <Rm> |
間接跳轉,Rm記憶體放資料為跳轉到目的地址,並且切換指令集 |
BL <Lable> |
跳轉到Label處,並將返回地址儲存在LR(Link Register)裡,可以回到跳轉前的位置 |
BLX <Rm> |
結合了BX和BL的特點,間接跳轉至Rm儲存的地址,並儲存返回地址再LR內,切換指令集。 |
B指令
最簡單的跳轉指令,跳轉後不考慮返回,一旦執行B指令,ARM處理器直接跳轉到指定目標地址
_start: ldr sp,=0x80200000 @設定堆疊指標 b main @跳轉到main函式
上面的程式碼就是用來初始化C語言的執行環境,然後跳轉到C底main函式處。這裡跳轉到main函式後不會再回到彙編,所以使用了B指令。
BL指令
BL指令和B指令相比,就是在LR(R14)中儲存PC(R15)的值,看下圖,處理器在各種模式下R0~R15各個核心暫存器作用,後面會大致提到。主要是這個R15,也就是PC,儲存了當前指令地址加8個位元組。也就是說R15記錄了當前程式的位置,我們需要回到跳轉前位置只需要讀取LR到值就可以了。這個後面有機會可以詳細講講。
上面就是我們常用的指令,除此之外還有算數運算指令、邏輯運算指令等, 需要用到話我們再來補充。