3. ARM指令集基础:ARM指令格式、数据处理指令、加载存储指令、分支指令、Thumb指令集

ARM指令集是嵌入式Bootloader开发的核心基础。Bootloader的启动代码、内存初始化、设备配置、以及从Flash到RAM的搬移操作,都直接依赖对ARM指令的精确理解。本章将系统讲解ARM指令的编码格式、四大类核心指令(数据处理、加载存储、分支)以及Thumb指令集,为后续的汇编级移植和调试打下坚实基础。

3.1 ARM指令格式与编码

ARM指令采用固定32位长度,所有指令均按统一的编码格式进行解析。理解指令的二进制布局是反汇编、手工编码和调试的基础。

核心要点: ARM指令的高4位[31:28]为条件码,决定指令是否执行;[27:25]为指令类别码;[24:21]为具体操作码;其余位根据指令类别不同,分别用于寄存器编号、立即数、偏移量等。

3.1.1 指令编码结构

ARM指令的通用编码格式如下表所示,不同指令类别在[27:25]位段上有所区分。

位段 宽度 名称 说明
[31:28] 4位 Cond 条件码,如EQ、NE、AL(始终执行)
[27:25] 3位 Class 指令类别:000=数据处理,001=加载存储,010=分支,011=协处理器等
[24:21] 4位 Opcode 具体操作码,如ADD、SUB、MOV等
[20] 1位 S 是否影响CPSR标志位(S=1时更新)
[19:16] 4位 Rn 第一操作数寄存器
[15:12] 4位 Rd 目标寄存器
[11:0] 12位 Operand2 第二操作数(寄存器、立即数或移位形式)
实战提示: 在Bootloader的start.S中,经常使用 MOV R0, #0 这样的指令。其编码中Cond=1110(AL)、Class=000、Opcode=1101(MOV)、S=0、Rd=R0、Operand2=0x00。理解这个编码过程,有助于在调试器中直接定位指令。

3.1.2 条件码详解

ARM指令的条件执行机制是其独特优势。每条指令都可以根据CPSR中的标志位决定是否执行,从而减少分支指令的使用,提高代码密度。

条件码 助记符 条件 典型应用场景
0000 EQ Z=1 比较相等后执行
0001 NE Z=0 比较不等后执行
0010 CS/HS C=1 无符号大于等于
0011 CC/LO C=0 无符号小于
0100 MI N=1 负数
0101 PL N=0 正数或零
1110 AL 无条件 默认条件,始终执行
常见错误: 在Bootloader中,条件执行指令容易与分支指令混淆。例如 ADDEQ R0, R1, R2 是条件执行加法,不是分支。如果忘记设置条件码,指令将始终执行,导致逻辑错误。
ARM指令编码结构(32位) Cond [31:28] Class [27:25] Opcode [24:21] S [20] Rn [19:16] Rd [15:12] Operand2 [11:0] 说明: • Cond:条件码,决定指令是否执行(如EQ、NE、AL) • Class:指令类别,区分数据处理、加载存储、分支等 • Operand2:灵活的第二操作数,可以是立即数、寄存器或移位后的值

3.2 数据处理指令

数据处理指令是ARM指令集中最常用的类别,用于在寄存器之间执行算术、逻辑、比较和移动操作。Bootloader中的寄存器初始化、地址计算、状态判断都依赖这类指令。

3.2.1 算术与逻辑指令

指令 语法 功能 Bootloader典型用途
ADD ADD Rd, Rn, Operand2 Rd = Rn + Operand2 地址偏移计算、栈指针调整
SUB SUB Rd, Rn, Operand2 Rd = Rn - Operand2 栈空间分配、循环计数
MOV MOV Rd, Operand2 Rd = Operand2 寄存器初始化、立即数加载
AND AND Rd, Rn, Operand2 Rd = Rn & Operand2 位掩码操作、状态寄存器读取
ORR ORR Rd, Rn, Operand2 Rd = Rn | Operand2 设置控制寄存器特定位
EOR EOR Rd, Rn, Operand2 Rd = Rn ^ Operand2 位翻转、加密算法基础
CMP CMP Rn, Operand2 更新CPSR标志位(Rn - Operand2) 条件判断、循环控制
关键技巧: 在Bootloader中,MOV R0, #0xFF000000 这样的立即数可能无法直接编码,因为ARM立即数是8位循环移位得到的。此时可以使用 MVN 指令或分两次加载。例如:MOV R0, #0xFF000000 会报错,应改为 MOV R0, #0xFF, 8(表示0xFF循环右移8位)。

3.2.2 第二操作数Operand2的灵活形式

Operand2是ARM数据处理指令的一大特色,它可以是立即数、寄存器,或者寄存器移位后的值。这种灵活性使得单条指令可以完成复杂的计算。

  • 立即数形式: 8位立即数循环移位得到32位值。格式:#imm8_rot,其中imm8是8位常数,rot是4位循环移位量(乘以2)。
  • 寄存器形式: 直接使用寄存器Rm的值。
  • 移位寄存器形式: 对Rm进行移位后再参与运算。移位类型包括LSL(逻辑左移)、LSR(逻辑右移)、ASR(算术右移)、ROR(循环右移)。
