购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 ARM指令集的类型

一般情况下,ARM指令集是加载/存储(Load/Store)类型的,用户只能通过加载/存储(Load/Store)指令来实现对系统存储器的访问,而其他类型的指令则是基于处理器内部的寄存器操作完成的。

ARM指令集按照指令的作用大体可以分为以下几个部分。

● 跳转指令:用于控制程序的执行流程、指令的特权等级,以及在ARM代码模式和Thumb代码模式之间的切换。

● 算术运算指令:用于进行乘法、加法、减法等算术运算。

● 逻辑运算指令:用于对操作数进行逻辑操作,如“与”操作、“或”操作,以及“非”操作等。

● 存储器访问指令:用于控制存储器和寄存器之间的数据传送。

● 数据传送指令:用于操作片上ALU、桶形移位器、乘法器,以及完成通用寄存器之间的高速数据处理。

● 协处理器指令:用于控制协处理器的操作。

● 异常产生指令:用于控制软件的异常。

下面将详细介绍ARM指令的具体格式、应用,以及在实际工程应用中需要注意的问题。

2.3.1 跳转指令

在ARM嵌入式代码中,很多场合都需要程序能够实现跳转功能。例如,分支语句(根据不同的运行结果选择不同的执行语句代码)、循环语句(重复执行某一个程序段代码时,当处理器运行到当前程序段的最后一条语句时)等。

最为典型的需要实现跳转的例子就是嵌入式代码中实现子函数的调用。无论是在C语言代码还是汇编代码中,当主程序需要调用子函数时,程序需要能够跳转到对应子函数的入口地址。否则,如果代码不能及时实现跳转功能或者跳转到一个错误的入口地址,那么程序就会出现不可预期的错误,甚至出现程序的“跑飞”。

在ARM指令集中也有这种跳转指令,用户可以通过这些跳转指令实现如下所述的功能:

● 实现程序代码的跳转;

● 在程序跳转前保存当前指令的下一条指令的地址,作为程序返回的标记;

● 实现ARM处理器工作模式的切换,即由ARM工作模式跳转到Thumb工作模式。

通常情况下,用户就是通过这些跳转指令来实现对ARM/Thumb指令集的操作的,并充分发挥各个指令集的优点。

在ARM指令中,有两种实现程序跳转的方法:一种是这里介绍的转移指令,如表2.6所示;还有一种是通过数据传送指令直接向PC寄存器(R15)中写入需要转移的目标地址值,即通过改变PC(程序计数器)的值来实现ARM代码的跳转。

通常情况下,特别是对于刚刚接触ARM嵌入式代码的用户,不建议使用第二种方法来实现程序的跳转。因为当用户在对程序结构代码不是很清晰的前提下,修改PC寄存器的数值可能会带来意想不到的错误。用户可以方便地使用ARM指令集中的跳转指令来实现对代码的转移操作。

表2.6 ARM指令中的转移指令

1.转移指令B

转移指令B在程序中用于完成简单的跳转指令,跳转到程序代码中某处指定的目的地址。在ARM嵌入式代码中,经常会需要从主程序跳转到子程序,并且同时要求当子程序执行结束后能够正确无误地返回发生代码跳转的位置。

为了实现上述目标,在绝大部分ARM嵌入式系统中,在代码进行跳转时会预先将执行跳转命令前程序计数器PC中的数值保存下来。在嵌入式ARM代码中,主要通过转移指令B来实现这个功能,即转移指令B除了用于完成指令的转移以外还用于保存数值。如果用户希望在进行程序跳转的同时还将发生转移指令的下一条指令的地址保存到链接寄存器LR(R14),则可以选择使用BL转移指令。

在ARM汇编语言中,转移指令B的一般形式为:

其中,LABEL为子函数或跳转地址的标号;[ADDR]为程序跳转的目标地址;{cond}为指令执行的条件。转移指令B的指令编码格式如图2.4所示。

图2.4 跳转指令B的编码格式

从转移指令B的编码格式中可以看出,该指令编码中除了执行条件cond、操作符代码opcode外,还包含了一个24位的有符号的偏移量。该偏移量主要用于计算当前跳转指令的目标地址。具体跳转目标地址的计算方法可以分为下面3个步骤:

(1)将24位的有符号偏移量扩展为有符号的32位偏移量;

(2)将扩展后的有符号32位偏移量左移2位,即乘以4;

(3)将左移2位后的偏移量与当前PC值相加,得到的结果就是转移指令的目标地址。

根据上述有关跳转目标地址的计算方法可知,有符号的24位偏移量(二进制)可以表示的最大数为2 24 -1,用字节表示该最大偏移量为32MB。因此,对于转移指令B而言,跳转地址应当在当前指令的32MB空间的范围。如果跳转目标地址超出了这个范围,将产生不可预期的错误。用户可以通过下面两个例子来对转移指令B有更多的了解。

一般情况下,在ARM指令系统中,很少建议用户单独使用转移指令B,主要是因为在ARM指令系统中,转移指令B只能进行指令的转移,而不能实现对当前代码地址的保存。因此用户通过转移指令B转移到子函数目标地址后,由于未对转移前的地址进行保存,所以在执行完子函数代码后无法返回。因此,转移指令B常被用于一些不需要返回的转移操作中,如分支选择语句等。

2.转移链接指令BL

为了解决转移指令B不能存储转移地址的的缺陷,在ARM嵌入式指令系统中还可以使用带有链接功能的转移指令BL。

BL转移指令除了可以实现与B转移指令一样的程序转移功能外,还可以在转移前将下一条指令的地址存储到返回地址链接寄存器LR(R14)中,用于转移程序的返回。由于BL转移指令的这个特性,使得该指令经常被用在实现子程序的调用中,类似于C程序代码中的子函数。需要注意的是,处理器在执行转移指令的时候并不保存状态寄存器PSR的状态。

在ARM汇编语言中,BL转移指令的一般形式如下所示:

其中,LABEL为子函数或转移地址的标号;[ADDR]为程序转移的目标地址;{cond}为指令执行的条件。当{cond}为默认时,则表示当前指令为无条件执行。该指令所对应的指令编码格式如图2.5所示。

图2.5 转移链接指令BL的编码格式

从指令的编码格式可以看出,转移链接指令BL也包含了一个24位的有符号立即数,即为转移地址的偏移量。有关转移地址与24位偏移量之间的关系及计算方法,与B指令中转移地址的计算方法一样,这里就不重复说明了。

用户可以通过下面这段代码来熟悉BL转移指令的具体使用方法。

在上述这段代码中,指令BL NEXT用于将主程序跳转到子程序的入口处,同时保存当前的PC值(程序地址指针寄存器)到寄存器R14中。

子程序中的最后一条指令MOV PC,R14用于将保存在寄存器R14中的地址恢复到PC(程序地址指针寄存器)中,使得程序在执行完子程序后从此处返回。

在使用BL转移指令时,用户不难发现,寄存器R14的主要作用就是保存当前转移指令的地址。在一些复杂的嵌入式代码中,除了需要使用BL转移指令来保存跳转地址外,还需要用户自行对R14寄存器中的内容进行保存,否则代码会出现逻辑上的错误。

例如,在递归调用程序中,子程序代码会继续循环调用子程序。在这种情况下,用户完全可以使用BL转移指令来实现子程序的调用。但需要注意的是,在这种情况下,用户必须自行对R14寄存器中的数值进行保存。否则,当程序下一次循环调用子程序时,R14寄存器中的内容会被新的返回地址覆盖。因此,对于在子程序中调用子程序的代码,除了需要保存当前的返回地址外,还需要保存上一次跳转(父节点)所保存的返回地址。

通常情况下,建议用户在调用子程序时,需要将程序的返回地址、变量值等推送(保存)到堆栈中,也称压栈,如图2.6所示。因此,用户代码中每调用一次子程序,系统就会消耗一定的堆栈空间,即消耗一定的系统资源,所以,在一些系统资源比较有限的ARM处理器中,用户在编写嵌入式代码时需要严格控制循环调用子程序的次数和层数。

图2.6 数据堆栈的压栈

3.带状态切换的转移指令BX

在前面的章节中,已经向用户介绍过ARM处理器具有两种不同的工作模式,即ARM模式和Thumb模式。用户可以根据不同的应用需求在两种模式间进行相互切换。同样,在转移指令中也支持带状态切换的转移指令BX。

在ARM指令集中,带状态切换的转移指令BX主要用于将32位的ARM状态切换到16位的Thumb状态。需要注意的是,在Thumb指令中,同样也有类似的状态切换跳转指令,用于将16位的Thumb状态切换到32位的ARM状态。

BX指令是带状态切换的跳转指令,其基本语法格式为:

其中,{[cond]}为指令执行的判决条件;[Rm]为ARM中的通用寄存器名。

BX转移指令根据寄存器Rm中的数值来决定跳转的目标地址,以及是否对ARM处理器进行模式状态上的切换,即由ARM状态切换到Thumb状态。

