1、I.MX6ULL學習筆記一彙編LED實驗 包含彙編基礎用法

2020-08-12 16:32:29

一、原理分析

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表示輸出低電平。

二、彙編簡介

1、 記憶體存取指令

常用的記憶體存取指令有: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

2、處理器內部數據傳輸指令

指令 目的 描述
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

3、壓棧和出棧指令

  我們通常會在 A 函數中呼叫 B 函數,當 B 函數執行完以後再回到 A 函數繼續執行。要想在跳回 A 函數以後程式碼能夠接着正常執行,那就必須在跳到 B 函數之前將當前處理器狀態儲存起來(就是儲存 R0 ~ R15 這些暫存器值),當 B 函數執行完成以後再用前面儲存的暫存器值恢復R0~ R15 即可。儲存 R0~ R15 暫存器的操作就叫做現場保護,恢復 R0~R15 暫存器的操作就叫做恢復現場。在進行現場保護的時候需要進行壓棧(入棧) 操作,恢復現場就要進行出棧操作。壓棧的指令爲 PUSH,出棧的指令爲 POP,PUSH 和 POP 是一種多儲存和多載入指令,即可以一次操作多個暫存器數據,他們利用當前的棧指針 SP 來生成地址,PUSH 和 POP 的用法如表

保護現場 壓棧PUSH
恢復現場 出棧POP
A函數
B函數
指令 描述
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

4、跳轉指令

 指令        描述
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 指令。

5、算術運算指令

彙編中也可以進行算術運算, 比如加減乘除,常用的運算指令用法如表

在这里插入图片描述
在嵌入式開發中最常會用的就是加減指令,乘除基本用不到。

6、邏輯運算指令

我們用 C 語言進行 CPU 暫存器設定的時候常常需要用到邏輯運算子號,比如「&」、「|」等邏輯運算子。使用匯編語言的時候也可以使用邏輯運算指令,常用的運算指令用法如表 :
在这里插入图片描述