硬件开发2-汇编2(ARMv7-A)
一、指令
1、b(Branch)
原型:B<c> <label>
作用:实现无条件跳转,常用于不返回的跳转场景
特点:仅跳转到目标地址,不保存返回地址
示例:b reset ;跳转到reset标号处执行
2、bl(Branch with Link)
原型: BL<label>
作用:硬件自动将返回地址(
PC+4
或PC+2
)存入 LR (带链接的跳转,用于子程序调用,跳转前将返回地址(PC+4
)保存到链接寄存器LR
(R14))特点:子程序执行完毕后可通过
mov pc, lr
或bx lr
返回调用点示例:
注意事项:一个嵌套可用,大于两个嵌套以上,需要搭配栈来使用(用法如下)
3、bx lr
原型:BX{<cond>} <Rm>
作用:将程序计数器(PC)设置为链接寄存器(LR/R14)中保存的地址,实现函数返回
特点:
BX LR
将 LR 的值加载到 PC,恢复原程序流示例:
4、stmdb 压栈
原型:STMDB<c><Rn>{!},<registers>
作用:保护现场/恢复现场(压栈/弹栈)
压栈操作,ARM通常默认采用满递减栈,栈指针先减后存或先读后增
参数:
<Rn>
是栈顶指针寄存器,通常为栈指针SP
(R13),指向当前栈顶位置!
后缀的作用:
- 不加!:仅按指令操作存储数据,不更新基址寄存器(
SP
的值不变)- 加! :存储完成后,自动更新基址寄存器(
SP = SP - 4*N
,N
为寄存器数量)
- 压栈保存寄存器,同时更新栈指针
<registers>
的存储规则(入栈出栈的寄存器列表):
存储顺序:寄存器按编号从大到小依次存储(如
R3
→R2
→R1
→R0
)内存地址:基址先递减,再存储
使用要点: 栈顶指针寄存器初始化
mov sp, #0x40001000 : 报错非立即数
ldr sp, =0x40001000
魔术棒 -> Target->IRAM1:#0x40000000 size:0x1000示例:
5、ldmfd 弹栈
原型:LDMFD{<cond>} SP!, {<registers>}
作用:恢复现场
从栈中按低地址到高地址依次加载恢复数据到寄存器,并递增栈指针(弹栈方向与压栈相反)
用法:类似stmdb
示例:同上
6、ldr 普通加载数据
原型:LDR<c> <Rt>, <label>
作用:加载非立即数寄存器中(初始化寄存器 、加载常量数据)
特点:
- 用于加载 32 位立即数(ARM 指令不能直接加载 32 位立即数,
ldr =
是一种伪指令)示例:
ldr r0, =0x40001000 ; 将地址 0x40001000 存入 r0
7、ldr 类(*p)操作
原型:
作用:从 内存地址 加载数据到寄存器(类指针操作)
特点:对应的是C语言中的
*p
(指针解引用)示例:
--- ldr r0, [r1] ; 将 r1 指向的内存数据加载到 r0
--- ldr r0, [r1, #4] ; r0 = *(r1 + 4)
ldr r0, [r1, r2] ; r0 = *(r1 + r2)
ldr r0, [r1, r2, LSL #2] ; r0 = *(r1 + (r2 << 2))
ldr r0, [r1], #4 ; r0 = *r1; r1 += 4 (后索引)
ldr r0, [r1, #4]! ; r0 = *(r1 + 4); r1 += 4 (前索引并更新基址)
二、汇编调用C语言
1、流程
(1) 创建main.c
(2) 在main中声明将要在filename.s文件中将要使用的文件 extern void c_add(void);
(3) 导入 import +文件名; (keil当中要求)导出文件文件用export +文件名
(4) 保护现场 bl函数调用 恢复现场
(5) 解决编译报错:
asm.axf: Error: L6238E: start.o(reset) contains invalid call from '~PRES8 (The user did not require code to preserve 8-byte aligment of 8-byte data objects)' function to 'REQ8 (Code was permitted to depend on the 8-byte aligment of 8-byte data items)' function c_add. asm.axf: Finished: 0 information, 0 warning and 1 error messages.
解决办法:在.s文件开头 写上
栈对齐伪指令:preserve8 用于确保函数调用时栈指针保持 8 字节对齐
(6) 创建工程自动添加了启动代码报错
(7)重设软件配置 ---(魔术棒)
- 魔术棒 -> Debug -> Use Simulator->Run to main(取消)
- 魔术棒 -> Linker -> Use Memory Layout from Taget Dialog(勾选)
- 魔术棒 -> Taget -> ROM1 -> Start: 0x0 Size:0x2000
(8)函数传参:基本参数只能传递四个超出部分需采用压栈传递
例:
main.c
start.s
2、 ARM的7种异常类型
异常类型 | 触发条件 | 进入模式 | 优先级 |
---|---|---|---|
复位(Reset) | 上电或硬件复位 | 管理模式(SVC) | 1(最高) |
数据中止(Data Abort) | 非法内存访问(如缺页) | 中止模式(ABT) | 2 |
快速中断(FIQ) | 高优先级外设中断(如DMA) | FIQ模式 | 3 |
普通中断(IRQ) | 常规外设中断(如定时器) | IRQ模式 | 4 |
预取中止(Prefetch Abort) | 指令预取失败 | 中止模式(ABT) | 5 |
软件中断(SWI/SVC) | SVC 指令触发(系统调用) | 管理模式(SVC) | 6 |
未定义指令 | 执行未知指令 | 未定义模式(UND) | 7(最低) |
3、ARM汇编调用C函数
(1)前4个参数:依次通过寄存器 R0
、R1
、R2
、R3
传递。
(2)超过4个参数:剩余参数按从右向左的顺序压栈(栈内存传递)。
(3)返回值:
32位整数:通过 R0
返回。
64位整数:通过 R0
(低32位)和 R1
(高32位)返回。
浮点数:通过 S0
(单精度)或 D0
(双精度)返回
三、ARM 裸机开发环境搭建
;标准伪指令
preserve8 ;
area reset, code, readonly
code32
entry
;<1>
ldr pc, =start_hander ; 复位异常(Reset)
ldr pc, =undefine_hander ; 未定义指令异常
ldr pc, =software_hander ; 软件中断(SWI/SVC)
ldr pc, =prefetch_hander ; 预取中止异常
ldr pc, =data_hander ; 数据中止异常
nop ; 保留位(ARMv5+)
ldr pc, =irq_hander ; 普通中断(IRQ)
ldr pc, =fiq_hander ; 快速中断(FIQ)
undefine_hander
b undefine_hander
import software_vectorsoftware_hander
stmfd sp!, {r0-r12, lr} ; 保存寄存器现场
bl software_vector ; 调用C函数处理SWI
ldmfd sp!, {r0-r12, pc}^ ; 恢复现场并返回(^表示恢复CPSR)
;注意:此处使用^修饰符,表示同时将SPSR恢复到CPSR(用于模式切换)
;默认异常处理均为死循环,实际项目中需替换为具体逻辑
prefetch_hander
b prefetch_hander ; 预取中止死循环
data_hander
b data_hander ; 数据中止死循环
irq_hander
b irq_hander ; IRQ死循环
fiq_hander
b fiq_hander ; FIQ死循环
;SWI触发函数
export asm_swi_fun
asm_swi_fun
swi #7 ;触发软件中断(编号7)bx lr ;返回调用者
start_hander ;(主开始位置)
ldr sp, =0x40001000 ; 设置栈指针初始位置<2>
import main ; 声明外部C入口函数
;切换处理器模式到用户模式(CPSR.M[4:0] = 0x10),并启用中断(清除I-bit)<3>
mrs r0, cpsr ; 读取CPSR
bic r0, r0, #(0x1F << 0) ; 清除模式位
bic r0, r0, #(1 << 7) ; 清除中断禁止位(I-bit)
orr r0, r0, #(0x10 << 0) ; 设置为用户模式(0x10)
msr cpsr_c, r0 ; 写回CPSR
;重新设置用户模式
ldr sp, =0x40001000 ; 重新设置栈指针
sub sp, sp, #1024 ; 预留栈空间(1KB)
b main ; 跳转到C的main函数
end
知识点:
1、中断向量表
位置:必须位于0x00000000地址(或可通过VBAR重定位)
组成:8个32位条目,按固定顺序对应不同异常类型
跳转方式:
ldr pc, =handler
:支持全地址范围跳转
b handler
:仅支持±32MB范围跳转
2、栈设置:
在进入用户模式前,先设置一次栈(ldr sp, =0x40001000),这是为了在切换模式前确保栈有效(因为不同模式有各自的SP寄存器)
切换到用户模式后,再次设置栈指针并预留空间(sub sp, sp, #1024),避免用户程序栈溢出破坏其他数据
3、ARM处理器中 CPSR(当前程序状态寄存器)的模式位
模式值(十六进制) | 模式名称 | 英文全称 | 用途说明 |
---|---|---|---|
0x10 | 用户模式 | User Mode | 运行普通应用程序的非特权模式,无法直接访问硬件资源或执行特权指令。 |
0x11 | FIQ模式 | Fast Interrupt Mode | 处理高速中断(FIQ),有专用的寄存器(R8-R14_fiq),用于低延迟中断响应。 |
0x12 | IRQ模式 | Interrupt Mode | 处理普通中断(IRQ),比 FIQ 优先级低,用于一般外设中断。 |
0x13 | SVC模式 | Supervisor Mode | 操作系统内核模式(如 Linux 的 Kernel Mode),用于处理软件中断(SWI/SVC)。 |
0x17 | Abort模式 | Abort Mode | 当发生内存访问异常(如缺页或权限错误)时进入此模式。 |
0x1B | Undef模式 | Undefined Mode | 当执行未定义指令时触发,用于模拟浮点指令或扩展指令集。 |
特权级别:
特权模式(0x11-0x1B):可以访问所有系统资源和 CPSR 寄存器
非特权模式(0x10):限制访问硬件和关键寄存器
典型应用场景:
用户程序运行在 User Mode(0x10)
操作系统通过 SVC 指令触发 SVC Mode(0x13)执行系统调用
硬件中断自动切换至 IRQ/FIQ Mode(0x12/0x11)
模式切换权限:
只有特权模式(如 SVC)才能修改 CPSR 的模式位,用户模式尝试修改会触发异常。
模式自动切换:
中断/异常发生时,处理器会自动切换到对应模式(如 IRQ → 0x12)。
寄存器组差异:
某些模式(如 FIQ)有专用寄存器(R8-R14_fiq),可加速中断处理