图2.7为带状态切换的转移指令BX的编码格式。显然BX指令的编码格式与前面介绍的B指令及BL指令格式有着比较大的差异。

图2.7 带状态切换的转移指令BX的编码格式

其中,寄存器Rm中的值是跳转目标。在执行BX指令时,ARM处理器自动将Rm寄存器中的最低位Rm[0]复制到CPSR中的T标志位,用于决定是否将32位的ARM模式切换到16位的Thumb模式;而剩余的31位数据[31:1]将被复制到程序地址指针寄存器PC中。

● 当寄存器Rm的最低位Rm[0]为1时,ARM处理器在跳转时自动将CPSR状态寄存器中的标志位T(状态位)设置为1,即跳转后ARM处理器将工作在Thumb模式下,并在Rm寄存器中的地址处开始执行。

● 当寄存器Rm的最低位Rm[0]为0时,ARM处理器在跳转时自动将CPSR状态寄存器中的标志位T(状态位)设置为0,即跳转后ARM处理器不进行状态的切换,继续工作在ARM模式下,同时在寄存器Rm中的地址处开始执行。

用户可以通过下面两个例子对BX指令的使用方法做进一步了解。

【例1】使用BX指令实现跳转并切换工作模式。

在上述两行代码中,使用BX指令实现程序的跳转。在第一行代码中,将立即数#0x0201赋值给寄存器R0,即R0中的最低位R0[0]=1。根据BX指令的功能描述,ARM处理器会自动跳转到地址0x0200处,同时将处理器切换到Thumb模式。

【例2】使用BX指令实现跳转并保持原有工作模式。

在【例2】中,虽然同样使用了BX指令来实现与【例1】几乎同样的功能,但运行的结果并非一致。

在第一行代码中,由于立即数#0x0202赋值给寄存器R0后,其最低位R0[0]=0。根据BX指令的功能描述,处理器将继续工作在ARM状态。

这里要提醒用户注意的是,【例2】中的立即数,即跳转地址#0x0202,并不是4字节对齐的,即不是4的整数倍,而ARM工作状态下的指令都是32位的,指令地址必须都是4字节对齐的。因此,【例2】中的指令在实际运行过程中可能会产生一个不可预知的后果,或者用户也可以认为在执行BX跳转指令时,Rm寄存器的最低两位Rm[1:0]不能为0b10,否则在执行该转移指令后会遇到不可预期的结果。

4.带链接和状态切换的转移指令BLX

与B转移指令类似,BX指令虽然能够实现处理器不同工作模式之间的切换,但还是不能实现子函数调用后返回地址保存的问题。或者可以认为,BX指令只能用于无返回类型的程序跳转,如分支选择语句等。

为了能在ARM代码中实现子函数的调用和返回,并且同时能实现处理器不同工作模式之间的自由切换,ARM指令集在上述3个转移指令的基础上支持了既能实现状态切换,也能实现返回地址保存的转移指令,即BLX指令。

从功能定义上看,BLX转移指令是上述B指令、BL指令及BX指令功能的结合。其有关状态切换功能,以及链接转移的功能和前面几个指令的功能是一样的。

在ARM汇编代码中,BLX指令的语法格式有如下两种。

其中,<targetaddress>为转移指令的目标地址;Rm为目标地址寄存器。与BX指令一样,该指令的第一种语法格式BLX Rm的二进制编码与BX指令的编码格式是一样的,如图2.7所示。

而在第二种语法格式BLX <targetaddress>中,其编码格式如图2.8所示。从图中可以看出,除了编码指令外,同样也包含了一个带符号的24位的立即数来作为跳转目标地址的偏移量。

图2.8 带链接和状态切换的转移指令BLX的编码格式

BLX指令从ARM指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM状态切换到Thumb状态,该指令同时将PC的当前内容保存到寄存器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令来实现子程序的调用和处理器工作状态的切换。同时,子程序的返回可以通过将寄存器R14值复制到PC中来完成。

在第二种语法格式中,转移指令进行跳转的目标地址的计算方法如下:

(1)将24位的有符号偏移量扩展为有符号的32位偏移量;

(2)将扩展后的有符号32位偏移量左移2位,即乘以2;

(3)将左移2位后的偏移量与当前PC值相加,并且将第24位(H bit)也加到目标地址的最低位,得到的结果就是跳转指令的目标地址。

根据上述跳转指令目标地址的计算方法,同样可以计算得到BLX指令的跳转地址范围为±2MB。用户可以通过下面的例子对BLX指令做进一步了解。

【例3】使用BLX和BX指令实现子程序的调用及处理器工作模式的切换。

通常情况下,BLX转移指令主要用于ARM指令调用Thumb子程序时保存返回地址,并实现程序调用和处理器状态的切换,即如本例中的代码所示。如果在程序中用户使用BX指令作为子程序的返回机制,则调用程序的指令集状态(ARM工作模式状态或Thumb工作模式状态)能连同返回地址一起被保存。因此,在本例代码TSUB子程序的末尾,采用了BX指令实现跳转返回,即在此处将返回到调用子程序之前的ARM工作模式状态。

2.3.2 算术运算指令

对于处理器而言,最初设计的根本目的在于算术运算。即使在当前能实现各种多媒体处理的情况下,算术运算仍然是ARM处理器中必不可少的功能,也是评价ARM处理器性能的一个重要指标。

通常而言,在ARM处理器中,算术运算指令主要包含了加、减、乘、累乘等操作,具体指令功能的说明及指令代码如表2.7所示。

表2.7 ARM指令集中的算术运算指令

在下面的内容中,将通过具体的实例代码介绍表2.7中各个算术运算指令的基本使用方法。

1.不带进位的加法指令ADD

在嵌入式汇编语言中,不带进位的加法指令ADD的通用格式为:

在该指令格式中,主要实现将数据[operand2]与[Rn]中的值相加,运算的结果被保存到[Rd]寄存器中。与其他指令的使用说明类似,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例4】使用ADD指令实现不带进位的加法操作。

上述3句代码都是不带进位的加法指令ADD的基本用法,包含了寄存器与寄存器、寄存器与立即数,以及寄存器与操作数之间的无进位加法。

第一条指令将寄存器R1与寄存器R2中的数值进行相加(不带进位操作),并将相加后的结果保存到寄存器R0中;

第二条指令将寄存器R2中的数值与立即数112相加(不带进位操作),并将相加后的结果保存到寄存器R0中;

第三条指令先将寄存器R2中的数值左移1位(乘以2),然后再与寄存器R1中的数值相加(不带进位操作),计算的结果将存储到寄存器R0中。

从【例4】中可以看出,ADD指令有多种确定操作数的方式。

(1)第一条指令中,将寄存器R2中的数值作为加法操作数;

(2)第二条指令中,将立即数112作为加法操作数;

(3)第三条指令中,将寄存器R2中的数值左移1位后作为加法操作数。

不仅ADD指令如此,在ARM指令集中还有其他指令,如SUB、MUL等都支持上述3种确定操作数的方式。通常情况下,指令所支持的确定操作数的方法,被统一称为指令的“寻址方式”。

2.带进位的加法指令ADC

在数值计算方法中,加法是最常用的操作运算。虽然用户可以通过ADD指令来实现加法操作,但该指令不能实现进位操作。因此,在ARM指令系统中,同样支持了ADC指令。该指令除了可以实现与ADD一样的加法操作外,还实现了运算进位功能。

在ARM嵌入式程序代码中,带进位加法指令ADC的语法格式为:

该指令将[operand2]中的数据与寄存器[Rn]中的数值相加,再加上CPSR寄存器中的C标志位(进位标志),将计算的结果保存到[Rd]寄存器中。其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

一般情况下,ARM处理器最多只能处理32位的数据。而实际运算的过程中,通常需要处理更大的数据。例如,在ADC采样过程中,为了提高采样结果的准确性,需要对采样数据进行数字滤波,最简单的方法就是多次累加取平均。因此,在这种情况下就会涉及超过32位的数据操作。

【例5】使用ADC指令实现96位带进位的加法操作。其中,96位的加数分别通过3个寄存器来实现,具体分配如图2.9所示。

图2.9 96位带进位的加法操作

在上述代码中,主要通过3条指令实现了96位带进位的加法操作。由于ARM中寄存器的最大宽度只有32位,因此在实现96位带进位的加法操作中,需要将加数、被加数,以及和分别由3个寄存器来存储。其中,第一个加数分别存储在R5、R4和R3寄存器中,第二个加数分别存储在R8、R7和R6寄存器中,和则分别存储在R2、R1和R0寄存器中。

第一条指令实现了加数与被加数最低位32位的加法运算。由于最低位32位的加法计算只需要考虑向前进位,而不需要考虑是否曾有进位标志,即最低位加法无进位,所以在第一条指令中使用了无进位加法指令ADD。但考虑到最低位32位的加法可能存在进位操作,因此需要在无进位加法指令ADD后添加后缀-S,用于在发生进位操作时更新CPSR寄存器,保存计算的进位信息。

