日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ARM汇编基础详解(PS学习汇编的原因)

發布時間:2023/12/10 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ARM汇编基础详解(PS学习汇编的原因) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 前言
  • 1.GNU 匯編語法
  • 2.Cortex-A7 常用匯編指令
    • 2.1 處理器內部數據傳輸指令(內部寄存器數據非內存數據)
    • 2.2 存儲器訪問指令(RAM)
    • 2.3 壓棧和出棧指令(了解)
    • 2.4 跳轉指令
    • 2.5 算術運算指令
    • 2.6 邏輯運算指令

前言

我們在進行嵌入式 Linux 開發的時候是絕對要掌握基本的 ARM 匯編,因為 Cortex-A 芯片一上電 SP 指針(指向一段起始地址,指向棧頂)還沒初始化C 環境還沒準備好,所以肯定不能運行 C 代碼,必須先用匯編語言設置好 C 環境,比如初始化 DDR、設置 SP指針等等,當匯編把 C 環境設置好了以后才可以運行 C 代碼。所以 Cortex-A 一開始肯定是匯編代碼,其實 STM32 也一樣的,一開始也是匯編,以 STM32F103 為例,啟動文件startup_stm32f10x_hd.s 就是匯編文件,只是這個文件 ST 已經寫好了,我們根本不用去修改,所以大部分學習者都沒有深入的去研究。匯編的知識很龐大,本章我們只講解最常用的一些指令,滿足我們后續學習即可。

至于要寫多少匯編程序,那就看你能在哪一步把C 語言環境準備好。所謂的C語言環境就是保證C 語言能夠正常運行。C 語言中的函數調用涉及到出棧入棧,出棧入棧就要對堆棧進行操作,所謂的堆棧其實就是一段內存,這段內存比較特殊,由SP 指針訪問,SP 指針指向棧頂。芯片一上電SP 指針還沒有初始化,所以C 語言沒法運行,對于有些芯片還需要初始化DDR,因為芯片本身沒有RAM,或者內部RAM 不開放給用戶使用,用戶代碼需要在DDR 中運行,因此一開始要用匯編來初始化DDR 控制器。后面學習Uboot 和Linux 內核的時候匯編是必須要會的。在芯片上電以后用匯編來初始化一些外設,不會涉及到復雜的代碼,而且使用到的指令都是很簡單的,用到的就那么十幾個指令

1.GNU 匯編語法

如果大家使用過 STM32 的話就會知道 MDK 和 IAR 下的啟動文件 startup_stm32f10x_hd.s其中的匯編語法是有所不同的,將 MDK 下的匯編文件直接復制到 IAR 下去編譯就會出錯,因為 MDK 和 IAR 的編譯器不同,因此對于匯編的語法就有一些小區別。我們要編寫的是 ARM匯編,編譯使用的 GCC 交叉編譯器,所以我們的匯編代碼要符合 GNU 語法。

GNU 匯編語法適用于所有的架構,并不是 ARM 獨享的,GNU 匯編由一系列的語句組成,每行一條語句,每條語句有三個可選部分,如下:

label:instruction @ comment

labe: l即標號,表示地址位置,有些指令前面可能會有標號,這樣就可以通過這個標號得到指令的地址,標號也可以用來表示數據地址。注意 label 后面的“:”,任何以“:”結尾的標識符都會被識別為一個標號。

instruction: 即指令,也就是匯編指令或偽指令

@ 符號:表示后面的是注釋,就跟 C 語言里面的“/”和“/”一樣,其實在 GNU 匯編文件中我們也可以使用“/”和“/”來注釋。

comment: 就是注釋內容。

比如如下代碼:

add: MOVS R0, #0X12 @設置 R0=0X12

上面代碼中“add:”就是標號,“MOVS R0,#0X12”就是指令,最后的“@設置 R0=0X12”就是注釋。

注意!ARM 中的指令、偽指令、偽操作、寄存器名等可以全部使用大寫,也可以全部使用小寫,但是不能大小寫混用。

用戶可以使用.section 偽操作來定義一個段,每個段以段名開始,以下一段名或者文件結尾結束,比如:

.section .testsection @定義一個 testsetcion 段

匯編系統預定義了一些段名:

定義段名
.text表示代碼段。
.data初始化的數據段。
.bss未初始化的數據段。
.rodata只讀數據段。

匯編程序的默認入口標號是_start,不過我們也可以在鏈接腳本中使用 ENTRY 來指明其它的入口點,下面的代碼就是使用_start 作為入口標號:

.global _start_start: ldr r0, =0x12 @r0=0x12

上面代碼中.global 是偽操作,表示_start 是一個全局標號,類似 C 語言里面的全局變量一樣,常見的偽操作有(了解即可):

偽操作含義
.byte定義單字節數據,比如.byte 0x12。
.short定義雙字節數據,比如.short 0x1234。
.long定義一個 4 字節數據,比如.long 0x12345678。
.equ賦值語句,格式為:.equ 變量名,表達式,比如.equ num, 0x12,表示 num=0x12。
.align數據字節對齊,比如:.align 4 表示 4 字節對齊。
.end表示源文件結束。
.global定義一個全局符號,格式為:.global symbol,比如:.global _start。

