3. ARM指令集基础:ARM指令格式、数据处理指令、加载存储指令、分支指令、Thumb指令集
ARM指令集是嵌入式Bootloader开发的核心基础。Bootloader的启动代码、内存初始化、设备配置、以及从Flash到RAM的搬移操作,都直接依赖对ARM指令的精确理解。本章将系统讲解ARM指令的编码格式、四大类核心指令(数据处理、加载存储、分支)以及Thumb指令集,为后续的汇编级移植和调试打下坚实基础。
3.1 ARM指令格式与编码
ARM指令采用固定32位长度,所有指令均按统一的编码格式进行解析。理解指令的二进制布局是反汇编、手工编码和调试的基础。
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 | 第二操作数(寄存器、立即数或移位形式) |
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 | 无条件 | 默认条件,始终执行 |
ADDEQ R0, R1, R2 是条件执行加法,不是分支。如果忘记设置条件码,指令将始终执行,导致逻辑错误。
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) | 条件判断、循环控制 |
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
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} ; 出栈恢复并返回
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) | 否 | 数组索引(字对齐) |
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中,从复位向量跳转到主函数时,必须确保状态一致。