同样,第二条指令和第三条指令用于实现次低位32位和高位32位的带进位加法操作。需要注意的是,次低位和高位的加法操作必须要考虑是否存在其低位向高位的进位标志,因此必须使用带进位的ADC加法指令,而不能使用不带进位的ADD加法指令。次低位和高位的基本原理与操作方法与最低位加法操作是一致的,在这里就不再赘述了。

3.不带进位的减法指令SUB

在数学理论中,减法是加法的逆运算。减法运算可以等价于一个正数与一个负数的加法操作。基于此,用户完全可以通过加上一个负数的方式来实现减法操作。但是在ARM指令集中,自带了减法操作指令SUB。

在ARM指令集中,不带进位减法指令SUB的基本语法格式为:

该指令将[operand2]中的数值与寄存器[Rn]中的数值进行相减,计算结果保存到[Rd]寄存器中。其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。用户可以通过下面的例子对不带进位的减法指令SUB做进一步了解。

【例6】使用SUB指令实现不带进位的减法操作。

上述这段代码类似于不带进位的加法操作指令。第一条指令将寄存器R1和R2中的数值相减,并将计算的结果保存到寄存器R0中;第二条指令将寄存器R1中的值减去立即数112,并将计算的结果保存到寄存器R0中;第三条指令首先将寄存器R2中的数值左移1位,然后再与寄存器R1中的值相减,将最后的计算结果保存到寄存器R0中。

4.带进位的减法指令SBC

与带进位的加法指令ADC类似,在减法操作过程中也同样存在着处理进位的问题。在ARM指令系统中,与带进位的加法指令类似,同样也支持了带进位的减法指令SBC。对于减法的进位,即为通常意义上的“借位”标志。

在ARM指令集中,带进位的减法指令的基本语法格式为:

该指令将寄存器[Rn]中的数值减去数值[operand2],再减去CPSR寄存器中的C标志位反码,并将计算结果保存到寄存器[Rd]中。

需要注意的是,带进位的减法指令SBC主要用于超过32位数据的减法运算。其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例7】使用SBC指令实现96位带进位的减法操作。其中,96位的加数分别通过3个寄存器来实现,具体分配如图2.10所示。

图2.10 96位带进位的减法操作

上述3句代码与ADC指令类似。第一条指令实现了最低位32位的不带进位减法运算。由于可能产生借位操作,因此在这里必须使用SUBS指令。同样,第二条指令与第三条指令分别实现了带进位的减法运算。

5.不带进位的逆向减法指令RSB

在ARM指令中,减法操作除了带进位与不带进位的区别外,还有减数与被减数次序的问题。在前面介绍的两种减法操作中,都是将寄存器[Rn]作为被减数,[operand2]作为减数;而在ARM指令集中,还支持了逆向减法指令。该指令之所以被称为逆向减法指令,是因为在该指令操作中,将寄存器[Rn]作为减数,而将[operand2]作为被减数,并将运算结果保存到[Rd]中。

不带进位的逆向减法指令RSB的基本语法格式如下:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例8】使用SUB指令实现不带进位的减法操作。

根据RSB指令的语法定义可知,上述3条指令的被减数分别为寄存器[R2]、立即数112和左移1位后的寄存器[R2];减数均为寄存器[R1]中的数值。

第一条指令将寄存器[R2]中的数值减去寄存器R1中的数值,并将计算结果保存在寄存器[R0]中。

第二条指令将立即数112减去寄存器[R1]中的数值,并将计算结果保存在寄存器[R0]中。

第三条指令先将寄存器[R2]中的数值左移1位(乘以2),然后再减去寄存器[R1]中的数值。

6.带进位的逆向减法指令RSC

与不带进位的逆向减法指令RSB相反,ARM指令集还支持了带进位的逆向减法指令RSC。其基本功能与RSB相似,只存在是否带进位的区别。

带进位逆向减法指令RSC的基本语法格式如下:

在该指令中,将[operand2]作为被减数,而将寄存器[Rn]作为减数,同时减去寄存器CPSR中的C标志位,最后将计算结果保存到寄存器[Rd]中。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例9】使用RSC指令实现96位带进位的减法操作。其中,96bit的减数/被减数分别通过3个寄存器来实现,具体分配如图2.11所示。

图2.11 96位带进位的减法操作

上述3行指令实现了与【例6】同样的功能,即实现了96位的减数/被减数之间的减法。二者不同的是在【例6】中使用的是带进位减法指令SBC实现“从左至右”的顺向减法操作,而在本例中主要使用了带进位逆向减法指令RSC实现“从右向左”的逆向减法操作。

第一条指令将寄存器[R6]中的数值减去寄存器[R3]中的数值,并将计算结果保存在寄存器[R0]中;

第二条指令将寄存器[R7]中的数值减去寄存器[R4]中的数值,并将计算结果保存在寄存器[R1]中;

第三条指令将寄存器[R8]中的数值减去寄存器[R5]中的数值,并将计算结果保存在寄存器[R2]中。

同样,在第一条指令中,由于处理的是最低32位数据的减法,不存在进位操作,因此只需要使用不带进位的逆向减法指令RSB;第二条和第三条指令则实现了中高位带进位的逆向减法。

7.比较指令CMP

在算术运算过程中,不可避免地存在数据的比较操作。例如,在跳转指令中,需要对执行条件进行判断,即进行数据的比较操作。

在ARM指令系统中,支持了通用的比较指令CMP,用来比较两个数的大小,其具体的语法格式如下:

该指令实际上是将寄存器[Rn]中的数值与操作数[operand2]相减,并根据相减计算的结果更新CPSR寄存器中的标志位。其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令。

需要注意的是,CMP指令只进行两个操作数的比较,而并不保存计算(减法操作)的结果。

【例10】使用CMP指令实现寄存器[R1]中的数值与立即数#0xA55A的比较。如果大于立即数,则跳转到LABEL处;如果小于等于,则不进行任何操作。

上述两行代码是某工程代码中的一部分,主要用于实现程序的跳转。第一条指令主要使用CMP指令实现寄存器 [R1]中的数值与立即数#0xA55A的比较。同样需要提醒注意的是,CMP指令只用于比较数据大小,而不保存比较(减法操作)计算的结果。

第二条指令则根据CPSR寄存器中的标志位判断是否需要跳转到标号LABEL处。这里简单地向读者介绍一下有关BGT指令的功能。BGT指令主要与CMP指令配合使用,若比较结果为“大于”,则进行跳转;若比较结果为“小于等于”,则不进行跳转。

本例中的两条代码也可以用C语言代码来描述,且代码更为简单。仅需一条if判断语句即可实现上述同样的功能,具体如下:

8.32位乘法指令MUL

对于普通的单片机处理器而言,乘法计算是比较困难的算术运算。通常情况下,单片机只能通过左移操作来实现乘法计算。每左移1位,相当于将操作数乘以2。

在ARM指令集中,32位乘法计算指令MUL的基本语法格式如下:

该指令主要用于将寄存器[Rm]中的数值作为乘数,寄存器[Rs]中的数值作为被乘数进行乘法运算,并将最终计算的结果保存到寄存器[Rd]中。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例11】使用MUL指令实现寄存器[R1]中的数值与寄存器[R2]中的数值进行相乘,并将乘积存放在寄存器[R0]中。并且分别要求:

(1)不自动更新CPSR寄存器;(2)自动更新CPSR寄存器。

在ARM指令集中,32位的乘法运算相对较为简单。只需要根据MUL指令相应的语法格式对寄存器进行操作就可以。

在上述两行代码中,第一条指令将寄存器[R1]中的数值与寄存器[R2]中的数值进行相乘,并将运算结果存放在寄存器[R0]中。由于不需要自动更新CPSR寄存器中的标志位,MUL指令后无须添加-S后缀。

同样,第二条指令除了实现与第一条指令完全一致的功能外,由于在MUL指令后添加了—-S后缀,因此会自动更新CPSR寄存器中的标志位。

需要说明的是,随着ARM处理器性能的逐步提高,部分ARM嵌入式处理器已经完全实现了乘法操作的瓶颈。对于普通的单片机而言,虽然可以通过汇编指令集中的MUL指令来实现数据的乘法操作,但其本质是通过反复的位移操作及加法组合来实现的,需要较长的处理时钟周期。归根到底在于这些乘法都是通过软件来实现的。因此,一般情况下在单片机中进行乘法和除法运算是不被推荐的。

随着硬件资源的改善和优化,部分高档的处理器中出现了硬件乘法,即单片机中乘法运算不再依赖于移位操作和加法操作,而是由专用的硬件乘法器来实现。例如,本书中向读者介绍的车载ARM嵌入式芯片—STM32F103XX系列处理器。因此,具有极快的运算速度和极高的效率,甚至还出现了部分支持硬件除法的单片机。有兴趣的读者可以查阅最新的ST公司的用户手册,这里就不再赘述了。

