前言:
目前针对ARM Cortex-A7裸机开发文档及视频进行了二次升级持续更新中,使其内容更加丰富,讲解更加细致,全文所使用的开发平台均为华清远见FS-MP1A开发板(STM32MP157开发板)
针对对FS-MP1A开发板,除了Cortex-A7裸机开发篇外,还包括其他多系列教程,包括Cortex-M4开发篇、FreeRTOS篇、Linux基础及应用开发篇、Linux系统移植篇、Linux驱动开发篇、硬件设计篇、人工智能机器视觉篇、Qt应用编程篇、Qt综合项目实战篇等。除此之外计划针对Linux系统移植篇、Linux驱动开发篇均会进行文档及视频的二次升级更新敬请关注!
开发板更多资料可在评论区下方留言领取~~~
ARM 微处理器指令系统
ARM 指令集可以分为跳转指令、数据处理指令、程序状态寄存器传输指令、Load/Store 指令、协处理
器指令和异常中断产生指令。根据使用的指令类型不同,指令的寻址方式分为数据处理指令寻址方式和内存访问指令寻址方式。
本章主要介绍 ARM 汇编语言。主要内容如下:
⚫ ARM 处理器的寻址方式。
⚫ ARM 处理器的指令集
ARM 指令的组成
<opcode> {<c>} {S} <Rd>,<Rn>,<shifter_operand>
指令解析:
<opcode>: 要执行的指令
{<c>}: 为指令执行的条件码。当忽略< c >时,指令为无条件执行。
表 44.7.2.1 指令执行条件
{S}: 决定指令的操作是否影响 CPSR。在异常返回时,如果操作的目标是 PC 寄存器时,S 标志会同
时将 SPSR 寄存器恢复到 CPSR 中。
<Rd>: 为目标寄存器。
<Rn>: 操作数所在的寄存器。
<shifter_operand>有 11 种形式,如表所示
以下是关于 MOV 指令的一些示例:
示例代码 45-1 示例
1 MOV R0,#2
2 ADDS R0,R0,R1
3 MOV R2,R0
4 MOV R1, R0, LSL #2
ARM 处理器寻址方式
ARM 指令的寻址方式分为数据处理指令寻址方式和内存访问指令寻址方式。
数据处理指令寻址方式
数据处理指令寻址方式可以分为以下几种。
⚫ 立即数寻址方式。
⚫ 寄存器寻址方式。
⚫ 寄存器移位寻址方式。
1、 立即数寻址方式
指令中的立即数是由一个 8bit 的常数移动 4bit 偶数位(0,2,4,…,26,28,30)得到的。所以,每一条指令都包含一个 8bit 的常数 X 和移位值 Y,得到的立即数 = X 循环右移(2×Y),如图所示。
立即数为什么需要通过上述运算得到呢?这里我们以 ldr 指令的机器码为例来分析原因。
上图中 imm12 就是立即数所占用的位域,在这里可以看到 imm12 的区域只有 12bit,要用一个 12bit
的编码来表示任意的 32bit 数是绝对不可能的。而然在实际的开发过程中又要用 12bit 的编码来表示 32bit
数。那么只有在表示数的数量上做限制,通过编码来实现用 12bit 的编码来表示 32bit 数。
在上面我们提到了立即数 = X 循环右移(2×Y),尽管表示的范围变大了, 但是 12 位所能表现的数字的个数是一定的. 因此, ARM 规定并不是所有的 32 位常数都是合法的立即数, 只有通过上面的构造方法得到的才是合法的立即数。
ARM 汇编编译器按照下面的规则来产生立即数的编码:
当立即数数值在 0~0xFF 范围时,令 X=立即数,Y=0。
其他情况下,汇编编译器选择使 Y 数值最小的编码方式。
则来产生立即数的编码:
下面列举了一些有效的立即数:
0xFF:X=0xFF,Y=0
0x104:X=0x41,Y=15
0xFF0:X=0xFF,Y=14
0xF000000F:X=0xFF,Y=2
下面是一些无效的立即数:
0x101、0x102、0xFF1、0xFF04、0xFF003、0xFFFFFFFF、0xF000001F
下面是一些应用立即数的指令
示例代码 45-2 立即数
1 MOV R0,#0 ;送0到R0
2 ADD R3,R3,#1 ;R3的值加1
3 CMP R7,#1000 ;将R7的值和1000比较
4 BIC R9,R8,#0xFF00 ;将 R8 中 8~15 位清零,结果保存在 R9 中
2、 寄存器寻址方式
寄存器的值可以被直接用于数据操作指令,这种寻址方式是各类处理器经常采用的一种方式,也是一种执行效率较高的寻址方式,如:
示例代码 45-3 寄存器寻址
1 MOV R2,R0 ;R0的值送R2
2 ADD R4,R3,R2 ;R2加R3,结果送R4
3 CMP R7,R8 ;比较 R7 和 R8 的值
3、 寄存器移位寻址方式
和寄存器寻址类似,只是操作前需要对寄存器操作数进行移位操作。寄存器的值在被送到 ALU 之前,可以事先经过桶形移位寄存器的处理。预处理和移位发生在同一周期内,所以有效地使用移位寄存器,可以增加代码的执行效率。
LSL<c> <Rd>, <Rm>, #<imm5>
LSR<c> <Rd>, <Rm>, #<imm>
ASR<c> <Rd>, <Rm>, #<imm>
ROR{S}<c> <Rd>, <Rm>, #<imm>
RRX{S}<c> <Rd>, <Rm>
{<c>}: 为指令执行的条件码。当忽略< c >时,指令为无条件执行。
<Rd>: 为目标寄存器。
<Rm>: 操作数所在的寄存器。
<imm>: 移位量,范围 1-32。
带扩展的循环右移(RRX): 操作数右移一位,移位空出的高位用 C 标志的值填充。
下面是一些在指令中使用了移位操作的例子:
示例代码 45-4 寄存器移位寻址
1 ADD R2, R0, R1, LSR #5 ;将R1的值逻辑右移5位后与R0相加结果传入R2
2 MOV R1, R0, LSL #2 ;将R0的值逻辑左移2位后将结果传入R1
3 SUB R1, R2, R0, LSR #4 ;用R2的值减去将R0的值逻辑右移4位后的值将结果传入R1
4 MOV R2, R4, ROR R0 ;将R4的值循环右移R0次,结果传入R2
内存访问指令寻址方式
内存访问指令的寻址方式可以分为以下几种。
⚫ 字及无符号字节的 Load/Store 指令的寻址方式。
⚫ 杂类 Load/Store 指令的寻址方式。
⚫ 批量 Load/Store 指令的寻址方式。
⚫ 协处理器 Load/Store 指令的寻址方式。
1、 字及无符号字节的 Load/Store 指令的寻址方式
字及无符号字节的 Load/Store 指令语法格式如下:
LDR|STR{<cond>}{B}{T} <Rd>,<addressing_mode>
上表中,“!”表示完成数据传输后要更新基址寄存器。
2、 杂类 Load/Store 指令的寻址方式
使用该类寻址方式的指令的语法格式如下:
LDR|STR{<cond>}H|SH|SB|D <Rd>, <addressing_mode>
使用该类寻址方式的指令包括(有符号/无符号)半字 Load/Store 指令、有符号字节 Load/Store 指令和双字 Load/Store 指令。
表 45.2.2.2 <addressing_mode>寻址方式
3、 堆栈操作寻址方式
堆栈操作寻址方式和批量 Load/Store 指令寻址方式十分类似。但对于堆栈的操作,数据写入内存和从内存中读出要使用不同的寻址模式,因为进栈操作(pop)和出栈操作(push)要在不同的方向上调整堆栈。
该类指令的语法格式如下:
LDM|STM {<amode>}{<cond>}<addressing_mode> <Rn>{!},<registers><^>
下面详细讨论如何使用合适的寻址方式实现数据的堆栈操作。
根据 amode 不同的寻址方式,将堆栈分为以下 4 种。
1) 满栈:堆栈指针指向栈顶元素(last used location)。
2) 空栈:堆栈指针指向第一个可用元素(the first unused location)。
3) 递减栈:堆栈向内存地址减小的方向生长。
4) 递增栈:堆栈向内存地址增加的方向生长。
根据堆栈的不同种类,将其寻址方式分为以下 4 种。
1) 满递减 FD(Full Descending)。
2) 空递减 ED(Empty Descending)。
3) 满递增 FA(Full Ascending)。
4) 空递增 EA(Empty Ascending)。
如表所示列出了堆栈的寻址方式和批量 Load/Store 指令寻址方式的对应关系。
批量 Load/Store 指令寻址方式
批量 Load/Store 指令将一片连续内存单元的数据加载到通用寄存器组中或将一组通用寄存器的数据存储到内存单元中。
批量 Load/Store 指令的寻址模式产生一个内存单元的地址范围,指令寄存器和内存单元的对应关系满足这样的规则,即编号低的寄存器对应于内存中低地址单元,编号高的寄存器对应于内存中的高地址单元。
该类指令的语法格式如下:
LDM|STM {<amode>}{<cond>}<addressing_mode> <Rn>{!},<registers><^>
协处理器 Load/Store 寻址方式
协处理器 Load/Store 指令的语法格式如下:
MCR<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
MRC<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
<c>:为指令执行的条件码。当忽略< c >时,指令为无条件执行
<coproc>:协处理器名称,范围 p0-p15;
<opc1>:协处理器操作码,范围 0-15;
<Rt>:源寄存器,MCR 指令是将 Rt 寄存器写入协处理器,MRC 指令是将协处理器的内容读取到 Rt 寄存器;
<CRn>:协处理器的目标寄存器;
<CRm>:协处理器中附加的目标寄存器或者源操作寄存器,如果不需要附加信息就设置为 C0,否则结果不可预测;
<opc2>:可选的协处理器特定操作码,当不需要时置 0。
关于 CRn、opc1、CRm、opc2 等操作,以下给出了 CP15 协处理器的寄存器排布。操作不同的协处理器时对应着不同的取值
例如前面讲到的 Cache 操作指令
示例代码 45-5 使能 ICache
1 /******Cache Test*******/
2 mrc p15,0,r1,c1,c0,0
3 orr r1, r1, #(1 << 2) // Set C bit 整体使能Cache
4 orr r1, r1, #(1 << 12) //Set I bit 使能ICache
5 mcr p15,0,r1,c1,c0,0
6 /******End Test******/
如果需要操作其它协处理器可以通过查阅《ARM® Architecture Reference Manual》或者《Cortex-A7 MPCore Technical Reference Manual》官方文档进行查阅。
ARM 处理器指令集
数据操作指令
数据操作指令是指对存放在寄存器中的数据进行操作的指令。主要包括数据传送指令、算术指令、逻辑指令、比较与测试指令及乘法指令。
如果在数据处理指令前使用 S 前缀,指令的执行结果将会影响 CPSR 中的标志位。数据处理指令如表所示。
1、 MOV 指令
MOV 指令是最简单的 ARM 指令,执行的结果就是把一个数 N 送到目标寄存器 Rd,其中 N 可以是寄存器,也可以是立即数。
MOV 指令多用于设置初始值或者在寄存器间传送数据。
MOV 指令将移位码(shifter_operand)表示的数据传送到目的寄存器 Rd,并根据操作的结果更新 CPSR中相应的条件标志位。
指令的语法格式:
MOV{<c>}{S} <Rd>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。如果 R15 是目的寄存器,将修改程序计数器或标志。这用于被调用的子函数结束后返回到调用代码,方法是把连接寄存器的内容传送到 R15。
<shifter_operand>:要传送到 Rd 寄存器的数据,可以是立即数、寄存器或者通过移位操作得到指令举例:
示例代码 45-6 mov 示例
1 mov r0, r0 ; R0=R0 NOP 指令
2 mov r0, r0, lsl#3 ; R0=R0*8
3 mov pc, lr ; 退出到调用者,用于普通函数返回,PC即是R15
4 movs pc, lr ; 退出到调用者并恢复标志位,用于异常函数返回
MOV 指令主要完成以下功能。
⚫ 将数据从一个寄存器传送到另一个寄存器。
⚫ 将一个常数值传送到寄存器中。
⚫ 当 PC(R15)用做目的寄存器时,可以实现程序跳转。如“MOV PC,LR”,所以这种跳转可以实现子程序调用及从子程序返回,代替指令“B,BL”。
⚫ 当 PC 作为目标寄存器且指令中 S 位被设置时,指令在执行跳转操作的同时,将当前处理器模式的 SPSR 寄存器的内容复制到 CPSR 中。这种指令“MOVS PC LR”可以实现从某些异常中断中返回。
MVN 指令
MVN 是反相传送(Move Negative)指令。它将操作数的反码传送到目的寄存器。
MVN 指令多用于向寄存器传送一个负数或生成位掩码。
MVN 指令将 shifter_operand 表示的数据的反码传送到目的寄存器 Rd,并根据操作结果更新 CPSR 中相应的条件标志位。
指令的语法格式:
MVN{<c>}{S} <Rd>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<shifter_operand>:要传送到 Rd 寄存器的数据,可以是立即数、寄存器或者通过移位操作得到。这是逻辑非操作而不是算术操作,这个取反的值加 1 才是它的取负的值。
指令举例:
示例代码 45-7 mvn 示例
1 mvn r0, #4 ; r0 = -5
2 mvn r0, #0 ; r0 = -1
MVN 指令主要完成以下功能:
⚫ 向寄存器中传送一个负数。
⚫ 生成位掩码(Bit Mask)。
⚫ 求一个数的反码。
AND 指令
AND 指令将 shifter_operand 表示的数值与寄存器 Rn 的值按位(bitwise)做逻辑与操作,并将结果保存到目标寄存器 Rd 中,同时根据操作的结果更新 CPSR 寄存器。
指令的语法格式:
AND{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:第一个操作数寄存器。
<shifter_operand>:要和 Rn 寄存器做与操作的数据
指令举例:
示例代码 45-8 and 示例
1 and r0, r0, #3 ;保留r0中的0位和1位,丢弃其余的位。
2 and r2,r1,r3 ;r2 = r1&r3
3 ands r0,r0,#0x01 ;r0 = r0&0x01,取出最低位数据
ORR 指令
ORR(Logical OR)为逻辑或操作指令,它将第 2 个源操作数 shifter_operand 的值与寄存器 Rn 的值按位做“逻辑或”操作,结果保存到 Rd 中。
指令的语法格式:
ORR{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的操作数
<Rn>:第一个操作数。
<shifter_operand>:要与 Rn 做逻辑或的数
指令举例:
示例代码 45-9 orr 示例
1 orr r0, r0, #3 ;设置r0中位0和1
2 orr r0,r0,#0x0f ;将r0的低4位置1
3 ; 使用orr指令将r2的高8位数据移入到r3的低8位中。
4 mov r1,r2,lsr #4
5 orr r3,r1,r3,lsl #8
BIC 位清零指令
BIC(Bit Clear)位清零指令,将寄存器 Rn 的值与第 2 个源操作数 shifter_operand 的值的反码按位
做“逻辑与”操作,结果保存到 Rd 中。
指令的语法格式:
BIC{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的操作数
<Rn>:第一个操作数。
<shifter_operand>:取反码与 Rn 做“逻辑与”操作
指令举例:
示例代码 45-10 bic 示例
1 bic r0, r0, #0x1011 ;清除r0中的位12、4和0位,保持其余的不变
2 bic r1, r2, r3 ;将 r3 和 r2 做“逻辑与”操作,结果保存到 r1 中
EOR 指令
EOR(Exclusive OR)指令将寄存器 Rn 中的值和 shifter_operand 的值执行按位“异或”操作,并将执行结果存储到目的寄存器 Rd 中,同时根据指令的执行结果更新 CPSR 中相应的条件标志位。
指令的语法格式:
EOR{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:第一个操作数寄存器。
<shifter_operand>:要和 Rn 寄存器做异或操作的数据
指令举例:
示例代码 45-11 eor 示例
1 eor r0, r0, #3 ;反转r0中的位0和1
2 eor r1,r1,#0x0f ;将r1的低4位取反
3 eor r2,r1,r0 ;r2=r1∧r0
4 eors r0,r5,#0x01 ;r0=r5∧0x01 影响标志位
SUB 指令
SUB(Subtract)指令从寄存器 Rn 中减去 shifter_operand 表示的数值,并将结果保存到目标寄存器 Rd
中,并根据指令的执行结果设置 CPSR 中相应的标志位。
指令的语法格式:
SUB{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:被减数。
<shifter_operand>:减数
指令举例:
示例代码 45-12 sub 示例
1 sub r0, r1, r2 ;r0 = r1−r2
2 sub r0, r1, #256 ;r0 = r1−256
3 sub r0, r2, r3,lsl#1 ;r0 = r2−(r3<<1)
RSB 指令
RSB(Reverse Subtract)指令从寄存器 shifter_operand 中减去 Rn 表示的数值,并将结果保存到目标寄
存器 Rd 中,并根据指令的执行结果设置 CPSR 中相应的标志位。
指令的语法格式:
RSB{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:减数。
<shifter_operand>:被减数
指令举例:
下面的指令序列可以求一个 64 位数值的负数。64 位数放在寄存器 R0 与 R1 中,其负数放在 R2 和 R3中。其中 R0 与 R2 中放低 32 位值。
示例代码 45-13 rsb 示例
1 rsbs r2,r0,#0
2 rsc r3,r1,#0
SBC 指令
SBC(Subtract with Carry)指令用于执行操作数大于 32 位时的减法操作。该指令从寄存器 Rn 中减去shifter_operand 表示的数值,再减去寄存器 CPSR 中 C 条件标志位的反码[NOT(Carry flag)],并将结果保存到目标寄存器 Rd 中,并根据指令的执行结果设置 CPSR 中相应的标志位。
指令的语法格式:
SBC{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:被减数。
<shifter_operand>:减数
指令举例:
下面的程序使用 SBC 实现 64 位减法,(R1,R0)−(R3,R2),结果存放到(R1,R0)。
示例代码 45-14 sbc 示例
1 subs r0,r0,r2
2 sbcs r1,r1,r3
0、RSC 指令
RSC(Reverse Subtract with Carry)指令从寄存器 shifter_operand 中减去 Rn 表示的数值,再减去寄存器 CPSR 中 C 条件标志位的反码[NOT(Carry Flag)],并将结果保存到目标寄存器 Rd 中,并根据指令的执行结果设置 CPSR 中相应的标志位。
指令的语法格式:
RSC{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:减数。
<shifter_operand>:被减数
指令举例:
下面的程序使用 RSC 指令实现求 64 位数值的负数。
示例代码 45-15 rsc 示例
1 rsbs r2,r0,#0
2 rsc r3,r1,#0
ADD 指令
ADD 指令将寄存器 shifter_operand 的值加上 Rn 表示的数值,并将结果保存到目标寄存器 Rd 中,并根据指令的执行结果设置 CPSR 中相应的标志位。
指令的语法格式:
ADD{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:第一个操作数。
<shifter_operand>:要加的数
指令举例:
示例代码 45-16 add 示例
1 add r0, r1, r2 ; r0 = r1 + r2
2 add r0, r1, #256 ; r0 = r1 + 256
3 add r0, r2, r3,lsl#1 ; r0 = r2 + (r3 << 1)
ADC 指令
ADC 指令将寄存器 shifter_operand 的值加上 Rn 表示的数值,再加上 CPSR 中的 C 条件标志位的值,将结果保存到目标寄存器 Rd 中,并根据指令的执行结果设置 CPSR 中相应的标志位。
指令的语法格式:
ADC{<c>}{S} <Rd>,<Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:第一个操作数。
<shifter_operand>:要加的数
指令举例:
ADC 指令将把两个操作数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就可以做比 32 位大的加法。下面的例子将加两个 128 位的数。
128 位结果:寄存器 R0、R1、R2 和 R3。
第一个 128 位数:寄存器 R4、R5、R6 和 R7。
第二个 128 位数:寄存器 R8、R9、R10 和 R11。
示例代码 45-17 adc 示例
1 adds r0, r4, r8 ;加低端的字
2 adcs r1, r5, r9 ;加下一个字,带进位
3 adcs r2, r6, r10 ;加第三个字,带进位
4 adcs r3, r7, r11 ;加高端的字,带进位
CMP 指令
CMP(Compare)指令使用寄存器 Rn 的值减去 shifter_operand 的值,根据操作的结果更新 CPSR 中相应的条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。
指令的语法格式:
CMP{<c>} <Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
<Rn>:第一个操作数。
<shifter_operand>:比较的数
指令举例:
CMP 指令允许把一个寄存器的内容与另一个寄存器的内容或立即值进行比较,更改状态标志来允许进行条件执行。它进行一次减法,但不存储结果,而是正确地更改标志位。标志位表示的是操作数 1 与操作数 2 比较的结果(其值可能为大于、小于、相等)。如果操作数 1 大于操作数 2,则此后的有 GT 后缀的
指令将可以执行。
显然,CMP 不需要显式地指定 S 后缀来更改状态标志。
示例代码 45-18 cmp 示例
1 cmp r1,#10 ;比较r1和立即数10是否相等
2 beq loop
通过上面的例子可以看出,CMP 指令与 SUBS 指令的区别在于 CMP 指令不保存运算结果,在进行两个数据大小判断时,常用 CMP 指令及相应的条件码来进行操作。
CMN 指令
CMN(Compare Negative)指令使用寄存器 Rn 的值减去 shifter_operand 的负数值,根据操作的结果更新 CPSR 中相应的条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。
指令的语法格式:
CMN{<c>} <Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略<c>时,指令为无条件执行。
<Rn>:第一个操作数。
<shifter_operand>:比较的数
指令举例:
CMN 指令将寄存器 Rn 中的值加上 shifter_operand 表示的数值,根据加法的结果设置 CPSR 中相应的条件标志位。寄存器 Rn 中的值加上 shifter_operand 的操作结果对 CPSR 中条件标志位的影响,与寄存器Rn 中的值减去 shifter_operand 的操作结果的相反数对 CPSR 中条件标志位的影响有细微差别。
下面的指令使 R0 值加 1,判断 R0 是否为 1 的补码,若是,则 Z 置位。
示例代码 45-19 cmn 示例
1 cmn r0,#1
TST 测试指令
TST(Test)测试指令用于将一个寄存器的值和一个值进行比较。条件标志位根据两个操作数做“逻辑与”后的结果设置。
TST{<c>} <Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
<Rn>:第一个操作数。
<shifter_operand>:比较的数
指令举例:
下面的指令测试在 R0 中是否设置了位 0。
示例代码 45-20 tst 示例
1 tst r7,#0x4 ;测试r7寄存器的值第2位是否置位
2 addeq r6,r7 ;如果置位执行r6+r7
3 addne r8,r7 ;如果没有置位执行 r8+r7
TST 指令类似于 CMP 指令,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用 TST 指令来检查是否设置了特定的位。操作数 1 是要测试的数据字而操作数 2 是一个位掩码。经过测试后,如果匹配则设置 Z 标志,否则清除它。与 CMP 指令一样,该指令不需要指定 S 后缀。
TEQ 指令
TEQ(Test Equivalence)指令用于将一个寄存器的值和一个算术值做比较。条件标志位根据两个操作数做“逻辑异或”后的结果设置。以便后面的指令根据相应的条件标志来判断是否执行。
指令的语法格式:
TEQ{<c>} <Rn>,<shifter_operand>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
<Rn>:第一个操作数。
<shifter_operand>:比较的数。
指令举例:
示例代码 45-21 teq 示例
1 teq r0, r1 ;r0与r1是否相等
2 addeq r0, r0, #1 ;若 r0==r1, eq 为真,则 r1=r1+1
乘法指令
ARM 乘法指令完成两个数据的乘法。两个 32 位二进制数相乘的结果是 64 位的积。在有些 ARM 的处理器版本中,将乘积的结果保存到两个独立的寄存器中。另外一些版本只将最低有效 32 位存放到一个寄存器中。无论是哪种版本的处理器,都有乘—累加的变型指令,将乘积连续累加得到总和。而且有符号数和无符号数都能使用。对于有符号数和无符号数,结果的最低有效位是一样的。因此,对于只保留 32 位结果的乘法指令,不需要区分有符号数和无符号数这两种情况。
如表所示为各种形式乘法指令的功能。
说明:
1) “RdHi:RdLo”是由 RdHi(最高有效 32 位)和 RdLo(最低有效 32 位)连接形成的 64 位
数,“[31:0]”只选取结果的最低有效 32 位。
2) 简单的赋值由“:=”表示。
3) 累加(将右边加到左边)是由“+=”表示。
4) 各个乘法指令中的位 S(参考下文具体指令的语法格式)控制条件码的设置会产生以下结果。
1) 对于产生 32 位结果的指令形式,将标志位 N 设置为 Rd 的第 31 位的值;对于产生长结果的
指令形式,将其设置为 RdHi 的第 31 位的值。
2) 对于产生 32 位结果的指令形式,如果 Rd 等于零,则标志位 Z 置位;对于产生长结果的指令形式,RdHi 和 RdLo 同时为零时,标志位 Z 置位。
3) 将标志位 C 设置成无意义的值。
4) 标志位 V 不变。
MUL 指令
MUL(Multiply)32 位乘法指令将 Rm 和 Rs 中的值相乘,结果的最低 32 位保存到 Rd 中。
指令的语法格式:
MUL{<c>}{S} <Rd>,<Rm>,<Rs>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rn>:第一个操作数。
<Rs>:要与 Rn 相乘的数
指令举例:
示例代码 45-22 mul 示例
1 mul r1, r2, r3 ;r1 = r2 × r3
2 muls r0, r3, r7 ;r0 = r3 × r7
MLA 指令
MLA(Multiply Accumulate)32 位乘—累加指令将 Rm 和 Rs 中的值相乘,再将乘积加上第 3 个操作数,结果的最低 32 位保存到 Rd 中。
指令的语法格式:
MLA{<c>}{S} <Rd>,<Rm>,<Rs>,<Rn>
{<c>}:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rd>:目的寄存器。
<Rm>:第一个乘数。
<Rs>:第二个乘数。
<Rn>:与 Rm 与 Rs 的积相加。
指令举例:
下面的指令完成 R1 = R2×R3+R0 的操作。
示例代码 45-23 mla 示例
1 mla r1, r2, r3, r0
UMULL 指令
UMULL(Unsigned Multiply Long)为 64 位无符号乘法指令。它将 Rm 和 Rs 中的值做无符号数相乘,结果的低 32 位保存到 RdLo 中,高 32 位保存到 RdHi 中。
指令的语法格式:
UMULL{<c>}{S} <RdLo>,<RdHi>,<Rm>,<Rs>
{<c>}:为指令执行的条件码。当忽略<c>时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rm>:第一个乘数。
<Rs>:第二个乘数
< RdHi >:Rm 与 Rs 的积的高 32 位
< RdLo >:Rm 与 Rs 的积的低 32 位
指令举例:
示例代码 45-24 umull 示例
1 umull r0, r1, r5, r8 ; (R1,R0) = R5 × R8
UMLAL 指令
UMLAL(Unsigned Multiply Accumulate Long)为 64 位无符号长乘—累加指令。指令将 Rm 和 Rs 中的值做无符号数相乘,64 位乘积与 RdHi、RdLo 相加,结果的低 32 位保存到 RdLo 中,高 32 位保存到RdHi 中。
指令的语法格式:
UMALL{<c>}{S} <RdLo>,<RdHi>,<Rm>,<Rs>
{<c>}:为指令执行的条件码。当忽略<c>时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rm>:第一个乘数。
<Rs>:第二个乘数
<RdHi>:与 Rm 与 Rs 的积的高 32 位相加
<RdLo>:与 Rm 与 Rs 的积的低 32 位相加
指令举例:
示例代码 45-25 umlal 示例
1 umlal r0, r1, r5,r8 ;(r1,r0) = r5 × r8+(r1,r0)
SMULL 指令
SMULL(Signed Multiply Long)为 64 位有符号长乘法指令。指令将 Rm 和 Rs 中的值做有符号数相乘,结果的低 32 位保存到 RdLo 中,高 32 位保存到 RdHi 中。
指令的语法格式:
SMULL{<c>}{S} <RdLo>,<RdHi>,<Rm>,<Rs>
{<c>}:为指令执行的条件码。当忽略<c>时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rm>:第一个乘数。
<Rs>:第二个乘数
< RdHi >:Rm 与 Rs 的积的高 32 位
< RdLo >:Rm 与 Rs 的积的低 32 位
指令举例:
示例代码 45-26 smull 示例
1 smull r2, r3, r7,r6 ;(r3,r2) = r7 × r6
SMLAL 指令
SMLAL(Signed Multiply Accumulate Long)为 64 位有符号长乘—累加指令。指令将 Rm 和 Rs 中的值做有符号数相乘,64 位乘积与 RdHi、RdLo 相加,结果的低 32 位保存到 RdLo 中,高 32 位保存到 RdHi
中。
指令的语法格式:
SMLAL{<c>}{S} <RdLo>,<RdHi>,<Rm>,<Rs>
{<c>}:为指令执行的条件码。当忽略<c>时,指令为无条件执行。
{S}:决定指令的操作是否影响 CPSR。
<Rm>:第一个乘数。
<Rs>:第二个乘数
<RdHi>:与 Rm 与 Rs 的积的高 32 位相加
<RdLo>:与 Rm 与 Rs 的积的低 32 位相加
指令举例:
示例代码 45-27 smlal 示例
1 smlal r2, r3, r7,r6 ;(r3,r2)=r7×r6+(r3,r2)
Load/Store 指令
Load/Store 内存访问指令在 ARM 寄存器和存储器之间传送数据。ARM 指令中有 3 种基本的数据传送指令。
⚫ 单寄存器 Load/Store 指令
这些指令在 ARM 寄存器和存储器之间提供更灵活的单数据项传送方式。数据项可以是字节、
16 位半字或 32 位字。
⚫ 多寄存器 Load/Store 内存访问指令
这些指令的灵活性比单寄存器传送指令差,但可以使大量的数据更有效地传送。它们用于进程的进入和退出、保存和恢复工作寄存器及复制存储器中的一块数据。
⚫ 单寄存器交换指令
这些指令允许寄存器和存储器中的数值进行交换,在一条指令中有效地完成 Load/Store 操作。它们在用户级编程中很少用到。它的主要用途是在多处理器系统中实现信号量(Semaphores)的操作,以保证不会同时访问公用的数据结构。
1、 单寄存器的 Load/Store 指令
LDR 指令
LDR 指令用于从内存中将一个 32 位的字读取到目标寄存器。
指令的语法格式:
LDR{<c>} <Rd>,<addr_mode>
指令举例:
示例代码 45-28 ldr 示例
1 ldr r1,[r0,#0x12] ;将r0+12地址处的数据读出,保存到r1中(r0的值不变)
2 ldr r1,[r0] ;将r0地址处的数据读出,保存到r1中(零偏移)
3 ldr r1,[r0,r2] ;将r0+r2地址的数据读出,保存到r1中(r0的值不变)
4 ldr r1,[r0,r2,lsl #2] ;将r0+r2×4地址处的数据读出,保存到r1中(r0、r2的值不变)
5 ldr pc,[pc, #0x18] ;将程序跳转到pc+0x18位置处
6 ldr rd,label ;label为程序标号,label必须是当前指令的-4~4kb范围内
7 ldr rd,[rn],#0x04 ;rn 的值用做传输数据的存储地址。在数据传送后,将偏移量 0x04 与 rn
相加,结果写回到 rn 中。rn 不允许是 r15
STR 指令
STR 指令用于将一个 32 位的字数据写入到指令中指定的内存单元。
指令的语法格式:
STR{<c>} <Rd>,<addr_mode>
指令举例:
示例代码 45-29 str 数据回写
1 ldr r0, =0xE0200000
2 ldr r1, =0x00002222
3 str r1, [r0, #0x20]
LDRB 指令
LDRB 指令根据 addr_mode 所确定的地址模式将 1 个字节(8bit)读取到指令中的目标寄存器 Rd。
指令的语法格式:
LDRB{<c>} <Rd>, <addr_mode>
STRB 指令
STRB 指令从寄存器中取出指定的 1 个字节(8bit)放入寄存器的低 8 位,并将寄存器的高位补0。
指令的语法格式:
STRB{<c>} <Rd>,<addr_mode>
) LDRH 指令
LDRH 指令用于从内存中将一个 16 位的半字读取到目标寄存器。
如果指令的内存地址不是半字节对齐的,指令的执行结果不可预知。
指令的语法格式:
LDRH{<c>} <Rd>,<addr_mode>
STRH 指令
STRH 指令从寄存器中取出指定的 16 位半字放入寄存器的低 16 位,并将寄存器的高位补0。
指令的语法格式:
STRH{<c>} <Rd>,<addr_mode>
多寄存器的 Load/Store 内存访问指令
多寄存器的 Load/Store 内存访问指令也叫批量加载/存储指令,它可以实现在一组寄存器和一块连续的内存单元之间传送数据。LDM 用于加载多个寄存器,STM 用于存储多个寄存器。多寄存器的 Load/Store内存访问指令允许一条指令传送 16 个寄存器的任何子集或所有寄存器。多寄存器的 Load/Store 内存访问指令主要用于现场保护、数据复制和参数传递等。如表所示列出了多寄存器的 Load/Store 内存访问指令。
表 45.3.3.2 多寄存器操作指令
LDM 指令
LDM 指令将数据从连续的内存单元中读取到指令中指定的寄存器列表中的各寄存器中。当 PC 包含在LDM 指令的寄存器列表中时,指令从内存中读取的字数据将被作为目标地址值,指令执行后程序将从目标地址处开始执行,从而实现了指令的跳转。
指令的语法格式:
LDM{<c>}<addressing_mode> <Rn>{!}, <registers>
STM 指令
STM 指令将指令中寄存器列表中的各寄存器数值写入到连续的内存单元中。主要用于块数据的写入、数据栈操作及进入子程序时保存相关寄存器的操作。
指令的语法格式:
STM{<c>}<addressing_mode> <Rn>{!}, <registers>
批量数据传送指令举例
LDM/STM 批量加载/存储指令可以实现在一组寄存器和一块连续的内存单元之间传输数据。LDM 为加载多个寄存器,STM 为存储多个寄存器。允许一条指令传送 16 个寄存器的任何子集或所有寄存器。指令格式如下:
LDM{c}<模式> Rn{!},regist{ˆ}
STM{c}<模式> Rn{!},regist{ˆ}
LDM/STM 的主要用途有现场保护、数据复制和参数传递等。其模式有 8 种,其中前面 4 种用于数据块的传输,后面 4 种是堆栈操作,如下所示。
(1) IA:每次传送后地址加 4。
(2) IB:每次传送前地址加 4。
(3) DA:每次传送后地址减 4。
(4) DB:每次传送前地址减 4。
(5) FD:满递减堆栈。
(6) ED:空递增堆栈。
(7) FA:满递增堆栈。
(8) EA:空递增堆栈。
其中,寄存器 Rn 为基址寄存器,装有传送数据的初始地址,Rn 不允许为 R15;后缀“!”表示最后的地址写回到 Rn 中;寄存器列表 reglist 可包含多于一个寄存器或寄存器范围,使用“,”分开,如{R1,R2,R6~R9},寄存器排列由小到大排列;“ˆ”后缀不允许在用户模式下使用,只能在系统模式下使用。若在LDM 指令用寄存器列表中包含有 PC 时使用,那么除了正常的多寄存器传送外,将 SPSR 复制到 CPSR 中,这可用于异常处理返回;使用“ˆ”后缀进行数据传送且寄存器列表不包含 PC 时,加载/存储的是用户模式寄存器,而不是当前模式寄存器。
示例代码 45-30 批量数据传送指令
1 LDMIA R0!,{R3~R9} ;加载R0指向的地址上的多字数据,保存到R3~R9中,R0值更新
2 STMIA R1!,{R3~R9} ;将R3~R9的数据存储到R1指向的地址上,R1值更新
3 STMFD SP!,{R0~R7,LR} ;现场保存,将R0~R7、LR入栈
4 LDMFD SP!,{R0~R7,PC}ˆ ;恢复现场,异常处理返回
在进行数据复制时,先设置好源数据指针,然后使用块复制寻址指令 LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB 进行读取和存储。而进行堆栈操作时,则要先设置堆栈指针,一般使用 SP 然后使用堆栈寻址指令 STMFD/LDMFD、STMED/LDMED、STMEA/LDMEA 实现堆栈操作。数据是存储在基址寄存器的地址之上还是之下,地址是存储第一个值之前还是之后、增加还是减少,如表所示。
使用 LDM/STM 进行数据复制。
示例代码 45-31 数据复制
1 LDR R0,=SrcData ;设置源数据地址
2 LDR R1,=DstData ;设置目标地址
3 LDMIA R0,{R2~R9} ;加载8字数据到寄存器R2~R9
4 STMIA R1,{R2~R9} ;存储寄存器 R2~R9 到目标地址
使用 LDM/STM 进行现场寄存器保护,常在子程序或异常处理使用。
示例代码 45-32 保护现场
1 SENDBYTE:
2 STMFD SP!,{R0~R7,LR} ;寄存器压栈保护
3 ...
4 BL DELAY ;调用DELAY子程序
5 ...
6 LDMFD SP!,{R0~R7,PC} ;恢复寄存器,并返回
单数据交换指令
交换指令是 Load/Store 指令的一种特例,它把一个寄存器单元的内容与寄存器内容交换。交换指令是一个原子操作(Atomic Operation),也就是说,在连续的总线操作中读/写一个存储单元,在操作期间阻止其他任何指令对该存储单元的读/写。交换指令如表所示。
SWP 字交换指令
SWP 指令用于将内存中的一个字单元和一个指定寄存器的值相交换。操作过程如下:假设内存单元地址存放在寄存器<Rn>中,指令将<Rn>中的数据读取到目的寄存器 Rd 中,同时将另一个寄存器<Rm>的内容写入到该内存单元中。
当<Rd>和<Rm>为同一个寄存器时,指令交换该寄存器和内存单元的内容。
指令的语法格式:
SWP{<c>} <Rd>,<Rm>,[<Rn>]
字节单元和一个指定寄存器的低 8 位值相交换,操作过程如下:假设内存单元地址存放在寄存器<Rn>中,指令将<Rn>中的数据读取到目的寄存器 Rd 中,寄存器 Rd 的高 24 位设为 0,同时将另一个寄存器<Rm>的低 8 位内容写入到该内存字节单元中。当<Rd>和<Rm>为同一个寄存器时,指令交换该寄存器低 8 位内容和内存字节单元的内容。
指令的语法格式:
SWP{<c>}B <Rd>,<Rm>,[<Rn>]
指令举例:
示例代码 45-33 swp 指令举例
1 SWP R1,R1,[R0] ;将R1的内容与R0指向的存储单元内容进行交换
2 SWPB R1,R2,[R0] ;将 R0 指向的存储单元内容读取一字节数据到 R1 中(高 24 位清零), 并将 R2 的内容 写入到该内存单元中(最低字节有效),使用 SWP 指令可以方便地进行信号量操作。
跳转指令
跳转(B)和跳转连接(BL)指令是改变指令执行顺序的标准方式。ARM 一般按照字地址顺序执行指令,需要时使用条件执行跳过某段指令。只要程序必须偏离顺序执行,就要使用控制流指令来修改程序计数器。尽管在特定情况下还有其他几种方式实现这个目的,但转移和转移连接指令是标准的方式。跳转指令改变程序的执行流程或者调用子程序。这种指令使得一个程序可以使用子程序、if-then-else 结构及循环。执行流程的改变迫使程序计数器(PC)指向一个新的地址。
另一种实现指令跳转的方式是通过直接向 PC 寄存器中写入目标地址值,实现在 4GB 地址空间中任意跳转,这种跳转指令又称为长跳转。如果在长跳转指令之前使用“MOV LR”或“MOV PC”等指令,可以保存将来返回的地址值,也就实现了在 4GB 的地址空间中的子程序调用。
1、 跳转指令 B 及带连接的跳转指令 BL
跳转指令 B 使程序跳转到指定的地址执行程序。带连接的跳转指令 BL 将下一条指令的地址复制到R14(即返回地址连接寄存器 LR)寄存器中,然后跳转到指定地址运行程序。需要注意的是,这两条指令和目标地址处的指令都要属于 ARM 指令集。两条指令都可以根据 CPSR 中的条件标志位的值决定指令是否执行。
指令的语法格式:
B{L}{<c>} <target_address>
BL 指令用于实现子程序调用。子程序的返回可以通过将 LR 寄存器的值复制到 PC 寄存器来实现。下面 3 种指令可以实现子程序返回。
1) BX R14(如果体系结构支持 BX 指令)。
2) MOV PC,R14。
3) 当子程序在入口处使用了压栈指令:
STMFD R13!,{<registers>,R14}
可以使用指令:
LDMFD R13!,{<registers>,PC}
将子程序返回地址放入 PC 中。
ARM 汇编器通过以下步骤计算指令编码中的 signed_immed_24。
1) 将 PC 寄存器的值作为本跳转指令的基地址值。
2) 从跳转的目标地址中减去上面所说的跳转的基地址,生成字节偏移量。由于 ARM 指令是字对齐的,该字节偏移量为 4 的倍数。
3) 当上面生成的字节偏移量超过−33 554 432~+33 554 430 时,不同的汇编器使用不同的代码产生策略。否则,将指令编码字中的 signed_immed_24 设置成上述字节偏移量的 bits[25:2]。
程序举例:
程序跳转到 LABLE 标号处。
示例代码 45-34 跳转到标号处
1 b lable
2 add r1,r2,#4
3 add r3,r2,#8
4 sub r3,r3,r1
5 lable:
6 sub r1,r2,#8
跳转到绝对地址
示例代码 45-35 跳转到绝对地址
1 b 0x1234
跳转到子程序 func 处执行,同时将当前 PC 值保存到 LR 中。
示例代码 45-36 子程序跳转
1 bl func
通过跳转指令建立一个无限循环。
示例代码 45-37 无限循环
1 loop:
2 add r1,r2,#4
3 add r3,r2,#8
4 sub r3,r3,r1
5 b loop
通过使用跳转使程序体循环 10 次。
示例代码 45-38 有限循环
1 mov r0,#10
2 loop:
3 subs r0,#1
4 bne loop
条件子程序调用示例。
示例代码 45-39 条件调用
1 cmp r0,#5 ;如果r0<5
2 bllt sub1 ;则调用
3 blge sub2 ;否则调用 sub2
带状态切换的跳转指令 BX
带状态切换的跳转指令(BX)使程序跳转到指令中指定的参数 Rm 指定的地址执行程序,Rm 的第 0位复制到 CPSR 中 T 位,bit[31∶1]移入 PC。若 Rm 的 bit[0]为 1,则跳转时自动将 CPSR 中的标志位 T 置位,即把目标地址的代码解释为 Thumb 代码;若 Rm 的位 bit[0]为 0,则跳转时自动将 CPSR 中的标志位T 复位,即把目标地址代码解释为 ARM 代码。
指令的语法格式:
BX{<c>} <Rm>
当 Rm[1∶0]=0b10 时,指令的执行结果不可预知。因为在 ARM 状态下,指令是 4 字节对齐的。PC 可以作为 Rm 寄存器使用,但这种用法不推荐使用。当 PC 作为<Rm>使用时,指令“BX PC”将程序跳转到当前指令下面第二条指令处执行。虽然这样跳转可以实现,但最好使用下面的指令完成这种跳转。
MOV PC, PC或ADD PC, PC, #0
指令举例:
转移到 R0 中的地址,如果 R0[0]=1,则进入 Thumb 状态。
示例代码 45-40 bx 指令示例
1 bx r0
带连接和状态切换的连接跳转指令 BLX 带连接和状态切换的跳转指令(Branch with Link Exchange,BLX)使用标号,用于使程序跳转到 Thumb状态或从 Thumb 状态返回。该指令为无条件执行指令,并用分支寄存器的最低位来更新 CPSR 中的 T 位,将返回地址写入到连接寄存器 LR 中。
语法格式:
BLX <target_add>
其中,<target_add>为指令的跳转目标地址。该地址根据以下规则计算。
1) 将指令中指定的 24 位偏移量进行符号扩展,形成 32 位立即数。
2) 将结果左移两位。
3) 位 H(bit[24])加到结果地址的第一位(bit[1])。
4) 将结果累加进程序计数器(PC)中。
计算偏移量的工作一般由 ARM 汇编器来完成。这种形式的跳转指令只能实现−32~32MB 空间的跳转。左移两位形成字偏移量,然后将其累加进程序计数器(PC)中。这时,程序计数器的内容为 BX 指令地址加 8 字节。位 H(bit[24])也加到结果地址的第一位(bit[1]),使目标地址成为半字地址,以执行接下来的 Thumb 指令。计算偏移量的工作一般由 ARM 汇编器来完成。这种形式的跳转指令只能实现−32~32MB 空间的跳转。
指令举例:
从 Thumb 状态返回到 ARM 状态,使用 BX 指令。
示例代码 45-41 Thumb 状态返回 ARM 状态
1 blx func
状态操作指令
ARM 指令集提供了两条指令,可直接控制程序状态寄存器(Program State Register,PSR)。MRS 指令用于把CPSR或SPSR的值传送到一个寄存器;MSR与之相反,把一个寄存器的内容传送到CPSR或SPSR。
这两条指令相结合,可用于对 CPSR 和 SPSR 进行读/写操作。程序状态寄存器指令如表所示。
表 45.3.5.1 状态操作指令
在指令语法中可看到一个称为 fields 的项,它可以是控制(C)、扩展(X)、状态(S)及标志(F)的组合。
1、 MRS
MRS 指令用于将程序状态寄存器的内容传送到通用寄存器中。
在 ARM 处理器中,只有 MRS 指令可以将状态寄存器 CPSR 或 SPSR 读出到通用寄存器中。
指令的语法格式:
MRS{c} Rd, PSR
其中,Rd 为目标寄存器,Rd 不允许为程序计数器(PC)。PSR 为 CPSR 或 SPSR。
指令举例:
示例代码 45-42 读取 CPSR 与 SPSR
1 mrs r1,cpsr ;将cpsr状态寄存器读取,保存到r1中
2 mrs r2,spsr ;将 spsr 状态寄存器读取,保存到 r1 中
MRS 指令读取 CPSR,可用来判断 ALU 的状态标志及 IRQ/FIQ 中断是否允许等;在异常处理程序中,读 SPSR 可指定进入异常前的处理器状态等。MRS 与 MSR 配合使用,实现 CPSR 或 SPSR 寄存器的读—修改—写操作,可用来进行处理器模式切换,允许/禁止 IRQ/FIQ 中断等设置。另外,进程切换或允许异常中断嵌套时,也需要使用 MRS 指令读取 SPSR 状态值并保存起来。
MSR
在 ARM 处理器中,只有 MSR 指令可以直接设置状态寄存器 CPSR 或 SPSR。
指令的语法格式:
MSR{c} PSR_fields, #immed_8r
MSR{c} PSR_fields, Rm
其中,PSR 是指 CPSR 或 SPSR。<fields>设置状态寄存器中需要操作的位。状态寄存器的 32 位可以分为 4 个 8 位的域(field)。bits[31:24]为条件标志位域,用 f 表示;bits[23:16]为状态位域,用 s 表示;bits[15:8]为扩展位域,用 x 表示;bits[7:0]为控制位域,用 c 表示;immed_8r 为要传送到状态寄存器指定域的立即数,8 位;Rm 为要传送到状态寄存器指定域的数据源寄存器。
指令举例:
示例代码 45-43 MSR 指令示例
1 msr cpsr,#0xd3 ;cpsr[7:0]=0xd3,切换到管理模式
2 msr cpsr,r3 ;cpsr=r3
注意:只有在特权模式下才能修改状态寄存器。
程序中不能通过 MSR 指令直接修改 CPSR 中的 T 位控制位来实现 ARM 状态/Thumb 状态的切换,必须使用 BX 指令来完成处理器状态的切换(因为 BX 指令属转移指令,它会打断流水线状态,实现处理器状态的切换)。MRS 与 MSR 配合使用,实现 CPSR 或 SPSR 寄存器的读—修改—写操作,可用来进行处理器模式切换及允许/禁止 IRQ/FIQ 中断等设置。
程序状态寄存器指令的应用
使能 IRQ 中断。
示例代码 45-44 使能 IRQ 中断
1 enable_irq:
2 mrs r0,cpsr
3 bic r0,r0,#0x80
4 msr cpsr_c,r0
5 mov pc,lr
禁止 IRQ 中断。
示例代码 45-45 禁止 IRQ 中断
1 disable_irq:
2 mrs r0,cpsr
3 orr r0,r0,#0x80
4 msr cpsr,r0
5 mov pc,lr
设置中断模式堆栈:
示例代码 45-46 irq 模式堆栈
1 msr cpsr,#0xd2
2 ldr sp,stacksvc
协处理器指令
ARM 体系结构允许通过增加协处理器来扩展指令集。最常用的协处理器是用于控制片上功能的系统协处理器。例如,控制 Cache 和存储管理单元的 cp15 寄存器。此外,还有用于浮点运算的浮点 ARM 协处理器,各生产商还可以根据需要开发自己的专用协处理器。
ARM 协处理器具有自己专用的寄存器组,它们的状态由控制 ARM 状态的指令的镜像指令来控制。程序的控制流指令由 ARM 处理器来处理,所有协处理器指令只能同数据处理和数据传送有关。按照 RISC 的Load/Store 体系原则,数据的处理和传送指令是被清楚分开的,所以它们有不同的指令格式。ARM 处理器支持 16 个协处理器,在程序执行过程中,每个协处理器忽略 ARM 和其他协处理器指令。当一个协处理器硬件不能执行属于它的协处理器指令时,将产生一个未定义指令异常中断,在该异常中断处理过程中,可以通过软件仿真该硬件操作。如果一个系统中不包含向量浮点运算器,则可以选择浮点运算软件包来支持向量浮点运算。
ARM 协处理器可以部分地执行一条指令,然后产生中断。如除法运算除数为 0 和溢出,这样可以更好地处理运行时产生(run-time-generated)的异常。但是,指令的部分执行是由协处理器完成的,此过程对ARM 来说是透明的。当 ARM 处理器重新获得执行时,它将从产生异常的指令处开始执行。对某一个协处理器来说,并不一定用到协处理器指令中的所有的域。具体协处理器如何定义和操作完全由协处理器的制造商自己决定,因此,ARM 协处理器指令中的协处理器寄存器的标识符及操作助记符也有各种不同的实现定义。程序员可以通过宏定义这些指令的语法格式。
ARM 协处理器指令可分为以下 3 类。
⚫ 协处理器数据操作。协处理器数据操作完全是协处理器内部操作,它完成协处理器寄存器的状态改变。如浮点加运算,在浮点协处理器中两个寄存器相加,结果放在第 3 个寄存器中。这类指令包括 CDP 指令。
⚫ 协处理器数据传送指令。这类指令从寄存器读取数据装入协处理器寄存器,或将协处理器寄存器的数据装入存储器。因为协处理器可以支持自己的数据类型,所以每个寄存器传送的字数与协处理器有关。ARM 处理器产生存储器地址,但传送的字节由协处理器控制。这类指令包括LDC 指令和 STC 指令。
⚫ 协处理器寄存器传送指令。在某些情况下,需要 ARM 处理器和协处理器之间传送数据。如一个浮点运算协处理器,FIX 指令从协处理器寄存器取得浮点数据,将它转换为整数,并将整数传送到 ARM 寄存器中。经常需要用浮点比较产生的结果来影响控制流,因此,比较结果必须传送到 ARM 的 CPSR 中。这类协处理器寄存器传送指令包括 MCR 和 MRC。
如表所示列出了所有协处理器处理指令。
表 45.3.6.1 协处理器操作指令
下面简单介绍一下比较常用的 MCR 及 MRC 命令的用法
ARM 寄存器到协处理器寄存器的数据传送指令 MCR
指令的语法格式
MCR<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
<c>:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
<coproc>:协处理器名称,范围 p0-p15;
<opc1>:协处理器操作码,范围 0-15;
<Rt>:源寄存器,将 Rt 寄存器写入协处理器;
<CRn>:协处理器的目标寄存器;
<CRm>:协处理器中附加的目标寄存器或者源操作寄存器,如果不需要附加信息就设置为 C0,否则
结果不可预测;
<opc2>:可选的协处理器特定操作码,当不需要时置 0。
协处理器寄存器到 ARM 寄存器的数据传送指令 MRC
指令的语法格式
MRC<c> <coproc>, <opc1>, <Rt>, <CRn>, <CRm>{, <opc2>}
<c>:为指令执行的条件码。当忽略< c >时,指令为无条件执行。
<coproc>:协处理器名称,范围 p0-p15;
<opc1>:协处理器操作码,范围 0-15;
<Rt>:源寄存器,将协处理器的内容读取到 Rt 寄存器;
<CRn>:协处理器的目标寄存器;
<CRm>:协处理器中附加的目标寄存器或者源操作寄存器,如果不需要附加信息就设置为 C0,否则
结果不可预测;
<opc2>:可选的协处理器特定操作码,当不需要时置 0。
指令举例
示例代码 45-47 使能 ICache
1 /******Cache Test*******/
2 mrc p15,0,r1,c1,c0,0
3 orr r1, r1, #(1 << 2) // Set C bit 整体使能Cache
4 orr r1, r1, #(1 << 12) //Set I bit 使能ICache
5 mcr p15,0,r1,c1,c0,0
6 /******End Test******/
异常产生指令
ARM 指令集中提供了两条产生异常的指令,通过这两条指令可以用软件的方法实现异常。如表所示为 ARM 异常产生指令。
软件中断指令(Software Interrupt,SWI)用于产生软中断,从而实现从用户模式变换到管理模式,CPSR 保存到管理模式的 SPSR 中,执行转移到 SWI 向量,在其他模式下也可以使用 SWI 指令,处理器同样切换到管理模式。
指令的语法格式。
SWI{<c>} <immed_24>
指令举例
示例代码 45-48 swi 指令示例
1 swi 0 ;产生软中断,中断号为0
2 swi 0x123456 ;产生软中断,中断立即数为 0x123456
使用 SWI 指令时,通常使用以下两种方法进行参数传递。
下面的程序产生一个中断号为 12 的软中断。
示例代码 45-49 swi 指令示例
1 mov r0,#34 ;设置功能号为34
2 swi 12 ;产生软中断,中断号为 12
下面的例子通过 R0 传递中断号,R1 传递中断的子功能号。
示例代码 45-50 swi 指令示例
1 mov r0,#12 ;设置12号软中断
2 mov r1,#34 ;设置功能号为34
3 swi 0
ARM 汇编实验
实验目的
了解程序的运行过程
掌握 ARM 汇编语言的基本使用;
熟悉 eclipse 开发工具建立汇编工程和仿真;
实验原理
根据上面阐述 RAM 汇编语言的使用语法和功能,编写汇编程序,实现一个简单的数据运算操作。
⚫ 可执行程序的组成部分
在 linux 下一个可执行的 elf 应用程序,通常包含以下几部分内容。
图 45-3 可执行程序的组成部分
这里涉及到了代码段、数据段、未初始化数据、堆、栈,几个部分:
代码段:代码段在内存中被映射为只读。通常是用来存放程序执行的指令。
数据段:通常用来存放程序中已初始化的(非 0)全局变量和静态局部变量,数据段的起始位置由链接定位文件确认,大小在编译链接时自动分配。
未初始化数据:通常用来存放程序中未初始化和初始化为 0 的全局变量的一块内存区域,在程序载入时清零。
堆:保存函数内部动态分配(malloc 或 new)的内存。
栈:保存函数的局部变量(不包括 static 修饰的变量),参数以及返回值。
在汇编语言中一个可执行程序一般至少包含:代码段+数据段+BSS 段。
⚫ 位置有关和位置无关:
位置无关码就是和位置无关,这种代码放在什么位置都能正常运行,所以地址是动态的不能固定的。而位置有关码就是和某个具体位置有关的代码,而这个具体位置就是我们的链接地址。
⚫ 链接地址:
在程序编译的时候,每个目标文件都是由源代码编译得到,最终多个目标文件链接生成一个最终的可执行文件,而链接地址就是指示链接器,各个目标文件的在可执行程序中的位置。比如,一个可执行程序 a.out 由 a.o、b.o、c.o 组成,那么最终的 a.out 中谁在前、谁在中间、谁在结尾,都可以通过制定链接地址来决定。
⚫ 运行地址:
程序实际在内存中运行时候的地址,比如 CPU 要执行一条指令,那么必然要通过给 PC 赋值,从对应的地址空间中去取出来,那么这个地址就是实际的运行地址。
⚫ 加载地址和存储地址:
每一个程序一开始都是存放在 flash 中的,而运行是在内存中,这个时候就需要从 flash 中将指令读取到内存中(运行地址),flash 的地址就是加载地址。
图 45-4 链接地址和运行地址对比图
实验内容
创建链接脚本
示例代码 45-51 链接脚本
1 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
2 OUTPUT_ARCH(arm)
3 ENTRY(_start)
4 SECTIONS
5 {
6 . = 0xc2000040;
7 . = ALIGN(4);
8 .text :
9 {
10 start.o(.text)
11 *(.text)
12 }
13 . = ALIGN(4);
14 .rodata :
15 { *(.rodata) }
16 . = ALIGN(4);
17 .data :
18 { *(.data) }
19 . = ALIGN(4);
20 .bss :
21 { *(.bss) }
22 }
创建 Makefile 编译脚本
示例代码 45-52 Makefile 文件
1 SHELL=C:\Windows\System32\cmd.exe
2
3 CROSS_COMPILE = arm-none-eabi4 NAME = h_project
5
6 CPPFLAGS := -nostdlib -nostdinc -g
7 CFLAGS := -Wall -O2 -fno-builtin -g
8
9 LD = $(CROSS_COMPILE)ld
10 CC = $(CROSS_COMPILE)gcc
11 OBJCOPY = $(CROSS_COMPILE)objcopy
12 OBJDUMP = $(CROSS_COMPILE)objdump
13
14 export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS
15
16 objs := start.o
17 all: $(objs)
18 $(LD) -T map.lds -o $(NAME).elf $^
19 $(OBJCOPY) -O binary $(NAME).elf $(NAME).bin
20 $(OBJDUMP) -D $(NAME).elf > $(NAME).dis
21
22 %.o : %.S
23 $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
24
25 %.o : %.c
26 $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
27
28 clean:
29 rm *.o *.elf *.bin
汇编程序设计如下
示例代码 45-53 汇编示例
1 .text
2 .global _start
3 _start:
4
5 mov r0, #0x9
6 nop
7 mov r1, #0x7
8
9 bl add_sub
10
11 stop:
12 b stop
13
14 add_sub:
15 add r2, r0, r1 ; r2=0x9+0x7=0x10
16 sub r3, r0, r1 ; r3=0x9-0x7=0x2
17
18 mul r4, r0,r1 ; r4=0x9*0x7=0x3f
19 mov pc, lr
实验步骤
1、 导入工程源码
请参考导入一个已有工程章节的导入一个已有工程。
光盘实验源码路径:【资料光盘\华清远见-FS-MP1A 开发资料-2020-11-06\02-程序源码\03-ARM 体系
结构与接口技术\Cortex-A7\h_project】
2、 打开“Register”显示框
单击 window -> show view -> Register,
3、 单步仿真
配置完成之后,点击“ ”开始仿真,弹出 Debug 框。
单击“ ”单步仿真。查看仿真现象。
实验现象
单击“ ”单步,查看 Rn 寄存器的变化。
单步运行可以看到 R2、R3 和 R4 的值变化。