Files

311 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [嵌入式系统, 复习, ARM指令集]
create time: 2026-06-10 11:11
---
# ARM指令集与汇编
## 概述
本文档详细讲解ARM指令集体系,包括ARM/Thumb指令集对比、指令格式、核心指令详解、条件码、寻址方式以及汇编程序设计。这是嵌入式系统课程中需要动手实践的重点部分。
## 正文
### 1. ARM指令集 vs Thumb指令集
| 比较维度 | ARM指令集 | Thumb指令集 |
|----------|-----------|-------------|
| 指令宽度 | **32位** | **16位** |
| 功能 | 完整功能 | ARM的子集 |
| 代码密度 | 低(占用空间大) | 高(占用空间小) |
| 性能 | 高 | 略低 |
| 状态标志 | CPSR.T = 0 | CPSR.T = 1 |
> [!warning] Thumb状态不能执行ARM指令
> 在Thumb状态下,处理器只能执行16位的Thumb指令。如果需要执行32位ARM指令,必须先切换到ARM状态(通过BX指令)。两种状态不能在同一时刻混合执行。
### 2. 指令格式
ARM指令的基本格式为:
```
opcode{condition}{S} Rd, operand1, operand2
```
| 字段 | 说明 |
|------|------|
| opcode | 操作码,如MOV, ADD, LDR |
| {condition} | 条件码,如EQ, NE, GT(可选) |
| {S} | 是否影响CPSR标志位(可选) |
| Rd | 目标寄存器 |
| operand1 | 第一操作数(通常是寄存器) |
| operand2 | 第二操作数(灵活,可带移位) |
```mermaid
graph LR
subgraph format["Instruction Format"]
direction LR
OP["opcode"] --> CON["condition"] --> S["S flag"] --> RD["Rd"] --> OP1["operand1"] --> OP2["operand2"]
end
style OP fill:#4fc3f7
style CON fill:#81d4fa
style S fill:#b3e5fc
style RD fill:#ffcc80
style OP1 fill:#ffab91
style OP2 fill:#a5d6a7
```
### 3. 桶形移位器(Barrel Shifter)
ARM指令的一个强大特性:**第二操作数可以在送入ALU之前先进行移位操作**。这意味着一条指令就能完成"移位+运算",无需额外的移位指令。
移位类型:
| 移位操作 | 全称 | 说明 |
|----------|------|------|
| LSL | Logical Shift Left | 逻辑左移,低位补0 |
| LSR | Logical Shift Right | 逻辑右移,高位补0 |
| ASR | Arithmetic Shift Right | 算术右移,高位补符号位 |
| ROR | Rotate Right | 循环右移 |
```arm
; 示例:利用桶形移位器
ADD R0, R1, R1, LSL #2 ; R0 = R1 + R1 * 4 = R1 * 5
SUB R2, R3, R3, LSR #1 ; R2 = R3 - R3/2 = R3/2
MOV R4, R5, ROR #8 ; R4 = R5循环右移8位
```
> [!tip] 桶形移位器的价值
> 在没有桶形移位器的架构中,`R1 * 5` 需要两条指令:先LSL #2再ADD。ARM用一条指令就完成了,这体现了RISC架构中"简单指令的巧妙组合"的设计哲学。
### 4. 核心指令详解
#### 4.1 数据传送指令 MOV
```arm
MOV R0, #10 ; R0 = 10(立即数传送)
MOV R1, R0 ; R1 = R0(寄存器传送)
MOV R2, R0, LSL #3 ; R2 = R0 << 3(带移位传送)
```
**MOV只能在寄存器之间传送数据,不能访问内存!** 这是与LDR的关键区别。
#### 4.2 加载/存储指令 LDR/STR
```arm
LDR R0, [R1] ; 从R1指向的内存地址加载数据到R0
STR R0, [R1] ; 将R0的值存储到R1指向的内存地址
LDR R0, [R1, #4] ; 从R1+4地址加载(基址+偏移)
STR R0, [R1], #4 ; 先存储,再更新R1 = R1 + 4(后索引)
LDR R0, [R1, #4]! ; 先更新R1 = R1 + 4,再加载(前索引)
```
> [!question] MOV R0, #0xFF 和 LDR R0, =0x12345678 有什么区别?
> `MOV R0, #0xFF` 是真正ARM指令,0xFF是合法的8位立即数(通过循环右移编码)。
> `LDR R0, =0x12345678` 是**伪指令**,汇编器会将0x12345678放入文字池(literal pool),然后用一条LDR从该地址加载。因为0x12345678无法编码为8位立即数。
#### 4.3 算术运算指令
```arm
ADD R0, R1, R2 ; R0 = R1 + R2
ADD R0, R1, #5 ; R0 = R1 + 5
ADDS R0, R1, R2 ; R0 = R1 + R2,并更新CPSR标志位
SUB R0, R1, R2 ; R0 = R1 - R2
SUBS R0, R1, #1 ; R0 = R1 - 1,更新标志位
MUL R0, R1, R2 ; R0 = R1 * R2
```
注意带`S`后缀的指令会更新CPSR的N、Z、C、V标志位。
#### 4.4 比较指令 CMP
```arm
CMP R0, R1 ; 比较R0和R1(计算R0-R1,但不保存结果)
BEQ label ; 如果相等(Z=1),跳转到label
BNE label ; 如果不相等(Z=0),跳转到label
```
CMP本质上是一条**不保存结果的减法指令**,只设置标志位。
#### 4.5 分支指令 B / BL
```arm
B label ; 无条件跳转到label
BL function ; 跳转到function,同时将返回地址保存到LR
```
- **B**:简单跳转,用于循环、条件分支
- **BL**:带链接的跳转,用于**函数调用**(LR自动保存返回地址)
#### 4.6 软中断指令 SWI
```arm
SWI #0x123456 ; 触发软中断,进入SVC模式
```
SWI用于**从用户模式请求操作系统服务**(系统调用)。执行SWI后:
1. 进入SVC模式
2. CPSR保存到SPSR_svc
3. 返回地址保存到LR_svc
4. PC跳转到向量地址0x00000008
#### 4.7 批量加载/存储 STMFD / LDMFD
```arm
STMFD SP!, {R0-R3, LR} ; 将R0-R3和LR入栈(满递减栈)
LDMFD SP!, {R0-R3, PC} ; 从栈中恢复R0-R3,并将PC出栈实现返回
```
这两个指令常用于**函数入口保存现场**和**函数出口恢复现场**。FD表示Full Descending(满递减栈),是ARM的默认栈类型。
#### 4.8 状态寄存器读写 MRS / MSR
```arm
MRS R0, CPSR ; 将CPSR读入R0
MSR CPSR_c, R0 ; 将R0写入CPSR的控制位域
MSR CPSR_f, R0 ; 将R0写入CPSR的标志位域
```
MRS/MSR用于在**特权模式下修改CPSR**,例如开关中断:
```arm
; 关中断
MRS R0, CPSR
ORR R0, R0, #0x80 ; I位(bit7)置1
MSR CPSR_c, R0
; 开中断
MRS R0, R1
BIC R0, R0, #0x80 ; I位(bit7)清0
MSR CPSR_c, R0
```
#### 4.9 测试等价指令 TEQ
```arm
TEQ R0, R1 ; 按位异或(EOR),只设置标志位,不保存结果
```
TEQ用于测试两个值是否相等(或测试某些位),与CMP类似但使用异或运算。
### 5. 条件码
ARM支持15种条件码,基于CPSR的标志位进行判断:
| 条件码 | 含义 | 标志位条件 |
|--------|------|------------|
| EQ | 相等 | Z=1 |
| NE | 不相等 | Z=0 |
| GT | 大于(有符号) | Z=0且N=V |
| LT | 小于(有符号) | N!=V |
| GE | 大于等于(有符号) | N=V |
| LE | 小于等于(有符号) | Z=1或N!=V |
| HI | 无符号大于 | C=1且Z=0 |
| LS | 无符号小于等于 | C=0或Z=1 |
| CS/HS | 无符号大于等于 | C=1 |
| CC/LO | 无符号小于 | C=0 |
| PL | 正数或零 | N=0 |
| MI | 负数 | N=1 |
| VS | 溢出 | V=1 |
| VC | 无溢出 | V=0 |
| AL | 无条件(默认) | 任意 |
> [!question] CMP R0, #0 后接 BMI label,什么时候会跳转?
> BMI是"负数"条件跳转。CMP R0, #0 实际上是R0 - 0,结果就是R0本身。如果R0 < 0(最高位为1),N标志位被置位,BMI条件成立,跳转到label。
### 6. 寻址方式
| 寻址方式 | 示例 | 说明 |
|----------|------|------|
| 立即数寻址 | `MOV R0, #10` | 操作数就在指令中 |
| 寄存器寻址 | `ADD R0, R1, R2` | 操作数在寄存器中 |
| 寄存器间接寻址 | `LDR R0, [R1]` | R1中存放的是内存地址 |
| 基址+偏移寻址 | `LDR R0, [R1, #4]` | R1为基地址,4为偏移量 |
| 基址+索引寻址 | `LDR R0, [R1, R2]` | R2的值作为索引偏移 |
### 7. 立即数编码
ARM指令是32位固定的,立即数编码只占用**12位**(4位旋转量 + 8位立即数值)。规则:
```
实际值 = 8位立即数 循环右移 (旋转量 * 2) 位
```
这意味着只有**特定的值**才能作为立即数。例如:
- `#0xFF`:合法,8位全1,旋转0位
- `#0x100`:合法,`#1`循环右移24位
- `#0x12345678`:**不合法**,无法用8位循环右移表示
> [!tip] 立即数合法性判断
> 判断一个数是否是合法的ARM立即数:将它写成二进制,看是否能通过循环右移偶数位变成8位以内的数。
### 8. 字对齐与大小端
**数据宽度**:
| 类型 | 位数 | 字节数 |
|------|------|--------|
| Word(字) | 32位 | 4字节 |
| Halfword(半字) | 16位 | 2字节 |
| Byte(字节) | 8位 | 1字节 |
**对齐要求**:Word访问的地址必须是4的倍数(地址 mod 4 = 0),Halfword访问地址必须是2的倍数。
**大小端**:
- **Little-Endian(小端)**:低字节存低地址(ARM常用)
- **Big-Endian(大端)**:高字节存低地址
```arm
; 假设内存地址0x1000存放值 0x12345678
; 小端模式:
; 0x1000: 0x78 (最低字节)
; 0x1001: 0x56
; 0x1002: 0x34
; 0x1003: 0x12 (最高字节)
; 大端模式:
; 0x1000: 0x12 (最高字节)
; 0x1001: 0x34
; 0x1002: 0x56
; 0x1003: 0x78 (最低字节)
```
### 9. 完整示例:C if-else 对应的ARM汇编
以下是C语言if-else语句及其对应的ARM汇编实现:
```c
// C代码
int max(int a, int b) {
if (a > b)
return a;
else
return b;
}
```
```arm
; ARM汇编实现
max:
CMP R0, R1 ; 比较a和b(R0-R1,设置标志位)
MOVGT R0, R0 ; 如果a > b(GT条件成立),R0保持不变
MOVLE R0, R1 ; 如果a <= b(LE条件成立),R0 = b
BX LR ; 返回,LR中保存了调用者的返回地址
```
更高效的写法:
```arm
; 优化版本:只用两条核心指令
max:
CMP R0, R1 ; 比较a和b
MOVLT R0, R1 ; 如果a < b,则R0 = b
BX LR ; 返回
```
> [!question] 为什么优化版本只判断了LT一种情况?
> 因为如果a >= b,R0的值本来就是a,不需要任何操作。只有当a < b时才需要将b写入R0。这体现了"条件执行"的强大——ARM的每条指令都可以条件执行,避免了不必要的跳转。
## 关联笔记
- [[嵌入式系统/试题册/index|试题册索引]]