第3章 CHAPTER 3 ARM指令集 在嵌入式系统开发中,目前最常用的编程语言是汇编语言和C语言。在较复杂的嵌入式软件中,由于C语言编写程序较方便,结构清晰,而且有大量支持库,所以大部分代码采用C语言编写,特别是基于操作系统的应用程序设计。但是在系统初始化、BootLoader、中断处理等,对时间和效率要求较严格的地方仍旧要使用汇编语言来编写相应代码块。本章将介绍ARM指令集指令及汇编语言的相关知识。 3.1ARM指令集概述 ARM处理器的指令集主要有: ARM指令集,是ARM处理器的原生32位指令集,所有指令长度都是32位,以字对齐(4字节边界对齐)方式存储; 该指令集效率高,但是代码密度较低。 Thumb指令集是16位指令集,2字节边界对齐,是ARM指令集的子集; 在具有较高代码密度的同时,仍然保持ARM的大多数性能优势。 Thumb2指令集是对Thumb指令集的扩展,提供了几乎与ARM指令集完全相同的功能,同时具有16位和32位指令,既继承了Thumb指令集的高代码密度,又能实现ARM指令集的高性能; 2字节边界对齐,16位和32位指令可自由混合。 Thumb2EE指令集是Thumb2指令集的一个变体,用于动态产生的代码; 不能与ARM指令集和Thumb指令集交织在一起。 除了上面介绍的指令集外,ARM处理器还有针对协处理器的扩展指令集,如普通协处理器指令、NEON和VFP扩展指令集、无线MMX技术扩展指令集等。 3.1.1指令格式 ARM指令集的指令基本格式如下: < opcode > {< cond >} { S } < Rd >, < Rn >, < shift_operand > 指令中“< >”内的项是必需的,“{ }”内的项是可选的。各个项目的具体含义如表31所示。 表31ARM指令格式 符号说明 opcode操作码,即指令助记符,如MOV、SUB、LDR等 cond条件码,描述指令执行的条件 S可选后缀,指令后加上S,指令执行成功完成后自动更新CPSR寄存器中的条件标志位 Rd目的寄存器 Rn存放第1个操作数的寄存器 shift_operand第2个操作数,可以是寄存器、立即数等 3.1.2指令的条件码 ARM指令集中几乎所有指令都可以是条件执行的,由cond可选条件码来决定,位于ARM指令的最高4位[31∶28],可以使用的条件码如表32所示。 表32ARM指令条件码 指令条件码助记符CPSR条件标志位值含义 0000EQZ=1相等 0001NEZ=0不相等 0010CS/HSC=1无符号数大于或等于 0011CC/LOC=0无符号数小于 0100MIN=1负数 0101PLN=0正数或零 0110VSV=1溢出 0111VCV=0没有溢出 1000HIC=1,Z=0无符号数大于 1001LSC=0,Z=1无符号数小于或等于 1010GEN=V有符号数大于或等于 1011LTN!=V有符号数小于 1100GTZ=0,N=V有符号数大于 1101LEZ=1,N!=V有符号数小于或等于 1110AL任何无条件执行(指令默认条件) 1111NV任何从不执行(不要执行) 每种条件码的助记符由两个英文符号表示,在指令助记符的后面和指令同时执行。根据程序状态寄存器CPSR中的条件标志位[31∶28]判断当前条件是否满足,若满足则执行指令。若指令中有后缀S,则根据执行结果更新程序状态寄存器CPSR中的条件标志位[31∶28]。 3.2ARM指令的寻址方式 寻址方式是指处理器根据指令中给出的地址信息,找出操作数所存放的物理地址,实现对操作数的访问。根据指令中给出的操作数的不同形式,ARM指令系统支持的寻址方式有: 立即寻址、寄存器寻址、寄存器间接寻址、寄存器移位寻址、变址寻址、多寄存器寻址、相对寻址、堆栈寻址、块复制寻址等。 视频讲解 3.2.1立即寻址 立即寻址也叫立即数寻址,指令的操作码字段后面的地址码部分不是操作数地址而是操作数本身,包含在指令的32位编码中。立即数前要加前缀“#”。 示例: ADD R0,R0,#1; R0 ← R0 + 1 MOVR0,#0x00ff; R0 ← 0x00ff 3.2.2寄存器寻址 寄存器寻址是指将操作数放在寄存器中,指令中地址码部分给出寄存器编号。这是各类微处理器常用的一种有较高执行效率的寻址方式。 示例: ADD R0,R1,R2; R0 ← R1 + R2 MOVR0,R1; R0 ← R1 视频讲解 3.2.3寄存器间接寻址 操作数存放在存储器中,并将所存放的存储单元地址放入某一通用寄存器中,在指令中的地址码部分给出该通用寄存器的编号。 示例: LDRR0,[R1]; R0 ← [R1] 如图31所示,该指令将寄存器R1中存放的值0xA0000008作为存储器地址,将该存储单元中的数据0x00000003传送到寄存器R0中。 图31寄存器间接寻址方式示意图 3.2.4寄存器移位寻址 该指令中,寄存器的值在被送到ALU之前,先进行移位操作。移位的方式由助记符给出,移位的位数可由立即数或寄存器直接寻址方式表示。 可以采用的移位操作有: LSL: 逻辑左移,寄存器值低端空出的位补0。 LSR: 逻辑右移,寄存器值高端空出的位补0。 ASR: 算术右移,算术移位操作对象是有符号数,位移过程中要保证操作数的符号不变,若操作数是正数,高端空出位补0; 若操作数是负数,高端空出位补1。 ROR: 循环右移,从低端移出的位填入高端空出的位中。 RRX: 带扩展的循环右移,操作数右移1位,高端空出的位用C标志位填充。 示例: MOV R0,R1, LSL #2; R0 ← R1中的数左移2位 ADDR0, R1, R2, LSR #3; R0 ←R1 + R2中的数右移3位 3.2.5变址寻址 变址寻址方式是将某个寄存器(基址寄存器)的值与指令中给出的偏移量相加,形成操作数的有效地址,再根据该有效地址访问存储器。该寻址方式常用于访问在基址附近的存储单元。 示例: LDRR0,[R1, #2]; R0 ← [R1+2] 该指令将R1寄存器的值0xA0000008加上位移量2,形成操作数的有效地址,将该有效地址单元中的数据传送到寄存器R0中。 3.2.6多寄存器寻址 多寄存器寻址方式可以在一条指令中传送多个寄存器的值,一条指令最多可以传送16个通用寄存器的值。连续的寄存器之间用“”连接,不连续的中间用“,”分隔。 示例: LDMIAR0!,{R1R3, R5}; R1 ← [R0] ; R2 ← [R0 + 4] ; R3 ← [R0 + 8] ; R5 ← [R0 + 12] 如图32所示,该指令将R0寄存器的值0xA0000004作为操作数地址,将存储器中该地址开始的连续单元中的数据传送到寄存器R1、R2、R3、R5中。 图32多寄存器寻址方式示意图 3.2.7相对寻址 相对寻址方式就是以程序寄存器(PC)为基址寄存器,以指令中的地址标号为偏移量,两者相加形成操作数的有效地址。偏移量指出的是当前指令和地址标号之间的相对位置。子程序调用指令即是相对寻址方式。 示例: BLADDR1; 跳转到子程序ADDR1处执行 … ADDR1: … MOVPC,LR; 从子程序返回 3.2.8堆栈寻址 视频讲解 堆栈是按“先进后出”或“后进先出”方式进行存取的存储区。堆栈寻址是隐含的,使用一个叫作堆栈指针的专门寄存器,指示当前堆栈的栈顶。 根据堆栈的生成方式不同,分为递增堆栈和递减堆栈。当堆栈向高地址方向生长时,叫作递增堆栈(向上生长); 当堆栈向低地址方向生长时,叫作递减堆栈(向下生长)。 堆栈指针指向最后压入堆栈的数据时,称为满堆栈; 堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈。 这样就有四种类型的堆栈工作方式: 满递增堆栈(FA)、满递减堆栈(FD)、空递增堆栈(EA)、空递减堆栈(ED)。 示例: STMFD SP!,{R1R3, LR}; 将寄存器R1~R3和LR压入堆栈,满递减堆栈 LDMFDSP!,{R1R3, LR}; 将堆栈数据出栈,放入寄存器R1~R3和LR 3.2.9块复制寻址 块复制寻址方式是多寄存器传送指令LDM/STM的寻址方式。LDM/STM指令可以将存储器中的一个数据块复制到多个寄存器中,或将多个寄存器中的值复制到存储器中。寻址操作中使用的寄存器可以是R0~R15这16个寄存器的所有或子集。 根据基地址的增长方向是向上还是向下,以及地址的增减与指令操作的先后顺序(操作先进行还是地址先增减)的关系,有四种寻址方式: IB(Increment Before): 地址先增加再完成操作,如STMIB、LDMIB。 IA(Increment After): 先完成操作再地址增加,如STMIA、LDMIA。 DB(Decrement Before): 地址先减少再完成操作,如STMDB、LDMDB。 DA(Decrement After): 先完成操作再地址减少,如STMDA、LDMDA。 3.3ARM指令集简介 ARM指令集主要有跳转指令、数据处理指令、程序状态寄存器处理指令、加载/存储指令、协处理器指令和异常产生指令六大类。 ARM指令集是加载/存储型的,指令的操作数都存储在寄存器中,处理结果直接放入目的寄存器中。采用专门的加载/存储指令来访问系统存储器。 视频讲解 本节介绍ARM指令集中常用指令的用法和使用要点。 3.3.1跳转指令 跳转指令用于实现程序流程的跳转。在ARM程序中有两种方式可以实现程序流程的跳转: ① 直接向程序计数器PC中写入跳转地址,可以实现4GB地址空间内的任意跳转。例如: LDRPC, [PC, # + 0x00FF]; PC ← [PC + 8 + 0x00FF] ② 使用专门的跳转指令。 ARM指令集中的跳转指令可以完成从当前指令向前或向后的32MB地址空间的跳转。跳转指令主要包含以下几种指令。 1. B指令 B{条件}目标地址 跳转指令B是最简单的跳转指令,跳转到给定的目标地址,从那里继续执行。 示例: BWAITA; 无条件跳转到标号WAITA处执行 B0x1234; 跳转到绝对地址0x1234处 2. BL指令 BL{条件}目标地址 BL指令用于子程序调用,在跳转之前,将下一条指令的地址复制到链接寄存器R14(LR)中,然后跳转到指定地址执行。 示例: BLFUNC1; 将当前PC值保存到R14中,然后跳转到标号FUNC1处执行 3. BLX指令 BLX{条件}目标地址 BLX指令从ARM指令集跳转到指定地址执行,并将处理器的工作状态由ARM状态切换到Thumb状态,同时将PC值保存到链接寄存器R14中。 示例: BLX FUNC1; 将当前PC值保存到R14中,然后跳转到标号FUNC1处执行, ; 并切换到Thumb状态 BLX R0; 将当前PC值保存到R14中,然后跳转R0中的地址处执行, ; 并切换到Thumb状态 4. BX指令 BX{条件}目标地址 BX指令是带状态切换的跳转指令,跳转到指定地址执行。若目标地址寄存器的位[0]为1,处理器的工作状态切换为Thumb状态,同时将CPSR中的T标志位置1,目标地址寄存器的位[31∶1]复制到PC中; 若目标地址寄存器的位[0]为0,处理器的工作状态切换为ARM状态,同时将CPSR中的T标志位清0,目标地址寄存器的位[31∶1]复制到PC中。 示例: BXR0; 跳转R0中的地址处执行,如果R0[0]=1,切换到Thumb状态 视频讲解 3.3.2数据处理指令 数据处理指令主要完成寄存器中数据的各种运算操作。数据处理指令的使用原则: 所有操作数都是32位,可以是寄存器或立即数。 如果数据操作有结果,结果也为32位,放在目的寄存器中。 指令使用“两操作数”或“三操作数”方式,即每一个操作数寄存器和目的寄存器分别指定。 数据处理指令只能对寄存器的内容进行操作。指令后都可以选择S后缀来影响标志位。比较指令不需要后缀S,这些指令执行后都会影响标志位。 1. MOV指令 MOV{条件}{S}目的寄存器,源操作数 MOV指令将一个立即数、一个寄存器或被移位的寄存器传送到目的寄存器中。后缀S表示指令的操作是否影响标志位。如果目的寄存器是寄存器PC可以实现程序流程的跳转,寄存器PC作为目的寄存器且后缀S被设置,则在跳转的同时,将当前处理器工作模式下的SPSR值复制到CPSR中。 示例: MOVR0, #0x01; 将立即数0x01装入R0 MOVR0, R1; 将寄存器R1的值传送到R0 MOVSR0, R1, LSL #3; 将寄存器R1的值左移3位后传送到R0,并影响标志位 MOVPC, LR; 将链接寄存器LR的值传送到PC中,用于子程序返回 2. MVN指令 MVN{条件}{S}目的寄存器,源操作数 MVN指令将一个立即数、一个寄存器或被移位的寄存器的值先按位求反,再传送到目的寄存器中,后缀S表示是否影响标志位。 示例: MVNR0, #0x0FF; 将立即数0xFF按位求反后装入R0,操作后R0=0xFFFFFF00 MVNR0, R1; 将寄存器R1的值按位求反后传送到R0 3. ADD指令 ADD{条件}{S}目的寄存器,操作数1,操作数2 ADD指令将两个操作数相加后,结果放入目的寄存器中,同时根据操作的结果影响标志位。 示例: ADDR0, R0, #1; R0 = R0 + 1 ADDR0, R1, R2; R0 = R1 + R2 ADDR0, R1, R2, LSL #3; R0 = R1 + (R2 << 3) 4. SUB指令 SUB{条件}{S}目的寄存器,操作数1,操作数2 SUB指令用于把操作数1减去操作数2,将结果放入目的寄存器中,同时根据操作的结果影响标志位。 示例: SUBR0, R0, #1; R0 = R0 - 1 SUBR0, R1, R2; R0 = R1- R2 SUBR0, R1, R2, LSL #3; R0 = R1 - (R2 << 3) 5. RSB指令 RSB{条件}{S}目的寄存器,操作数1,操作数2 RSB指令称为逆向减法指令,用于把操作数2减去操作数1,将结果放入目的寄存器中,同时根据操作的结果影响标志位。 示例: RSBR0, R0, #0xFFFF; R0 = 0xFFFF- R0 RSBR0, R1, R2; R0 = R2 - R1 6. ADC指令 ADC{条件}{S}目的寄存器,操作数1,操作数2 ADC指令将两个操作数相加后,再加上CPSR中的C标志位的值,将结果放入目的寄存器中,同时根据操作的结果影响标志位。 示例: ADDSR0, R0, R2 ADCR1, R1, R3; 用于64位数据加法,(R1,R0)=(R1,R0)+(R3,R2) 7. SBC指令 SBC{条件}{S}目的寄存器,操作数1,操作数2 SBC指令用于操作数1减去操作数2,再减去CPSR中的C标志位值的反码,将结果放入目的寄存器中,同时根据操作的结果影响标志位。 示例: SUBSR0, R0, R2 SBCR1, R1, R3; 用于64位数据减法,(R1,R0)=(R1,R0)-(R3,R2) 8. RSC指令 RSC{条件}{S}目的寄存器,操作数1,操作数2 RSC指令用于操作数2减去操作数1,再减去CPSR中的C标志位值的反码,将结果放入目的寄存器中,同时根据操作的结果影响标志位。 示例: RSBSR2, R0, #0 RSCR3, R1, #0; 用于求64位数据的负数 RSCR0, R1, R2; R0 = R2 - R1-!C 9. AND指令 AND{条件}{S}目的寄存器,操作数1,操作数2 AND指令实现两个操作数的逻辑与操作,将结果放入目的寄存器中,同时根据操作的结果影响标志位; 常用于将操作数某些位清0。 示例: ANDR0, R1, R2; R0 = R1 & R2 ANDR0, R0, #3; R0的位0和位1不变,其余位清0 10. ORR指令 ORR{条件}{S}目的寄存器,操作数1,操作数2 ORR指令实现两个操作数的逻辑或操作,将结果放入目的寄存器中,同时根据操作的结果影响标志位。常用于将操作数某些位置1。 示例: ORRR0, R0, #3; R0的位0和位1置1,其余位不变 11. EOR指令 EOR{条件}{S}目的寄存器,操作数1,操作数2 EOR指令实现两个操作数的逻辑异或操作,将结果放入目的寄存器中,同时根据操作的结果影响标志位。常用于将操作数某些位置取反。 示例: EORR0, R0, #0F; R0的低4位取反 12. BIC指令 BIC{条件}{S}目的寄存器,操作数1,操作数2 BIC指令用于清除操作数1的某些位,将结果放入目的寄存器中,同时根据操作的结果影响标志位。操作数2为32位掩码,掩码中设置了哪些位则清除操作数1中这些位。 示例: BICR0, R0, #0F; 将R1的低4位清0,其他位不变 13. CMP指令 CMP{条件}操作数1,操作数2 CMP指令用于把一个寄存器的值减去另一个寄存器的值或立即数,根据结果设置CPSR中的标志位,但不保存结果。 示例: CMPR1, R0; 将R1的值减去R0的值,并根据结果设置CPSR的标志位 CMPR1, #0x200; 将R1的值减去0x200,并根据结果设置CPSR的标志位 14. CMN指令 CMN{条件}操作数1,操作数2 CMN指令用于把一个寄存器的值减去另一个寄存器或立即数取反的值,根据结果设置CPSR中的标志位,但不保存结果。该指令实际完成两个操作数的加法。 示例: CMNR1, R0; 将R1的值和R0的值相加,并根据结果设置CPSR的标志位 CMNR1, #0x200; 将R1的值和立即数0x200相加,并根据结果设置CPSR的标志位 15. TST指令 TST{条件}操作数1,操作数2 TST指令用于把一个寄存器的值和另一个寄存器的值或立即数进行按位与运算,根据结果设置CPSR中的标志位,但不保存结果。该指令常用于检测特定位的值。 示例: TSTR1, #0x0F; 检测R1的低4为是否为0 16. TEQ指令 TEQ{条件}操作数1,操作数2 TST指令用于把一个寄存器的值和另一个寄存器的值或立即数进行按位异或运算,根据结果设置CPSR中的标志位,但不保存结果。该指令常用于检测两个操作数是否相等。 示例: TEQR1, R2; 将R1的值和R2的值进行异或运算,并根据结果设置CPSR的标志位 3.3.3程序状态寄存器处理指令 视频讲解 MRS指令和MSR指令用于在状态寄存器和通用寄存器间传输数据。状态寄存器的值要通过“读取→修改→写回”三个步骤操作来实现,可先用MRS指令将状态寄存器的值复制到通用寄存器中,修改后再通过MSR指令把通用寄存器的值写回状态寄存器。 示例: MRSR0, CPSR; 将CPSR的值复制到R0中 ORRR0, R0, #C0; R0的位6和位7置1,即屏蔽外部中断和快速中断 MSRCPSR, R0; 将R0值写回到CPSR中 MRS指令和MSR指令的格式如下: MRS{条件}通用寄存器, 程序状态寄存器(CPSR或SPSR) MSR{条件}程序状态寄存器(CPSR或SPSR)_<域>, 操作数 其中,MSR指令中的<域>可用于设置程序状态寄存器中需要操作的位: 位[31∶24]为条件标志位域,用f表示。 位[23∶16]为状态位域,用s表示。 位[15∶8]为扩展位域,用x表示。 位[7∶0]为控制位域,用c表示。 示例: MSRCPSR_cxsf, R3 视频讲解 3.3.4加载/存储指令 加载/存储指令用于在寄存器和存储器之间传输数据,Load指令用于将存储器中的数据传输到寄存器中,Store指令用于将寄存器中的数据保存到存储器中。 1. LDR指令 LDR{条件}目的寄存器, <存储器地址> LDR指令将一个32位字数据传输到目的寄存器中。如果目的寄存器是PC,从存储器中读出的数据将作为目的地址,以实现程序流程的跳转。 示例: LDRR1, [R0, #0x12]; 将存储器地址为R0+0x12的字数据写入R1 LDRR1, [R0, R2]; 将存储器地址为(R0+R2)的字数据写入R1 2. STR指令 STR{条件}源寄存器, <存储器地址> STR指令用于从源寄存器中将一个32位字数据写入存储器中。 示例: STRR1, [R0, #0x12]; 将R1中的字数据写入以R0+0x12为地址的存储器中 STRR1, [R0], #0x12; 将R1中的字数据写入以R0+0x12为地址的存储器中, ; 并将新地址R0+0x12写入R0 3. LDM和STM指令 LDM(或STM){条件}{类型}基址寄存器{!},寄存器列表{∧} LDM指令和STM指令实现一组寄存器和一片连续存储空间之间的数据传输。LDM指令加载多个寄存器,STM指令存储多个寄存器,它们常用于现场保护、数据复制、参数传输等,有8种模式: IA: 每次传送后地址加4。 IB: 每次传送前地址加4。 DA: 每次传送后地址减4。 DB: 每次传送前地址减4。 FD: 满递减堆栈。 ED: 空递减堆栈。 FA: 满递增堆栈。 EA: 空递增堆栈。 可选后缀{!},选用该后缀,当数据传输完成后,将最后地址写入基址寄存器中,否则基址寄存器值不变; 基址寄存器不能为R15(PC),寄存器列表可以是R0~R15的任意组合。 可选后缀{∧},当指令为LDM且寄存器列表中有R15(PC),选用该后缀表示除了完成数据传输以外,还将SPSR复制到CPSR。 示例: LDMIAR0, {R3R9}; R0指向的存储器单元的数据,保存到R3~R9中,R0值不更新 STMIAR1!, {R3R9}; 将R3~R9数据存储到R1指向的存储器单元中,R1的值更新 4. SWP指令 SWP{条件}目的寄存器,源寄存器1, [源寄存器2] SWP指令用于将源寄存器2所指向的存储器中的字数据传输到目的寄存器中,同时将源寄存器1中的字数据传输到源寄存器2所指向的存储器中。当源寄存器1和目的寄存器为同一个寄存器时,该指令完成该寄存器和存储器内容的交换。 示例: SWPR1, R1, [R0]; 将R1的内容与R0指向的存储单元的内容进行交换 SWPR1, R2, [R0]; 将R0指向的存储单元的内容写入R1中,并将R2的内容写入 ; 该内存单元中 3.3.5协处理器指令 视频讲解 ARM体系结构允许通过增加协处理器来扩展指令集。ARM协处理器具有自己专用的寄存器组,它们的状态由控制ARM状态的指令的镜像指令来控制。程序的控制流指令由ARM处理器来处理,所有的协处理器指令只能同数据处理和数据传送有关。 ARM协处理器指令可完成下面3类操作: ARM协处理器的数据处理操作。 ARM处理器和协处理器的寄存器之间数据传输。 ARM协处理器的寄存器和存储器之间数据传输。 ARM协处理器指令主要包括5条,它们的格式和功能如表33所示。 表33ARM协处理器指令 助记符说明功能 CDP coproc,opcodel,CRd,CRn,CRm{,opcode2}协处理器数据操作指令用于ARM处理器通知协处理器执行特定的操作 LDC{L} coproc,CRd <地址>协处理器数据读取指令从某一连续的存储单元将数据读取到协处理器的寄存器中 STC{L} coproc,CRd <地址>协处理器数据写入指令将协处理器的寄存器数据写入某一连续的存储单元中 MCR coproc,opcodel,Rd,CRn{,opcode2}ARM寄存器到协处理器寄存器的数据传输指令将ARM处理器的寄存器中的数据传输到协处理器的寄存器中 MRC coproc,opcodel,Rd,CRn{,opcode2}协处理器寄存器到ARM寄存器的数据传输指令将协处理器的寄存器中的数据传输到ARM处理器的寄存器中 3.3.6异常产生指令 视频讲解 ARM处理器有两条异常产生指令,软中断指令(SWI)和断点中断指令(BKPT)。 1. SWI指令 SWI{条件}24位立即数 SWI指令用于产生SWI异常中断,实现从用户模式切换到管理模式,CPSR保存到管理模式下的SPSR中,执行转移到SWI向量。其他模式下也可使用SWI指令,同样切换到管理模式。该指令不影响条件码标志。 示例: SWI0x02; 软中断,调用操作系统编号为0x02的系统例程 2. BKPT指令 BKPT16位立即数 BKPT指令产生软件断点中断,软件调试程序可以使用该中断。立即数会被ARM硬件忽视,但能被调试工具利用来得到有用的信息。 示例: BKPT0xFF32 视频讲解 3.4Thumb指令集简介 为了兼容存储系统总线宽度为16位的应用系统,ARM体系结构中提供了16位Thumb指令集,它可以被看作ARM指令压缩形式的子集,是针对代码密度的问题而提出的,它具有16位的代码密度,这对于嵌入式系统来说至关重要。 Thumb不是一个完整的体系结构,不能指望处理器只执行Thumb指令而不支持ARM指令集。因此,Thumb指令只需要支持通用功能,必要时可以借助完善的ARM指令集。只要遵循一定的调用规则,Thumb子程序和ARM子程序可以互相调用。当处理器在执行ARM程序段时,称ARM处理器处于ARM工作状态; 当处理器在执行Thumb程序段时,称ARM处理器处于Thumb工作状态。 Thumb指令集没有协处理器指令、信号量指令以及访问CPSR或SPSR的指令,没有乘加指令及64位乘法指令等,并且指令的第二操作数受到限制; 除了分支指令B有条件执行功能外,其他指令均为无条件执行; 大多数Thumb数据处理指令采用2地址格式。 Thumb指令集与ARM指令集的区别一般有如下4点: ① 跳转指令。 程序相对转移,特别是条件跳转与ARM代码下的跳转相比,在范围上有更多的限制,转向子程序是无条件的转移。 ② 数据处理指令。 Thumb数据处理指令是对通用寄存器进行操作,在大多数情况下,操作的结果必须放入其中一个操作数寄存器中,而不是第3个寄存器中。Thumb数据处理操作比ARM状态的更少。访问R8~R15受到一定限制: 除MOV和ADD指令访问寄存器R8~R15外,其他数据处理指令总是更新CPSR中ALU状态标志; 访问寄存器R8~R15的Thumb数据处理指令不能更新CPSR中的ALU状态标志。 ③ 单寄存器加载和存储指令。 在Thumb状态下,单寄存器加载和存储指令只能访问寄存器R0~R7。 ④ 多寄存器加载和多寄存器存储指令。 LDM和STM指令可以将任何范围为R0~R7的寄存器子集加载或存储。PUSH和POP指令使用堆栈指针R13作为基址实现满递减堆栈。除R0~R7外,PUSH指令还可以存储链接寄存器R14,并且POP指令可以加载程序计数器PC。 3.5ARM汇编语言编程简介 3.5.1伪操作 ARM汇编语言程序是由机器指令、伪指令和伪操作组成的。伪操作是ARM汇编语言程序里的一些特殊的指令助记符,和指令系统中的助记符不同,这些助记符没有相应的操作码。伪操作主要是为完成汇编程序做一些准备工作,在源程序汇编过程中起作用,一旦汇编完成,伪操作的使命就完成。 宏是一段独立的程序代码,通过伪操作定义,在程序中使用宏指令即可调用宏。当程序被汇编时,汇编程序校对每个宏调用进行展开,用宏定义代替源程序中的宏指令。 1. 符号定义伪操作 符号定义伪操作用于定义ARM汇编程序中的变量、对变量赋值及定义寄存器名称等。常用伪操作有: GBLA、GBLL和GBLS: 定义全局变量。 LCLA、LCLL和LCLS: 定义局部变量。 SETA、SETL和SETS: 为变量赋值。 RLIST: 为通用寄存器列表定义名称。 CN: 为协处理器的寄存器定义名称。 CP: 为协处理器定义名称。 DN和SN: 为VFP的寄存器定义名称。 FN: 为FPA的浮点寄存器定义名称。 2. 数据定义伪操作 数据定义伪操作用于数据表定义、文字池定义、数据空间分配等。常用伪操作有: LTORG: 声明一个数据缓冲池的开始。 MAP: 定义一个结构化的内存表的首地址。 FIELD: 定义结构化内存表的一个数据域。 SPACE: 分配一块内存空间,并用0初始化。 DCB: 分配一段字节的内存单元,并用指定的数据初始化。 DCD和DCDU: 分配一段字的内存单元,并用指定的数据初始化。 DCFD和DCFDU: 分配一段双字的内存单元,并用双精度的浮点数据初始化。 DCFS和DCFSU: 分配一段字的内存单元,并用单精度的浮点数据初始化。 DCQ和DCQU: 分配一段双字的内存单元,并用64位整型数据初始化。 DCW和DCWU: 分配一段半字的内存单元,并用指定的数据初始化。 3. 汇编控制伪操作 汇编控制伪操作用于条件汇编、宏定义、重复汇编控制等,常用伪操作有: IF、ELSE和ENDIF: 根据条件把一段源程序代码包括在汇编程序内或排除在程序之外。 WHILE和WEND: 根据条件重复汇编相同的源程序代码段。 MACRO和MEND: MACRO标识宏定义的开始,MEND标识宏定义结束。用MACRO和MEND定义一段代码,称为宏定义体,在程序中可以通过宏指令多次调用该代码段。 MEXIT: 用于从宏中跳转出去。 4. 其他伪操作 其他伪操作常用的有段定义伪操作、入口点设置伪操作、包含文件伪操作、标号导出或引入声明等。 ALIGN: 边界对齐。 AREA: 段定义。 CODE16和CODE32: 指令集定义。 END: 汇编结束。 ENTRY: 程序入口。 EQU: 常量定义。 EXPORT和GLORBAL: 声明一个符号可以被其他文件引用。 IMPORT和EXTERN: 声明一个外部符号。 GET和INCLUDE: 包含文件。 视频讲解 INCBIN: 包含不被汇编的文件。 RN: 给特定的寄存器命名。 ROUT: 标记局部标号使用范围的界限。 3.5.2伪指令 ARM中的伪指令并不是真正的ARM或Thumb指令,这些伪指令在汇编编译器对源程序进行汇编处理时被替换成对应ARM或Thumb指令(序列)。常用的伪指令如下。 (1) ADR ADR为小范围的地址读取伪指令,该指令将基于PC的相对偏移地址或基于寄存器的相对偏移地址读取到寄存器中。格式为 ADR {cond} register, expr cond是可选的指令执行条件。 register是目的寄存器。 expr是基于PC或基于寄存器的地址表达式,当地址值是字节对齐时,取值范围为-255~255B; 当地址值是字对齐时,取值范围为-1020~1020B; 当地址值是16字节对齐时,取值范围更大。 (2) ADRL ADRL为中等范围的地址读取伪指令,该指令比ADR的取值范围更大。格式为 ADRL {cond} register, expr cond是可选的指令执行条件。 register是目的寄存器。 expr是基于PC或基于寄存器的地址表达式,当地址值是字节对齐时,取值范围为-64~64KB; 当地址值是字对齐时,取值范围为-256~256KB; 当地址值是16字节对齐时,取值范围更大; 在32位的Thumb2指令中,取值范围可为-1~1MB。 (3) LDR LDR为大范围的地址读取伪指令,将一个32位的常数或者一个地址值读取到寄存器中。格式为 LDR {cond} register, = [expr|labelexpr] cond是可选的指令执行条件。 register是目的寄存器。 expr是32位常量。 (4) NOP NOP是空操作伪指令,在汇编时被替换成ARM中的空操作。 3.5.3汇编语句格式 ARM(Thumb)汇编语言的语句格式为 [标号] <指令|条件|S> <操作数>[; 注释] 在ARM汇编程序中,ARM指令、伪操作、伪指令、伪操作的助记符全部用大写字母,或者全部用小写字母,不能既有大写也有小写字母。 所有标号在一行的顶格书写,后面不要添加“: ”,所有指令不能顶格书写。 注释内容以“;”开头到本行结束。 源程序中允许有空行,如果单行太长,可以用字符“\”将其分开,“\”后不能有任何字符,包括空格和制表符等。 变量的设置,常量的定义,其标识符必须在一行顶格书写。 3.5.4汇编语言的程序结构 段(section)是ARM汇编语言组织源文件的基本单位,是独立的、具有特定名称的、不可分割的指令或数据序列。段分为代码段和数据段,代码段存放执行代码,数据段存放代码执行时需要的数据。一个ARM汇编程序至少需要一个代码段,较大的程序可以包含多个代码段和数据段。 ARM汇编语言源程序经过汇编后生成可执行的映像文件,文件格式有axm、bin、elf、hex等。可执行的映像文件通常包括3部分: 一个或多个代码段,代码段的属性为只读。 0个或多个包含初始化数据的数据段,属性为可读可写。 0个或多个不包含初始化数据的数据段,属性为可读可写。 链接器根据一定的规则将各个段安排到内存的相应位置。源程序中段之间的相对位置与可执行的映像文件中段的相对位置不一定相同。 下面的程序说明了ARM汇编语言程序的基本结构: AREABUF, DATA, READWRITE; 声明数据段BUF countDCB30; 定义一个字节单元count AREAEXAMPLE1, CODE, READONLY; 声明代码段EXAMPLE1 ENTRY; 程序入口 CODE32; 声明32位ARM指令 START LDRBR0, count; R0 = count MOVR1, #10; R1 = 8 ADDR0, R0, R1; R0 = R0 + R1 BSTART END 视频讲解 3.6C语言与汇编语言的混合编程 在嵌入式开发中,C语言是一种常见的程序设计语言。C语言程序可读性强、易维护、可移植性和可靠性高。ARM体系结构不仅支持汇编语言也支持C语言,在一些情况下需要采用汇编语言和C语言混合编程。 视频讲解 视频讲解 3.6.1C程序中内嵌汇编 在C语言程序中嵌入汇编可以完成一些C语言不能完成的操作,同时代码效率也比较高。 在ARM C语言程序中使用关键词__asm来标识一段汇编指令代码,格式为: __asm//asm前2个下画线 { instruction [; instruction] … [instruction] } 如果一行有多条汇编指令,指令间用“;”隔开; 如果一条指令占多行,要使用续行符号“\”。 在ARM C语言程序中也可以使用关键词asm来标识一段汇编指令代码,格式为: asm("instruction [; instruction]"); 3.6.2汇编中访问C语言程序变量 在C语言中声明的全局变量可以被汇编语言通过地址间接访问。 例如,在C语言程序中已经声明了一个全局变量glovbvar,通过IMPORT伪指令声明外部变量的方式访问: AREAEXAMPLE2, CODE, REDAONLY IMPORTglovbvar; 声明外部变量glovbvar START LDRR1, =glovbvar; 装载外部变量地址 LDR R0, [R1]; 读出全局变量glovbvar数据 ADDR0, R0, #1 STRR0,[R1]; 保存变量值 MOVPC, LR END 3.6.3ARM中的汇编和C语言相互调用 为了使单独编译的C语言程序和汇编语言程序之间能够相互调用,必须遵守ATPCS规则。ATPCS即ARMThumb Procedure Call Standard(ARMThumb过程调用标准)的简称。 ATPCS规定了应用程序的函数可以如何分开地写,分开地编译,最后将它们连接在一起,所以它实际上定义了一套有关过程(函数)调用者与被调用者之间的协议。基本ATPCS规定了在子程序调用时的一些基本规则,包括3个方面的内容: 各寄存器的使用规则及其相应的名称。 数据栈的使用规则。 参数传递的规则。 1. C程序调用汇编程序 C程序调用汇编程序首先通过extern声明要调用的汇编程序模块,声明中形参个数要与汇编程序模块中需要的变量个数一致,且参数传递要满足ATPCS规则,然后在C程序中调用。 示例: #include<stdio.h> extern void strcopy(char *d, char *s); //使用关键词声明 int main() { char *srcstr = "first"; char *dststr = "second"; strcopy(dststr,srcstr); //汇编模块调用 } 被调用的汇编程序: AREAScopy, CODE, REDAONLY EXPORT strcopy ; 使用EXPORT伪操作声明本汇编程序 strcopy LDRB R2, [R1], #1 STRB R2, [R0], #1 CMPR2, #0 BNE strcopy MOV PC, LR END 2. 汇编程序调用C程序 在调用之前必须根据C语言模块中需要的参数个数,以及ATPCS参数规则,完成参数传递,即前4个参数通过R0~R3传递,后面的参数通过堆栈传递,然后再利用B、BL指令调用。 示例: int g(int a,int b,int c,int d,int e)//C语言函数原型 { return (a+b+c+d+e); } //汇编程序调用C程序g()计算i+2*i+3*i+4*i+5*i的结果 EXPORT f AREAf, CODE, REDAONLY IMPORT g //声明C程序函数g() STR LR, {SP, #4}!//保存PC ADD R1 ,R0, R0 ADD R2, R1, R0 ADD R3, R1, R2 STR R3, {SP, #4}! ADD R3, R1, R1 BL g //调用C程序函数g() ADD SP, SP, #4 LDR PC, [SP], #4 END 3.7本章小结 本章首先对ARM处理器指令的9种寻址方式进行了说明,并详细介绍了ARM指令集中各种指令的格式、功能和使用方法,简单介绍了16位的Thumb指令; 随后介绍了ARM汇编语言的伪操作、伪指令和汇编语句格式,通过示例讲述了汇编语言程序的结构; 最后讲述了C语言和汇编语言混合编程的规则和方法。通过本章,读者了解到ARM程序设计的基本知识,为基于ARM处理器的嵌入式软硬件开发奠定基础。 习题 1. 请写出对应C代码的ARM指令。 C代码: If(a > b) a++; Else b++; 2. 请写出下列ARM指令的功能。 MOVR1,#0x10; MOVR0,R1; MOVSR3,R1,LSL #2; MOVPC,LR 3. 在前文提到了实现Thumb状态和ThumbEE状态之间切换的指令为ENTERX指令和LEAVEX指令,请查阅相关资料对这两个命令进行比较。 4. 请查阅资料,试比较ARM、Thumb、Thumb2、Thumb2EE指令集的区别。 5. 存储器从0x400000开始的100个单元中存放着ASCII码,编写汇编语言程序,将其所有的小写字母转换成大写字母,对其他的ASCII码不做转换。 6. 使用ARM汇编语言指令编写一个实现冒泡排序功能的程序段。 7. 编写一个实现数组排序的C语言程序,要求调用第6题中用汇编语言编写的冒泡排序程序段。 8. 嵌入式系统中经常要用到无限循环,怎样用C语言编写死循环呢? 9. 请评论如下代码段: unsigned int zero = 0; unsigned int compzero = 0xFFFF; //1's complement of zero