vault backup: 2026-06-10 11:17:10
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
---
|
||||
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|试题册索引]]
|
||||
Reference in New Issue
Block a user