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

2.1 ARM指令系统简介

ARM嵌入式处理器可以支持32位的ARM指令集和16位的Thumb指令集。从本质上说,Thumb指令集是ARM指令集压缩后的一个子集。由于ARM处理器是基于精简指令集原理设计的,与传统的基于复杂指令集原理设计的处理器比较,其指令集及译码机制相对简单。

一般而言,在ARM嵌入式系统中,需要正确处理好两个工作状态:32位的ARM状态和16位的Thumb状态。这两种工作状态的性能是完全不同的,特别是在代码密度和处理性能方面有着比较大的差别。两种状态模式的切换如图2.1所示。

图2.1 ARM处理器状态模式切换

当处理器工作在ARM状态时,所有的指令都是32位的。在这种工作状态下,处理器具有较高的性能;当处理器工作在Thumb状态时,所有的指令都是16位的,代码密度提高了一倍。但需要说明的是,处理器在Thumb状态下的指令功能只是其在ARM状态下指令功能的一个子集。在执行同样任务的前提下,Thumb状态需要更多的指令来完成相应的工作,导致处理能力下降。

为了弥补ARM指令集和Thumb指令集自身的缺陷,部分嵌入式系统代码混合使用ARM指令集和Thumb指令集,集合了高代码密度和高处理性能的优势。但这种混合使用的指令是以“额外开销”(Overhead)为代价的,特别是ARM处理器在ARM状态和Thumb状态相互切换时,会在时间和空间上均产生这种“额外开销”。除此之外,ARM状态下的指令代码和Thumb状态下的指令代码需要以不同的方式进行编译,这也在一定程度上增加了嵌入式软件开发的复杂度。

值得注意的是,STM32F103XX系列处理器使用了Thumb-2指令集。该指令集允许处理器同时使用32位的ARM指令和16位的Thumb指令,且同时兼顾系统的代码密度和处理性能。该Thumb-2指令集不仅功能强大,而且指令语句易于使用。

在Thumb-2指令集中,嵌入式处理器可以在单一的工作模式下进行所有的指令操作,也不需要像混合编码一样对工作模式进行来回切换,节省了系统在时间上的“额外开销”,比传统的ARM处理器性能更先进。这种混合编码的特点如下:

● 默认了ARM状态与Thumb状态模式切换的“额外开销”,在节省指令空间的同时也提高了指令的执行效率;

● 源代码文件不需要分成“按ARM指令编译”和“按Thumb指令编译”,提高了软件开发的效率;

● 无须进行不同状态模式的切换,在编写代码的过程中也无须反复求证和测试究竟在何时何处切换到何种状态。

由于STM32F103XX系列处理器支持Thumb-2指令集,因此需要对原有的应用程序进行移植和重建。对于一般的C语言程序而言,用户只需要简单的项目工程重建并对新的工程文件进行编译就可以了;如果源代码是采用汇编语言编写的,则可能需要大面积的修改和重写代码,才能重新融入到统一的汇编框架(Unified Assembler Framework)中。

需要提醒用户注意的是,STM32F103XX系列处理器虽然支持Thumb-2指令集,但并不支持所有的Thumb-2指令。从严格意义上来说,该处理器只实现了其中的大部分指令而不是全部指令,即STM32F103XX处理器中支持的指令集只是Thumb-2指令集的一个子集。例如,该指令集中裁剪掉了协处理器指令,且也未支持SIMD指令集。

2.1.1 ARM汇编语言的基本语法

在正式介绍ARM指令集之前,需要先简单介绍一下有关ARM汇编器的基本语法。需要注意的是,不同的处理器所支持的汇编语言的语法也各不相同,本章中的汇编语言代码都采用了ARM汇编器的语法格式。

在ARM处理器中,汇编指令的典型语法结构如下。

在ARM处理器汇编指令中,标号是可选项。在汇编语言中,标号的作用是让汇编器计算程序转移的地址,类似于高速公路上的指路牌。但是在汇编代码中,不一定每条指令都配备一个标号。但如果有标号,则标号必须顶格写。

操作码是汇编指令的助记符,且在相邻操作码之间必须至少有一个空格。通常情况下,空格可以使用1或2个Tab键(制表符)来代替。操作码之后通常跟随若干个操作数。

