目录
ARM微处理器指令系统
ARM处理器寻址方式
数据处理指令寻址方式
内存访问指令寻址方式
ARM处理器指令集
数据操作指令
乘法指令
Load/Store指令
跳转指令
状态操作指令
协处理器指令
异常产生指令
其他指令介绍
ARM 汇编实验
实验目的
实验原理
实验内容
实验步骤
实验现象
写在前面:
本文章为《ARM Cortex-A7裸机开发篇》系列中的一篇,全系列总计11篇。笔者使用的开发平台为华清远见FS-MP1A开发板(STM32MP157开发板)。
针对FS-MP1A开发板,除了Cortex-A7裸机开发篇外,还包括其他多系列教程,包括Cortex-M4开发篇、FreeRTOS篇、Linux基础及应用开发篇、Linux系统移植篇、Linux驱动开发篇、硬件设计篇、人工智能机器视觉篇、Qt应用编程篇、Qt综合项目实战篇等。欢迎关注,更多stm32mp157开发教程及视频,可加技术交流Q群483143191,感谢关注。
FS-MP1A开发板详情介绍:https://item.taobao.com/item.htm?id=622457259672
ARM指令集可以分为跳转指令、数据处理指令、程序状态寄存器传输指令、Load/Store指令、协处理器指令和异常中断产生指令。根据使用的指令类型不同,指令的寻址方式分为数据处理指令寻址方式和内存访问指令寻址方式。
本章主要介绍ARM汇编语言。主要内容如下:
ARM指令的寻址方式分为数据处理指令寻址方式和内存访问指令寻址方式。
数据处理指令的基本语法格式如下:
其中,
表
语 法 | 寻 址 方 式 | |
1 | # |
立即数寻址 |
2 | 寄存器寻址 | |
3 | 立即数逻辑左移 | |
4 | 寄存器逻辑左移 | |
5 | 立即数逻辑右移 | |
6 | 寄存器逻辑右移 | |
7 | 立即数算术右移 | |
8 | 寄存器算术右移 | |
9 | 立即数循环右移 | |
10 | 寄存器循环右移 | |
11 | 寄存器扩展循环右移 |
数据处理指令寻址方式可以分为以下几种。
指令中的立即数是由一个8bit的常数移动4bit偶数位(0,2,4,…,26,28,30)得到的。所以,每一条指令都包含一个8bit的常数X和移位值Y,得到的立即数 = X循环右移(2×Y),如图所示。
下面列举了一些有效的立即数:
0xFF、0x104、0xFF0、0xFF00、0xFF000、0xFF000000、0xF000000F
下面是一些无效的立即数:
0x101、0x102、0xFF1、0xFF04、0xFF003、0xFFFFFFFF、0xF000001F
下面是一些应用立即数的指令:
MOV R0,#0 ;送0到R0
ADD R3,R3,#1 ;R3的值加1
CMP R7,#1000 ;将R7的值和1000比较
BIC R9,R8,#0xFF00 ;将R8中8~15位清零,结果保存在R9中
寄存器的值可以被直接用于数据操作指令,这种寻址方式是各类处理器经常采用的一种方式,也是一种执行效率较高的寻址方式,如:
MOV R2,R0 ;R0的值送R2
ADD R4,R3,R2 ;R2加R3,结果送R4
CMP R7,R8 ;比较R7和R8的值
寄存器的值在被送到ALU之前,可以事先经过桶形移位寄存器的处理。预处理和移位发生在同一周期内,所以有效地使用移位寄存器,可以增加代码的执行效率。
下面是一些在指令中使用了移位操作的例子:
ADD R2,R0,R1,LSR #5
MOV R1,R0,LSL #2
RSB R9,R5,R5,LSL #1
SUB R1,R2,R0,LSR #4
MOV R2,R4,ROR R0
内存访问指令的寻址方式可以分为以下几种。
字及无符号字节的Load/Store指令语法格式如下:
LDR|STR{
其中,
格 式 | 模 式 | |
1 | [Rn,#± |
立即数偏移寻址(Immediate offset) |
2 | [Rn,±Rm] | 寄存器偏移寻址(Register offset) |
3 | [Rn,Rm, |
带移位的寄存器偏移寻址(Scaled register offset) |
4 | [Rn,#±< offset_12>]! | 立即数前索引寻址 (Immediate pre-indexed) |
5 | [Rn,±Rm]! | 寄存器前索引寻址(Register post-indexed) |
6 | [Rn,Rm, |
带移位的寄存器前索引寻址(Scaled register pre-indexed) |
7 | [Rn],#±< offset_12> | 立即数后索引寻址(Immediate post-indeted) |
8 | [Rn],± |
寄存器后索引寻址(Register post-indexed) |
9 | [Rn],± |
带移位的寄存器后索引寻址(Scaled register post-indexed) |
上表中,“!”表示完成数据传输后要更新基址寄存器。
使用该类寻址方式的指令的语法格式如下:
LDR|STR{
使用该类寻址方式的指令包括(有符号/无符号)半字Load/Store指令、有符号字节Load/Store指令和双字Load/Store指令。
该类寻址方式分为6种类型,如表所示。
格 式 | 模 式 | |
1 | [Rn,#± |
立即数偏移寻址(Immediate offset) |
2 | [Rn,±Rm] | 寄存器偏移寻址(Register offset) |
3 | [Rn,#±< offset_8>]! | 立即数前索引寻址(Immediate pre-indexed) |
4 | [Rn,±Rm]! | 寄存器前索引寻址(Register post-indexed) |
5 | [Rn],#±< offset_8> | 立即数后索引寻址(Immediate post-indexed) |
6 | [Rn],± |
寄存器后索引寻址(Register post-indexed) |
批量Load/Store指令将一片连续内存单元的数据加载到通用寄存器组中或将一组通用寄存器的数据存储到内存单元中。
批量Load/Store指令的寻址模式产生一个内存单元的地址范围,指令寄存器和内存单元的对应关系满足这样的规则,即编号低的寄存器对应于内存中低地址单元,编号高的寄存器对应于内存中的高地址单元。
该类指令的语法格式如下:
LDM|STM{
该类指令的寻址方式如表所示。
格 式 | 模 式 | |
1 | IA(Increment After) | 后递增方式 |
2 | IB(Increment Before) | 先递增方式 |
3 | DA(Decrement After) | 后递减方式 |
4 | DB(Decrement Before) | 先递减方式 |
堆栈操作寻址方式和批量Load/Store指令寻址方式十分类似。但对于堆栈的操作,数据写入内存和从内存中读出要使用不同的寻址模式,因为进栈操作(pop)和出栈操作(push)要在不同的方向上调整堆栈。
下面详细讨论如何使用合适的寻址方式实现数据的堆栈操作。
根据不同的寻址方式,将堆栈分为以下4种。
根据堆栈的不同种类,将其寻址方式分为以下4种。
如表所示列出了堆栈的寻址方式和批量Load/Store指令寻址方式的对应关系。
批量数据寻址方式 | 堆栈寻址方式 | L位 | P位 | U位 |
LDMDA | LDMFA | 1 | 0 | 0 |
LDMIA | LDMFD | 1 | 0 | 1 |
LDMDB | LDMEA | 1 | 1 | 0 |
LDMIB | LDMED | 1 | 1 | 1 |
STMDA | STMED | 0 | 0 | 0 |
STMIA | STMEA | 0 | 0 | 1 |
STMDB | STMFD | 0 | 1 | 0 |
STMIB | STMFA | 0 | 1 | 1 |
协处理器Load/Store指令的语法格式如下:
数据操作指令是指对存放在寄存器中的数据进行操作的指令。主要包括数据传送指令、算术指令、逻辑指令、比较与测试指令及乘法指令。
如果在数据处理指令前使用S前缀,指令的执行结果将会影响CPSR中的标志位。数据处理指令如表所示。
助 记 符 | 操 作 | 行 为 |
MOV | 数据传送 | |
MVN | 数据取反传送 | |
AND | 逻辑与 | Rd:=Rn AND op2 |
EOR | 逻辑异或 | Rd:=Rn EOR op2 |
SUB | 减 | Rd:=Rn − op2 |
RSB | 翻转减 | Rd:=op2 − Rn |
ADD | 加 | Rd:=Rn + op2 |
ADC | 带进位的加 | Rd:=Rn + op2 + C |
SBC | 带进位的减 | Rd:=Rn− op2 + C − 1 |
RSC | 带进位的翻转减 | Rd:=op2 − Rn + C − 1 |
TST | 测试 | Rn AND op2并更新标志位 |
TEQ | 测试相等 | Rn EOR op2并更新标志位 |
CMP | 比较 | Rn−op2并更新标志位 |
CMN | 负数比较 | Rn+op2并更新标志位 |
ORR | 逻辑或 | Rd:=Rn OR op2 |
BIC | 位清0 | Rd:=Rn AND NOT(op2) |
MOV指令是最简单的ARM指令,执行的结果就是把一个数N送到目标寄存器Rd,其中N可以是寄存器,也可以是立即数。
MOV指令多用于设置初始值或者在寄存器间传送数据。
MOV指令将移位码(shifter_operand)表示的数据传送到目的寄存器Rd,并根据操作的结果更新CPSR中相应的条件标志位。
MOV{
MOV R0, R0 ; R0 = R0… NOP 指令
MOV R0, R0, LSL#3 ; R0 = R0 * 8
如果R15是目的寄存器,将修改程序计数器或标志。这用于被调用的子函数结束后返回到调用代码,方法是把连接寄存器的内容传送到R15。
MOV PC, R14 ; 退出到调用者,用于普通函数返回,PC即是R15
MOVS PC, R14 ; 退出到调用者并恢复标志位,用于异常函数返回
MOV指令主要完成以下功能。
将数据从一个寄存器传送到另一个寄存器。
将一个常数值传送到寄存器中。
实现无算术和逻辑运算的单纯移位操作,操作数乘以2n可以用左移n位来实现。
当PC(R15)用做目的寄存器时,可以实现程序跳转。如“MOV PC,LR”,所以这种跳转可以实现子程序调用及从子程序返回,代替指令“B,BL”。
当PC作为目标寄存器且指令中S位被设置时,指令在执行跳转操作的同时,将当前处理器模式的SPSR寄存器的内容复制到CPSR中。这种指令“MOVS PC LR”可以实现从某些异常中断中返回。
MVN是反相传送(Move Negative)指令。它将操作数的反码传送到目的寄存器。
MVN指令多用于向寄存器传送一个负数或生成位掩码。
MVN指令将shifter_operand表示的数据的反码传送到目的寄存器Rd,并根据操作结果更新CPSR中相应的条件标志位。
MNV{
指令举例如下
MVN R0, #4 ; R0 = -5
MVN R0, #0 ; R0 = -1
MVN指令主要完成以下功能:
向寄存器中传送一个负数。
生成位掩码(Bit Mask)。
求一个数的反码。
AND指令将shifter_operand表示的数值与寄存器Rn的值按位(bitwise)做逻辑与操作,并将结果保存到目标寄存器Rd中,同时根据操作的结果更新CPSR寄存器。
AND{
AND R0, R0, #3
AND R2,R1,R3
ANDS R0,R0,#0x01
EOR(Exclusive OR)指令将寄存器Rn中的值和shifter_operand的值执行按位“异或”操作,并将执行结果存储到目的寄存器Rd中,同时根据指令的执行结果更新CPSR中相应的条件标志位。
EOR{
EOR R0, R0, #3
EOR R1,R1,#0x0F
EOR R2,R1,R0
EORS R0,R5,#0x01
SUB(Subtract)指令从寄存器Rn中减去shifter_operand表示的数值,并将结果保存到目标寄存器Rd中,并根据指令的执行结果设置CPSR中相应的标志位。
SUB{
SUB R0, R1, R2
SUB R0, R1, #256
SUB R0, R2, R3,LSL#1
RSB(Reverse Subtract)指令从寄存器shifter_operand中减去Rn表示的数值,并将结果保存到目标寄存器Rd中,并根据指令的执行结果设置CPSR中相应的标志位。
RSB{
下面的指令序列可以求一个64位数值的负数。64位数放在寄存器R0与R1中,其负数放在R2和R3中。其中R0与R2中放低32位值。
RSBS R2,R0,#0
RSC R3,R1,#0
ADD指令将寄存器shifter_operand的值加上Rn表示的数值,并将结果保存到目标寄存器Rd中,并根据指令的执行结果设置CPSR中相应的标志位。
ADD{
ADD R0, R1, R2 ; R0 = R1 + R2
ADD R0, R1, #256 ; R0 = R1 + 256
ADD R0, R2, R3,LSL#1 ; R0 = R2 + (R3 << 1)
ADC指令将寄存器shifter_operand的值加上Rn表示的数值,再加上CPSR中的C条件标志位的值,将结果保存到目标寄存器Rd中,并根据指令的执行结果设置CPSR中相应的标志位。
ADC{
ADC指令将把两个操作数加起来,并把结果放置到目的寄存器中。它使用一个进位标志位,这样就可以做比32位大的加法。下面的例子将加两个128位的数。
128位结果:寄存器R0、R1、R2和R3。
第一个128位数:寄存器R4、R5、R6和R7。
第二个128位数:寄存器R8、R9、R10和R11。
ADDS R0, R4, R8 ;加低端的字
ADCS R1, R5, R9 ;加下一个字,带进位
ADCS R2, R6, R10 ;加第三个字,带进位
ADCS R3, R7, R11 ;加高端的字,带进位
SBC(Subtract with Carry)指令用于执行操作数大于32位时的减法操作。该指令从寄存器Rn中减去shifter_operand表示的数值,再减去寄存器CPSR中C条件标志位的反码[NOT(Carry flag)],并将结果保存到目标寄存器Rd中,并根据指令的执行结果设置CPSR中相应的标志位。
SBC{
下面的程序使用SBC实现64位减法,(R1,R0)−(R3,R2),结果存放到(R1,R0)。
SUBS R0,R0,R2
SBCS R1,R1,R3
RSC(Reverse Subtract with Carry)指令从寄存器shifter_operand中减去Rn表示的数值,再减去寄存器CPSR中C条件标志位的反码[NOT(Carry Flag)],并将结果保存到目标寄存器Rd中,并根据指令的执行结果设置CPSR中相应的标志位。
RSC{
下面的程序使用RSC指令实现求64位数值的负数。
RSBS R2,R0,#0
RSC R3,R1,#0
TST(Test)测试指令用于将一个寄存器的值和一个算术值进行比较。条件标志位根据两个操作数做“逻辑与”后的结果设置。
TST{
TST指令类似于CMP指令,不产生放置到目的寄存器中的结果。而是在给出的两个操作数上进行操作并把结果反映到状态标志上。使用TST指令来检查是否设置了特定的位。操作数1是要测试的数据字而操作数2是一个位掩码。经过测试后,如果匹配则设置Zero标志,否则清除它。与CMP指令一样,该指令不需要指定S后缀。
下面的指令测试在R0中是否设置了位0。
TST R0, #1
TEQ(Test Equivalence)指令用于将一个寄存器的值和一个算术值做比较。条件标志位根据两个操作数做“逻辑异或”后的结果设置。以便后面的指令根据相应的条件标志来判断是否执行。
TEQ{
下面的指令是比较R0和R1是否相等,该指令不影响CPSR中的V位和C位。
TEQ R0,R1
TST指令与EORS指令的区别在于TST指令不保存运算结果。使用TEQ进行相等测试,常与EQ和NE条件码配合使用,当两个数据相等时,条件码EQ有效;否则条件码NE有效。
CMP(Compare)指令使用寄存器Rn的值减去operand2的值,根据操作的结果更新CPSR中相应的条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。
CMP{
CMP指令允许把一个寄存器的内容与另一个寄存器的内容或立即值进行比较,更改状态标志来允许进行条件执行。它进行一次减法,但不存储结果,而是正确地更改标志位。标志位表示的是操作数1与操作数2比较的结果(其值可能为大于、小于、相等)。如果操作数1大于操作数2,则此后的有GT后缀的指令将可以执行。
显然,CMP不需要显式地指定S后缀来更改状态标志。
CMP R1,#10
CMP R1,R2
通过上面的例子可以看出,CMP指令与SUBS指令的区别在于CMP指令不保存运算结果,在进行两个数据大小判断时,常用CMP指令及相应的条件码来进行操作。
CMN(Compare Negative)指令使用寄存器Rn的值减去operand2的负数值(加上operand2),根据操作的结果更新CPSR中相应的条件标志位,以便后面的指令根据相应的条件标志来判断是否执行。
CMN{
CMN指令将寄存器Rn中的值加上shifter_operand表示的数值,根据加法的结果设置CPSR中相应的条件标志位。寄存器Rn中的值加上shifter_operand的操作结果对CPSR中条件标志位的影响,与寄存器Rn中的值减去shifter_operand的操作结果的相反数对CPSR中条件标志位的影响有细微差别。当第2个操作数为0或者为0x80000000时两者结果不同。比如下面两条指令。
CMP Rn,#0
CMN Rn,#0
第1条指令使标志位C值为1,第2条指令使标志位C值为0。
下面的指令使R0值加1,判断R0是否为1的补码,若是,则Z置位。
CMN R0,#1
ORR(Logical OR)为逻辑或操作指令,它将第2个源操作数shifter_operand的值与寄存器Rn的值按位做“逻辑或”操作,结果保存到Rd中。
ORR{
ORR R0, R0, #3
ORR R0,R0,#0x0F
MOV R1,R2,LSR #4
ORR R3,R1,R3,LSL #8
BIC(Bit Clear)位清零指令,将寄存器Rn的值与第2个源操作数shifter_operand的值的反码按位做“逻辑与”操作,结果保存到Rd中。
BIC{
BIC R0, R0, #0x1011
BIC R1,R2,R3
ARM乘法指令完成两个数据的乘法。两个32位二进制数相乘的结果是64位的积。在有些ARM的处理器版本中,将乘积的结果保存到两个独立的寄存器中。另外一些版本只将最低有效32位存放到一个寄存器中。无论是哪种版本的处理器,都有乘—累加的变型指令,将乘积连续累加得到总和。而且有符号数和无符号数都能使用。对于有符号数和无符号数,结果的最低有效位是一样的。因此,对于只保留32位结果的乘法指令,不需要区分有符号数和无符号数这两种情况。
如表所示为各种形式乘法指令的功能。
操作码[23∶21] | 助 记 符 | 意 义 | 操 作 |
000 | MUL | 乘(保留32位结果) | Rd:=(Rm×Rs)[31∶0] |
001 | MLA | 乘—累加(保留32位结果) | Rd:=(Rm×Rs+Rn)[31∶0] |
100 | UMULL | 无符号数长乘 | RdHi:RdLo:=Rm×Rs |
101 | UMLAL | 无符号长乘—累加 | RdHi:RdLo:+=Rm×Rs |
110 | SMULL | 有符号数长乘 | RdHi:RdLo:=Rm×Rs |
111 | SMLAL | 有符号数长乘—累加 | RdHi:RdLo:+=Rm×Rs |
其中:
各个乘法指令中的位S(参考下文具体指令的语法格式)控制条件码的设置会产生以下结果。
MUL(Multiply)32位乘法指令将Rm和Rs中的值相乘,结果的最低32位保存到Rd中。
MUL{
MUL R1, R2, R3
MULS R0, R3, R7
MLA(Multiply Accumulate)32位乘—累加指令将Rm和Rs中的值相乘,再将乘积加上第3个操作数,结果的最低32位保存到Rd中。
MLA{
下面的指令完成R1 = R2×R3 + 10的操作。
MOV R0, #0x0A
MLA R1, R2, R3, R0
UMULL(Unsigned Multiply Long)为64位无符号乘法指令。它将Rm和Rs中的值做无符号数相乘,结果的低32位保存到RsLo中,高32位保存到RdHi中。
UMULL{
下面的指令完成(R1,R0) = R5 × R8操作。
UMULL R0, R1, R5, R8;
UMLAL(Unsigned Multiply Accumulate Long)为64位无符号长乘—累加指令。指令将Rm和Rs中的值做无符号数相乘,64位乘积与RdHi、RdLo相加,结果的低32位保存到RsLo中,高32位保存到RdHi中。
UMALL{
下面的指令完成(R1,R0) = R5 × R8+(R1,R0)操作。
UMLAL R0, R1, R5,R8;
SMULL(Signed Multiply Long)为64位有符号长乘法指令。指令将Rm和Rs中的值做有符号数相乘,结果的低32位保存到RsLo中,高32位保存到RdHi中。
SMULL{
下面的指令完成(R3,R2) = R7 × R6操作。
SMULL R2, R3, R7,R6;
SMLAL(Signed Multiply Accumulate Long)为64位有符号长乘—累加指令。指令将Rm和Rs中的值做有符号数相乘,64位乘积与RdHi、RdLo相加,结果的低32位保存到RsLo中,高32位保存到RdHi中。
SMLAL{
下面的指令完成(R3,R2) = R7 × R6 +(R3,R2)操作。
SMLAL R2, R3, R7,R6;
Load/Store内存访问指令在ARM寄存器和存储器之间传送数据。ARM指令中有3种基本的数据传送指令。
如表所示列出了所有单寄存器的Load/Store指令。
指 令 | 作 用 | 操 作 |
LDR | 把存储器中的一个字装入一个寄存器 | Rd←mem32[address] |
STR | 将寄存器中的字保存到存储器 | Rd→mem32[address] |
LDRB | 把一个字节装入一个寄存器 | Rd←mem8[address] |
STRB | 将寄存器中的低8位字节保存到存储器 | Rd→mem8[address] |
LDRH | 把一个半字装入一个寄存器 | Rd←mem16[address] |
STRH | 将寄存器中的低16位半字保存到存储器 | Rd→mem16[address] |
LDRBT | 用户模式下将一个字节装入寄存器 | Rd←mem8[address] under user mode |
STRBT | 用户模式下将寄存器中的低8位字节保存到存储器 | Rd→mem8[address] under user mode |
LDRT | 用户模式下把一个字装入一个寄存器 | Rd←mem32[address]under user mode |
STRT | 用户模式下将存储器中的字保存到寄存器 | Rd←mem32[address] ]under user mode |
LDRSB | 把一个有符号字节装入一个寄存器 | Rd←sign{mem8[address]} |
LDRSH | 把一个有符号半字装入一个寄存器 | Rd←sign{mem16[address]} |
LDR指令用于从内存中将一个32位的字读取到目标寄存器。
LDR{
LDR R1,[R0,#0x12] ;将R0+12地址处的数据读出,保存到R1中(R0的值不变)
LDR R1,[R0] ;将R0地址处的数据读出,保存到R1中(零偏移)
LDR R1,[R0,R2] ;将R0+R2地址的数据读出,保存到R1中(R0的值不变)
LDR R1,[R0,R2,LSL #2] ;将R0+R2×4地址处的数据读出,保存到R1中(R0、R2的值不变)
LDR Rd,label ;label为程序标号,label必须是当前指令的-4~4KB范围内
LDR Rd,[Rn],#0x04 ;Rn的值用做传输数据的存储地址。在数据传送后,将偏移量0x04与Rn相加,结果写回到Rn中。Rn不允许是R15
STR指令用于将一个32位的字数据写入到指令中指定的内存单元。
STR{
NumCount .equ 0x40003000 ;定义变量NumCount
LDR R0,=NumCount ;使用LDR伪指令装载NumCount的地址到R0
LDR R1,[R0] ;取出变量值
ADD R1,R1,#1 ;NumCount=NumCount+1
STR R1,[R0] ;保存变量
GPIO—BASE .equ 0xe0028000 ;定义GPIO寄存器的基地址
…
LDR R0,=GPIO—BASE
LDR R1,=0x00ffff00 ;将设置值放入寄存器
STR R1,[R0,#0x0C] ;IODIR=0x00ffff00,IOSET的地址为0xE0028004
…
MOV R2,R2,LSL #2 ;功能号乘以4,以便查表
LDR PC,[PC,R2] ;查表取得对应功能子程序地址并跳转
NOP
FUN—TAB .word FUN—SUB0
.word FUN—SUB1
.word FUN—SUB2
…
LDRB指令根据addr_mode所确定的地址模式将一个8位字节读取到指令中的目标寄存器Rd。
指令的语法格式:
LDR{
STRB指令从寄存器中取出指定的8位字节放入寄存器的低8位,并将寄存器的高位补0。指令的语法格式:
STR{
LDRH指令用于从内存中将一个16位的半字读取到目标寄存器。
如果指令的内存地址不是半字节对齐的,指令的执行结果不可预知。
指令的语法格式:
LDR{
STRH指令从寄存器中取出指定的16位半字放入寄存器的低16位,并将寄存器的高位补0。
指令的语法格式:
STR{
多寄存器的Load/Store内存访问指令也叫批量加载/存储指令,它可以实现在一组寄存器和一块连续的内存单元之间传送数据。LDM用于加载多个寄存器,STM用于存储多个寄存器。多寄存器的Load/Store内存访问指令允许一条指令传送16个寄存器的任何子集或所有寄存器。多寄存器的Load/Store内存访问指令主要用于现场保护、数据复制和参数传递等。如表所示列出了多寄存器的Load/Store内存访问指令。
表 多寄存器的Load/Store内存访问指令
指 令 | 作 用 | 操 作 |
LDM | 装载多个寄存器 | {Rd}*N←mem32[start address+4*N] |
STM | 保存多个寄存器 | {Rd}*N→mem32[start address+4*N] |
LDM指令将数据从连续的内存单元中读取到指令中指定的寄存器列表中的各寄存器中。当PC包含在LDM指令的寄存器列表中时,指令从内存中读取的字数据将被作为目标地址值,指令执行后程序将从目标地址处开始执行,从而实现了指令的跳转。
指令的语法格式:
LDM{
寄存器R0~R15分别对应于指令编码中bit[0]~bit[15]位。如果Ri存在于寄存器列表中,则相应的位等于1,否则为0。LDM指令将数据从连续的内存单元中读取到指令中指定的寄存器列表中的各寄存器中。
指令的语法格式:
LDM{
STM指令将指令中寄存器列表中的各寄存器数值写入到连续的内存单元中。主要用于块数据的写入、数据栈操作及进入子程序时保存相关寄存器的操作。
指令的语法格式:
STM{
LDM/STM批量加载/存储指令可以实现在一组寄存器和一块连续的内存单元之间传输数据。LDM为加载多个寄存器,STM为存储多个寄存器。允许一条指令传送16个寄存器的任何子集或所有寄存器。指令格式如下:
LDM{cond}<模式> Rn{!},regist{ˆ}
STM{cond}<模式> 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时,加载/存储的是用户模式寄存器,而不是当前模式寄存器。
LDMIA R0!,{R3~R9} ;加载R0指向的地址上的多字数据,保存到R3~R9中,R0值更新
STMIA R1!,{R3~R9} ;将R3~R9的数据存储到R1指向的地址上,R1值更新
STMFD SP!,{R0~R7,LR} ;现场保存,将R0~R7、LR入栈
LDMFD SP!,{R0~R7,PC}ˆ ;恢复现场,异常处理返回
在进行数据复制时,先设置好源数据指针,然后使用块复制寻址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB/STMDB进行读取和存储。而进行堆栈操作时,则要先设置堆栈指针,一般使用SP然后使用堆栈寻址指令STMFD/LDMFD、STMED/LDMED、STMEA/LDMEA实现堆栈操作。数据是存储在基址寄存器的地址之上还是之下,地址是存储第一个值之前还是之后、增加还是减少,如表所示。
向 上 生 长 | 向 下 生 长 | ||||
满 | 空 | 满 | 空 | ||
增加 | 之前 | STMIB | LDMIB | ||
STMFA | LDMED | ||||
之后 | STMIA | LDMIA | |||
STMEA | LDMFD | ||||
增加 | 之前 | LDMDB | STMDB | ||
LDMEA | STMFD | ||||
之后 | LDMDA | STMDA | |||
LDMFA | STMED |
【举例】 使用LDM/STM进行数据复制。
LDR R0,=SrcData ;设置源数据地址
LDR R1,=DstData ;设置目标地址
LDMIA R0,{R2~R9} ;加载8字数据到寄存器R2~R9
STMIA R1,{R2~R9} ;存储寄存器R2~R9到目标地址
【举例】 使用LDM/STM进行现场寄存器保护,常在子程序或异常处理使用。
SENDBYTE:
STMFD SP!,{R0~R7,LR} ;寄存器压栈保护
…
BL DELAY ;调用DELAY子程序
…
LDMFD SP!,{R0~R7,PC} ;恢复寄存器,并返回
交换指令是Load/Store指令的一种特例,它把一个寄存器单元的内容与寄存器内容交换。交换指令是一个原子操作(Atomic Operation),也就是说,在连续的总线操作中读/写一个存储单元,在操作期间阻止其他任何指令对该存储单元的读/写。交换指令如表所示。
指 令 | 作 用 | 操 作 |
SWP | 字交换 | tmp=men32[Rn]mem32[Rn]=RmRd=tmp |
SWPB | 字节交换 | tmp=men8[Rn]mem8[Rn]=RmRd=tmp |
SWP指令用于将内存中的一个字单元和一个指定寄存器的值相交换。操作过程如下:假设内存单元地址存放在寄存器
当
指令的语法格式:
SWP{
SWPB指令用于将内存中的一个字节单元和一个指定寄存器的低8位值相交换,操作过程如下:假设内存单元地址存放在寄存器
指令的语法格式:
SWP{
SWP指令用于将一个内存单元(该单元地址放在寄存器Rn中)的内容读取到一个寄存器Rd中,同时将另一个寄存器Rm的内容写到该内存单元中,使用SWP可实现信号量操作。
指令的语法格式:
SWP{cond}B Rd,Rm,[Rn]
其中,B为可选后缀,若有B,则交换字节;否则交换32位字。Rd为目的寄存器,存储从存储器中加载的数据,同时,Rm中的数据将会被存储到存储器中。若Rm与Rn相同,则为寄存器与存储器内容进行交换。Rn为要进行数据交换的存储器地址,Rn不能与Rd和Rm相同。
SWP指令举例:
SWP R1,R1,[R0] ;将R1的内容与R0指向的存储单元内容进行交换
SWPB R1,R2,[R0] ;将R0指向的存储单元内容读取一字节数据到R1中(高24位清零), 并将R2的内容 写入到该内存单元中(最低字节有效),使用SWP指令可以方便地进行信号量操作
12C_SEM .equ 0x40003000
…
12C_SEM_WAIT:
MOV R0,#0
LDR R0,=12C_SEM
SWP R1,R1,[R0] ;取出信号量,并将其设为0
CMP R1,#0 ;判断是否有信号
BEQ 12C_SEM_WAIT ;若没有信号则等待
跳转(B)和跳转连接(BL)指令是改变指令执行顺序的标准方式。ARM一般按照字地址顺序执行指令,需要时使用条件执行跳过某段指令。只要程序必须偏离顺序执行,就要使用控制流指令来修改程序计数器。尽管在特定情况下还有其他几种方式实现这个目的,但转移和转移连接指令是标准的方式。跳转指令改变程序的执行流程或者调用子程序。这种指令使得一个程序可以使用子程序、if-then-else结构及循环。执行流程的改变迫使程序计数器(PC)指向一个新的地址,ARMv5架构指令集包含的跳转指令如表所示。
助 记 符 | 说 明 | 操 作 |
B | 跳转指令 | pc←label |
BL | 带返回的连接跳转 | pc←label(lr←BL后面的第一条指令) |
BX | 跳转并切换状态 | pc←Rm&0xfffffffe, T←Rm&1 |
BLX | 带返回的跳转并切换状态 | pc←lable, T←1pc←Rm&0xfffffffe, T←Rm&1lr←BL后面的第一条指令 |
另一种实现指令跳转的方式是通过直接向PC寄存器中写入目标地址值,实现在4GB地址空间中任意跳转,这种跳转指令又称为长跳转。如果在长跳转指令之前使用“MOV LR”或“MOV PC”等指令,可以保存将来返回的地址值,也就实现了在4GB的地址空间中的子程序调用。
跳转指令B使程序跳转到指定的地址执行程序。带连接的跳转指令BL将下一条指令的地址复制到R14(即返回地址连接寄存器LR)寄存器中,然后跳转到指定地址运行程序。需要注意的是,这两条指令和目标地址处的指令都要属于ARM指令集。两条指令都可以根据CPSR中的条件标志位的值决定指令是否执行。
B{L}{
BL指令用于实现子程序调用。子程序的返回可以通过将LR寄存器的值复制到PC寄存器来实现。下面3种指令可以实现子程序返回。
STMFD R13!,{
可以使用指令:
LDMFD R13!,{
将子程序返回地址放入PC中。
ARM汇编器通过以下步骤计算指令编码中的signed_immed_24。
B LABLE ;
ADD R1,R2,#4
ADD R3,R2,#8
SUB R3,R3,R1
LABLE:
SUB R1,R2,#8
B 0x1234
BL func
BCC LABLE
LOOP:
ADD R1,R2,#4
ADD R3,R2,#8
SUB R3,R3,R1
B LOOP
MOV R0,#10
LOOP:
SUBS R0,#1
BNE LOOP
…
CMP R0,#5 ;如果R0<5
BLLT SUB1 ;则调用
BLGE SUB2 ;否则调用SUB2
带状态切换的跳转指令(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{
MOV PC, PC
或
ADD PC, PC, #0
BX R0;
ADRL R0,ThumbFun+1 ;
BX R0;
带连接和状态切换的跳转指令(Branch with Link Exchange,BLX)使用标号,用于使程序跳转到Thumb状态或从Thumb状态返回。该指令为无条件执行指令,并用分支寄存器的最低位来更新CPSR中的T位,将返回地址写入到连接寄存器LR中。
BLX
其中,
计算偏移量的工作一般由ARM汇编器来完成。这种形式的跳转指令只能实现−32~32MB空间的跳转。左移两位形成字偏移量,然后将其累加进程序计数器(PC)中。这时,程序计数器的内容为BX指令地址加8字节。位H(bit[24])也加到结果地址的第一位(bit[1]),使目标地址成为半字地址,以执行接下来的Thumb指令。计算偏移量的工作一般由ARM汇编器来完成。这种形式的跳转指令只能实现−32~32MB空间的跳转。
BX R14
PUSH {
POP {
ARM指令集提供了两条指令,可直接控制程序状态寄存器(Program State Register,PSR)。MRS指令用于把CPSR或SPSR的值传送到一个寄存器;MSR与之相反,把一个寄存器的内容传送到CPSR或SPSR。这两条指令相结合,可用于对CPSR和SPSR进行读/写操作。程序状态寄存器指令如表所示。
指 令 | 作 用 | 操 作 |
MRS | 把程序状态寄存器的值送到一个通用寄存器 | Rd=SPR |
MSR | 把通用寄存器的值送到程序状态寄存器或把一个立即数送到程序状态字 | PSR[field]=Rm或PSR[field]=immediate |
在指令语法中可看到一个称为fields的项,它可以是控制(C)、扩展(X)、状态(S)及标志(F)的组合。
MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。
在ARM处理器中,只有MRS指令可以将状态寄存器CPSR或SPSR读出到通用寄存器中。
MRS{cond} Rd,PSR
其中,Rd为目标寄存器,Rd不允许为程序计数器(PC)。PSR为CPSR或SPSR。
MRS R1,CPSR ;将CPSR状态寄存器读取,保存到R1中
MRS R2,SPSR ;将SPSR状态寄存器读取,保存到R1中
MRS指令读取CPSR,可用来判断ALU的状态标志及IRQ/FIQ中断是否允许等;在异常处理程序中,读SPSR可指定进入异常前的处理器状态等。MRS与MSR配合使用,实现CPSR或SPSR寄存器的读—修改—写操作,可用来进行处理器模式切换,允许/禁止IRQ/FIQ中断等设置。另外,进程切换或允许异常中断嵌套时,也需要使用MRS指令读取SPSR状态值并保存起来。
在ARM处理器中,只有MSR指令可以直接设置状态寄存器CPSR或SPSR。
MSR{cond} PSR_field,#immed_8r
MSR{cond} PSR_field,Rm
其中,PSR是指CPSR或SPSR。
MSR CPSR_c,#0xD3 ;CPSR[7:0]=0xD3,切换到管理模式
MSR CPSR_cxsf,R3 ;CPSR=R3
注意:
只有在特权模式下才能修改状态寄存器。
程序中不能通过MSR指令直接修改CPSR中的T位控制位来实现ARM状态/Thumb状态的切换,必须使用BX指令来完成处理器状态的切换(因为BX指令属转移指令,它会打断流水线状态,实现处理器状态的切换)。MRS与MSR配合使用,实现CPSR或SPSR寄存器的读—修改—写操作,可用来进行处理器模式切换及允许/禁止IRQ/FIQ中断等设置。
【举例】 使能IRQ中断。
ENABLE_IRQ:
MRS R0,CPSR
BIC R0,R0,#0x80
MSR CPSR_c,R0
MOV PC,LR
【举例】 禁止IRQ中断。
DISABLE_IRQ:
MRS R0,CPSR
ORR R0,R0,#0x80
MSR CPSR_c,R0
MOV PC,LR
【举例】 堆栈指令初始化。
INITSTACK:
MOV R0,LR ;保存返回地址
设置管理模式堆栈:
MSR CPSR_c,#0xD3
LDR SP,StackSvc
设置中断模式堆栈:
MSR CPSR_c,#0xD2
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类。
如表所示列出了所有协处理器处理指令。
助 记 符 | 操 作 |
CDP | 协处理器数据操作 |
LDC | 装载协处理器寄存器 |
MCR | 从ARM寄存器传数据到协处理器寄存器 |
MRC | 从协处理器寄存器传数据到ARM寄存器 |
STC | 存储协处理器寄存器 |
下面简单介绍一下比较常用的MCR及MRC命令的用法:
ARM寄存器到协处理器寄存器的数据传送指令MCR(Move to Coprocessor from ARM Register)将ARM寄存器
MCR{
①
为指令编码中的条件域。它指示指令在什么条件下执行。当
②
指定协处理器的编号,标准的协处理器的名字为p0、p1、…、p15。
③
指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。
④
确定哪一个ARM寄存器的数值将被传送。如果程序计数器PC的值被传送,指令的执行结果不可预知。
⑤
确定包含第一个操作数的协处理器寄存器。
⑥
确定包含第二个操作数的协处理器寄存器。
⑦
指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。通常与
将ARM寄存器r7中的值传送到协处理器p14的寄存器c7中,第一操作数opcode_1=1,第二操作数opcode_2=6。
MCR p14,1,r7,c7,c12 ,6
指令的编码格式中,bits[31∶24]、bit[20]、bits[15∶8]和bit[4]为ARM体系结构定义。其他域由各生产商定义。硬件协处理器支持与否完全由生产商定义,某款ARM芯片中,是否支持协处理器或支持哪个协处理器与ARM版本无关。生产商可以选择实现部分协处理器指令或者完全不支持协处理器。
协处理器寄存器到ARM寄存器的数据传送指令MRC(Move to ARM register from Coprocessor)将协处理器cp_num 的寄存器的值传送到ARM寄存器中。如果没有协处理器执行指定操作,将产生未定义指令异常。指令的编码格式如图所示。
MRC{
为指令编码中的条件域。它指示指令在什么条件下执行。当
①
指定协处理器的编号,标准的协处理器的名字为p0、p1、…、p15。
②
指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。
③
确定哪一个ARM寄存器接受协处理器传送的数值。如果程序计数器PC被用做目的寄存器,指令的执行结果不可预知 。
④
确定包含第一个操作数的协处理器寄存器。
⑤
确定包含第二个操作数的协处理器寄存器。
⑥
指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。通常与
协处理器源寄存器为c0和c2,目的寄存器为ARM寄存器r4,第一操作数opcode_1=5,第二操作数opcode_2=3。
MRC p15,5,r4,c0,c2,3
如果目的寄存器为程序计数器r15,则程序状态字条件标准位根据传送数据的前4bit确定,后28bit被忽略。指令的编码格式中,bits[31:24]、bit[20]、bits[15:8]和bit[4]为ARM体系结构定义。其他域由各生产商定义。
硬件协处理器支持与否完全由生产商定义,某款ARM芯片中,是否支持协处理器或支持哪个协处理器与ARM版本无关。生产商可以选择实现部分协处理器指令或者完全不支持协处理器。
如果协处理器必须完成一些内部工作来准备一个32位数据向ARM传送(例如,浮点FIX操作必须将浮点值转换为等效的定点值),那么这些工作必须在协处理器提交传送前进行。因此,在准备数据时经常需要协处理器握手信号处于“忙-等待”状态。ARM可以在忙-等待时间内产生中断。如果它确实得以中断,那么它将暂停握手以服务中断。当它从中断服务程序返回时,将可能重试协处理器指令,但也可能不重试。例如,中断可能导致任务切换,无论哪种情况,协处理器必须给出一致结果,因此,在握手提交阶段之前的准备工作不允许改变处理器的可见状态。
如图所示列出了cp15的各个寄存器的目的。
ARM指令集中提供了两条产生异常的指令,通过这两条指令可以用软件的方法实现异常。如表所示为ARM异常产生指令。
助 记 符 | 含 义 | 操 作 |
SWI | 软中断指令 | 产生软中断,处理器进入管理模式 |
BKPT | 断点中断指令 | 处理器产生软件断点 |
软件中断指令(Software Interrupt,SWI)用于产生软中断,从而实现从用户模式变换到管理模式,CPSR保存到管理模式的SPSR中,执行转移到SWI向量,在其他模式下也可以使用SWI指令,处理器同样切换到管理模式。
SWI{
① 下面指令产生软中断,中断立即数为0。
SWI 0;
② 产生软中断,中断立即数为0x123456。
SWI 0x123456;
③ 使用SWI指令时,通常使用以下两种方法进行参数传递。
a.指令24位的立即数指定了用户请求的类型,中断服务程序的参数通过寄存器传递。
下面的程序产生一个中断号为12的软中断。
MOV R0,#34 ;设置功能号为34
SWI 12 ;产生软中断,中断号为12
b.另一种情况,指令中的24位立即数被忽略,用户请求的服务类型由寄存器R0的值决定,参数通过其他寄存器传递。
下面的例子通过R0传递中断号,R1传递中断的子功能号。
MOV R0,#12 ;设置12号软中断
MOV R1,#34 ;设置功能号为34
SWI 0
Fmxr /Fmrx指令是NEON下的扩展指令,在做浮点运算的时候,要先打开vfp,因此需要用到Fmxr指令。
Fmxr:由arm寄存器将数据转移到协处理器中。
Fmrx:由协处理器转移到arm寄存器中。
如图所示为浮点异常寄存器格式。
如表所示为FPEXC的位定义。
位 | 域 | 功能描述 |
[31] | EX | 异常位,该位指定了有多少信息需要存储记录SIMD/VFP协处理器的状态 |
[30] | EN | NEON/VFP使能位,设置EN位1则开启NEON/VFP协处理器,复位会将EN置0 |
[29:0] | 保留 |
FPEXC<浮点异常寄存器>,该寄存器是一个可控制SIMD及VFP的全局使能寄存器,并指定了这些扩展技术是如何记录的。
如果要打开VFP协处理器的话,可以用以下指令:
mov r0, #0x40000000
fmxr fpexc, r0 @ enable NEON and VFP coprocessor
CLZ {cond} Rd,Rm
其中:
这是用来设计饱和算法的一组指令,所谓饱和是指出现下列3种情况:
只要出现这情况,就称为饱和,并且饱和指令会设置Q标记,下面简单介绍一下QADD带符号加法。
QSUB:带符号减法。
QDADD:带符号加倍加法。
QDSUB:带符号加倍减法。
将结果饱和导入符号范围(-231≤x ≤231-1)内。
op{cond} {Rd} ,Rm,Rn
其中:
QADD r0 ,r1,r9
QSUBLT r9,r0,r1
根据上面阐述RAM 汇编语言的使用语法和功能,编写汇编程序,实现一个简单的数据运算操作。
汇编程序设计如下
.text
.global _start
_start:
mov r0, #0x9
nop
mov r1, #0x7
loop:
bl add_sub
stop:
b stop
add_sub:
add r2, r0, r1
sub r3, r0, r1
mul r4, r0,r1
mov pc, lr
请参考第37章的导入已有工程章节。
光盘实验源码路径:【资料光盘\华清远见-FS-MP1A开发资料-2020-11-06\02-程序源码\03-ARM体系结构与接口技术\Cortex-A7\h_project】
单击window -> show view -> Register,
配置完成之后,点击“
”开始仿真,弹出Debug框。
单击“
”单步仿真。查看仿真现象。
”单步,查看Rn寄存器的变化。
单步运行可以看到R2、R3和R4的值变化。
硬件平台:华清远见FS-MP1A开发板(STM32MP157)
部分开发教程下载:加QQ群483143191,群文件里有。
部分视频课程收看:华清远见研发中心的个人空间_哔哩哔哩_Bilibili
淘宝购买链接:https://item.taobao.com/item.htm?id=622457259672
手机淘宝分享码:复制本行文字打开手淘₤T4FPXn3YYJ2₤