Files
final-exam/嵌入式系统/复习文档/ARM指令集与汇编.md
T

9.8 KiB
Raw Blame History

tags, create time
tags create time
嵌入式系统
复习
ARM指令集
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 第二操作数(灵活,可带移位)
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 循环右移
; 示例:利用桶形移位器
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

MOV R0, #10          ; R0 = 10(立即数传送)
MOV R1, R0           ; R1 = R0(寄存器传送)
MOV R2, R0, LSL #3   ; R2 = R0 << 3(带移位传送)

MOV只能在寄存器之间传送数据,不能访问内存! 这是与LDR的关键区别。

4.2 加载/存储指令 LDR/STR

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 算术运算指令

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

CMP R0, R1            ; 比较R0和R1(计算R0-R1,但不保存结果)
BEQ label             ; 如果相等(Z=1),跳转到label
BNE label             ; 如果不相等(Z=0),跳转到label

CMP本质上是一条不保存结果的减法指令,只设置标志位。

4.5 分支指令 B / BL

B label               ; 无条件跳转到label
BL function           ; 跳转到function,同时将返回地址保存到LR
  • B:简单跳转,用于循环、条件分支
  • BL:带链接的跳转,用于函数调用(LR自动保存返回地址)

4.6 软中断指令 SWI

SWI #0x123456         ; 触发软中断,进入SVC模式

SWI用于从用户模式请求操作系统服务(系统调用)。执行SWI后:

  1. 进入SVC模式
  2. CPSR保存到SPSR_svc
  3. 返回地址保存到LR_svc
  4. PC跳转到向量地址0x00000008

4.7 批量加载/存储 STMFD / LDMFD

STMFD SP!, {R0-R3, LR}   ; 将R0-R3和LR入栈(满递减栈)
LDMFD SP!, {R0-R3, PC}   ; 从栈中恢复R0-R3,并将PC出栈实现返回

这两个指令常用于函数入口保存现场和函数出口恢复现场。FD表示Full Descending(满递减栈),是ARM的默认栈类型。

4.8 状态寄存器读写 MRS / MSR

MRS R0, CPSR           ; 将CPSR读入R0
MSR CPSR_c, R0         ; 将R0写入CPSR的控制位域
MSR CPSR_f, R0         ; 将R0写入CPSR的标志位域

MRS/MSR用于在特权模式下修改CPSR,例如开关中断:

; 关中断
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

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(大端):高字节存低地址
; 假设内存地址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代码
int max(int a, int b) {
    if (a > b)
        return a;
    else
        return b;
}
; 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中保存了调用者的返回地址

更高效的写法:

; 优化版本:只用两条核心指令
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的每条指令都可以条件执行,避免了不必要的跳转。

关联笔记