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, Op2(Rd = Rn + Op2) - SUB(减法):
SUB{S}{cond} Rd, Rn, Op2(Rd = 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。 - 全速运行:程序在
Main和Delay_Func间循环,R3始终保持0xFF。 
四、总结
Cortex-M3/M4汇编的核心逻辑可提炼为三句话:
- 操作对象是寄存器:核心寄存器仅需掌握R0-R7(数据)、SP/LR/PC(控制)和APSR(条件标志)。
 - 内存访问靠Load/Store:遵循RISC原则,仅
LDR/STR指令与内存交互。函数上下文保护使用推荐的PUSH/POP指令。 - 程序流靠PC控制:跳转用
B,函数调用用BL(依赖LR),返回推荐用BX LR或POP {PC}。 
掌握以上内容,即可为理解和应对嵌入式开发中启动代码/硬件初始化、中断服务例程(ISR)编写、性能关键代码段优化等场景打下坚实基础。
                    
                
                
            
        
浙公网安备 33010602011771号