9.32位乘法-累加指令MLA

在ARM指令集中,用户可以通过MUL指令来实现不同寄存器中数值的乘法运算。为了进一步简化代码长度,ARM指令集中还支持了有关乘法-累加操作MLA指令。

乘法-累加指令MLA的具体语法格式如下:

该指令主要用于将寄存器[Rm]中的数值作为乘数,将寄存器[Rs]中的数值作为被乘数进行乘法运算,再将乘积与寄存器[Rn]中的数值相加,最后将计算得到的结果保存到寄存器[Rd]中。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

MLA指令也相对比较简单,即实现了四则运算中的“先乘后加”运算。用户可以通过【例11】对MLA指令进行了解。

【例12】使用MLA指令实现寄存器[R1]中的数值与寄存器[R2]中的数值进行相乘,并与寄存器[R3]中的数值进行相加,将计算结果存放在寄存器[R0]中,并且分别要求:

(1)不自动更新CPSR寄存器;(2)自动更新CPSR寄存器。

32位的乘法-累加运算与乘法运算类似,只需要根据MLA指令相应的语法格式对寄存器进行操作就可以了。

在上述两行代码中,第一条指令将寄存器[R1]中的数值与寄存器[R2]中的数值进行相乘,并与寄存器[R3]中的数值相加,将运算的结果存放在寄存器[R0]中。由于不需要自动更新CPSR寄存器中的标志位,因此MLA指令后无须添加-S后缀。

同样,第二条指令除了实现与第一条指令完全一致的功能外,由于在MLA指令后添加了——-S后缀,因此该指令将自动更新CPSR寄存器中的标志位。

10.无符号长乘(64位乘法)指令UMULL

在加法/减法运算中,如果操作数超出了ARM处理器的范围(32位),则可以将其分为若干个寄存器通过带进位的ADC指令和带进位的SBC指令分别进行求和操作和求差操作。同样,在ARM嵌入式代码中,也会经常遇到超过32位的乘法计算。此时,如果仍然采用普通的MUL乘法指令,则会产生数据溢出,甚至出现不可预知的错误。

在ARM指令集中,支持了无符号长乘(64位乘法)指令UMULL。这里需要说明的是,无符号数据是指不带正负号的数据,因此不能进行64位的正负数及负负数之间的乘法。该长乘指令的基本语法格式如下:

该指令主要用于将寄存器[Rm]中的数值作为乘数,将寄存器[Rs]中的数值作为被乘数进行乘法运算,将两者进行“无符号乘法”,最后将乘积的低32位保存到寄存器[Rd_L]中,而乘积的高32位被保存到寄存器[Rd_H]中。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例13】使用UMULL指令实现寄存器[R5]中的数值与寄存器[R8]中的数值进行无符号乘法,并将乘积的低32位保存到寄存器[R0]中,而乘积的高32位被保存到寄存器[R1]中。其中,寄存器[R5]中的数值为0x01,寄存器[R8]中的数值为0x02。

64位的长乘运算与乘法运算在运算的结果处理方式上有比较大的区别。普通的32位乘法运行的结果由于没有超出ARM处理器的宽度(32位)范围,只需要一个普通的32位寄存器就可以保存计算结果了。

而在64位的长乘运算指令中,虽然乘数和被乘数都是32位的,可以通过通用的32位寄存器进行保存,但乘积的结果可能超过32位,因此无法通过一个32位的通用寄存器进行保存。在ARM指令集中,处理这个问题的方式与长整数加法的处理方式一致,即通过两个或多个32位寄存器对运算的结果分“高、低位”进行存储。需要特别提醒读者注意的是,在UMULL指令中,一定要分清楚哪个寄存器是用于保存低字节数据,以及哪个寄存器是用于保存高字节数据的。

在上述3条指令中,第一条指令将立即数#0x01保存到寄存器[R5]中。

第二条指令将立即数#0x02保存到寄存器[R8]中。

第三条指令实现寄存器[R5]中的数值与寄存器[R8]中的数值进行无符号乘法,并将乘积的低32位保存到寄存器[R0]中,而乘积的高32位被保存到寄存器[R1]中。

11.无符号长乘-累加指令UMLAL

通常情况下在嵌入式算术运算中,对无符号64位数据的操作同样需要多条指令来实现。为了进一步简化代码的长度,提高CPU处理指令的效率,ARM指令集还支持了对无符号长乘-累加指令UMLAL。

与有符号乘法-累加指令MLA类似,无符号长乘-累加指令UMLAL实现了不同寄存器中数值的四则运算。而唯一的区别在于数据的宽度由32位变为64位。

该无符号长乘-累加指令的基本语法格式如下:

该指令主要用于将寄存器[Rm]中的数值作为乘数,寄存器[Rs]中的数值作为被乘数进行乘法运算,将两者进行“无符号乘法”,并将乘积与寄存器[Rd_L]和[Rd_H]构成的加数进行累加操作,最后将计算结果的低32位保存到寄存器[Rd_L]中,而计算结果的高32位被保存到寄存器[Rd_H]中。且[Rd_L]、[Rd_H]、[Rm]、[Rs]均为通用寄存器(32位)。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

这里需要注意的是,虽然无符号长乘-累加指令UMLAL的语法格式与有符号乘法-累加指令MLA指令类似,但在加数及最终计算结果的保存位置上有着本质区别。

在有符号乘法-累加指令MLA中,加数被单独存放在一个32位的通用寄存器中;而在无符号长乘-累加指令UMLAL中,加数被预先分高、低位放置在用于保存最终结果的寄存器[Rd_L]和[Rd_H]中。在累加操作完成之后,运算得到的结果被重新写入寄存器[Rd_L]和[Rd_H]中,即计算的结果覆盖了加数寄存器。

【例14】使用UMLAL指令实现寄存器[R5]中的数值与寄存器[R8]中的数值进行无符号乘法,并与寄存器[R0]、[R1]中的数据(0x0201)进行累加操作,将最终计算结果的低32位保存到寄存器[R0]中,而高32位被保存到寄存器[R1]中。其中,寄存器[R0]中的数值为0x01,寄存器[R1]中的数值为0x02,寄存器[R5]中的数值为0x01,寄存器[R8]中的数值为0x02。

64位无符号长乘-累加运算与32位有符号乘法-累加运算在运算结果的保存方式上有比较大的区别。计算结果的保存寄存器与累加操作的加数寄存器是同一组寄存器,即实现了通用寄存器的复用功能,在保存最终计算结果时直接覆盖了原有的加数。

在ARM指令集中,64位无符号长乘-累加运算的处理与长整数加法的处理一致,也是通过两个或多个32位寄存器对运算的结果分“高、低位”进行存储的。需要特别提醒读者注意的是,在UMLAL指令中,累加加法寄存器与最终的计算结果寄存器是一致的,并且同样也是分高、低位来存储的,所以同样要分清哪个寄存器是用于保存低字节数据,以及哪个寄存器是用于保存高字节数据的。

在上述5条指令中,第一条指令将立即数#0x01保存到寄存器[R0]中。

第二条指令将立即数#0x02保存到寄存器[R1]中。

这两条指令实现了累加加数的保存工作,即由寄存器[R0]中的数值构成加数的低32位,而寄存器[R1]中的数值构成加数中的高32位。

第三条指令将立即数#0x01保存到寄存器[R5]中。

第四条指令将立即数#0x02保存到寄存器[R8]中。

第五条指令实现寄存器[R5]中的数值与寄存器[R8]中的数值进行“无符号乘法”,并将乘积与寄存器[R0]和[R1]构成的加数进行累加操作,最后将计算结果的低32位保存到寄存器[R0]中,高32位则被保存到寄存器[R1]中。

12.有符号长乘指令SMULL

为了进一步提高ARM处理器的数据处理能力,在ARM指令集中还支持了对有符号64位数据的长乘操作指令SMULL。

与无符号长乘指令UMULL类似,有符号长乘指令SMULL实现了不同寄存器中数值的乘法运算。而唯一的区别在于CPU处理的数据可以是有符号的。

该有符号长乘指令的基本语法格式如下:

该指令主要用于将寄存器[Rm]中的数值作为乘数,寄存器[Rs]中的数值作为被乘数进行乘法运算,将两者进行“有符号乘法”,最后将乘积的低32位保存到寄存器[Rd_L]中,乘积的高32位则被保存到寄存器[Rd_H]中,且[Rd_L]、[Rd_H]、[Rm]、[Rs]均为通用寄存器(32位)。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

【例15】使用SMULL指令实现寄存器[R5]中的数值与寄存器[R8]中的数值进行有符号乘法,将最终计算结果的低32位保存到寄存器[R0]中,高32位则被保存到寄存器[R1]中。其中,寄存器[R5]中的数值为#-1,寄存器[R8]中的数值为0x02