通常情况下,第一个操作数将作为当前指令执行结果的存储空间。不同的指令会跟随不同数目的操作数,并且对于操作数的语法要求也各不相同。一般而言,立即数必须以“#”开头,例如:

汇编语言中,有关代码的注释说明都是通过“;”为标志的。这些注释代码虽然并不影响编译器工作,但在编写的过程中添加必要的注释代码可以增加代码的可读性。同时适当的注释代码可以提高程序代码的可读性和可维护性,让程序代码更容易理解,降低后期代码维护的工作量。在一些大型的软件公司,如微软、西门子等,都对嵌入式代码中的注释量有一定的要求。一般而言,一个高质量嵌入式代码的注释量应不低于30%。

与C语言和C++类似的是,用户可以在汇编语言中通过使用“EQU”来定义常量,然后在代码中引用自定义的符号来使用这些预先定义好的数据。例如:

上述几行ARM汇编代码在前几句使用了“EQU”指令来定义了两个常量,并在程序代码中使用了用户自定义的标号(NVIC_IRQ_SETEN0,NVIC_IRQ0_ENABLE)来替代常量。

在代码中可以看出,用户在使用“EQU”指令来定义常量时,指令代码同样需要顶格写。另外需要补充说明的是,这里的LDR指令是一个特殊的汇编指令,通常被称为“伪指令”。由于ARM采用的是RISC结构,数据从内存到CPU之间的移动只能通过LDR/STR指令来完成。

细心的读者会发现在上述几行代码中,MOV指令也实现了同样的功能,即实现了存储数据的转移,但这两条指令却有着本质的不同。

例如,如果用户希望将数据从内存某处读取到寄存器中,只能使用LDR指令,具体代码如下所示:

上述代码的功能就是将0x12345678这个地址中的数据存放到寄存器R0中。

LDR指令是和x86系列处理器所采用的CISC架构芯片区别最大的地方。在x86系列处理器中并没有LDR这种指令,因为x86中的MOV指令可以将数据从内存移动到寄存器中。

特别需要引起读者注意的是,LDR伪指令和通常ARM的LDR指令还是有所区别的。例如,本例中所使用的LDR就是一个伪指令,它可以在立即数前面加上“=”,以表示把一个地址写到某寄存器中。

所以从功能角度而言,LDR伪指令和MOV是比较相似的。只不过MOV指令限制了立即数的长度为8位,也就是数据不能超过512bit。而对LDR伪指令没有这个限制。事实上,在编译器编译的过程中,如果用户在使用LDR伪指令时,后面跟的立即数没有超过8位,则该LDR伪指令会被编译器自动转换为MOV指令。

在某些条件下,ARM编译器可能会出现不认识某些特殊指令助记符的情况。在这种情况下,需要用户对该指令进行“手动汇编”,即查出当前指令所对应的二进制机器码,然后使用DCI指令对该指令进行编译。

例如,通过查询相关资料可知BKPT指令用于产生软件中断,主要用于汇编代码的调试,其机器码是0xBE00。如果编译器在编译的过程中无法识别BKPT指令,则可以使用下面的语句替代:

与此类似的是,用户还可以使用DCB指令来定义一串字节常数,字节常数也可以使用字符串的形式来定义。也可以使用DCD指令来定义一串32位的整数。通常这两条指令被使用在汇编代码中书写表格。用户可以通过下面这段代码来掌握这两条指令的具体使用方法。

不同编译器对标志符和具体的语法指令可能并不完全一致。本书中所有的代码都是基于ARM编译器的语法格式进行编写的。

2.1.2 ARM汇编指令中的后缀

在汇编指令中,部分指令可以带有后缀,具体如表2.1所示。

表2.1 汇编指令中的后缀

在STM32F103XX系列处理器中,编译器对条件后缀的使用有严格的限制。只有转移指令才可以随便使用条件后缀,而其他指令只能通过系统所提供的IF-THEN指令块才可以,并且必须加上必要的条件后缀。

2.1.3 ARM汇编指令的书写格式

