ARM汇编概述:Cortex-M3/M4实战指南

Cortex-M3/M4是嵌入式开发中最主流的ARM内核,广泛应用于STM32等微控制器。其汇编语言遵循RISC架构"精简高效"的设计理念。本文从核心寄存器到常用指令,再到实战示例,层层递进讲解ARM汇编的核心逻辑。

一、核心寄存器(汇编的"操作对象")

Cortex-M3/M4的寄存器是汇编指令的直接操作对象,无需记忆所有寄存器,仅需掌握以下高频核心寄存器,即可覆盖绝大多数嵌入式开发场景。

1. 通用寄存器

寄存器 别名 / 功能 核心用途
R0-R7 低寄存器 暂存运算数据、传递函数前4个参数、存储内存地址(配合LDR/STR访问RAM/ROM)
R13 SP(栈指针) 指向栈顶,管理函数调用时的上下文保存与恢复。Cortex-M默认采用满递减栈模型
R14 LR(链接寄存器) 保存函数调用后的返回地址(BL指令自动存入)。
R15 PC(程序计数器) 指向当前执行的指令地址,修改PC可实现跳转。Cortex-M始终处于Thumb状态,故PC的BIT0必须为1。

2. 程序状态寄存器

程序状态寄存器(xPSR)由APSR(应用状态寄存器)、IPSR(中断程序状态寄存器)和EPSR(执行程序状态寄存器)三个部分组合而成,其中APSR需重点关注,其4个标志位是"条件指令"的核心判断依据:

  • N(负标志):运算结果为负(最高位为1)时N=1,否则为0;用于判断有符号数的正负。
  • Z(零标志):运算结果为0时Z=1,否则为0;常用于循环结束判断(如计数到0)、相等比较(如CMP R0, R1后用BEQ跳转)。
  • C(进位/借位标志):加法有进位或减法无借位时C=1,否则为0;用于多字节运算(如64位数据加法)。
  • V(溢出标志):有符号数运算超出32位范围时V=1,否则为0;用于检测有符号数运算错误(如0x7FFFFFFF + 1会溢出)。

二、ARM常用汇编指令

Cortex-M3/M4架构遵循Load-Store原则,即数据处理指令只操作寄存器,与内存的数据交换必须通过LDR(从内存加载数据到寄存器)和STR(将寄存器数据存储到内存)指令完成。

1. 数据处理指令(CPU内部寄存器运算)

仅操作通用寄存器,不直接访问内存,是实现"加减、比较、位操作"等逻辑的核心。

(1)MOV:数据传送指令

核心作用:实现数据在寄存器间的传递,或立即数到寄存器的加载。