; 示例:Operand2的灵活使用
MOV R0, #0x10          ; 立即数加载
ADD R1, R2, R3         ; 寄存器加法
SUB R4, R5, R6, LSL #2 ; R4 = R5 - (R6 << 2)
CMP R7, #0xFF          ; 比较R7与0xFF
数据处理指令执行流程 取指(Fetch) 译码(Decode) 执行(Execute) 读取Rn 解析Operand2 ALU运算 写回Rd 注:Operand2在译码阶段解析,ALU在单周期内完成运算,结果写回目标寄存器Rd

3.3 加载存储指令

ARM采用加载/存储架构(Load/Store Architecture),所有内存访问必须通过加载(LDR)和存储(STR)指令完成。Bootloader中从Flash加载代码到RAM、初始化全局变量、配置外设寄存器都离不开这类指令。

3.3.1 单寄存器加载存储

指令 语法 功能 寻址模式示例
LDR LDR Rd, [Rn, #offset] 从内存加载32位字到Rd LDR R0, [R1, #0x100]
STR STR Rd, [Rn, #offset] 将Rd存储到内存 STR R0, [R1, #-4]
LDRB LDRB Rd, [Rn, #offset] 加载8位字节(零扩展) LDRB R0, [R1]
STRB STRB Rd, [Rn, #offset] 存储8位字节 STRB R0, [R1, #1]
LDRH LDRH Rd, [Rn, #offset] 加载16位半字(零扩展) LDRH R0, [R1, #2]
STRH STRH Rd, [Rn, #offset] 存储16位半字 STRH R0, [R1, #0x20]

3.3.2 多寄存器加载存储

LDM(Load Multiple)和STM(Store Multiple)指令可以一次加载/存储多个寄存器,非常适合Bootloader中的上下文保存和恢复、以及数据块搬移。

; 多寄存器加载存储示例
STMIA R0!, {R1-R4, LR}   ; 将R1-R4和LR保存到R0指向的内存,R0自动增加
LDMIA R0!, {R1-R4, PC}   ; 从内存恢复R1-R4和PC(实现函数返回)

; 栈操作(满递减栈)
STMFD SP!, {R0-R3, LR}   ; 压栈保存
LDMFD SP!, {R0-R3, PC}   ; 出栈恢复并返回
Bootloader实战: 在启动代码中,经常使用 LDMIA 从Flash加载数据段到RAM:
LDMIA R0!, {R1-R12} 一次加载12个寄存器,效率远高于12条LDR指令。注意R0必须是字对齐地址。

3.3.3 寻址模式对比

寻址模式 语法示例 地址计算 是否更新基址 典型用途
前变址 LDR R0, [R1, #4] R1+4 否 结构体成员访问
后变址 LDR R0, [R1], #4 R1(然后R1+=4) 是 数组遍历
前变址回写 LDR R0, [R1, #4]! R1+4(然后R1+=4) 是 栈操作
寄存器偏移 LDR R0, [R1, R2, LSL #2] R1 + (R2<<2) 否 数组索引(字对齐)
加载存储指令寻址模式 前变址(Pre-index) LDR R0, [R1, #4] 地址 = R1 + 4 基址R1不变 后变址(Post-index) LDR R0, [R1], #4 地址 = R1(原值) 然后 R1 += 4 前变址回写 LDR R0, [R1, #4]! 地址 = R1 + 4 然后 R1 += 4 寻址模式选择建议: • 前变址:访问结构体成员、外设寄存器(固定偏移) • 后变址:顺序遍历数组、字符串操作 • 前变址回写:栈操作(LDM/STM内部使用)、循环缓冲区

3.4 分支指令

分支指令用于改变程序执行流,实现函数调用、条件跳转和循环控制。Bootloader中的异常向量表、模式切换、函数调用都依赖分支指令。

3.4.1 分支指令分类

指令 语法 功能 跳转范围
B B label 无条件跳转 ±32MB
BL BL label 跳转并保存返回地址到LR ±32MB
BX BX Rm 跳转到寄存器Rm指定的地址 4GB(全地址空间)
BLX BLX Rm / BLX label 跳转并保存LR,可切换ARM/Thumb状态 ±32MB(标签)或全地址(寄存器)

3.4.2 条件分支与状态切换

分支指令可以与条件码结合,实现条件跳转。在Bootloader中,BL 用于函数调用,BX 用于返回和模式切换。

; 条件分支示例
CMP R0, #0
BEQ zero_case        ; 如果R0==0,跳转到zero_case
BL  handle_nonzero   ; 否则调用函数handle_nonzero
B   continue

zero_case:
    MOV R0, #1
    B   continue

handle_nonzero:
    ; 函数体
    BX  LR            ; 返回

; ARM/Thumb状态切换
; 从ARM切换到Thumb
LDR R0, =thumb_func + 1  ; +1表示Thumb状态
BLX R0

; 从Thumb返回ARM
BX  LR                    ; LR的bit0=0表示ARM状态
关键注意: 使用 BX 和 BLX 时,目标地址的bit0决定处理器状态:bit0=0表示ARM状态,bit0=1表示Thumb状态。如果忘记设置bit0,会导致未定义指令异常。在Bootloader中,从复位向量跳转到主函数时,必须确保状态一致。
分支指令执行流程 顺序执行 条件成立? <