前面已经向读者介绍过,STM32F103XX系列处理器中的编译器可以支持Thumb-2指令集,以实现处理器在ARM模式和Thumb模式之间的自由转换。因此,STM32F103XX系列处理器中的编译器引入了“统一汇编语言”(UAL)语法机制。

在汇编语言中,有部分指令操作(通常为数据处理操作)都可以通过16位的Thumb指令和32位的ARM指令来实现。在这种情况下,用户可以使用统一的32位Thumb-2指令的语法格式书写这些指令,并且由编译器来决定是使用16位的Thumb指令,还是使用32位的ARM指令。

通常情况下,对于一些只支持Thumb指令和ARM指令,而不支持Thumb-2指令的ARM处理器,需要根据当前处理器工作模式的不同,选择实际的操作数,以及对指令中立即数长度的限制。因此,在STM32F103XX系列处理器中,通过UAL语言机制,将Thumb语法和ARM语法结合起来,使得汇编语言具有统一的书写格式。用户可以通过下面的例子对Thumb-2指令及UAL语法的格式有基本了解。

在STM32F103XX系列处理器中,虽然引入了UAL语法格式,但编译器仍然支持用户使用传统的Thumb指令集,但特别需要提醒用户注意的是,在传统的Thumb指令集中,部分指令默认在完成相应的指令操作后,无论指令是否有S后缀,都会自动更新寄存器APSR(应用状态寄存器)中的内容;相比UAL语法格式,用户必须指定S后缀才会更新寄存器APSR中的内容。用户可以通过下面2行汇编语句自行比对这两条的异同。

从上述执行的代码分析中可以看出,这两条汇编语句的功能是一样的,即将寄存器R0与R1中的数值进行“与”操作。如果第二句代码中使用的是“AND”而不是“ANDS”指令,则指令运行的结果也是一致的,唯一的区别在于代码执行后的应用状态寄存器APSR不会被更新。

在Thumb-2指令集中,有部分指令操作既可以由16位的Thumb指令来实现,也可以由32位的ARM指令来实现。在UAL语法中,编译器会根据指令占用的资源情况来主动确定用哪一种指令。除此之外,用户也可以在代码通过后缀的方式手动设定使用ARM指令或者Thumb指令。

(1)如果系统没有对当前指令进行设定操作(无后缀),编译器则会根据当前系统资源的使用情况,选择最为节省代码空间的指令进行编译。具体的操作步骤为:编译器先以16位的Thumb指令进行编译,以实现较小的代码存储空间。如果无法采用Thumb指令对该指令进行编译,则再次使用ARM指令进行编译。

(2)在指令后加注“.N”后缀,则处理器将指定编译器在编译代码的过程中使用16位的Thumb指令,其中后缀“.N”表示“Narrow”,即16位Thumb窄位指令。结合(1)中无后缀的指令来看,在指令后加注“.N”是比较多余的。因为不管用户是否在指令代码后加注“.N”后缀,编译器都会首先采用16位的Thumb指令进行编译。

(3)在指令后加注“.W”后缀,则将指定编译器在编译代码的过程中使用32位的ARM指令,其中后缀“.W”表示“Wide”,即16位Thumb宽位指令。

用户可以对比下面几行代码,对设定编译器的指令模式做进一步的了解。

从上述几行代码的分析过程中可以看出,在ARM编译器中,如果代码中没有给出特定的后缀,编译器总是尽量选择更为简短的指令方式进行编译。由于绝大部分的ARM嵌入式代码都是通过C语言编写的,C编译器同样也会尽可能地使用较短的编译指令。但是,当指令中的立即数超出一定的范围,或者32位的ARM指令能更好地处理当前指令时,系统也会择优选择使用32位的ARM指令。

最后,需要提醒用户注意的是,绝大部分16位Thumb指令只能访问R0~R7寄存器;而32位Thumb-2指令可以自由访问R0~R15寄存器。但应特别注意,对寄存器R15的操作容易出现意想不到的错误,导致代码程序跑飞等。因此,在不是必需的情况下,不建议用户对其进行操作。 okDADlkuIGFqYBL+Lh6GDuhiVT7ZtUb9yec4zopI7mhwnoKmb/vG87q1uNGOCnD6

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