ARM 架构核心知识笔记(整理与补充版)

ARM 架构核心知识笔记(整理与补充版)

一、ARM CPU 与内存、外设的交互逻辑

核心原理

ARM CPU 通过地址(addr) 与外部设备交互,所有交互需经过内存控制器(memory controller) ;内存控制器根据 CPU 发出的地址范围,选择访问目标设备(内存、UART、USB 控制器等)。关键特性:内存与 I/O(外设)访问方式统一------ 在 CPU 视角,操作内存和操作外设无本质区别,均通过 "发送地址→内存控制器译码→访问目标设备" 的流程实现,所有设备共享同一地址空间。

补充说明

  • 地址范围由芯片厂商预先划分,例如:0x00000000~0x0FFFFFFF 分配给 Flash 内存,0x20000000~0x2000FFFF 分配给 SRAM,0x40000000~0x4000FFFF 分配给 UART 外设。
  • 内存控制器的核心作用是 "地址译码",确保不同地址对应唯一设备,避免访问冲突。

二、ARM 的 RISC 架构特点

ARM 芯片属于精简指令集计算机(RISC) ,指令设计简洁,核心特点与运算流程如下:

1. RISC 核心特点

  • 对内存仅支持 "读(Load)" 和 "写(Store)" 两类指令,无直接对内存的运算指令。
  • 数据运算(加减乘除、逻辑运算)均在 CPU 内部的ALU(算术逻辑单元) 中完成,需先将内存数据读入 CPU 寄存器。
  • CPU 硬件复杂度低,易于设计和优化,指令执行周期固定,适合流水线操作。

2. 乘法运算(a = a * b)指令流程(用户代码补充注释)

以 "a 和 b 存储在内存,结果写回 a" 为例,需 4 条汇编指令,流程如下:

arm

复制代码
; 1. 从内存地址a处,读取数据到CPU寄存器(暂存,为运算做准备)
LDR R0, [a]  ; R0 = 内存[a](即a的原始值)
; 2. 从内存地址b处,读取数据到另一寄存器
LDR R1, [b]  ; R1 = 内存[b](即b的值)
; 3. CPU内部ALU执行乘法运算,结果存入R0(覆盖原a的值)
MUL R0, R0, R1  ; R0 = R0 * R1(a*b的结果)
; 4. 将运算结果从寄存器写回内存地址a,更新a的值
STR R0, [a]  ; 内存[a] = R0(完成a = a*b的更新)

三、ARM CPU 内部寄存器结构

ARM CPU(如 Cortex-M3/M4/A7)内部包含R0~R15 共 16 个通用寄存器(32 位宽),部分寄存器具有特殊功能,需按 "普通寄存器" 和 "特殊功能寄存器" 分类理解。

1. 通用寄存器(R0~R15)

(1)普通数据寄存器
  • 低寄存器(Low Registers):R0~R7,所有指令均支持访问,是最常用的临时数据存储寄存器(如传递函数参数、暂存运算中间结果)。
  • 高寄存器(High Registers) :R8~R12,仅部分指令支持访问(如 32 位 Thumb2 指令),通常用于存储局部变量或不常用数据。补充:R12 又称IP(Internal Procedure Register,内部过程寄存器),在函数调用时临时保存中间结果,用户代码未提及,需注意其辅助作用。
(2)特殊功能寄存器(R13~R15)
寄存器 别名 核心功能 补充说明
R13 SP 栈指针,管理栈空间的当前地址 分主栈指针(SP_main)和进程栈指针(SP_process),适配操作系统多任务场景
R14 LR 链接寄存器,保存函数返回地址 函数调用(如 BL 指令)时,自动将 "下一条指令地址" 存入 LR;返回时需通过 "MOV PC, LR" 或 "BX LR" 恢复执行流程
R15 PC 程序计数器,指示下一条要执行的指令地址 PC 的值始终比当前执行指令地址大 4(ARM 指令)或 2(Thumb 指令),修改 PC 即可实现程序跳转

2. 加法运算(a = a + b)指令示例(用户代码补充注释)

arm

