4. ARM指令系统:常用指令、寻址方式与Thumb/Thumb-2指令集
ARM处理器采用RISC(精简指令集计算机)架构,其指令系统具有定长(ARM状态为32位)、Load/Store结构、条件执行等特点。嵌入式系统开发中,掌握ARM指令系统是编写启动代码、驱动程序和底层控制逻辑的基础。本章围绕ARM指令系统的三大核心——常用指令分类、寻址方式、Thumb/Thumb-2指令集——展开,帮助考生建立从指令编码到实际编程的完整知识链路。
4.1 常用指令分类与核心原理
ARM指令集按功能可分为四大类:数据传送指令、算术逻辑运算指令、分支跳转指令和存储器访问指令。理解这些指令的编码格式、操作数来源和条件执行特性,是编写高效汇编代码的前提。
4.1.1 数据传送指令
数据传送指令用于在寄存器之间、寄存器与立即数之间移动数据,不改变CPSR(当前程序状态寄存器)中的条件标志位(除非指令带S后缀)。核心指令包括:
- MOV:将立即数或寄存器值传送到目标寄存器。例如
MOV R0, #0x100将立即数0x100装入R0。 - MVN:将操作数按位取反后传送。例如
MVN R1, R2将R2取反后存入R1,常用于生成掩码。 - LDR:从内存加载数据到寄存器(伪指令
LDR R0, =0x40000000用于加载32位立即数)。 - STR:将寄存器数据存储到内存。
LDR R0, =value 伪指令。
4.1.2 算术逻辑运算指令
算术逻辑指令执行加法、减法、位运算等操作,可选项S后缀决定是否更新CPSR中的N、Z、C、V标志位。常用指令如下:
| 指令 | 功能 | 示例 | 说明 |
|---|---|---|---|
| ADD | 加法 | ADD R0, R1, R2 | R0 = R1 + R2 |
| SUB | 减法 | SUB R0, R1, #1 | R0 = R1 - 1 |
| RSB | 反向减法 | RSB R0, R1, #0 | R0 = 0 - R1(取负) |
| AND | 按位与 | AND R0, R1, R2 | R0 = R1 & R2 |
| ORR | 按位或 | ORR R0, R1, #0xF0 | R0 = R1 | 0xF0 |
| EOR | 按位异或 | EOR R0, R1, R2 | R0 = R1 ^ R2 |
| BIC | 位清零 | BIC R0, R1, #0xFF | R0 = R1 & ~0xFF |
| CMP | 比较(减法不存结果) | CMP R0, R1 | 更新标志位,不写寄存器 |
4.1.3 分支跳转指令
分支指令用于改变程序执行流,支持条件执行。ARM分支指令的跳转范围为±32MB(24位偏移量左移2位)。核心指令包括:
- B:无条件跳转,如
B loop。 - BL:带链接跳转,跳转前将返回地址(PC+4)存入LR寄存器,用于函数调用。
- BX:跳转并切换指令集状态(ARM/Thumb),目标地址最低位为1表示Thumb状态。
- BLX:带链接的跳转并切换状态,结合BL和BX功能。
ADDEQ R0, R1, R2 仅在Z=1(相等)时执行加法。这一特性可减少分支指令数量,提高代码密度。
4.1.4 存储器访问指令
ARM采用Load/Store架构,只有存储器访问指令才能读写内存。除LDR/STR单寄存器访问外,还有多寄存器批量传输指令:
- LDMIA / STMIA:加载/存储多个寄存器,地址递增(Increment After)。
- LDMDB / STMDB:地址递减(Decrement Before),常用于栈操作。
- SWP:交换寄存器与内存值,实现原子操作(ARMv6之前)。
; 栈操作示例:使用STMDB和LDMIA实现压栈/出栈
STMDB SP!, {R0-R3, LR} ; 压栈:SP先减4,再存储R0-R3和LR
LDMIA SP!, {R0-R3, PC} ; 出栈:加载R0-R3和PC(实现函数返回)
4.2 寻址方式详解
寻址方式决定了指令如何获取操作数。ARM支持多种寻址方式,理解其区别对正确编写和阅读汇编代码至关重要。
4.2.1 立即数寻址
操作数直接编码在指令中,格式为8位立即数循环右移偶数位。例如 MOV R0, #0xFF 中0xFF可直接表示;而 MOV R0, #0x12345678 无法直接编码,需用伪指令。
4.2.2 寄存器寻址
操作数存放在寄存器中,指令中直接指定寄存器编号。例如 ADD R0, R1, R2 表示R0 = R1 + R2。这是最常用的寻址方式,执行速度快。
4.2.3 寄存器移位寻址
第二个操作数可先经过移位再参与运算,移位量由立即数(5位)或寄存器指定。ARM支持的移位类型包括:
- LSL:逻辑左移,低位补0。
- LSR:逻辑右移,高位补0。
- ASR:算术右移,高位用符号位填充。
- ROR:循环右移,移出的位补到高位。
- RRX:带扩展的循环右移(含C标志位)。
; 移位寻址示例
MOV R0, #0x80000000
MOV R1, R0, LSR #4 ; R1 = 0x08000000,逻辑右移4位
MOV R2, R0, ASR #4 ; R2 = 0xF8000000,算术右移保留符号
MOV R3, R0, ROR #4 ; R3 = 0x08000000,循环右移
4.2.4 基址变址寻址
通过基址寄存器加偏移量访问内存,偏移量可为立即数、寄存器或移位后的寄存器。ARM提供三种形式:
| 形式 | 语法 | 说明 |
|---|---|---|
| 前变址 | LDR R0, [R1, #4] | 地址 = R1 + 4,R1不变 |
| 前变址回写 | LDR R0, [R1, #4]! | 地址 = R1 + 4,R1更新为R1+4 |
| 后变址 | LDR R0, [R1], #4 | 地址 = R1,R1更新为R1+4 |
4.2.5 多寄存器寻址
一条指令可同时访问多个寄存器,寄存器列表用花括号表示,支持范围表示和连续列表。例如 LDMIA R0!, {R1-R4, R6} 从R0指向的内存连续加载5个寄存器,R0自动更新。
4.3 Thumb/Thumb-2指令集
Thumb指令集是ARM指令集的16位压缩版本,将常用指令压缩为16位编码,代码密度提升约30%。Thumb-2在ARMv6T2及以后架构中引入,支持16位和32位混合编码,兼顾代码密度与性能。
4.3.1 Thumb指令集特点
- 指令长度固定为16位,代码密度高,适合存储受限的嵌入式系统。
- 大部分指令只能访问低8个寄存器(R0-R7),高寄存器(R8-R15)仅可用于MOV、ADD、CMP等少数指令。
- 条件执行仅保留分支指令的条件码,普通指令不支持条件执行。
- 立即数范围和移位操作受限,某些复杂操作需多条指令完成。
4.3.2 Thumb-2指令集特点
Thumb-2是Thumb的扩展,引入32位指令编码,解决了Thumb指令功能受限的问题。其关键特性包括:
- 同时支持16位和32位指令,处理器自动识别指令长度。
- 支持全部ARM指令功能,包括条件执行、大范围立即数、多寄存器操作等。
- 保持高代码密度,平均代码尺寸比ARM状态减少约26%。
- Cortex-M系列处理器仅支持Thumb-2指令集,不支持ARM状态。
4.3.3 ARM/Thumb状态切换
ARM处理器(Cortex-A/R系列)可在ARM状态和Thumb状态间切换,切换方法包括:
- BX指令:跳转到目标地址,若地址最低位为1则切换到Thumb状态。
- BLX指令:带链接的跳转并切换状态。
- 异常返回:从异常处理返回时,通过SPSR的T位恢复状态。
; ARM状态切换到Thumb状态
LDR R0, =thumb_func
BX R0 ; R0最低位为1,切换到Thumb状态
; Thumb状态切换到ARM状态
LDR R0, =arm_func
BX R0 ; R0最低位为0,切换到ARM状态
4.4 实战案例:启动代码中的指令应用
以STM32F407(Cortex-M4内核)的启动代码为例,分析Thumb-2指令在实际工程中的应用。Cortex-M4仅支持Thumb-2指令集,因此所有代码均以Thumb-2形式编写。
场景背景:系统上电后,需要完成栈指针初始化、中断向量表设置、系统时钟配置和C运行环境初始化。启动代码使用汇编编写,涉及多种指令类型。
关键代码片段分析:
; 启动代码关键部分(简化)
__Vectors:
DCD __initial_sp ; 栈顶地址
DCD Reset_Handler ; 复位向量
; ... 其他中断向量
Reset_Handler:
LDR R0, =__initial_sp ; 加载栈顶地址
MOV SP, R0 ; 设置栈指针
LDR R0, =SystemInit ; 加载系统初始化函数地址
BLX R0 ; 调用并切换到Thumb状态
LDR R0, =__main ; 加载C库入口
BX R0 ; 跳转到C运行环境
指令应用分析:
| 指令 | 作用 | 寻址方式 |
|---|---|---|
| LDR R0, =__initial_sp | 加载32位立即数(伪指令) | 立即数寻址(扩展) |
| MOV SP, R0 | 设置栈指针 | 寄存器寻址 |
| BLX R0 | 带链接跳转并切换状态 | 寄存器间接跳转 |
| BX R0 | 跳转并切换状态 | 寄存器间接跳转 |
结果与复盘:该启动代码在Cortex-M4上运行正常,代码尺寸约200字节。通过使用Thumb-2指令,相比纯ARM状态代码节省约30%存储空间。实际开发中需注意:Cortex-M系列不支持ARM状态,所有函数指针最低位必须为1(表示Thumb状态),否则触发硬件错误。
4.5 常见误区与总结复盘
学习ARM指令系统时,考生常出现以下误区,需特别注意:
- 误区一:认为MOV指令可以加载任意32位立即数。实际上,只有符合"8位立即数循环右移偶数位"规则的值才能直接编码,其他值需用LDR伪指令。
- 误区二:混淆LDR伪指令与LDR加载指令。
LDR R0, =value是伪指令(编译器可能转换为MOV或字面量池加载),而LDR R0, [R1]是真正的内存加载指令。 - 误区三:忽略条件执行标志位的影响。CMP指令会更新CPSR,后续条件指令依赖这些标志,中间插入其他指令可能改变标志位。
- 误区四:在Cortex-M上使用ARM状态指令。Cortex-M仅支持Thumb-2,使用ARM指令会导致硬件错误。
- 误区五:多寄存器指令中寄存器列表顺序理解错误。LDM/STM指令总是从低地址到高地址依次对应低编号到高编号寄存器,与列表书写顺序无关。
本章核心要点回顾:
- ARM指令系统四大类:数据传送、算术逻辑、分支、存储器访问,每类指令有特定应用场景。
- 寻址方式决定操作数获取方式,基址变址寻址是内存访问的核心,需掌握前变址、回写、后变址的区别。
- Thumb-2指令集是Cortex-M系列的基础,兼具代码密度和性能优势,无需手动状态切换。
- 条件执行是ARM指令的特色功能,可减少分支指令,提高代码效率。
- 实际开发中,汇编指令主要用于启动代码和底层驱动,业务逻辑以C语言为主。