ARM架构与编程——2.ARM汇编与指令

参考:<ARM编程与架构> 韦东山

ARM内部寄存器及汇编指令概述

寄存器:
我们说”寄存器”时,需要分辨是哪种:
1.CPU内部的寄存器
2.CPU之外设备自己的寄存器
这是不一样的:
CPU通过各类汇编指令访问内部寄存器
CPU要访问设备的寄存器时,就像访问内存一样,要先知道地址
举例:外部寄存器的地址是0x1000,要读它,使用如下汇编指令:

mov r0,#0x1000	  //把外部存器地址值赋给CPU内部寄存器r0
ldr r1,[r0]		 // 使用r0所表示的地址来读数据,存入CPU内部寄存器r1

一、ARM内部寄存器

无论是cortex-M3/M4,还是cortex-A7,CPU内部都有R0、R1、……R15寄存器;它们可以用来“暂存”数据。
其中R0~R12为通用目的寄存器;
对于R13、R14、R15,还另有用途:
R13:别名SP(Stack Pointer),栈指针
R14:别名LR(Link Register),用来保存返回地址
R15:别名PC(Program Counter),程序计数器,表示当前指令地址,写入新值即可跳转

1.1 M3/M4/A7 CPU内部寄存器

cortex-M3/M4

在这里插入图片描述
1)对于cortex-M3/M4来说,比较两个数时,结果保存在哪?
结果就保存在xPSR(Program Status Register)寄存器中
在这里插入图片描述
2)对于cortex-M3/M4来说,xPSR实际上对应3个寄存器:
① APSR:Application PSR,应用PSR
② IPSR:Interrupt PSR,中断PSR
③ EPSR:Exectution PSR,执行PSR
这3个寄存器的含义如下图所示
在这里插入图片描述
在这里插入图片描述
3)这3个寄存器,可以单独访问:
MRS R0, APSR ;读APSR
MRS R0, IPSR ;读IPSR
MSR APSR, R0 ;写APSR

这3个寄存器,也可以一次性访问:
MRS R0, PSR ; 读组合程序状态
MSR PSR, R0 ; 写组合程序状态
所谓组合程序状态,如下图所示:
在这里插入图片描述

cortex-A7

A7架构的寄存器介绍及相应的工作模式中的专用寄存器
在这里插入图片描述
1)对于A7来说:比较两个数时,结果保存在哪?
结果保存在CPSR(Current Program Status Register)寄存器中
2)组合程序状态,与M3/M4的类似,但是异常编号不同
在这里插入图片描述
在这里插入图片描述

二、ARM汇编概述

一开始,ARM公司发布两类指令集:
① ARM指令集,这是32位的,每条指令占据32位,高效,但是太占空间
② Thumb指令集,这是16位的,每条指令占据16位,节省空间
要节省空间时用Thumb指令,要效率时用ARM指令。
一个CPU既可以运行Thumb指令,也能运行ARM指令。

怎么区分当前指令是Thumb还是ARM指令呢?
程序状态寄存器中有一位,名为“T”,它等于1时表示当前运行的是Thumb指令。
假设函数A是使用Thumb指令写的,函数B是使用ARM指令写的,怎么调用A/B?
我们可以往PC寄存器里写入函数A或B的地址,就可以调用A或B,
但是怎么让CPU在执行A函数是进入Thumb状态,在执行B函数时进入ARM状态?
做个手脚:
调用函数A时,让PC寄存器的BIT0等于1,即:PC=函数A地址+(1<<0);
调用函数B时,让PC寄存器的BIT0等于0:,即:PC=函数B地址
麻烦吧?麻烦!
引入Thumb2指令集,它支持16位指令、32位指令混合编程。
有那么多指令集:ARM、Thumb、Thumb2,
不好记啊!
不用区分它们,不用担心,
ARM公司推出了: Unified Assembly Language
UAL,统一汇编语言,你不需要去区分这些指令集

2.1 日常工作中常用的几条汇编指令

MOV
LDR/STR
LDM/STM
AND/OR
ADD/SUB
B/BL
DCD
ADR/LDR
CMP
注:写不出很复杂的汇编程序?没必要写很复杂的,设置栈后就用C语言来写函数

2.2 汇编指令格式

以“数据处理”指令为例,UAL汇编格式为:
在这里插入图片描述

Operation表示各类汇编指令,比如ADD、MOV;
cond表示conditon,即该指令执行的条件;
S表示该指令执行后,会去修改程序状态寄存器;
Rd为目的寄存器,用来存储运算的结果;
Rn、Operand2是两个源操作数
参考:参考《DEN0013D_cortex_a_series_PG.pdf》P70、《ARM Cortex-M3与Cortex-M4权威指南.pdf》第5章
2.2.1 数据处理指令

在这里插入图片描述Operation表示各类汇编指令,比如ADD、MOV;如下图:

在这里插入图片描述
cond有多种取值,如下:
在这里插入图片描述

// 几条数据处理指令
1.加法指令ADD:
      ADD  R1, R2, R3         ; R1 = R2 + R3
      ADD  R1, R2, #0x12   ; R1 = R2 + 0x12

2. 减法指令SUB:
      SUB  R1, R2, R3         ; R1 = R2 - R3
      SUB  R1, R2, #0x12   ; R1 = R2 - 0x12

3. 位操作:
     ;  VisUAL里不支持(1<<4)这样的写法,写成:0x10
      AND R1, R2, #(1<<4)   ;  位与,R1 = R2 & (1<<4)
      AND  R1, R2, R3           ; 位与,R1 = R2 & R3
      BIC  R1, R2, #(1<<4)    ; 清除某位,R1 = R2 & ~(1<<4)
      BIC  R1, R2, R3             ; 清除某位,R1 = R2 & ~R3
      ORR   R1, R2, R3	; 或操作

4. 比较:
    CMP R0, R1                ;  比较R0-R1的结果
    CMP R0, #0x12            ; 比较R0-0x12的结果
    TST  R0, R1                 ;  测试 R0 & R1的结果
    TST  R0, #(1<<4)        ;  测试 R0 & (1<<4)的结果


2.2.2 内存访问指令

读内存指令LDR/LDM:参考《DEN0013D_cortex_a_series_PG.pdf》P340、P341
写内存指令STR/STM:参考《DEN0013D_cortex_a_series_PG.pdf》P377、P378

LDR/STR:
LDR:Load Register;LDM:Load Multiple Register;
在这里插入图片描述

STR:Store Register;STM:Store Multiple Register。
在这里插入图片描述

// 示例
		MOV		R0, #0x20000
		MOV		R1, #0x10
		MOV		R2, #0x12
		STR		R2, [R0]                 ; R2的值存到R0所示地址
		STR		R2, [R0, #4]          ; R2的值存到R0+4所示地址
		STR		R2, [R0, #8]!         ; R2的值存到R0+8所示地址, R0=R0+8
		STR		R2, [R0, R1]          ; R2的值存到R0+R1所示地址
		STR		R2, [R0, R1, LSL #4]  ; R2的值存到R0+(R1<<4)所示地址
		STR		R2, [R0], #0X20           ; R2的值存到R0所示地址, R0=R0+0x20
		MOV		R2, #0x34
		STR		R2, [R0]                          ; R2的值存到R0所示地址
		LDR		R3, [R0], +R1, LSL #1 ; R3的值等于R0+(R1<<1)所示地址上的值

LDM/STM:
在这里插入图片描述
在这里插入图片描述

//LDM/STM的地址模式:
addr_mode:
IA - Increment After,  每次传输后才增加Rn的值(默认,可省)
IB - Increment Before, 每次传输前就增加Rn的值(ARM指令才能用)
DA – Decrement After,  每次传输后才减小Rn的值(ARM指令才能用)
DB – Decrement Before, 每次传输前就减小Rn的值

! : 表示修改后的Rn值会写入Rn寄存器, 
    如果没有"!", 指令执行完后Rn恢复/保持原值

^ : 会影响CPSR, 在讲异常时再细讲
// 示例
		MOV		R1, #1
		MOV		R2, #2
		MOV		R3, #3
		MOV		R0, #0x20000
		STMIA	R0,  {R1-R3}               ; R1,R2,R3分别存入R0,R0+4,R0+8地址处
		ADD	R0, R0, #0x10
		STMIA	R0!, {R1-R3}              ; R1,R2,R3分别存入R0,R0+4,R0+8地址处, R0=R0+3*4

在这里插入图片描述

1.低标号的寄存器对应低地址;高标号的寄存器对应高地址
2.代码详解:
从内存中把R1-R3读出来,并且读到R1-R3的位置,这就是栈的作用;
栈就是用来保存现场的,所谓现场就是保存那些寄存器
调用某个函数之前,先把当前寄存器的值保存起来,保存
在栈中(栈就是内存),调用完之后,再把对应寄存器中的值读出来

2.2.3 分支/跳转指令

参考《DEN0013D_cortex_a_series_PG.pdf》P327、P328、P329
核心指令是B、BL:
C程序中,函数A调用函数B的实质是什么?
void A()
{
int a = 10;
B(a);
printf(“ok”);
}
实质是:跳转去执行函数B的代码,函数B执行完后,还要回到函数A继续执行后面的代码。

对应的汇编指令就是跳转指令

B:Branch,跳转
在这里插入图片描述
BL:Branch with Link,跳转前先把返回地址保持在LR寄存器中
在这里插入图片描述
BX:Branch and eXchange,根据跳转地址的BIT0切换为ARM或Thumb状态(0:ARM状态,1:Thumb状态)
在这里插入图片描述
BLX:Branch with Link and eXchange
根据跳转地址的BIT0切换为ARM或Thumb状态(0:ARM状态,1:Thumb状态)

在这里插入图片描述

// 几条跳转指令实现
1.B指令示例:
		B		Delay
Delay
		MOV		R0, #1000
Loop
		SUBS		R0, R0, #1
		BNE		Loop
		MOV		R1, #1
		
2. BL指令示例:
		BL		Delay    ; 跳转前把返回地址保持在LR寄存器里
		MOV		R1, #1
Delay
		MOV		R0, #1000
Loop
		SUBS		R0, R0, #1
		BNE		Loop
		MOV		PC, LR    ; 把LR赋给PC,返回

3.给PC直接赋值
		ADR		LR, Ret    ; 伪指令,读取Ret标号的地址赋给LR,这是返回地址
		ADR		PC, Delay  ; 伪指令,读取Delay标号的地址赋给PC,直接跳转
Ret
		MOV		R1, #1
Delay
		MOV		R0, #1000
Loop
		SUBS		R0, R0, #1
		BNE		Loop
		MOV		PC, LR    ; 把LR赋给PC,返回

2.2.4 立即数

这样一条指令:
MOV R0, #VAL
意图是把VAL这个值存入R0寄存器。

问:VAL可以是任意值吗?
答:不可以,必须是立即数。符合某些规则的数
问:为什么?
答:假设VAL可以是任意数,”MOV R0, #VAL”本身是16位或32位,哪来的空间保存任意数值的VAL?所以,VAL必须符合某些规定。
在这里插入图片描述
在这里插入图片描述

2.2.4 LDR伪指令

去判断一个VAL是否立即数,麻烦!
并且我就是想把任意数值赋给R0,怎么办?
可以使用伪指令:
LDR R0, =VAL
“伪指令”,就是假的、不存在的指令。
注意LDR作为“伪指令”时,指令中有一个“=”,否则它就是真实的LDR(load regisgter)指令了。

编译器会把“伪指令”替换成真实的指令,比如:
LDR  R0,  =0x12    
0x12是立即数,那么替换为:MOV  R0,  #0x12

LDR  R0, =0x12345678
0x12345678不是立即数,那么替换为:
LDR  R0, [PC, #offset]          // 2. 使用Load Register读内存指令读出值,offset是链接程序时确定的
……
Label  DCD  0x12345678    // 1. 编译器在程序某个地方保存有这个值
2.2.4 ADR伪指令

ADR的意思是:address,用来读某个标号的地址
在这里插入图片描述

示例:
ADR  R0,  Loop

Loop
    ADD  R0, R0, #1
它是“伪指令”,会被转换成某条真实的指令,比如:
ADD R0, PC, #val   ; val在链接时确定
Loop
    ADD  R0, R0, #1

2.2.5 ARM编译器与GCC编译器语法差异

在这里插入图片描述

2.3 栈的四种方式

根据栈指针指向,可分为满(Full)/空(Empty):
满SP指向最后一个入栈的数据,需要先修改SP再入栈
空SP指向下一个空位置,先入栈再修改SP

根据压栈时SP的增长方向,可分为增/减:
增(Ascending):SP变大
减(Descending):SP变小
组合后,就有4种方式:
满增、满减,
空增,空减。
常用的“满减”:
入栈时用STMDB,也可以用STMFD,作用一样;
出栈时用LDMIA,也可以用LDMFD,作用一样。
在这里插入图片描述

最常用的就是满减这种类型的栈
在这里插入图片描述

// 示例
STMFD sp!, {r0-r5} ; Push onto a Full Descending Stack
LDMFD sp!, {r0-r5} ; Pop from a Full Descending Stack

在这里插入图片描述
压栈:先减SP,再存数据
出栈:先读地址,再加
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值