复制代码
; 场景:a(内存地址0x12)、b(内存地址0x34),结果写回a
LDR R0, [a]    ; 1. 读取内存a的值(0x12处数据)到R0
LDR R1, [b]    ; 2. 读取内存b的值(0x34处数据)到R1
ADD R0, R0, R1 ; 3. CPU内部ALU执行加法:R0 = R0 + R1(a+b结果)
STR R0, [a]    ; 4. 结果写回内存a:0x12处数据更新为R0的值

四、Cortex-M3/M4 的程序状态寄存器(xPSR)

xPSR(Program Status Register)是记录 CPU 运行状态的核心寄存器,并非单个寄存器,而是由APSR、IPSR、EPSR 三个子寄存器组合而成,支持单独或整体访问。

1. xPSR 的结构拆分

子寄存器 全称 核心功能
APSR Application PSR(应用状态寄存器) 记录应用层运算状态,如结果正负(N)、是否为零(Z)、进位(C)、溢出(V)等标志
IPSR Interrupt PSR(中断状态寄存器) 记录当前正在处理的中断 / 异常编号(0~255),CPU 通过该编号找到中断服务函数入口
EPSR Execution PSR(执行状态寄存器) 记录指令执行相关状态,如 Thumb 模式标志(T 位,Cortex-M 系列始终为 1,清除会触发异常)、条件执行标志(ICI/IT 位)

2. xPSR 的访问方式(MRS/MSR 指令)

xPSR 需通过MRS(读特殊寄存器)MSR(写特殊寄存器) 指令访问,支持 "单独访问子寄存器" 和 "整体访问组合寄存器" 两种方式:

(1)单独访问子寄存器

arm

复制代码
; 读取APSR到通用寄存器R0(获取运算标志)
MRS R0, APSR  
; 读取IPSR到R0(获取当前中断编号)
MRS R0, IPSR  
; 将R0的值写入APSR(修改运算标志,如清除溢出标志)
MSR APSR, R0  
(2)整体访问组合寄存器(PSR)

arm

复制代码
; 一次性读取APSR+IPSR+EPSR的组合状态到R0
MRS R0, PSR  
; 一次性将R0的值写入组合PSR(整体配置CPU状态,需谨慎使用)
MSR PSR, R0  

3. xPSR 关键位域含义(补充用户表格细节)

