1 关键寄存器详解
1.1 R13 (SP) 栈指针寄存器
功能说明:
栈指针 (Stack Pointer):指向当前堆栈的顶部
ARM CortexM 处理器有两个独立的栈指针:
1. MSP (Main Stack Pointer):主栈指针
用于异常处理程序(中断、异常)
操作系统内核代码使用
2. PSP (Process Stack Pointer):进程栈指针
用于普通应用程序代码
每个任务可以有自己的PSP
在FreeRTOS中的应用:
; 在任务切换时保存和恢复PSP
mrs r0, psp ; 读取当前任务的PSP到r0
; ... 保存任务上下文 ...
msr psp, r0 ; 恢复新任务的PSP
栈操作示例:
; 压栈操作(Push)
push {r0-r3, lr} ; 将r0、r1、r2、r3和lr寄存器的值依次压入栈中
; 作用:保存函数调用前的寄存器状态,lr存储返回地址
; 出栈操作(Pop)
pop {r0-r3, pc} ; 从栈中依次恢复r0、r1、r2、r3的值,并将原lr的值写入pc
; 作用:恢复寄存器状态,同时通过pc跳回原调用地址(实现函数返回)
1.2 R14 (LR) 链接寄存器
功能说明:
链接寄存器 (Link Register):存储函数调用的返回地址
调用子程序时,处理器自动将返回地址保存在LR中
从子程序返回时,可以使用 BX LR 指令返回
特殊用法:
1. 异常处理中的LR:
进入异常时,LR被设置为特殊的EXC_RETURN值
EXC_RETURN指示返回时应使用的堆栈指针和处理器模式
2. EXC_RETURN值:
CortexM3/M4 EXC_RETURN值
0xFFFFFFF9 返回线程模式,使用MSP
0xFFFFFFFD 返回线程模式,使用PSP
0xFFFFFFF1 返回Handler模式,使用MSP
使用示例:
; 调用函数
bl function_name ; 1. 将下一条指令地址存入LR
; 2. 跳转到function_name
; 函数返回
bx lr ; 跳转到LR中的地址,返回调用者
; 嵌套调用时保护LR
push {lr} ; 保存LR到栈中
bl another_function
pop {lr} ; 从栈中恢复LR
bx lr ; 返回
1.3 R15 (PC) 程序计数器
功能说明:
程序计数器 (Program Counter):存储下一条要执行的指令地址
正常执行时,PC自动递增(ARM状态+4,Thumb状态+2)
通过修改PC的值可以跳转到任意地址执行
特殊性质:
1. 读取PC:读取PC时,返回当前指令地址+4(ARM)或+2(Thumb)
2. 修改PC:写入PC会立即跳转到新地址执行
3. 对齐要求:PC值必须对齐到指令长度(Thumb为2字节对齐)
使用示例:
; 直接跳转
ldr pc, =0x08001000 ; 跳转到绝对地址
; 相对跳转
add pc, pc, 0x100 ; PC相对跳转
; 条件跳转
cmp r0, 10
beq target_label ; 如果相等则跳转
2 ARM汇编指令详解
2.1 内存访问指令
LDR (Load Register) 加载寄存器
; 基本语法:LDR{条件} Rd, [Rn, offset]
LDR R0, [R1] ; 从R1指向的地址读取4字节到R0
LDR R0, [R1, 4] ; 从R1+4的地址读取4字节到R0
LDR R0, [R1, 4]! ; 从R1+4读取,然后R1=R1+4(前变址)
LDR R0, [R1], 4 ; 从R1读取,然后R1=R1+4(后变址)
LDR R0, [R1, R2] ; 从R1+R2的地址读取
LDR R0, [R1, R2, LSL 2] ; 从R1+(R2<<2)的地址读取
STR (Store Register) 存储寄存器
; 基本语法:STR{条件} Rd, [Rn, offset]
STR R0, [R1] ; 将R0的4字节写入R1指向的地址
STR R0, [R1, 4] ; 将R0的4字节写入R1+4的地址
STR R0, [R1, 4]! ; 写入R1+4,然后R1=R1+4
STR R0, [R1], 4 ; 写入R1,然后R1=R1+4
批量加载/存储指令
; LDM/STM指令格式:op{条件}{模式} Rn{!}, reglist{^}
; 模式:IA(后递增)、IB(前递增)、DA(后递减)、DB(前递减)
LDMIA R0!, {R1-R3} ; 从R0读取到R1,R2,R3,R0每次增加4
STMIA R0!, {R1-R3} ; 将R1,R2,R3写入R0,R0每次增加4
; 在FreeRTOS任务切换中的应用
STMFD SP!, {R0-R12, LR} ; 保存所有寄存器到栈(全递减)
LDMFD SP!, {R0-R12, PC} ; 从栈恢复所有寄存器并返回
2.2 算术运算指令
ADD (Addition) 加法
; 语法:ADD{条件}{S} Rd, Rn, Operand2
ADD R0, R1, R2 ; R0 = R1 + R2
ADD R0, R1, 0xFF ; R0 = R1 + 255
ADD R0, R0, 1 ; R0++,相当于R0=R0+1
ADDS R0, R1, R2 ; 加法并更新标志位
SUB (Subtraction) 减法
; 语法:SUB{条件}{S} Rd, Rn, Operand2
SUB R0, R1, R2 ; R0 = R1 - R2
SUB R0, R1, 0xFF ; R0 = R1 - 255
SUB R0, R0, 1 ; R0,相当于R0=R0-1
SUBS R0, R1, R2 ; 减法并更新标志位
其他算术指令
MUL R0, R1, R2 ; 乘法:R0 = R1 × R2
AND R0, R1, R2 ; 逻辑与:R0 = R1 & R2
ORR R0, R1, R2 ; 逻辑或:R0 = R1 | R2
EOR R0, R1, R2 ; 逻辑异或:R0 = R1 ^ R2
MOV R0, R1 ; 移动:R0 = R1
MOV R0, 0x100 ; 立即数移动:R0 = 256
2.3 比较和跳转指令
CMP (Compare) 比较
; 语法:CMP{条件} Rn, Operand2
; 实际执行:Rn Operand2,结果不保存,只更新标志位
CMP R0, 10 ; 比较R0和10
B (Branch) 无条件跳转
; 语法:B{条件} label
B main ; 无条件跳转到main标签
BEQ label1 ; 如果相等(Z=1)则跳转到label1
BNE label2 ; 如果不相等(Z=0)则跳转到label2
BGT label3 ; 如果大于(有符号)则跳转到label3
BL (Branch with Link) 带链接的跳转
; 语法:BL{条件} label
; 功能:1. 将下一条指令地址存入LR
; 2. 跳转到label
BL function_name ; 调用函数
; 调用后LR = 返回地址
; 函数中应该使用BX LR返回
BX (Branch and Exchange) 跳转并切换指令集
; 语法:BX{条件} Rm
; 功能:跳转到Rm指定的地址,并根据Rm[0]决定使用ARM还是Thumb状态
BX LR ; 返回到LR中的地址(常用)
BX R0 ; 跳转到R0中的地址
2.4 特殊指令
MRS/MSR 状态寄存器访问
; 读取状态寄存器到通用寄存器
MRS R0, CPSR ; 读取当前程序状态寄存器
MRS R0, SPSR ; 读取保存的程序状态寄存器(异常模式)
MRS R0, APSR ; 读取应用程序状态寄存器
MRS R0, MSP ; 读取主栈指针
MRS R0, PSP ; 读取进程栈指针
; 写通用寄存器到状态寄存器
MSR CPSR, R0 ; 写当前程序状态寄存器
MSR PSP, R0 ; 写进程栈指针
数据传送指令
; LDR伪指令 加载任意32位常数
LDR R0, =0x12345678 ; 加载32位立即数
LDR R0, =label ; 加载标签地址
; MOV/MVN 移动和取反移动
MOV R0, 0x100 ; R0 = 256
MVN R0, 0xFF ; R0 = ~0xFF = 0xFFFFFF00
3 常用指令组合模式
4.1 函数调用标准序言/尾声
; 函数序言(保存寄存器)
PUSH {R4R11, LR} ; 保存被调用者保存的寄存器和返回地址
SUB SP, SP, LOCAL_SIZE ; 为局部变量分配栈空间
; 函数尾声(恢复寄存器)
ADD SP, SP, LOCAL_SIZE ; 释放局部变量空间
POP {R4R11, PC} ; 恢复寄存器并返回(直接将返回地址弹出到PC)
4.2 原子操作实现
; 使用LDREX/STREX实现原子操作
atomic_increment:
LDREX R1, [R0] ; 独占加载
ADD R1, R1, 1 ; 递增
STREX R2, R1, [R0] ; 尝试独占存储
CMP R2, 0 ; 检查是否成功
BNE atomic_increment ; 失败则重试
BX LR ; 返回
4.3 循环和条件判断
; 简单的for循环
MOV R0, 0 ; i = 0
loop_start:
CMP R0, 10 ; 比较i和10
BGE loop_end ; 如果i>=10,跳出循环
; 循环体代码...
ADD R0, R0, 1 ; i++
B loop_start ; 继续循环
loop_end: