首页天道酬勤,

,

张世龙 05-13 02:56 111次浏览

ARM学习笔记(一)汇编语言ARM编程模型ARM寄存器概要关于ARM7的工作模式汇编指令数据处理指令补充知识点 ARM寻址方式补充知识点第二操作数寄存器的偏移寻址即时数寻址数据传输指令单一寄存器传输

你好! 这是自动化专业“嵌入式系统设计”课程的总结笔记。 参考文献: 《嵌入式计算系统设计原理》 《ARM7数据手册》

ARM编程模型编程模型是所有用户都能看到的寄存器的集合。

ARM寄存器概述在ARM用户模式下,用户看到17个寄存器,包括r0-r15和程序状态寄存器CPSR。 ARM寄存器的字长和数据总线宽度一致,均为32位。

r0-r12 :通用寄存器、保存变量r13 :栈顶指针寄存器r14 :连接寄存器。 调用函数时,将以下指令的地址存储在r14中,然后将r15设置为目标函数的地址。 返回函数时,r14的值被分配给r15。 如果发生函数嵌套,请为每个嵌套调用堆栈r14并将r15分配给r14。 r15 )程序计数器(PC )是指接下来应采取的指令的地址。 假设当前正在执行的指令位于地址a,由于ARM是三级流水线,PC指针实际指向的地址是(a 8)。 ARM要求所有指令均为32位宽,起始地址为32位对齐,而不管Thumb指令集如何,这意味着指令地址的后2位必须为0。 下图为ARM的3级管线结构。

CPSR :当前程序状态寄存器。 包括NZCV的四个状态位和三个控制选项。

标志功能n负数标志位。 运算结果ans为负,N=1; 否则N=0Z零标志位。 ans=0,Z=1; ans=1,Z=0C进位标志位。 加法,最高位有进位时保存C=1,否则保存C=0减法,最高位有借用时保存C=0,否则保存C=1移位运算,c保存移位后的位。 v溢出标志位,结果溢出到1,否则变为0IF中断屏蔽标志位,分为I和f两个位。 分别负责IRQ和FIQ的屏蔽,设置1时屏蔽对应的中断TT=1时使用Thumb指令集,T=0时使用ARM指令集MODE选择当前处理器的工作模式。 很多关于ARM7的博客都提到ARM7有七种工作模式。 但是我找到的ARM7中文数据手册只有六种,缺少系统模式(SYS )。

USR :用户模式,程序正常运行时的模式SYS :系统模式IRQ :正常中断模式FIQ :高速中断模式,有更高的优先级和响应速度。 可以中断常见的IRQ中断,并在进入FIQ中断处理程序后关闭IRQ。 SVC )管理模式,即以SWI指令进入的管理模式ABT )中止模式、非法访问存储器时进入的模式UND )指无定义模式、访问的指令缺乏协处理器这样的硬件支持,在该模式下USR和SYS以外的总称异常模式。

汇编指令ARM指令集可以分为几个子类,每个指令类别具有相同的编码方案。 以下介绍三种一般命令。

数据处理指令数据处理指令的编码格式如下图所示。

命令代码的注释如下。

条件代码: ARM中的所有数据处理指令都支持条件执行。 例如,ADDNE指示当CPSR中的z标志位为0时执行该相加指令。 这里的“NE”被编码为4位条件代码。 I :指示第二操作数是立即数(1)还是寄存器)的操作码)数据处理指令共有16个,指示当前操作是否修改CPSR (使用4位二进制代码s ),S=1进行修改。 RN )第一操作数RD )目标寄存器操作数)第二操作数指令介绍下图描述了ARM7中文数据手册中的数据处理指令。 其中,SBC和RSC写错了。 修改为:

SBC - Rd :=Op1 - Op2 -1 C

RSC - Rd :=Op2 - Op1 -1 C

ps1:TEQ命令记述中的EOR是异或运算。

ps2:BIC命令的作用是逐位清除0,找到Op2中有1的位,然后将Op1中相应的位清除为0。 (位清除)

知识点 ARM地址方式这一部分参考了博主dongdong0071的文章,想详细了解ARM的地址方式

因为ARM指令集都是32位宽,同时地址总线也是32位,所以汇编指令不能直接指定内存的绝对地址(进不去,怎么想也填不进去,何况很多指令都是3个操作数),内存

但是,寄存器只有16个,可以用4位进行地址,所以寄存器地址是可能的。

这一点与MU0处理器不同,因为MU0是单一操作数指令,且地址空间小,所以所有存储器单元直接指定存储器的绝对地址。

ARM的地址方式共有8种。

即时数寻址:满足特定条件的即时数可以直接作为操作数,仅限于第二个操作数。 例如,MOV R0、#38; MOV R0,#0x6400; ps :就在所有前面#;0x和都表示十六进制。

寄存器直接寻址: ADD R0、R1、R2

间接寻址: ARM处理器具有加载存储结构,所有数据运算的操作数都是寄存器。 要操作内存,必须首先将内存数据加载到寄存器中。 所

以这种寻址方式只会出现在 LDR,STR 等数据传送指令中。例如,LDR R0,[R1]

寄存器偏移寻址:个人认为这不算一种寻址方式,它只是在寄存器直接寻址的基础上对取出的操作数增加了移位操作。例如,MOV R0,R2,LSL #3; R2的值左移3位,结果赋给R0。

寄存器基址变址寻址:分为三类:
① 前变址:LDR r0, [r1, #4] ; r0 = mem[r0 + 4]
② 前变址,自动变址:STR r0, [r1, #4]! ; r1 = r1 + 4, r0 = mem[r1]
③ 后变址,自动变址:LDR r0, [r1], #4 ; r0 = mem[r1], r1 = r1 + 4

相对寻址:在B、BL等控制流指令中用到。常见格式:BL init 。详见控制流指令章节。

堆栈寻址

多寄存器寻址

在以上寻址方式中,寄存器间接寻址、基址变址寻址、堆栈寻址(堆栈也是内存)、多寄存器寻址、块拷贝寻址都涉及内存操作,只能用于数据传送指令(存疑,操作数在内存上的命令只能是Load-Store命令吗?欢迎评论区指出)。详见【数据传送指令】章节。

补充知识点 ② 关于第二操作数

ARM 的数据处理指令是三操作数指令。( 按照ARM7手册的分类,MUL/MLA不属于数据处理指令,它是根据编码格式分类的 )分别是目的操作数、第一操作数、第二操作数。其中目的操作数和第一操作数使用 4 位二进制编址,因此只能使用只有16种地址选择的寄存器直接寻址。第二操作数的变址空间有12位,因此它的使用更加灵活。在数据处理指令中,还可以使用寄存器偏移寻址和立即数寻址。

寄存器偏移寻址

如前所述,寄存器偏移寻址是对第二操作数的移位。所以第二操作数的 12 位要指明三件事:
① 操作数所在的寄存器(4位)
② 移位方式 (四种:逻辑左或右, 算术右或循环右、2位)
③ 移位次数 (0-31、5位)
共需要11位,所以可以在单个指令编码中塞下。具体实现见下图。

ARM7手册种还给出了另一种编码方式,感兴趣的朋友可以参阅 ARM7数据手册

立即数寻址

这部分内容强烈推荐在到处之间找我的博文:ARM 立即寻址之立即数的形成 —— 如何判断有效立即数

ARM的数据总线有 32 位,但是留给第二操作数的空间只有 12 位。所以立即数的范围必须有所限制。在这 12 位中,0 ~ 7位是数值部分;8~11 位乘以 2 是 0~7 位要进行右移位操作的次数。因此,立即数的表示范围是:(0 - 255)* 22N 。

一个判断立即数是否合法的简便方法:将它写成二进制,观察是否能通过右移偶数次,使得所有的 “1” 都位于低八位上。

数据传送指令

数据传送指令用于在寄存器和内存中传输数据。ARM 处理器的Load-Store结构要求参与运算的操作数如果保存在内存中,那么应该在运算前从内存中取出,或者在运算后将结果保存在内存里。

单寄存器传送

LDR r0, [r1] ; 将r1 指向的内存单元的内容加载到 r0 寄存器。
STR r0, [r2] ; 将 r0 寄存器的值存入 r2 指向的内存单元。

前面提到的数据处理指令要求第一个操作数是目的操作数,但在数据传送指令中不是这样。数据传送指令中,使用寄存器间接寻址来访问内存的永远是第二个操作数,与它是否是源操作数无关。

单寄存器传送的第二操作数不仅支持寄存器间接寻址,它的编码方式使它还支持基地址变址的寻址方式。下图是 LDR / STR 指令的机器码编码方式。

COND:条件执行I:等于 0 表示偏移量是立即数,否则偏移量保存在寄存器里P:0 - 后变址;1 - 前变址U:0 - 减偏移;1 - 加偏移B:0 - 字传送;1 - 字节传送W:0 - 不自动变址;1 - 自动变址L:0 - STR;1 - LDRRN:基址寄存器,即第二操作数RD:第一操作数的寄存器编码OFFSET:偏移量,无符号 12 位

LDR/STR 指令不仅可以传送字,还可以传送半字(STRH/LDRH),传送字节(STRB/LDRB)。

多寄存器传送

批量数据传送时,寻址方式可分为多寄存器寻址和堆栈寻址两种类型。每种寻址方式都对应了一些特定的多寄存器传送命令。不同于单寄存器传送和寄存器交换,这两种寻址方式不需要在作为指针的寄存器两侧加中括号。

多寄存器寻址:STM / LDM + I/D + A/B

I : 地址递增;D:地址递减;A:后变址;B:前变址
例如,LDMIA R0, {R1-R4, R7}
R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12], R7←[R0+16]
在这里,R0 在指令执行前后没有改变。如果将原指令改为 LDMIA R0! , {R1-R4, R7},那么在指令执行的过程中,R0 会改变。在本例中会增加 20。

堆栈寻址:STM / LDM + E/F + A/D

E:栈顶指针指向的内存单元没有有效数据;
F:栈顶指针指向的内存单元存在有效数据;
A:堆栈向高地址端生长;
D:堆栈向低地址端生长;
STM 和 LDM 分别可以作为入栈和出栈操作。至于操作时地址是增加还是减少,以及是前变址还是后变址,则完全由后两个字母决定。
例如,LDMFD sp!, {R1-R4, R7},表示将堆栈内容弹出到那五个寄存器中,同时堆栈指针随之改变。先出栈(后变址)、堆栈指针增加。

ARM 只提供入栈出栈的指令,至于编写程序时使用的堆栈是向上还是向下,是满栈还是空栈,取决于程序员自己的规定。

寄存器交换

SWP Rd, Rm, [Rn]
作用:将 Rm 的值存入 Rn 指向的内存,将 Rn 指向的内存中的原数据取出,并装载到 Rd 里。
使用场景:信号量的置位。

在多任务实时操作系统中,为了避免两个进程同时访问同一个内存块,通常会设置一个表示该内存是否正在被占用的信号量。当它为 0,内存块空闲;为 1,内存被占用,其他进程不得访问。当进程成功访问到内存块时,应该将相应的信号量置1。
但是,进程 “观察信号量是否为 1” 和 “ 将信号量置为1 ” 这两个操作是分开的。如果存在两个进程:第一个进程观测到信号量为 0,第二个进程紧接着观测,信号量也为 0。之后,这两个进程才会将信号量置为 1。但是这样一来,两个进程都会认为自己独占这个内存块,无法避免同时访问内存带来的错误。
寄存器交换指令可以解决这个问题。MOV r0, #1 和 SWP r0,r0,[r1] 语句可以实现 r0 寄存器和 r1 指向的内存交换数据。如果信号量为 1 ,则它保持不变,同时进程也会知道有其他进程在占用这个信号量;如果信号量为 0 ,则它被置为 1,表示该内存块被本进程独占了。SWP 在 ARM 汇编中是一个原子操作,不可以再被分割。所以可以避免之前提到的错误情况。

控制流指令

控制流指令包括 B 和 BL,调用格式:BL init ; B init

B:将操作数(标号,如例子中的 init)的地址赋给r15,从而跳转到另一段代码继续执行。BL:转移链接指令,相比 B 指令,BL 指令还会在跳转前将下一条指令赋给 r14,以便目标地址的代码执行完成后返回。多用于函数调用。

上图是控制流指令的编码,编码注解如下:

COND:条件码,用于条件跳转,和数据传送指令相同101:特征位,用来将该编码与其他命令的编码区分开L:连接位。L=1,代表 BL 指令;L=0,代表 B 指令偏移量:跳转到的地址,24位

该编码给跳转到的地址留了 24 位,这是一个有符号的相对于当前 PC 指针的地址,最高位是符号位。因为 ARM 指令是 32 位的,4 字节对齐,地址低两位一定是 0。所以 ARM 规定将命令中的偏移量左移两位得到实际的偏移地址。于是控制流指令能跳转到的地址范围是从 PC 指针出发的 ±32M 字节。

注意这里的相对地址,并不是相对于 B/BL 命令,而是相对于 PC 指针。因为 ARM 是 3 级流水线架构,如果采用延迟分支的方法解决控制阻滞问题,那么执行到跳转指令时,PC 指针应为当前执行的指令地址加 8。

伪指令

伪指令并不是 ARM 指令集的一部分,而更像是编译器提供给汇编程序开发人员的语法糖。伪指令可以改变编译器的编译方式,也可以将许多麻烦的操作使用一条伪指令解决。编译器会将伪指令替换成优化的 ARM 汇编指令。一方面,伪指令独立于 ARM 指令集;另一方面,伪指令也和具体的编译环境紧密相关。编译环境大体分为两类——我们常用的 keil 属于 ARM 编译环境,在 linux 环境下常用的 gcc 编译器属于 GNU 编译环境。两种环境下的伪指令集合从功能上来讲大同小异,不过书写风格不同。keil 环境的伪指令通常全部大写,gcc 编译器的伪指令通常全部小写且前面有个小数点。不过指令的大小写并无严格限制,只是要求要么全都大写,要么全都小写。

ADR:将标号对应的地址加载到寄存器。例:ADR r0, tableLDR:眼熟不?LDR也有伪指令的用法。
例:LDR r0, =__main ; LDR r0, #&3F
LDR 伪指令约等于源操作数是立即数的 MOV,不过由于这是伪指令,没有 MOV 指令对立即数的限制。比如上面的第一个例子:它可以将 __main 标号对应的 32 位地址赋给 r0。由编译器负责将该伪指令替换成优化的代码。EQU:定义一个标号,并指定标号对应的数值。类似于宏定义。例:Stack_Size EQU 0x00000400 后记

第一次写博客,感觉文章里有不少多余的东西,而且肯定有地方写得不严谨或者有错误。欢迎评论区指出 ^ ^

, ,