位域 符号 含义 应用场景
31 N 负标志:运算结果为负时置 1 判断减法结果是否为负(如比较两个数大小)
30 Z 零标志:运算结果为 0 时置 1 循环判断(如 SUBS R0,R0,#1 后,BNE Loop)
29 C 进位 / 借位标志:加法有进位或减法无借位时置 1 多字节加法(如 64 位数据运算)、无符号数比较
28 V 溢出标志:运算结果超出数据表示范围时置 1 有符号数运算(如 int8_t 类型计算 127+1 时置 1)
8 T Thumb 模式标志:Cortex-M 系列始终为 1 清除 T 位会触发 "未定义指令异常",不可修改
4~0 - 异常编号:IPSR 中的位,指示当前中断编号 中断服务函数中判断中断来源(如 UART 中断编号为 28)

补充说明

  • MSR 指令仅能修改 APSR 和 EPSR 的部分位,IPSR(中断编号)由硬件自动更新,软件无法手动修改。
  • xPSR 的状态会影响条件指令的执行(如 BEQ、BNE 等),是程序分支控制的核心依据。

五、ARM CPU 核心操作类型

ARM CPU 的所有功能可归纳为 4 类核心操作,覆盖数据处理、存储交互、流程控制的全场景:

操作类型 核心功能 常用指令 / 模块 补充说明
内存 R/W 与内存进行数据读写 LDR(读)、STR(写)、LDM(批量读)、STM(批量写) 所有内存操作均需通过寄存器中转,无直接内存运算指令(RISC 特性)
运算 数据的数学 / 逻辑运算 数学运算:ADD、SUB、MUL;逻辑运算:AND、ORR、EOR 运算均在 CPU 内部 ALU 完成,运算对象必须是寄存器(或立即数)
跳转 / 分支 改变程序执行顺序 无条件跳转:B;带返回跳转:BL;指令集切换跳转:BX、BLX 跳转地址可通过标签(如 B Loop)或寄存器(如 BX R0)指定
比较 数据比较,更新 xPSR 标志 CMP(比较)、CMN(负数比较)、TST(位测试) 比较指令本质是 "执行减法但不保存结果,仅更新 xPSR 标志"(如 CMP R0,R1 等价于 SUBS R2,R0,R1 但不保存 R2)

六、ARM 指令集演进(ARM/Thumb/Thumb2)

ARM 最初支持两类指令集,后续通过 Thumb2 优化切换复杂度,不同指令集的特性与切换逻辑如下:

1. 早期两类指令集对比

指令集 指令长度 核心优势 核心劣势 适用场景
ARM 32 位 运行效率高,单条指令功能强 占用存储空间大(相同功能下比 Thumb 多占 50% 空间) 对性能要求高、存储空间充足的场景(如早期高端处理器)
Thumb 16 位 存储空间占用小,代码密度高 部分复杂操作需多条指令实现,效率略低 存储空间有限的场景(如嵌入式微控制器)

2. 指令集识别与切换(T 位控制)

CPU 通过xPSR 中的 T 位识别当前指令集状态:

  • T=0:ARM 状态,执行 32 位 ARM 指令;
  • T=1:Thumb 状态,执行 16/32 位 Thumb/Thumb2 指令。
手动切换指令集(用户代码逻辑补充)

调用不同指令集编写的函数时,需修改 PC 寄存器最低位(BIT0)实现切换:

arm

复制代码
; 场景1:调用ARM指令集函数B(地址0x1000)
MOV PC, #0x1000  ; PC的BIT0=0,切换为ARM状态,跳转到0x1000执行
; 场景2:调用Thumb指令集函数A(地址0x2000)
MOV PC, #0x2001  ; PC的BIT0=1,切换为Thumb状态,跳转到0x2000执行(BIT0仅用于状态识别,地址实际为0x2000)

3. Thumb2 指令集的优化(关键补充)

为解决 "手动切换麻烦" 的问题,ARM 推出Thumb2 指令集,核心优势:

  • 兼容 16 位 Thumb 指令和 32 位 "Thumb 风格" 指令,无需手动切换状态;
  • 代码密度与 Thumb 相当,性能接近 ARM 指令集,兼顾空间与效率;
  • 现代 ARM 芯片(如 Cortex-M3/M4/M7)仅支持 Thumb/Thumb2 指令集,不再支持 ARM 指令集,用户代码未提及此点,需重点注意。

七、ARM 汇编指令分类与格式

1. 汇编指令五大类(参考 ARM 官方文档)

指令类别 核心功能 代表指令 补充说明
数据处理 寄存器间数据运算、立即数操作 ADD(加法)、SUB(减法)、AND(与)、MOV(数据移动) 运算对象可为 "寄存器 + 寄存器""寄存器 + 立即数",部分指令带 S 后缀(如 SUBS)可更新 xPSR
内存访问 寄存器与内存的数据传输 LDR(读内存到寄存器)、STR(写寄存器到内存)、LDM(批量读)、STM(批量写) 支持多种寻址方式(如立即数偏移、寄存器偏移、堆栈寻址)
跳转 改变程序执行流程 B(基础跳转)、BL(带返回跳转)、BX(跳转 + 指令集切换)、BLX(带返回 + 切换) 跳转范围受指令长度限制(如 Thumb 指令的 B 指令跳转范围为 ±2KB)
饱和运算 处理数值溢出,避免结果错乱 QADD(饱和加法)、QSUB(饱和减法)、QDADD(双倍饱和加法) 适用于信号处理、传感器数据采集等场景(如 8 位数据 127+1 时,饱和结果为 127 而非 - 128)
其他指令 系统控制、中断管理等杂项操作 MRS(读特殊寄存器)、MSR(写特殊寄存器)、SWI(软件中断)、NOP(空操作) 多为特权指令,仅能在系统模式或特权级下执行

2. 数据处理指令格式(UAL 统一汇编语言)

数据处理指令的标准格式为:Operation{cond}{S} Rd, Rn, Operand2,各字段含义如下:

  • Operation:指令操作码,指定指令功能(如 ADD、MOV、AND);

  • {cond}(可选) :条件后缀,指定指令执行的条件(如 EQ = 相等、NE = 不相等、GT = 大于),省略则无条件执行;补充:常见条件后缀表(用户未提及,需补充)

    条件后缀 含义 xPSR 标志依据
    EQ 相等 Z=1
    NE 不相等 Z=0
    GT 大于(有符号) N=V
    LT 小于(有符号) N≠V
    CS/HS 有进位(无符号) C=1
    CC/LO 无进位(无符号) C=0
  • {S}(可选):状态更新标志,带 S 则指令执行后更新 APSR 的 N/Z/C/V 标志(如 SUBS 会更新标志,SUB 不更新);

  • Rd:目的寄存器,运算结果存储的目标寄存器;

  • Rn:第一个源操作数寄存器,存放参与运算的第一个数据;

  • Operand2:第二个源操作数,可分为 "立即数"(如 #0x12)、"寄存器"(如 R1)、"寄存器移位"(如 R1,LSL#2,即 R1 左移 2 位)。

指令格式示例(补充注释)

arm

复制代码
; 1. ADD{cond}{S} Rd, Rn, Operand2:无条件加法,不更新APSR(无S)
ADD R0, R1, #0x05  ; R0 = R1 + 5,不更新N/Z/C/V标志
; 2. SUBS{cond} Rd, Rn, Operand2:相等时执行减法,更新APSR(带S)
SUBS EQ R0, R1, R2  ; 若Z=1(前次运算结果相等),则R0 = R1 - R2,同时更新N/Z/C/V标志
; 3. AND{S} Rd, Rn, Operand2:逻辑与,更新APSR
AND S R0, R1, R2    ; R0 = R1 & R2,更新N/Z/C/V标志(Z=1表示结果为0)

八、寄存器赋值:MOV 指令限制与 LDR 伪指令

1. MOV 指令的 "立即数限制"(核心问题)

MOV Rd, #VAL 是寄存器赋值的基础指令,但VAL 必须是 "合法立即数",原因:

  • MOV 指令长度为 16 位(Thumb)或 32 位(ARM),其中用于存储立即数的位宽有限(如 Thumb 的 MOV 指令仅支持 8 位立即数 + 4 位移位);
  • 合法立即数需满足 "二进制可表示为 8 位数据循环右移偶数位"(如 0x12、0x1200、0x12000000,而 0x12345678 不合法)。
示例(合法与非法 MOV 指令)

arm

复制代码
MOV R0, #0x12      ; 合法:0x12是8位立即数,无需移位
MOV R0, #0x1200    ; 合法:0x12循环右移16位(偶数位)
; MOV R0, #0x12345678  ; 非法:无法通过"8位数据+偶数移位"表示

2. LDR 伪指令:解决 "任意值赋值" 问题

若需给寄存器赋 "非合法立即数",需使用LDR 伪指令(格式:LDR Rd, =VAL),核心特点:

  • "伪指令" 并非 CPU 原生指令,由编译器在编译阶段自动替换为 "合法原生指令";
  • 必须带 "=",否则为 "原生 LDR 指令"(用于读内存),二者需严格区分。

3. LDR 伪指令的编译器替换逻辑(用户代码补充)

编译器根据 VAL 的类型自动适配,分两种情况:

(1)VAL 是合法立即数:替换为 MOV 指令

arm

复制代码
; 原伪指令:LDR R0, =0x12(0x12是合法立即数)
; 编译器替换为原生MOV指令:
MOV R0, #0x12  
(2)VAL 是非合法立即数:替换为 "DCD 定义 + 原生 LDR 读内存"

arm

复制代码
; 原伪指令:LDR R0, =0x12345678(非合法立即数)
; 编译器处理步骤:
; 1. 在程序数据段用DCD指令定义该数值(存入内存)
Data_Sec: DCD 0x12345678  ; 内存地址假设为0x20000000
; 2. 替换为原生LDR指令(从内存读取该数值到R0)
LDR R0, [PC, #offset]    ; offset = 0x20000000 - PC当前值(由链接器计算)

补充说明

  • LDR 伪指令的偏移范围由 PC 的寻址范围决定(通常为 ±4KB),若 VAL 的地址超出范围,链接器会报错;
  • 对于常量地址(如外设寄存器地址),推荐使用 LDR 伪指令(如 LDR R0, =0x40001000),避免手动计算立即数合法性。

九、ADR 伪指令:基于 PC 的地址加载

1. ADR 伪指令的本质

ADR 是 "地址加载伪指令",非 CPU 原生指令,由编译器在编译阶段替换为 "基于 PC 的加法指令"(如 ADD Rd, PC, #offset),核心作用是 "通过标签快速获取内存地址"。

2. 代码示例与替换逻辑(用户代码补充注释)

arm

复制代码
; 场景:获取Loop标签的地址,存入R0
ADR R0, Loop  ; 原伪指令:加载Loop标签地址到R0
; 编译器替换逻辑:
; 1. 链接阶段计算Loop标签与当前PC的偏移量offset(假设PC当前值为0x1000,Loop地址为0x1010,则offset=0x10)
; 2. 替换为原生ADD指令:
ADD R0, PC, #0x10  ; R0 = PC + 0x10 = 0x1000 + 0x10 = 0x1010(即Loop地址)
; 后续可通过R0访问Loop标签处的代码或数据
Loop:
SUBS R1, R1, #1  ; Loop标签处的指令
BNE Loop

3. ADR 与 LDR = label 的区别(补充用户疏漏)

对比维度 ADR 伪指令 LDR = label 伪指令
地址来源 基于 PC 的偏移(小范围,通常 ±4KB) 直接加载标签的绝对地址(大范围,无偏移限制)
替换结果 替换为 ADD/ SUB 指令(无内存访问) 若地址是立即数,替换为 MOV;否则替换为 LDR 读内存
适用场景 位置无关代码(如 Bootloader) 位置相关代码(如固定地址的外设寄存器访问)

十、ARM 汇编的内存操作示例

1. 基础内存写操作(用户代码补充注释)

场景:将数据 0x1234 写入内存地址 0x20000,需 3 条指令:

arm

复制代码
; 1. 给R0赋值为目标内存地址(0x20000),R0作为"地址寄存器"
MOV R0, #0X20000  
; 2. 用LDR伪指令给R1赋值为要存储的数据(0x1234),R1作为"数据寄存器"
LDR R1, =0X1234  
; 3. 用STR指令将R1的数据写入R0指向的内存地址(0x20000)
STR R1, [R0]      

2. 内存查看窗口验证(用户图示补充)

内存窗口(地址 0x20000~0x21000)显示结果:

  • Word Value(32 位数据):0x1234,与 R1 的数据一致,说明写入成功;
  • 字节分布(Byte 3~Byte 0) :0x00、0x00、0x12、0x34,符合 ARM小端模式(低字节存低地址)------0x1234 分解为低字节 0x34(存 Byte 0,地址 0x20000)、高字节 0x12(存 Byte 1,地址 0x20001),高位补 0。

3. 多种 STR 寻址方式(用户代码补充注释)

STR 指令支持多种寻址方式,适配不同内存访问场景:

arm

复制代码
MOV R0, #0x20000  ; 基地址:0x20000
MOV R1, #0x10     ; 偏移寄存器:R1=0x10
MOV R2, #0x12     ; 待存储数据:R2=0x12

; 1. 立即数偏移:存储到R0+4地址(地址=0x20004),R0不变
STR R2, [R0, #4]  
; 2. 立即数偏移+R0更新:存储到R0+8地址(0x20008),之后R0=R0+8(0x20008)
STR R2, [R0, #8]! 
; 3. 寄存器偏移:存储到R0+R1地址(0x20008 + 0x10 = 0x20018),R0不变
STR R2, [R0, R1]  
; 4. 寄存器移位偏移:存储到R0+(R1<<4)地址(0x20008 + (0x10<<4) = 0x20108),R0不变
STR R2, [R0, R1, LSL #4]  
; 5. 后递增偏移:先存储到R0地址(0x20008),之后R0=R0+0x20(0x20028)
STR R2, [R0], #0X20  

补充说明

  • 所有内存访问指令的地址必须4 字节对齐(Cortex-M 系列默认要求),否则会触发 "对齐错误异常";
  • 小端模式是 ARM 芯片的默认存储模式,部分芯片支持大端模式(需通过寄存器配置),但嵌入式场景极少使用。

十一、LDM/STM 指令:多寄存器批量内存操作

LDM(Load Multiple)和 STM(Store Multiple)是 "批量读写多寄存器与内存" 的指令,核心用于函数上下文保存、栈操作等场景。

1. 指令语法格式

指令类型 语法格式 核心作用
LDM(批量读) LDM{addr_mode}{cond} Rn{!}, reglist{^} 从 Rn 指向的内存地址,批量读取数据到 reglist 中的寄存器
STM(批量写) STM{addr_mode}{cond} Rn{!}, reglist{^} 将 reglist 中的寄存器数据,批量写入 Rn 指向的内存地址

2. 关键参数解释

(1)addr_mode:地址模式(控制 Rn 增减时机)
地址模式 全称 核心逻辑 适用场景
IA Increment After(后递增,默认) 先传输数据,再将 Rn += 4(4 字节对齐) 栈出栈(LDMIA)、数组连续读取
IB Increment Before(前递增) 先将 Rn += 4,再传输数据 仅 ARM 指令支持,嵌入式场景极少用
DA Decrement After(后递减) 先传输数据,再将 Rn -= 4 仅 ARM 指令支持,用于特殊内存布局
DB Decrement Before(前递减) 先将 Rn -= 4,再传输数据 栈入栈(STMDB)、批量数据保存
(2)其他参数
  • {cond}(可选):条件后缀,如 EQ(相等时执行)、NE(不相等时执行);
  • Rn(基址寄存器):内存访问的基准地址,通常为 SP(栈指针)或外设基地址;
  • {!}(可选):Rn 更新标志,带!则指令执行后将 "增减后的 Rn" 写回 Rn(如 STMDB SP!, {R0-R3} 会更新 SP);
  • reglist(寄存器列表):批量操作的寄存器集合,格式为 {R0-R3, R5}(注意:低编号寄存器对应低内存地址);
  • {^}(可选):状态寄存器影响标志,带 ^ 则操作时更新 CPSR(仅用于异常处理,如中断返回)。

3. Rn 按 4 字节增减的原因(补充细节)

  • ARM 寄存器为 32 位(4 字节),批量传输时 "一个寄存器对应 4 字节内存",需保证地址对齐;
  • 若 Rn 按非 4 字节增减(如 2 字节),会导致内存地址未对齐,触发 "对齐错误异常"。
示例:STMDB 批量保存寄存器(栈入栈)

arm

复制代码
MOV SP, #0x20000  ; 初始化栈指针SP=0x20000(满减栈)
; 批量保存R0~R3到栈:先SP -= 4×4=16(4个寄存器,每个4字节),再传输数据
STMDB SP!, {R0-R3}  
; 执行后:SP=0x20000 - 16=0x1FFFC,R3→0x1FFFC、R2→0x1FFF8、R1→0x1FFF4、R0→0x1FFF0(低寄存器存低地址)
示例:LDMIA 批量恢复寄存器(栈出栈)

arm

复制代码
; 批量恢复R0~R3从栈:先传输数据,再SP += 16
LDMIA SP!, {R0-R3}  
; 执行后:SP=0x1FFFC + 16=0x20000,R0=0x1FFF0数据、R1=0x1FFF4数据、R2=0x1FFF8数据、R3=0x1FFFC数据

十二、ARM 栈操作(满减栈为核心)

栈是 "LIFO(后进先出)" 的临时存储区域,由 SP(栈指针)管理,ARM 支持 4 种栈类型,其中满减栈(Full Descending) 是嵌入式场景的默认选择。

1. 栈的分类规则

栈的类型由 "SP 指向" 和 "SP 增长方向" 共同决定:

分类维度 类型 核心定义
SP 指向 满栈(Full) SP 指向 "最后一个入栈的数据"
空栈(Empty) SP 指向 "下一个待入栈的空地址"
SP 增长方向 增栈(Ascending) 入栈时 SP 增大(向高地址扩展)
减栈(Descending) 入栈时 SP 减小(向低地址扩展)
4 种栈类型对比
栈类型 入栈逻辑 出栈逻辑 常用指令
满增栈 先存数据,再 SP += 4 先 SP -= 4,再读数据 STMIA、LDMDB
满减栈 先 SP -= 4,再存数据 先读数据,再 SP += 4 STMFD(=STMDB)、LDMFD(=LDMIA)
空增栈 先存数据,再 SP += 4 先读数据,再 SP -= 4 STMIB、LDMDA
空减栈 先 SP -= 4,再存数据 先 SP += 4,再读数据 STMDD、LDMEA

2. 常用满减栈操作(用户代码补充注释)

场景:保存 R1~R3 到栈,修改后恢复,SP 初始值 0x20000:

arm

复制代码
; 1. 准备数据:R1=1、R2=2、R3=3
MOV R1, #1  
MOV R2, #2  
MOV R3, #3  
; 2. 初始化栈指针SP=0x20000(满减栈基地址)
MOV SP, #0x20000  

; 3. 入栈(STMFD = STMDB):批量保存R1~R3到栈
STMFD SP!, {R1-R3}  
; 入栈逻辑:SP先减12(3个寄存器×4字节)→0x1FFF4,再存R3→0x1FFF4、R2→0x1FFF8、R1→0x1FFFC

; 4. 模拟中间操作:修改R1~R3的值
MOV R1, #0  
MOV R2, #0  
MOV R3, #0  

; 5. 出栈(LDMFD = LDMIA):从栈恢复R1~R3的值
LDMFD SP!, {R1-R3}  
; 出栈逻辑:先读R1→0x1FFFC数据(1)、R2→0x1FFF8数据(2)、R3→0x1FFF4数据(3),再SP加12→0x20000

; 执行后:R1=1、R2=2、R3=3(恢复原始值),SP=0x20000(恢复初始值)

补充说明

  • 栈的大小由编译器或程序员配置(如在链接脚本中指定栈空间为 0x1000),超出栈大小会导致 "栈溢出",破坏其他数据;
  • 函数调用时,栈用于保存函数参数、返回地址(LR)和局部变量,是函数上下文切换的核心载体。

十三、ARM 跳转指令(4 类核心指令)

跳转指令用于控制程序执行流程,支持 "基础跳转""带返回跳转""指令集切换跳转",是函数调用、循环控制的核心。

1. 基础跳转指令:B(Branch)

  • 作用 :无条件或条件跳转至标签,不保存返回地址(跳转后无法直接返回);
  • 语法B{cond}{.W} label
    • {cond}:条件后缀(如 EQ、NE),省略则无条件跳转;
    • {.W}:指定为 Thumb2 的 32 位指令,扩大跳转范围(±16MB);
  • 示例(用户场景补充)

arm

复制代码
; 无条件跳转至Loop标签(16位Thumb指令,跳转范围±2KB)
B Loop  
; 相等时跳转至Exit标签(32位Thumb2指令,跳转范围±16MB)
B.W EQ Exit  
Loop:
SUBS R0, R0, #1  
BNE Loop  ; 不相等则跳回Loop(B指令的条件变体)
Exit:
NOP  ; 跳转目标

2. 带返回跳转指令:BL(Branch with Link)

  • 作用 :跳转前将 "下一条指令地址" 存入 LR 寄存器,支持跳转后返回(核心用于函数调用);
  • 语法BL{cond} label
  • 示例(函数调用与返回)

arm

复制代码
; 调用func函数:跳转前将"MOV R1, #0"的地址存入LR
BL func  
MOV R1, #0  ; 函数返回后执行此指令
func:
ADD R0, R0, #1  ; 函数逻辑
BX LR  ; 函数返回:将LR的值赋给PC,回到调用处

3. 跳转 + 指令集切换:BX(Branch and eXchange)

  • 作用:根据寄存器 Rm 的 BIT0 切换指令集,再跳转到 Rm 指向的地址;
  • 语法BX{cond} Rm
    • Rm 的 BIT0=0:切换为 ARM 状态(执行 32 位 ARM 指令);
    • Rm 的 BIT0=1:切换为 Thumb 状态(执行 16/32 位 Thumb 指令);
  • 示例(指令集切换)

arm

复制代码
; R0=0x1001(BIT0=1):切换为Thumb状态,跳转到0x1000(BIT0仅用于状态识别)
BX R0  
; R0=0x2000(BIT0=0):切换为ARM状态,跳转到0x2000
BX R0  
  • 作用:结合 BL 和 BX 的功能 ------ 保存返回地址到 LR,同时根据目标地址 BIT0 切换指令集;
  • 语法 :两种形式:
    • BLX{cond} label:跳转到标签,自动根据标签地址 BIT0 切换指令集;
    • BLX{cond} Rm:根据 Rm 的 BIT0 切换指令集,跳转到 Rm 指向的地址;
  • 示例(跨指令集函数调用)

arm

复制代码
; 调用Thumb指令集的func函数:保存返回地址到LR,切换为Thumb状态
BLX func  
; 调用ARM指令集的func2函数:通过R0指定地址,保存返回地址到LR,切换为ARM状态
MOV R0, #0x3000  ; R0=0x3000(BIT0=0,ARM状态)
BLX R0  

补充说明

  • Cortex-M 系列仅支持 Thumb/Thumb2 指令集,BX/BLX 的 "ARM 状态切换" 在该系列中无效(会触发异常),仅在 Cortex-A 系列(如 A7/A9)中有用;
  • 函数返回优先使用 "BX LR",若需支持协处理器,可使用 "BLX LR"(但嵌入式场景极少)。

十四、延迟循环代码示例

代码实现 "延迟循环 + 函数式返回",核心利用 ADR 伪指令保存返回地址、LR 寄存器存储返回点,整理并补充注释如下:

代码分段与注释

arm

复制代码
; 1. 保存返回地址:用ADR伪指令将Ret标签地址加载到LR(链接寄存器)
; 作用:记住"延迟逻辑执行完后,要回到Ret处继续执行"
ADR LR, Ret  

; 2. 跳转到延迟逻辑入口:用ADR伪指令将Delay标签地址加载到PC(程序计数器)
; 作用:修改PC,让程序跳转到Delay处执行延迟代码
ADR PC, Delay  

; 3. 延迟逻辑入口:Delay标签
Delay:
; 初始化R1=1(R1暂未参与核心延迟,可作为临时变量)
MOV R1, #1  
; 初始化延迟循环计数器R0=5(循环将执行5次)
MOV R0, #5  

; 4. 延迟循环体:Loop标签
Loop:
; R0 = R0 - 1,带S后缀(更新xPSR的Z标志)
SUBS R0, R0, #1  
; 若Z=0(R0≠0),则跳回Loop继续循环;Z=1(R0=0)则退出循环
BNE Loop  

; 5. 延迟结束后返回:将LR中保存的Ret地址赋给PC,回到初始调用处
MOV PC, LR  

; 6. 返回目标地址:Ret标签(延迟结束后执行此指令)
Ret:
MOV R1, #1  ; 示例后续逻辑,可根据需求修改

补充说明

  • 延迟时间计算:延迟总时间 = 循环次数 × 单循环指令周期数 × 指令周期(如晶振 16MHz,指令周期 = 62.5ns,5 次循环总延迟≈5×2×62.5ns=625ns);
  • ADR 伪指令的偏移范围有限(通常 ±4KB),若 Delay 标签地址超出范围,需改用 LDR 伪指令(如 LDR PC, =Delay)。
相关推荐
小猪咪piggy4 小时前
【微服务】(2) 环境和工程搭建
微服务·云原生·架构
xrkhy4 小时前
微服务之配置中心Nacos
微服务·架构
xrkhy4 小时前
微服务之Gateway网关(1)
微服务·架构·gateway
喵叔哟4 小时前
62.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--新增功能--自训练ML模型
微服务·架构·.net
paishishaba4 小时前
JAVA面试复习笔记(待完善)
java·笔记·后端·面试
四谎真好看5 小时前
Java 黑马程序员学习笔记(进阶篇19)
java·笔记·学习·学习笔记
szxinmai主板定制专家6 小时前
【NI测试方案】基于ARM+FPGA的整车仿真与电池标定
arm开发·人工智能·yolo·fpga开发
新子y6 小时前
【小白笔记】最大交换 (Maximum Swap)问题
笔记·python
你要飞10 小时前
Hexo + Butterfly 博客添加 Live2D 看板娘指南
笔记