在上述3条指令中,第一条指令将有符号立即数#-1保存到寄存器[R5]中。

第二条指令将立即数#0x02保存到寄存器[R8]中。

第三条指令实现寄存器[R5]中的数值与寄存器[R8]中的数值进行有符号乘法,最后将计算结果的低32位保存到寄存器[R0]中,高32位则被保存到寄存器[R1]中。

13.有符号长乘-累加指令SMLAL

在ARM指令集中,除了上述介绍的相关运算操作指令外,同样还支持了有符号长乘-累加指令SMLAL。

与无符号长乘-累加指令UMLAL类似,有符号长乘-累加指令SMLAL也同样实现了不同寄存器中数值的四则运算。而唯一的区别在于SMLAL指令实现了对数据的有符号运算。

该有符号长乘-累加指令的基本语法格式如下:

该指令主要用于将寄存器[Rm]中的数值作为乘数,寄存器[Rs]中的数值作为被乘数进行乘法运算,将两者进行“有符号乘法”,并将乘积与寄存器[Rd_L]和[Rd_H]构成的加数进行累加操作,最后将计算结果的低32位保存到寄存器[Rd_L]中,乘积的高32位则被保存到寄存器[Rd_H]中,且[Rd_L]、[Rd_H]、[Rm]、[Rs]均为通用寄存器(32位)。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

有符号长乘-累加指令SMLAL与无符号长乘-累加指令UMLAL完全类似,唯一的不同之处在于该指令是针对有符号数据处理的运算指令。用户可以对照UMLAL指令的例题进行对比,这里就不再举例说明了。

14.取反比较指令CMN

前面已经介绍了有关数据比较的指令CMP。但CMP指令只能用于正数之间的比较,不能用于负数之间的比较。

为了支持负数之间的比较操作,ARM指令集中支持了用于取反比较指令CMN,具体的语法格式如下:

该指令实际上是将寄存器[Rn]中的数值与操作数[operand2]相加,并根据计算的结果更新CPSR寄存器中的标志位。其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令。

但需要注意的是,与CMP指令类似,取反比较指令CMN只进行两个操作数的比较,并不保存计算(加法操作)的结果。

【例16】对寄存器[R1]中的数值与立即数#0x5A进行取反比较操作。

在该语句中,实现了寄存器[R1]中的数值与立即数#0x5A的相加,根据计算的结果来更新条件标志位。

需要说明的是,CMN指令除了不保存计算的结果外,其他操作与ADD指令是完全一致的。

【例17】判断寄存器[R0]中的数值是否为1的补码。

在该语句中,将寄存器[R0]中的数值与立即数1相加。如果寄存器[R0]中的数值等于1的补码,累加后的结果为0,则将状态标志位Z置1;如果寄存器[R0]中的数值不等于1的补码,则累加后的结果不为0,不会将状态标志位Z置1。

2.3.3 逻辑运算指令

在ARM嵌入式中,CPU只能处理数字信号,即与处理器直接打交道的只能是二进制代码。因此,在实际的应用过程中,往往需要对操作数中的某一位或几位进行特殊操作,如屏蔽某些位、进行奇偶校验、提取某1位等。在这种情况下,用户可以通过使用逻辑运算指令来实现上述功能的操作。

在ARM指令集中,常用的逻辑运算指令有6个,如表2.8所示。

表2.8 ARM指令集中的逻辑运算指令

下面将结合具体的代码操作向读者依次介绍ARM指令集中的逻辑运算操作。

1.逻辑与操作AND

在ARM指令集中,逻辑与操作指令AND的基本语法格式为:

与普通的与操作类似,ARM指令集中的逻辑与操作AND用于对两个操作数[Rn]和[operand2]进行逻辑与运算,并将运算结果保存到寄存器[Rd]中。

所谓“逻辑与”操作,即在各个操作位上,数值均为1,则结果为1,否则结果为0。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

根据逻辑与操作AND指令的功能描述,不难看出该操作指令可以用来屏蔽操作数中的某一位或几位。用户可以通过下面的例子对AND指令的功能做进一步了解。

【例18】使用逻辑与操作指令AND取出寄存器[R0]中数值的最后两位。其中,寄存器R0中的数值为0xFF。

在上述代码中,主要完成“位提取”操作。从本质上来说,即使用逻辑与操作指令AND屏蔽操作数中的某些位,其基本原理是,设置一个全为0,且与操作数位数一致的十六进制数0x00,将需要屏蔽的位设置为0,不需要屏蔽的位设置为1。依据逻辑与操作的规则,数值均为1则结果为1,否则结果为0,被设置为1的那些位将被提取保存。

第一条指令将立即数0xFF保存到寄存器R0中;

第二条指令通过AND指令与立即数#0x03操作,取出R0的最低2位,并将新得到的结果仍然保存到寄存器R0中。

不难发现,上述代码运行后,寄存器中的结果为0x03,即为0xFF的最低2位。

2.逻辑或操作ORR

在ARM指令集中,逻辑或操作指令ORR的基本语法格式为:

ARM指令集中的逻辑或操作指令ORR用于对两个操作数[Rn]和[operand2]进行逻辑或运算,并将运算的结果保存到寄存器[Rd]中。

所谓“逻辑或”操作,与前面的“逻辑与”操作相反,即在各个操作位上,数值均为0则结果为0,数值不同则结果为1。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

与前面的逻辑与操作指令AND不同的是,逻辑与操作ORR指令并不能实现操作数某1位或某几位的提取操作。相反的是,逻辑或指令ORR能实现对操作数某1位或某几位的置位(置1)操作。

用户可以通过下面的例子对ORR指令的功能做进一步了解。

【例19】使用逻辑或操作指令ORR将寄存器[R0]中数值的最后两位设置为1。其中,寄存器R0中的数值为0x5A。

在上述代码中,主要完成“置1”操作。从本质上来说,即使用逻辑或操作指令ORR将操作数中的某些位设置为1。其基本原理是,设置一个全为0的,且与操作数位数一致的十六进制数0x00,将需要设置为1的位填1,不需要设置为1的位填0。依据逻辑或操作的规则,数值均为0则结果为0,数值不均为0则结果为1,被设置为1的那些位通过ORR操作后将被设置为1。

第一条指令将立即数0x5A保存到寄存器R0中。

第二条指令通过AND指令与立即数#0x03操作,取出R0的最低2位,并将新得到的结果仍然保存到寄存器R0中。

上述代码运行后,寄存器中的结果为0x03,即将0xFF的最低2位成功设置为1。

3.逻辑异或操作EOR

在ARM指令集中,逻辑异或操作指令EOR的基本语法格式与上述“逻辑与操作”及“逻辑或操作”都是类似的,具体格式为:

ARM指令集中的逻辑异或操作指令EOR用于对两个操作数[Rn]和[operand2]进行逻辑异或运算,并将运算的结果保存到寄存器[Rd]中。

所谓“逻辑异或”操作,即在各个操作位上,数值对应相同则结果为0,数值对应不同则结果为1。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

用户可以通过下面的例子对EOR指令的功能做进一步了解。

【例20】使用逻辑异或操作指令EOR将寄存器[R0]中的数值分别与立即数#0x5A和#0xA5进行异或操作。其中,寄存器R0中的数值为0x5A。

从本质上来说,逻辑异或操作指令ERR将两个操作数中的各个位进行比较。如果对应的位上数值相同,则结果为0;如果对应的位上数值不同,则结果为1。

在上述代码中,第一条指令将立即数0x5A保存到寄存器R0中,用于与后面的立即数进行异或操作。

第二条指令通过EOR指令与立即数#0x5A进行异或操作,并将新得到的结果仍然保存到寄存器R0中。由于每一个位上的数值都对应相同,根据异或操作的规则,该指令运行后寄存器R0中的数值为0x00。

第三条指令通过EOR指令与立即数#0xA5进行异或操作,并将新得到的结果仍然保存到寄存器R0中。由于每一个位上的数值都不相同,根据异或操作的规则,该指令运行后寄存器R0中的数值为0xFF。

4.位清除操作BIC

前面已经介绍过,可以通过“逻辑与”操作来屏蔽操作数中的某些位。实际上在ARM指令集中,已经支持了专门用于位清除的操作指令BIC。通过BIC指令,可以将操作数中的某1位或几位设置为0。

有关位清除操作指令BIC的具体语法格式为:

ARM指令集中的位清除操作指令BIC主要用于清除操作数中的某些位,并将最终的操作结果保存到寄存器中。寄存器[Rd]中为需要进行位清除操作的操作数;寄存器[operand2]为位清除操作的掩码,当寄存器[operand2]中的某一位被设置为1时,寄存器[Rn]中对应的位会被清零,并且被保存到寄存器[Rd]中。

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

用户可以通过下面的例子对BIC指令的功能做进一步了解。

【例21】使用位清除操作指令BIC将寄存器[R0]中数值的最高位和最低位清零。其中,寄存器R0中的数值为0xA5。

