出栈入栈就要对堆栈进行操作,所谓的堆栈其实就是一段内存,这段内存比较特殊,由 SP 指针访问, SP 指针指向栈顶。芯片一上电 SP 指针还没有初始化,所以 C 语言没法运行,对于有些芯片还需要初始化 DDR【负责内存与CPU之间数据交换的重要组成部分。】,因为芯片本身没有 RAM,或者内部 RAM 不开放给用户使用,用户代码需要在DDR 中运行,因此一开始要用汇编来初始化 DDR 控制器。
GNU 汇编语法适用于所有的架构,并不是 ARM 独享的, GNU 汇编由一系列的语句组成,每行一条语句,每条语句有三个可选部分:
label: instruction @ comment
注意! ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。
常见伪操作
.byte 定义单字节数据,比如.byte 0x12。
.short 定义双字节数据,比如.short 0x1234。
.long 定义一个 4 字节数据,比如.long 0x12345678。
.equ 赋值语句,格式为: .equ 变量名,表达式,比如.equ num, 0x12,表示 num=0x12。
.align 数据字节对齐,比如: .align 4 表示 4 字节对齐。
.end 表示源文件结束。
.global 定义一个全局符号,格式为: .global symbol,比如: .global _start
函数格式如下:
函数名:
函数体
返回语句【不是必须滴】
/* 未定义中断 */
Undefined_Handler:
ldr r0, =Undefined_Handler
bx r0 @bx r0”是函数返回语句,
处理器内部数据传输指令
数据传输常用的指令有三个: MOV、 MRS 和 MSR
常用的存储器访问指令有两种:
LDR:LDR 主要用于从存储加载数据到寄存器 Rx 中
STR:LDR 是从存储器读取数据, STR 就是将数据写入到存储器中
在 A 函数中调用 B 函数,当 B 函数执行完以后再回到 A 函数继续执行。要想在跳回 A 函数以后代码能够接着正常运行,那就必须在跳到 B 函数之前将当前处理器状态保存起来(就是保存 R0~R15 这些寄存器值),当 B 函数执行完成以后再用前面保存的寄存器值恢复R0~R15 即可。保存 R0~R15 寄存器的操作就叫做现场保护【压栈】,恢复 R0~R15 寄存器的操作就叫做恢复现场。【出栈】
压栈的指令为 PUSH,出栈的指令为 POP,
PUSH 和 POP 是一种多存储和多加载指令,即可以一次操作多个寄存器数据.
PUSH 和 POP 的另外一种写法是“STMFD SP!”和“LDMFD SP!”, STMFD 可以分为两部分: STM 和 FD,
示例代码 STMFD 和 LDMFD 指令
1 STMFD SP!,{R0~R3, R12} @R0~R3,R12 入栈
2 STMFD SP!,{LR} @LR 入栈
4LDMFD SP!, {LR} @先恢复 LR
5 LDMFD SP!, {R0~R3, R12} @再恢复 R0~R3, R12
有多种跳转操作,比如:
①、直接使用跳转指令 B、 BL、 BX 等。
②、直接向 PC 寄存器里面写入数据。
上述两种方法都可以完成跳转操作,但是一般常用的还是 B、 BL 或 BX
ADD Rd, Rn, Rm |
Rd = Rn + Rm |
加法运算,指令为 ADD |
||
ADD Rd, Rn, #immed |
Rd = Rn + #immed |
|||
SUB Rd, Rn, Rm |
Rd = Rn – Rm |
减法 |
||
SUB Rd, #immed |
Rd = Rd - #immed |
|||
SUB Rd, Rn, #immed |
Rd = Rn - #immed |
|||
基本很好理解,和c一样