语法MOV{S}{cond} Rd, Op2

  • {S}:可选,指令执行后更新APSR标志(如MOVS R0, #0会设置Z=1)。
  • {cond}:可选,条件执行后缀(如MOVNE R0, #0xFF表示"若Z=0(前序运算结果非0),则执行")。
  • Op2:可以是立即数或另一个寄存器,并可包含移位操作(如R3, LSL #2)。

示例

MOV R0, #0x20000000  ; R0 = 0x20000000(加载RAM基地址)
MOVS R1, #0          ; R1 = 0,同时更新APSR的Z标志(Z=1)
MOV R2, R3, LSL #2   ; R2 = R3 << 2(将R3的值左移2位后存入R2)

(2)ADD/SUB:加减指令

核心作用:实现寄存器或立即数的加减运算。

语法

  • ADD(加法)ADD{S}{cond} Rd, Rn, Op2Rd = Rn + Op2
  • SUB(减法)SUB{S}{cond} Rd, Rn, Op2Rd = Rn - Op2

示例

; ADD示例
ADD R0, R1, #5       ; R0 = R1 + 5
ADDS R2, R3, R4      ; R2 = R3 + R4,同时更新APSR

; SUB示例  
SUB R0, R1, #10      ; R0 = R1 - 10
SUBS R5, R5, #1      ; R5自减1(循环计数器),并更新标志位

(3)CMP:比较指令

核心作用:隐性计算"Rn - Op2",不保存结果,仅更新APSR标志,为后续"条件跳转"做准备。

语法CMP{cond} Rn, Op2

示例

CMP R0, #100         ; 比较R0与100
BEQ LoopEnd          ; 若Z=1(R0=100),跳转到LoopEnd
BNE LoopContinue     ; 若Z=0(R0≠100),跳转到LoopContinue

2. 内存访问指令

(1)LDR:读内存指令

核心作用:将内存中的数据读取到寄存器。

语法LDR{type}{cond} Rd, [Rn {, #offset}]

  • {type}:可选,指定数据类型(B=无符号字节、H=无符号半字、默认=字)。
  • 寻址方式:偏移寻址([Rn, #4]):地址 = Rn + 4,Rn不变。前索引([Rn, #4]!):地址 = Rn + 4,然后更新Rn = Rn + 4。后索引([Rn], #4):地址 = Rn,然后更新Rn = Rn + 4。

示例

LDR R0, [R1]         ; 读R1指向的4字节数据到R0
LDRB R2, [R1, #1]    ; 读R1+1地址的1字节到R2
LDRH R3, [R1], #2    ; 读R1指向的2字节到R3,然后R1 = R1 + 2

(2)STR:写内存指令

核心作用:将寄存器中的数据写入内存。

语法:与LDR一致(Rd为源寄存器)。

示例

STR R0, [R1]         ; 将R0的4字节数据写入R1指向的地址
STRH R2, [R1, #4]!   ; 将R2的2字节数据写入R1+4,然后R1 = R1 + 4

(3)PUSH/POP:栈操作指令

核心作用:批量保存/恢复寄存器到栈,是函数调用时保护上下文的标准且推荐的方式。它们是STMFD SP!LDMFD SP!的别名,专用于栈操作,更简洁直观。

语法

  • 入栈(保存寄存器):PUSH {reglist}
  • 出栈(恢复寄存器):POP {reglist}

示例

; 函数入口:保存R4-R6(需保护的寄存器)和LR(返回地址)
PUSH {R4-R6, LR}     ; 入栈,SP相应递减

; 函数体 ... (可安全使用R4-R6)

; 函数出口:恢复寄存器并返回
POP {R4-R6, PC}      ; 出栈,恢复R4-R6,并将LR的值直接弹出到PC(实现返回)

3. 跳转与函数调用指令(程序流控制)

(1)B:无条件/条件跳转

核心作用:直接修改PC值,跳转到指定标号,适用于"循环、分支判断"。

语法B{cond} Label

示例

B MainLoop           ; 无条件跳转到MainLoop
CMP R0, #0
BNE ErrorHandler     ; 若R0≠0,跳转到ErrorHandler

(2)BL:函数调用指令

核心作用:跳转前自动将返回地址(下一条指令地址)存入LR,用于函数调用。

语法BL{cond} Label

示例

BL Delay             ; 调用Delay函数,LR = 返回地址
MOV R1, #1           ; Delay返回后,从此处继续执行

Delay:               
    MOV R0, #100000
DelayLoop:
    SUBS R0, R0, #1
    BNE DelayLoop
    BX LR            ; 使用 BX LR 返回调用处

(3)伪指令

LDR =val:加载任意32位数值到寄存器。

LDR R0, =0x12345678  ; 加载非立即数
LDR R1, =0x10        ; 编译器可能优化为 MOV R1, #0x10

ADR:获取标号的相对地址(短距离)。

ADR R0, DataBuf      ; 将DataBuf的地址加载到R0
DataBuf DCD 0x00, 0x01, 0x02

三、完整示例程序

以下示例覆盖"栈操作、函数调用、内存读写、数据校验"四大核心场景,并使用推荐的PUSH/POP指令。

; 程序说明:Cortex-M3/M4汇编实战示例
; 核心功能:1.栈保存寄存器 2.调用延时函数 3.读写RAM数据 4.校验数据一致性 5.循环执行
    AREA    ARM_Demo, CODE, READONLY
    ENTRY
    THUMB                              ; 明确指定使用Thumb指令集
    ALIGN 4

; --------------------------
; 主函数:程序核心逻辑入口
; --------------------------
Main
    ; 1.栈操作:在函数入口保存可能被使用的寄存器及返回地址,遵守调用规范
    PUSH   {R0-R2, LR}                ; 使用PUSH保存寄存器

    ; 2.内存读写:向RAM地址(0x20000000)写入数据,再读取校验
    MOV     R0, #0x20000000           ; R0 = RAM基地址
    LDR     R1, =0x12345678           ; R1 = 待写入数据
    STR     R1, [R0]                  ; 写操作:将数据写入内存
    LDR     R2, [R0]                  ; 读操作:从内存读取数据

    ; 3.数据校验:比较"写入值(R1)"与"读取值(R2)"
    CMP     R1, R2
    BEQ     Data_OK                   ; 若数据一致,跳转
    MOV     R3, #0x00                 ; 数据不一致:R3 = 0x00(错误标志)
    B       Call_Delay
Data_OK
    MOV     R3, #0xFF                 ; 数据一致:R3 = 0xFF(成功标志)

    ; 4.调用延时函数
Call_Delay
    BL      Delay_Func                ; 调用延时函数

    ; 5.恢复寄存器并返回:从栈中恢复R0-R2,并通过将LR弹出至PC来返回到调用者,实现循环
    POP    {R0-R2, PC}                ; 使用POP恢复寄存器并返回

; --------------------------
; 延时函数:简单递减延时
; --------------------------
Delay_Func
    PUSH   {R0, LR}                   ; 延时函数也保护它用到的寄存器和LR
    LDR    R0, =500000
Delay_Loop
    SUBS   R0, R0, #1
    BNE    Delay_Loop
    POP    {R0, PC}                   ; 恢复R0,并通过弹出LR到PC来返回

    ALIGN 4
    END

执行效果

  • 入栈后:SP相应递减。
  • 写内存后:查看0x20000000地址,值为0x12345678
  • 校验后:APSR的Z标志为1,R3被设为0xFF
  • 全速运行:程序在MainDelay_Func间循环,R3始终保持0xFF

四、总结

Cortex-M3/M4汇编的核心逻辑可提炼为三句话:

  1. 操作对象是寄存器:核心寄存器仅需掌握R0-R7(数据)、SP/LR/PC(控制)和APSR(条件标志)。
  2. 内存访问靠Load/Store:遵循RISC原则,仅LDR/STR指令与内存交互。函数上下文保护使用推荐的PUSH/POP指令。
  3. 程序流靠PC控制:跳转用B,函数调用用BL(依赖LR),返回推荐用BX LRPOP {PC}

掌握以上内容,即可为理解和应对嵌入式开发中启动代码/硬件初始化、中断服务例程(ISR)编写、性能关键代码段优化等场景打下坚实基础。

posted @ 2025-11-03 23:19  比特向阳  阅读(30)  评论(0)    收藏  举报