在BIC指令操作中,语法格式相对比较简单,只要按照BIC语法指令格式填写好就可以,而关键在于清除掩码的确定。

本例需要对操作数的最高位和最低位进行清除,在设置掩码时需要将最低位和最高位设置为1,而其他各位设置为零,得到立即数#0x81。

在上述两行代码中,第一条指令将立即数0xA5保存到寄存器R0中,作为清除位操作的对象。

第二条指令通过BIC指令与掩码#0x81操作,并将新得到的结果仍然保存到寄存器R0中。由于掩码#0x81所对应的二进制格式为1000,0001b,即最高位与最低位为1,而其他位为0。根据位清除操作的规则,该指令运行后会将寄存器R0中数值的最高位和最低位清除为0,而其他位保持不变。

5.测试比较操作TST

在ARM指令集中,用户除了可以通过CMP等数据比较指令来实现对数据进行比较外,还提供了测试比较指令TST。

有关测试比较操作指令TST的具体语法格式为:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令。寄存器[Rd]中的数据为需要进行测试比较操作的对象;寄存器[operand2]中的数据为测试比较操作的掩码,当寄存器[operand2]中的某一个位被设置为1时,寄存器[Rn]中对应的位会进行测试比较操作。

测试比较指令TST用于将一个寄存器的值和一个算术值进行逻辑与操作,并根据这两个操作数之间“逻辑与”操作的结果来更新CPSR寄存器中的值。从本质上来说,除了不保存比较的结果外,测试比较指令TST与逻辑与操作指令AND是一样的。通常情况下,测试比较指令TST常被用于测试寄存器中的某些位是0还是1。

用户可以通过下面的例子对TST指令的功能做进一步了解。

【例22】使用测试比较操作指令TST来检验寄存器[R0]中数值的最高位和最低位是否为零。其中,寄存器R0中的数值为0xA5。

在TST指令操作中,关键在于如何确定测试比较掩码的数值。需要提醒用户的是,TST指令会根据测试比较的结果来更新寄存器CPSR中的N、Z、C和V标志位,但测试比较的结果不会被保存在任何寄存器中。

本例需要对操作数的最高位和最低位进行测试,在设置掩码时需要将最低位和最高位设置为1,而其他各位设置为零,得到立即数#0x81。

在上述两行代码中,第一条指令将立即数0xA5保存到寄存器R0中来作为测试比较操作的对象;

第二条指令通过TST指令与掩码#0x81进行操作。由于掩码#0x81所对应的二进制格式为1000,0001b,即最高位与最低位为1,而其他位为0。根据测试比较操作的规则,该指令运行后会对寄存器R0中数值的最高位和最低位进行测试,并根据最终测试的结果来更新寄存器CPSR中的标志位。

6.异或测试指令TEQ

与测试比较指令类似的是,ARM指令集还支持了异或测试指令TEQ。该指令的基本语法格式如下:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令。寄存器[Rd]中的数据为需要进行异或测试操作的对象;寄存器[operand2]中的数据为异或测试操作的掩码,当寄存器[operand2]中的某一位被设置为1时,寄存器[Rn]中对应的位会进行异或测试操作。

异或测试指令TEQ用于将一个寄存器的值和另一个寄存器的值或立即数进行比较,并根据这两个操作数之间的“逻辑异或”操作的结果来更新CPSR寄存器中的值。

与测试比较指令TST不同的是,异或测试指令TEQ常被用于测试两个寄存器中的数值是否相等。

用户可以通过下面的例子对TEQ指令的功能做进一步了解。

【例23】使用异或测试指令TEQ来检验寄存器[R0]中数值是否与寄存器[R1]中的数值相等。

在TEQ指令操作中,只涉及寄存器之间的操作,而不存在类似TST指令中的掩码。除此之外,与TST指令相同的是,TEQ指令同样会根据比较的结果来更新寄存器CPSR中的标志位。

在上述两行代码中,第一条指令将立即数0xA5保存到寄存器R0中;第二条指令将立即数0x5A保存到寄存器R1中。

第三条指令通过TEQ指令对寄存器R0和寄存器R1进行异或测试操作,并根据最终测试结果更新寄存器CPSR中的标志位。

2.3.4 存储器访问指令

ARM处理器属于加载/存储类型的,即处理器对数据的操作通过将数据从存储器加载到片内寄存器中进行处理。数据处理结束后,再由寄存器反存到存储器中,这样的操作方式可以在一定程度上加快对片外存储器进行数据处理的执行速度。

在ARM指令集中,数据存取指令加载/存储是唯一用于寄存器和存储器之间进行数据传送的指令。通常情况下,由存储器向寄存器传送数据的指令称为加载指令;而由寄存器向存储器传送数据的指令称为存储指令。

ARM指令集中的数据存取指令可以分为以下3类。

1.单寄存器存取指令

单寄存器的数据存储指令主要用于ARM中寄存器和存储器之间数据的传送,也是最为灵活的一种数据传送方式。单寄存器中传送的数据可以是8bit的,也可以是16bit的,甚至还可以传送32bit的数据。

在ARM指令集中,常用的单寄存器存取指令有LDR指令和STR指令等。

2.多寄存器存取指令

用户可以通过单寄存器存取指令实现寄存器中数据的传送,但这类指令每次只能传送一个数据。因此,在一些需要批量传输数据的场合,如果仍然采用单寄存器存取指令的方式来进行数据传送,显然是不适合的。

在ARM指令集中,除了上述的单寄存器存取指令之外,还支持了多寄存器存取指令。虽然与单寄存器存取指令相比,这类指令的灵活性相对要差一些,但可以更有效地实现大批数据的存取。

通常情况下,多寄存器存取指令主要用于进行启动、退出、保存和恢复工作的寄存器,以及成块地复制寄存器中的数据。

在ARM指令集中,常用的单寄存器存取指令有LDM指令和STM指令等。

3.单寄存器交换指令

在ARM处理器操作中,数据交换是数据加载/存储的一种特殊形式,主要用于在存储器和寄存器之间交换数据。在ARM指令集中,常用的单寄存器交换指令有SWP指令。

单寄存器交换指令SWP可以将字或者无符号字节的读取和存储组合在一条指令中。通常情况下,这两种数据传送的操作是一对操作偶,即成对出现。因此,单寄存器交换指令SWP通常用于处理器之间或者处理器与DMA控制器之间共享信号量、数据结构,以及进行互斥性质的访问。

上述三类存储器访问指令实现了ARM处理器内部的数据传送,这主要是由于部分ARM处理器内部不支持RAM存储器,数据的传送必须以寄存器为媒介进行传送。除此之外,由于ARM处理器结构中的所有的外部设备都和存储单元一样,分配了不同的地址单元,而ARM处理器则将这些外部设备统一作为外部存储器来操作。因此,在ARM系统中,同样需要使用加载和存储指令来实现对数据的传送。

实际上,随着硬件资源的不断完善,目前绝大部分的ARM处理器都集成了RAM存储器。因此,对数据的操作也可以不再依赖寄存器或存储器之间的传送,但这并不意味着存储器访问指令已经完全被淘汰了。

在嵌入式底层汇编代码中,即使是在支持RAM存储器的ARM芯片中,存储器访问指令仍然发挥着不可忽视的作用。并且,由于存储器访问指令直接与芯片底层的寄存器及硬件资源密切相关,用户也只有对这些底层寄存器及硬件资源的分配有了充分的了解和掌握后,才能更好地实现对ARM芯片的操作。

有关ARM指令集中主要存储器访问指令,用户可以通过表2.9进行了解各个指令的功能,在此就不再赘述了。

表2.9 ARM指令集中的存储器访问指令

2.3.5 数据传送指令

前面已经大致介绍了有关存储器访问的操作指令。在ARM嵌入式系统中,还需要实现寄存器之间的数据传送。

ARM指令集提供了几个用于寄存器之间的数据传送指令,如表2.10所示。用户可以利用这些指令来完成ARM处理器中寄存器之间的数据传送。

表2.10 ARM指令集中的数据传送指令

1.通用数据传送指令MOV

在ARM指令集中,MOV指令是最为简单和常见的数据传送指令。通常情况下,用户可以使用MOV指令将立即数传送到目标寄存器中,也可以将一个寄存器中的数值传送到另一个寄存器中。

在ARM指令集中,通用数据传送指令MOV的基本语法格式为:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

在MOV指令操作中,操作数[operand2]将被传送到寄存器[Rd]中。需要说明的是,操作数[operand2]可以是一个立即数,也可以是某一个寄存器的数值,甚至还可以是对寄存器中的数值进行移位操作后的数值。

【例24】使用通用数据传送指令MOV完成以下操作:

(1)将立即数0x5A传送到寄存器[R1]中;

(2)将寄存器[R1]中的数值传送给寄存器[R0];

(3)将寄存器[R1]中的数值左移2位后传送给寄存器[R0]。

在上述代码中,主要使用MOV指令完成对通用数据传送的基本操作。

第一条指令将立即数0x5A保存到寄存器[R1]中。

第二条指令通过MOV指令将寄存器[R1]中的数值传送给寄存器[R0]。

第三条指令首先对寄存器[R1]中的数值进行操作,通过LSL指令将其左移2位。然后再将平移后的数值传送给寄存器[R0]。

2.反向数据传送指令MVN

通常情况下,在数据传送的过程中不对数据做特殊处理,即直接将源寄存器中的数据传送给目标寄存器。

在部分特殊场合下,需要对数据进行预期处理。例如,在执行减法操作运算时,首先需要计算减数的补码,然后再执行加法操作。因此,在数据传送过程中,可以先对源寄存器中的数据进行预先取反操作。

在ARM指令集中,支持了反向数据传送指令MVN,用于将操作数的反码传送到目标寄存器。该指令的一般格式为:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令;参数[S]用于决定是否影响CPSR寄存器中的进位标志位,即当[S]为默认状态时,指令执行的结果对CPSR寄存器中的进位标志位不会产生任何影响。

从语法格式上看,反向数据传送指令MVN与MOV指令操作几乎完全类似,在反向数据传送指令MVN中,先对操作数[operand2]进行取反操作,再被传送到寄存器[Rd]中。同样,操作数[operand2]可以是一个立即数,也可以是某一个寄存器的数值,甚至还可以是对寄存器中的数值进行移位操作后的数值。

【例25】使用反向数据传送指令MVN将立即数8传送到寄存器[R1],并思考此时寄存器[R1]中的数值是否为8?

在上述代码中,从表面上看,是使用MVN指令将立即数8传送到寄存器[R1]中。但再仔细分析,不难看出在进行数据传送开始前,首先对立即数8(0110b)进行取反处理,得到1001b。然后再将取反后的二进制数传送给寄存器[R1]。这样就不难看出,实际上寄存器[R1]中的数值为-9,而不是8。

3.程序状态字传送数据至通用寄存器的指令MRS

前面介绍了通用寄存器之间的数据传送。在这类数据传送的过程中,数据对象对用户是透明的,即用户可以明确地知道传送对象的具体数值。

在ARM处理器中,除了上述的通用寄存器外,还存在程序状态寄存器。在ARM的程序设计过程中,可能需要根据程序状态寄存器中的某一个或几个标志位来进行选择或者判决操作。而ARM处理器中的程序状态寄存器对用户却不是透明的,因此,如果用户希望知道程序状态寄存器中的状态标志位,需要通过MRS指令将状态标志位传送到通用寄存器。

在ARM处理器中,程序状态字传送数据至通用寄存器指令MRS可以将状态寄存器CPSR或者SPSR中的数值传送到通用寄存器中。并且在ARM指令集中,MRS指令也是唯一能读取状态寄存器的指令,其具体的语法格式为:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令。

【例26】使用程序状态字传送数据至通用寄存器指令MRS读取程序状态寄存器CPSR及SPSR中的数值。

上述代码相对比较简单,用户可以对比MRS指令的语法格式及代码行中的注释来查看代码的功能,这里就不再赘述了。

4.写状态寄存器指令MSR

在ARM处理器中,除了可以读取程序状态寄存器中的数值之外,在特殊场合中还需要能够实现对状态寄存器进行写入数据的操作可以看出,写状态寄存器中的操作与读状态寄存器中的操作是相反的操作。在ARM指令集中,用户可以使用MSR指令实现写状态寄存器操作,其具体的语法格式为:

其中,[cond]为指令执行的条件,即当[cond]为默认时,将无条件执行当前的指令。

参数[field]为寄存器中需要写入的操作位。对于ARM中的状态寄存器而言,可以大致分为4个区域:[31:24]为条件标志位域,通常使用f来表示;[23:16]为状态位域,通常使用s来表示;[15:8]为扩展位域,通常使用x来表示;[7:0]为控制位域,通常使用c来表示。

参数#[immediate]为需要传送到指定状态寄存器指定域中的8bit立即数。

参数[Rm]为需要传送到指定状态寄存器指定域中的寄存器值。

【例27】使用MSR指令修改状态寄存器CPSR,并试将控制位域的数值改为0x11。

2.3.6 协处理器指令

通常情况下,ARM处理器一般只能处理加法、乘法,以及乘加运算,而不能支持除法或其他复杂的操作运算。但实际工程应用中经常需要对除法进行运算操作。为了提高系统的处理速度,通常情况下可以使用协处理器的形式来协同ARM处理器以完成这些复杂的操作。

一般来说,协处理器不能单独工作,需要与ARM处理器配合共同操作。协处理器可以使用多种存在的方式,可以连接在ARM处理器的片外,也可以嵌入在ARM处理器的内部,最为典型的协处理器就是浮点协处理器。

通常情况下,ARM处理器最多可以支持16个协处理器,用于配合ARM处理器实现各种复杂的协处理器操作。在执行ARM代码时,协处理器会忽略属于ARM处理器及其他协处理器的指令。

如果某一个协处理器不能执行完成属于它的操作指令时,将产生一个未定义指令异常中断。在该异常中断处理程序中,也可以使用软件模拟此类硬件操作。例如,在一个不包含浮点协处理器的系统中进行浮点运算操作,系统将产生未定义异常中断。用户也可以选择在这个异常中断中用浮点运算软件来模拟该浮点操作。

需要说明的是,ARM处理器芯片中是否支持协处理器及协处理器的类型完全取决于该ARM芯片生产厂商,且与ARM的版本无关。

ARM协处理器指令根据具体的用途和操作可以大致分为以下3类:

(1)用于ARM处理器初始化和ARM协处理器的数据操作指令;

(2)用于ARM处理器中的寄存器和ARM协处理器之间的数据操作指令;

(3)用于ARM协处理器的寄存器和内存单元之间的数据操作指令。

值得注意的是,ARM协处理器也有专门的协处理器接口,同时在ARM指令集中也支持了专门用于控制协处理器操作的指令,用于协处理器的初始化等操作,具体如表2.11所示。在下面的内容中将分别介绍这些协处理器指令。

表2.11 ARM指令集中的协处理器指令

续表

1.协处理器数据操作指令CDP

协处理器数据操作指令CDP完全是ARM协处理器内部的指令操作,用于完成协处理器寄存器状态的改变。例如,在浮点加法操作中,需要实现在浮点协处理器中进行两个寄存器数值的相加操作,并将结果存放在第3个寄存器中。此时,协处理器数据操作指令用于控制数据在协处理器寄存器内部的操作。

在ARM指令集中,协处理器数据操作指令CDP的通用格式为:

如前文中所描述的一样,该指令主要用于控制数据在协处理器寄存器内部的操作。如果协处理器不能完成当前的指令操作,将产生一个未定义的指令异常中断。

其中,指令语法格式中的各个参数的含义如下。

● 参数[cond]为指令中的条件域。当参数[cond]省略时,当前指令无条件执行。

● 参数[coproc]为协处理器的编号。在ARM中,协处理器的编号顺序为p0,p1,…,P15。

● 参数[opcode1]为协处理器执行的操作码,用于确定哪一个协处理器指令将被执行。

● 参数[CRd]是协处理器的寄存器之一,在协处理器指令中作为目标寄存器。

● 参数[CRn]是协处理器的寄存器之一,在协处理器指令中作为第一个操作数。

● 参数[CRm]是协处理器的寄存器之一,在协处理器指令中作为第二个操作数。

● 参数[opcode2]通常与参数[opcode1]配合使用,用来指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。

在ARM处理器中,CPU对可能存在的所有协处理器提供CDP指令。如果该指令被某一个协处理器接受,则ARM处理器继续执行下一条程序代码;如果所有协处理器均未接受该CDP指令,则ARM处理器将产生一个未定义的中止陷阱(中断),此时可以在中断处理程序中使用软件模拟协处理器的方式来实现该指令功能。

【例28】使用CDP指令实现对协处理器p1的数据操作,指令操作代码为1。

根据要求,需要对协处理器p1进行操作,且指令的操作代码为1。需要注意的是,协处理器的操作代码功能因各个处理器而各不相同。其中,c2,c3和c4均为ARM协处理器p1中的寄存器,主要作为ARM数据操作中的操作数及目标寄存器。另外,CDP指令运行后的结果并不能改变状态寄存器CPSR中的状态标志位。

2.协处理器数据读取指令LDC

协处理器数据读取指令LDC从存储器中读取数据并装入协处理器寄存器。由于协处理器只能支持特定的数据类型,因此读取数据的过程中寄存器中所存储的数据与协处理器的类型也相关。

在ARM指令集中,协处理器数据读取指令LDC的通用格式为:

该指令通过一定的寻址方式将指定内存单元中的数据存储到协处理器的寄存器中。与数据操作指令CDP一样,如果协处理器不能完成当前的指令操作,将产生未定义的指令异常中断。

