1、ALPHA開發板LED燈硬體原理分析
STM32 IO初始化流程:
①、使能GPIO時鐘
②、設定IO複用,將其複用爲GPIO
③、設定GPIO的電氣屬性
④、使用GPIO,輸出高/低電平
2、I.MX6ULL IO初始化
①、時鐘使能,CCGR0-CCGR6這7個暫存器控制着6ULL所有外設時鐘使能,爲了簡單,設定CCGRo-CCGR6這7個暫存器全部爲0xFFFFFFFF,相當於使能所有外設時鐘。 (參考手冊P699)
②、IO複用,暫存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3-0設定爲0101=5,這樣GPIO1_IO03就複用爲GPIO。(參考手冊P1517)
③、設定電氣屬性,暫存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是設定GPIO1_IO03的電氣屬性,包括:壓擺率、速度、驅動能力、開漏、上下拉等。(參考手冊P1793)
④、設定GPIO功能,設定輸入/輸出,設定GPIO1_GDIR暫存器bit3設定爲輸出模式,即爲1。(參考手冊P1359) 設定GPIO1_DR暫存器bit3爲1表示輸出高電平,爲0表示輸出低電平。
常用的記憶體存取指令有:LDR和STR
指令 | 描述 |
---|---|
LDR Rd, [Rn , #offset] | 從記憶體 Rn+offset 的位置讀取數據存放到 Rd 中 |
STR Rd, [Rn, #offset] | 將 Rd 中的數據寫入到記憶體中的 Rn+offset 位置 |
例如:c語言中 賦值a=b
int a,b;
a=b;
而彙編則爲:
假設a的地址爲0X20,b的地址爲0X30
LDR R0,=0X30
LDR R1,[R0] @R1中儲存了變數b中的值
LDR R0,=0X20
STR R1,[R0] @R1中的數據寫入到a中,即實現了a=b
指令 | 目的 | 源 | 描述 |
---|---|---|---|
MOV | R0 | R1 | 將 R1 裏面的數據複製到 R0 中。 |
MRS | R0 | CPSR | 將特殊暫存器 CPSR 裏面的數據複製到 R0 中。 |
MSR | CPSR | R1 | 將 R1 裏面的數據複製到特殊暫存器 CPSR 裡中。 |
1、MOV 指令
MOV 指令用於將數據從一個暫存器拷貝到另外一個暫存器,或者將一個立即數傳遞到寄
存器裏面,使用範例如下:
MOV R0,R1 @將暫存器 R1 中的數據傳遞給 R0,即 R0=R1
MOV R0, #0X12 @將立即數 0X12 傳遞給 R0暫存器,即 R0=0X12
2、MRS 指令
MRS 指令用於將特殊暫存器(如 CPSR 和 SPSR)中的數據傳遞給通用暫存器,要讀取特殊暫存器的數據只能使用 MRS 指令!
使用範例如下:
MRS R0, CPSR @將特殊暫存器 CPSR 裏面的數據傳遞給 R0,即 R0=CPSR
3、MSR 指令
MSR 指令和 MRS 剛好相反,MSR 指令用來將普通暫存器的數據傳遞給特殊暫存器,也就是寫特殊暫存器,寫特殊暫存器只能使用 MSR,使用範例如下:
MSR CPSR, R0 @將 R0 中的數據複製到 CPSR 中,即 CPSR=R0
我們通常會在 A 函數中呼叫 B 函數,當 B 函數執行完以後再回到 A 函數繼續執行。要想在跳回 A 函數以後程式碼能夠接着正常執行,那就必須在跳到 B 函數之前將當前處理器狀態儲存起來(就是儲存 R0 ~ R15 這些暫存器值),當 B 函數執行完成以後再用前面儲存的暫存器值恢復R0~ R15 即可。儲存 R0~ R15 暫存器的操作就叫做現場保護,恢復 R0~R15 暫存器的操作就叫做恢復現場。在進行現場保護的時候需要進行壓棧(入棧) 操作,恢復現場就要進行出棧操作。壓棧的指令爲 PUSH,出棧的指令爲 POP,PUSH 和 POP 是一種多儲存和多載入指令,即可以一次操作多個暫存器數據,他們利用當前的棧指針 SP 來生成地址,PUSH 和 POP 的用法如表
指令 | 描述 |
---|---|
PUSH | 將暫存器列表存入棧中。 |
POP | 從棧中恢復暫存器列表。 |
假如我們現在要將 R0~R3 和 R12 這 5 個暫存器壓棧,當前的 SP 指針指向0X80000000,處理器的堆疊是向下增長的,使用的彙編程式碼如下:
PUSH {R0~ R3, R12} @將 R0~R3 和 R12 壓棧
壓棧完成以後的堆疊如圖:
上圖就是對R0~R3,R12進行壓棧以後的堆疊示意圖,此時的SP指向了0X7FFFFFEC,假如我們現在要再將 LR 進行壓棧,彙編程式碼如下:
PUSH {LR} @將 LR 進行壓棧
對 LR 進行壓棧完成以後的堆疊模型如圖:
上圖就是分兩步對 R0~R3,R2 和 LR 進行壓棧以後的堆疊模型,如果我們要出棧的話就是使用如下程式碼:
POP {LR} @先恢復 LR
POP {R0~ R3,R12} @在恢復 R0~R3,R12
出棧的就是從棧頂,也就是 SP 當前執行的位置開始,地址依次減小來提取堆疊中的數據到要恢復的暫存器列表中。PUSH 和 POP 的另外一種寫法是「STMFD SP!」和「LDMFD SP!」。
因此上面的彙編程式碼可以改爲:
1 STMFD SP!,{R0~ R3, R12} @R0~R3,R12 入棧
2 STMFD SP!,{LR} @LR 入棧
3
4LDMFD SP!, {LR} @先恢復 LR
5 LDMFD SP!, {R0~ R3, R12} @再恢復 R0~R3, R12
指令 | 描述 |
---|---|
B < label > | 跳轉到 label,如果跳轉範圍超過了+/-2KB,可以指定 B.W < label>使用 32 位版本的跳轉指令, 這樣可以得到較大範圍的跳轉 |
BX < Rm > | 間接跳轉,跳轉到存放於 Rm 中的地址處,並且切換指令集 |
BL < label> | 跳轉到標號地址,並將返回地址儲存在 LR 中。 |
BLX < Rm> | 結合 BX 和 BL 的特點,跳轉到 Rm 指定的地址,並將返回地址儲存在 LR 中,切換指令集。 |
1、B 指令
這是最簡單的跳轉指令,B 指令會將 PC 暫存器的值設定爲跳轉目標地址, 一旦執行 B 指令,ARM 處理器就會立即跳轉到指定的目標地址。如果要呼叫的函數不會再返回到原來的執行處,那就可以用 B 指令,如下範例:
1 _start:
2
3 ldr sp,=0X80200000 @設定棧指針
4 b main @跳轉到 main 函數
上述程式碼就是典型的在彙編中初始化 C 執行環境,然後跳轉到 C 檔案的 main 函數中執行,上述程式碼只是初始化了 SP 指針,有些處理器還需要做其他的初始化,比如初始化 DDR 等等。因爲跳轉到 C 檔案以後再也不會回到彙編了,所以在第 4 行使用了 B 指令來完成跳轉。
2、BL 指令
BL 指令相比 B 指令,在跳轉之前會在暫存器 LR(R14)中儲存當前 PC 暫存器值,所以可以通過將 LR 暫存器中的值重新載入到 PC 中來繼續從跳轉之前的程式碼處執行,這是子程式呼叫一個基本但常用的手段。比如 Cortex-A 處理器的 irq 中斷服務函數都是彙編寫的,主要用匯編來實現現場的保護和恢復、獲取中斷號等。但是具體的中斷處理過程都是 C 函數,所以就會存在彙編中呼叫 C 函數的問題。而且當 C 語言版本的中斷處理常式執行完成以後是需要返回到irq 彙編中斷服務函數,因爲還要處理其他的工作,一般是恢復現場。這個時候就不能直接使用B 指令了,因爲 B 指令一旦跳轉就再也不會回來了,這個時候要使用 BL 指令,範例程式碼如下:
1 push {r0, r1} @儲存 r0,r1
2 cps #0x13 @進入 SVC 模式,允許其他中斷再次進去
3
4 bl system_irqhandler @載入 C 語言中斷處理常式到 r2 暫存器中
5
6 cps #0x12 @進入 IRQ 模式
7 pop {r0, r1}
8 str r0, [r1, #0X10] @中斷執行完成,寫 EOIR
上述程式碼中第 4 行就是執行 C 語言版的中斷處理常式,當處理完成以後是需要返回來繼續執行下面 下麪的程式,所以使用了 BL 指令。
彙編中也可以進行算術運算, 比如加減乘除,常用的運算指令用法如表
在嵌入式開發中最常會用的就是加減指令,乘除基本用不到。
我們用 C 語言進行 CPU 暫存器設定的時候常常需要用到邏輯運算子號,比如「&」、「|」等邏輯運算子。使用匯編語言的時候也可以使用邏輯運算指令,常用的運算指令用法如表 :