GNU 匯編還有其它的偽操作,但是最常見的就是上面這些,如果想詳細的了解全部的偽操作,可以參考《ARM Cortex-A(armV7)編程手冊 V4.0.pdf》的 57 頁。

GNU 匯編同樣也支持函數,函數格式如下:

函數名:函數體返回語句

GNU 匯編函數返回語句不是必須的,如下代碼就是用匯編寫的。 Cortex-A7 中斷服務函數:

匯編函數的定義 /* 未定義中斷 */ Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC 中斷 */ SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 預取終止中斷 */ PrefAbort_Handler:ldr r0, =PrefAbort_Handlerbx r0

上述代碼中定義了三個匯編函數:Undefined_Handler、SVC_Handler 和PrefAbort_Handler。以函數 Undefined_Handler 為例我們來看一下匯編函數組成:

  • “Undefined_Handler”就是函數名
  • “ldr r0, =Undefined_Handler”是函數體
  • “bx r0”是函數返回語句,“bx”指令是返回指令,函數返回語句不是必須的。

2.Cortex-A7 常用匯編指令

介紹一些常用的 Cortex-A7 匯編指令,如果想系統的了解 Cortex-A7 的所有匯編指令請參考《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的 A4章節。

2.1 處理器內部數據傳輸指令(內部寄存器數據非內存數據)

使用處理器做的最多事情就是在處理器內部來回的傳遞數據,常見的操作有:

①、將數據從一個寄存器傳遞到另外一個寄存器。
②、將數據從一個寄存器傳遞到特殊寄存器,如 CPSR 和 SPSR 寄存器。
③、將立即數傳遞到寄存器。

數據傳輸常用的指令有三個:MOV、MRS 和 MSR,這三個指令的用法如表 7.2.1.1 所示:

指令目的源描述
MOVR0R1將 R1 里面的數據復制到 R0 中。
MRSR0CPSR將特殊寄存器 CPSR 里面的數據復制到 R0 中。
MSRCPSRR1將 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(賦值),因為他們直接操作的是處理器內部寄存器(上篇博文給出了內部寄存器的示意圖),而a=b(賦值)是對內存(RAM)的操作。
下面的存儲器(即RAM,不是SD卡,不是FLASH)訪問指令才可以實現。

2.2 存儲器訪問指令(RAM)

ARM的CPU 不能直接訪問存儲器(RAM)中的數據,I.MX6UL 中的寄存器就是 RAM 類型的,我們用匯編來配置 I.MX6UL 寄存器的時候需要借助存儲器訪問指令,一般先將要配置的值寫入到 Rx(x=0~12)(通用)寄存器中,然后借助存儲器訪問指令將 Rx 中的數據寫入到 I.MX6UL 寄存器中。讀取 I.MX6UL 寄存器也是一樣的,只是過程相反。常用的存儲器訪問指令有兩種:LDR 和STR,用法如表:

指令描述
LDR Rd, [Rn , #offset]存儲器(RAM) Rn+offset 的位置讀取數據存放到 Rd 中。
STR Rd, [Rn, #offset]將 Rd 中的數據寫入到存儲器(RAM) 中的 Rn+offset 位置。

分別來詳細的介紹一下如何使用這兩個指令(寫驅動最常用的兩個指令):

1 、LDR 指令

LDR 主要用于從存儲器(RAM)加載數據到寄存器 Rx 中,LDR 也可以將一個立即數加載到寄存器 Rx中,LDR 加載立即數的時候要使用“=”,而不是“#”。在嵌入式開發中,LDR 最常用的就是讀取 CPU 的寄存器值,比如 I.MX6UL 有個寄存器 GPIO1_GDIR,其地址為 0X0209C004,我們現在要讀取這個寄存器中的數據,示例代碼如下:

示例代碼 LDR 指令使用LDR R0, =0X0209C004 @將寄存器地址 0X0209C004 加載到 R0 中,即 R0=0X0209C004LDR R1, [R0] @讀取地址 0X0209C004 中的數據到 R1 寄存器中

上述代碼就是讀取寄存器 GPIO1_GDIR 中的值,讀取到的寄存器值保存在 R1 寄存器中,上面代碼中 offset 是 0,也就是沒有用到 offset。

2 、STR 指令

LDR 是從存儲器讀取數據,STR 就是將數據寫入到存儲器中,同樣以 I.MX6UL 寄存器GPIO1_GDIR 為例,現在我們要配置寄存器 GPIO1_GDIR 的值為 0X2000002,示例代碼如下:

示例代碼 STR 指令使用LDR R0, =0X0209C004 @將寄存器地址 0X0209C004 加載到 R0 中,即 R0=0X0209C004LDR R1, =0X20000002 @R1 保存要寫入到寄存器的值,即 R1=0X20000002STR R1, [R0] @將 R1 中的值寫入到 R0 中所保存的地址中

LDR 和 STR 都是按照字進行讀取和寫入的,也就是操作的 32 位數據,如果要按照字節、半字進行操作的話可以在指令“LDR”后面加上 B 或 H,比如按字節操作的指令就是 LDRB 和STRB,按半字操作的指令就是 LDRH 和 STRH。

2.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從棧中恢復寄存器列表。

假如我們現在要將 R0~R3 和 R12 這 5 個寄存器壓棧,當前的 SP 指針指向 0X80000000,處理器的堆棧是向下增長的,使用的匯編代碼如下:

PUSH {R0~R3, R12} @將 R0~R3 和 R12 壓棧

壓棧完成以后的堆棧如圖所示:


就是分兩步對 R0~R3,R2 和 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} @先恢復 LRLDMFD SP!, {R0~R3, R12} @再恢復 R0~R3, R12

STMFD 可以分為兩部分:STM 和 FD,同理,LDMFD 也可以分為 LDM 和 FD。看到 STM和 LDM 有沒有覺得似曾相識(不是 STM32 啊啊啊啊),前面我們講了 LDR 和 STR,這兩個是數據加載和存儲指令,但是每次只能讀寫存儲器中的一個數據。STM 和 LDM 就是多存儲和多加載,可以連續的讀寫存儲器中的多個連續數據。FD 是 Full Descending 的縮寫,即滿遞減的意思。根據 ATPCS 規則,ARM 使用的 FD 類型的堆棧,SP 指向最后一個入棧的數值,堆棧是由高地址向下增長的,也就是前面說的向下增長的堆棧,因此最常用的指令就是 STMFD 和 LDMFD。STM 和 LDM 的指令寄存器列表中編號小的對應低地址,編號高的對應高地址。

2.4 跳轉指令

有多種跳轉操作,比如:

①、直接使用跳轉指令 B、BL、BX 等。
②、直接向 PC 寄存器里面寫入數據。
上述兩種方法都可以完成跳轉操作,但是一般常用的還是 B、BL 或 BX,用法如表:

指令描述
B(回不來)跳轉到 label,如果跳轉范圍超過了+/-2KB,可以指定 B.W使用 32 位版本的跳轉指令, 這樣可以得到較大范圍的跳轉
BX間接跳轉,跳轉到存放于 Rm 中的地址處,并且切換指令集
BL(回得來)跳轉到標號地址,并將返回地址保存在 LR 中。
BLX結合 BX 和 BL 的特點,跳轉到 Rm 指定的地址,并將返回地址保存在 LR 中,切換指令集。

我們重點來看一下 B 和 BL 指令,因為這兩個是我們用的最多的,如果要在匯編中進行函數調用使用的就是 B 和 BL 指令:

1 、B 指令

這是最簡單的跳轉指令,B 指令會將 PC 寄存器的值設置為跳轉目標地址,一旦執行 B 指令,ARM 處理器就會立即跳轉到指定的目標地址。如果要調用的函數不會再返回到原來的執行處,那就可以用 B 指令,如下示例:

示例代碼 B 指令示例_start:ldr sp,=0X80200000 @設置棧指針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 指令,示例代碼如下:

示例代碼 BL 指令示例 1 push {r0, r1} @保存 r0,r1 2 cps #0x13 @進入 SVC 模式,允許其他中斷再次進去 3 5 bl system_irqhandler @加載 C 語言中斷處理函數到 r2 寄存器中 6 7 cps #0x12 @進入 IRQ 模式 8 pop {r0, r1} 9 str r0, [r1, #0X10] @中斷執行完成,寫 EOIR

上述代碼中第 5 行就是執行 C 語言版的中斷處理函數,當處理完成以后是需要返回來繼續執行下面的程序,所以使用了 BL 指令。

2.5 算術運算指令

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

指令計算公式備注
ADD Rd, Rn, RmRd = Rn + Rm加法運算,指令為 ADD
ADD Rd, Rn, #immedRd = Rn + #immed加法運算,指令為 ADD
ADC Rd, Rn, RmRd = Rn + Rm + 進位帶進位的加法運算,指令為 ADC
ADC Rd, Rn, #immedRd = Rn + #immed +進位帶進位的加法運算,指令為 ADC
SUB Rd, Rn, RmRd = Rn – Rm減法
SUB Rd, #immedRd = Rd - #immed減法
SUB Rd, Rn, #immedRd = Rn - #immed減法
SBC Rd, Rn, #immedRd = Rn - #immed – 借位帶借位的減法
SBC Rd, Rn ,RmRd = Rn – Rm – 借位帶借位的減法
MUL Rd, Rn, RmRd = Rn * Rm乘法(32 位)
UDIV Rd, Rn, RmRd = Rn / Rm無符號除法
SDIV Rd, Rn, RmRd = Rn / Rm有符號除法

在嵌入式開發中最常會用的就是加減指令,乘除基本用不到。

2.6 邏輯運算指令

我們用 C 語言進行 CPU 寄存器配置的時候常常需要用到邏輯運算符號,比如“&”、“|”等邏輯運算符。使用匯編語言的時候也可以使用邏輯運算指令,常用的運算指令用法如表 所示:

邏輯運算指令都很好理解,后面時候匯編配置 I.MX6UL 的外設寄存器的時候可能會用到

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的ARM汇编基础详解(PS学习汇编的原因)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。