其中,指令语法格式中各个参数的含义如下。

● 参数[cond]为指令中的条件域。当参数[cond]省略时,当前指令无条件执行。

● 参数[L]表示当前指令为长读取指令。例如,用于双精度数据的传输。

● 参数[coproc]为协处理器的编号。在ARM中,协处理器的编号顺序为p0,p1,…,P15。

● 参数[CRd]是协处理器的寄存器之一,在协处理器指令中作为目标寄存器。

● 参数[Addr_mode]为指令的寻址方式,由基址寄存器[Rn]和8bit的立即数偏移量进行计算。

在该指令中,地址计算是在ARM内部进行的。主要算法为使用ARM基址寄存器[Rn]和8bit的立即数偏移量进行计算。其中,8bit的立即数偏移量应该左移2bit以产生字偏移。

【例29】使用LDC指令实现对协处理器p3的读取操作。

第一条指令将内存地址[R0+4]中的数据传送到协处理器p3的寄存器c2中。

第二条指令将内存地址[R0+R1]处的数据传送到协处理器p3的寄存器c2中。

需要说明的是,LDC指令还可以采用其他的寻址方式来指定操作数的内存地址,具体的寻址方式可以查看相关资料,这里就不再赘述了。

3.协处理器数据写入指令STC

与LDC指令对应,ARM指令集还支持了协处理器数据写入指令STC。显然,数据写入指令STC将协处理器寄存器中的数据写入到存储器中。

与LDC指令类似的是,协处理器只支持特定的数据类型,因此写入数据的过程中同样需要注意数据类型的匹配。

在ARM指令集中,协处理器数据写入指令STC的通用格式为:

该指令将协处理器寄存器中的数据存储到连续的内存单元中。如果协处理器不能完成当前的指令操作,将产生未定义的指令异常中断。

其中,指令语法格式中各个参数的含义如下。

● 参数[cond]为指令中的条件域。当参数[cond]省略时,当前指令无条件执行。

● 参数[L]表示当前指令为长读取指令。例如,用于双精度数据的传输。

● 参数[coproc]为协处理器的编号。在ARM中,协处理器的编号顺序为p0,p1,…,P15。

● 参数[CRd]是协处理器的寄存器之一,在协处理器指令中作为目标寄存器。

● 参数[Addr_mode]为指令的寻址方式,由基址寄存器[Rn]和8bit的立即数偏移量进行计算。

【例30】使用STC指令实现对协处理器p3的写入操作。

该指令相对比较简单,将协处理器p3中的寄存器c2中的数据写入到内存地址[R0+4]中。

4.ARM处理器到协处理器的数据传送指令MCR

在实际嵌入式系统中,从ARM处理器到协处理器之间的数据传送操作是非常常见的操作。这些协处理器传送指令使得ARM处理器中的数据能够直接被传送到协处理器的内存中,并且影响ARM状态寄存器中的标志位。

在一些复杂的ARM处理器中,通常使用系统控制协处理器来控制系统的高速缓存(Cache)和存储器管理功能(MMU)。在这一类处理器中,一般可以使用ARM处理器到协处理器之间的数据传送指令来实现对片上控制寄存器的访问和修改操作。

在ARM指令集中,ARM处理器到协处理器的数据传送指令MCR的通用格式为:

其中,指令语法格式中各个参数的含义如下。

● 参数[cond]为指令中的条件域。当参数[cond]省略时,当前指令无条件执行。

● 参数[coproc]为协处理器的编号。在ARM中,协处理器的编号顺序为p0,p1,…,P15。

● 参数[opcode_1]为协处理器的操作码,确定哪个协处理器指令将被执行。

● 参数[Rd]为ARM处理器中的寄存器。该寄存器中的数据将被传送到协处理器寄存器中。

● 参数[CRn]是协处理器的寄存器之一,用于确定协处理器指令的第一个操作数。

● 参数[CRm]是协处理器的寄存器之一,用于确定协处理器指令的第二个操作数。

● 参数[opcode_2]通常与参数[opcode_1]配合使用,用来指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。

该指令用于将数据从ARM寄存器传送到协处理器的寄存器中。与其他协处理器指令类似,如果协处理器不能完成当前的指令操作,将产生未定义的指令异常中断。

5.协处理器寄存器到ARM处理器的数据传送指令MRC

由于协处理器和ARM处理器之间的数据传送操作是可以双向执行的,因此在ARM指令集中还支持了从协处理器寄存器到ARM处理器的数据传送指令MRC。

在ARM指令集中,协处理器寄存器到ARM处理器的数据传送指令MRC的通用格式为:

其中,指令语法格式中各个参数的含义如下。

● 参数[cond]为指令中的条件域。当参数[cond]省略时,当前指令无条件执行。

● 参数[coproc]为协处理器的编号。在ARM中,协处理器的编号顺序为p0,p1,…,P15。

● 参数[opcode_1]为协处理器的操作码,确定哪个协处理器指令将被执行。

● 参数[Rd]为ARM处理器中的寄存器。该寄存器中的数据将被传送到协处理器寄存器中。

● 参数[CRn]是协处理器的寄存器之一,用于确定协处理器指令的第一个操作数。

● 参数[CRm]是协处理器的寄存器之一,用于确定协处理器指令的第二个操作数。

● 参数[opcode_2]通常与参数[opcode_1]配合使用,用来指定协处理器执行的操作码,确定哪一个协处理器指令将被执行。

通常情况下,如果协处理器[coproc]接受了这条协处理器指令,将执行对源操作数[CRn]和[CRm]的数据操作,并将32位的结果返回到ARM寄存器[Rd]中。

需要注意的是,该指令的另一个重要用途是从协处理器到ARM处理器传送数据的过程中,根据操作运算的结果更新状态寄存器CPSR中的标志位,并以此作为判断、跳转指令的操作条件。例如,当协处理器中的两个浮点数进行比较后,将更新状态寄存器CPSR中的标志状态位,用来控制后续代码的分支选择。

2.3.7 异常中断产生指令

ARM指令集中支持了两个异常中断产生指令,分别为软中断指令SWI和断点中断指令BKPT。通过这两条指令可以使用软件的方式来实现异常中断。

1.软中断指令SWI

在ARM操作系统中,软中断指令SWI主要用于产生SWI异常中断,用于实现用户调用操作系统的系统例程。因此,该指令也通常被称为“监控调用”。该指令将处理器设置为监控模式(SVC),并从0x08开始执行该指令。

在ARM指令集中,软中断指令SWI的基本语法格式为:

其中,指令语法格式中各个参数的含义如下。

● 参数[cond]为指令中的条件域。当参数[cond]省略时,当前指令无条件执行。

● 参数[immediate_24]为一个24位的立即数,用来决定指令请求的服务类型。

当符合执行条件时,该软中断指令SWI将使用标准的ARM异常入口程序进入监控模式。具体的操作行为如下:

(1)将SWI指令的下一条指令地址保存到R14_svc中;

(2)将状态寄存器CPSR中的内容保存到SPSR_svc中;

(3)进入监控模式,将CPSR[4:0]设置为0b10011,并将CPSR[7]设置为1,实现禁止IRQ功能;

(4)将程序计数器指针设置为0x08,并从该处进行执行代码。

【例31】使用SWI指令实现一个中断号为12的软中断。

2.断点中断指令BKPT

在嵌入式系统调试过程中,虽然编译器能够发现程序的语法错误,但代码中不可避免地还存在逻辑错误。这些错误需要通过观察程序运行过程中各个寄存器的变量来排除。通常,用户可以使用断点的方式来观察程序运行过程中的实时数据。

在ARM指令集中,断点中断指令BKPT通常被用来设置软件断点。其一般的语法格式如下:

其中,参数[immediate_16]为一个16位的立即数,用来决定指令请求的服务类型。

需要说明的是,在ARM代码调试的过程中,BKPT指令必须结合具体的调试操作来进行。一般情况下,BKPT指令是无条件执行的,但如果用户需要忽略所有的断点中断,则可以在当前系统的调试硬件中进行屏蔽BKPT指令的操作。

2.3.8 ARM指令小结

本小节主要介绍了ARM指令集中的常用指令,通过这些指令可以实现绝大部分ARM处理器的操作功能。

由于内容限制,上述介绍的这些ARM指令不可能覆盖指令集中所有的指令。更多有关ARM指令集的内容,可以参考更多由ARM公司发布的技术资料。

一般情况下,即使介绍完上述所有的ARM指令之后,用户也难以在较短的时间内熟练掌握对ARM嵌入式代码的操作,这也是正常的。在后续内容中,将继续向用户介绍有关ARM汇编程序中更多的设计知识,即Thumb指令集。 CGpwAEmqkwAgDpGFSwdh60KuF9DDyqLh2dZgpXDlW8sW6hE2B8AeV3W9bPtcWrcv

点击中间区域
呼出菜单
上一章
目录
下一章
×