接下来,总结一些ARM v7-A架构中常用的汇编指令,如下:
1. 处理器内部数据传输
在处理器内部来回传递数据,常见的操作有:
- 数据从一个寄存器传输到另一个寄存器
- 数据传输到特殊寄存器,例如CPSR寄存器
- 将立即数传输到寄存器
常用的数据传输指令有3个,分别是MOV、MRS和MSR,这3个指令的用法如下:
指令 | 目的 | 源 | 作用 |
---|---|---|---|
MOV | R0 | R1 | 将R1里面的数据赋值到R0中 |
MRS | R0 | CPSR | 将CPSR里面的数据赋值到R0中 |
MSR | CPSR | R1 | 将R1的数据赋值到CPSR中 |
MOV指令
MOV指令用于将数据从一个寄存器赋值到另一个寄存器,或将一个立即数赋值到寄存器里面,使用示例如下:
MOV R0,R1 @将R1中的数据传递到R0,也就是R0=R1
MOV R0,#0x14 @将立即数0x14传递到R0,也就是R0=0x14
MRS指令
MRS指令用于将特殊寄存器,例如CPSR或SPSR中的数据传递到通用寄存器,使用如下:
MRS R0,CPSR @将CPSR中的数据传递到R0,也就是R0=CPSR
MSR指令
MSR指令用来将通用寄存器中的数据传递到特殊寄存器,例如CPSR和CPSR,使用如下:
MSR CPSR,R0 @将R0的数据传递到CPSR中,也就是CPSR=R0
2. 存储器访问指令
ARM架构不能直接去访问存储器,例如RAM中的数据,I.MX6UL中的寄存器就是RAM类型的,当我们使用汇编指令来配置SoC寄存器的时候就需要借助存储器访问指令,一般的步骤为,先将要配置的寄存器值写入到Rx寄存器中,然后使用存储器访问指令将Rx中的数据写入到SoC的寄存器中,读取SoC寄存器的值类似,常用的存储器访问指令有LDR和STR,用法如下:
指令 | 作用 |
---|---|
LDR Rd,[Rn,#offset] | 将存储器Rn+offset位置的数据读取到Rd中 |
STR Rd,[Rn,#offset] | 将Rd中的数据写入到存储器Rn+offset位置中 |
LDR指令
在嵌入式ARM中,LDR指令用于从存储器中加载数据到通用寄存器Rx中,另外,LDR指令也能将一个立即数加载到寄存器Rx中,但是要使用”=”,而不是”#”,在ARM开发中,LDR最常用的就是读取SoC的寄存器值,例如,I.MX6UL有一个寄存器GPIO1_GDIR,该寄存器地址为0x0209C004,如果想要读取该寄存器中的数值,可以使用下面汇编代码:
LDR R0,=0x0209C004 @将寄存器地址0x0209C004加载到R0中
LDR R1,[R0] @读取寄存器地址0x0209C004中的数据到R1中
上述示例代码中,没有使用到offset,也就是offset为0。
STR指令
LDR指令可以用来读取存储器的数据都Rx寄存器中,STR指令可以将数据写入到存储器中,例如,I.MX6UL有一个寄存器GPIO1_GDIR,该寄存器地址为0x0209C004,如果想要往该寄存器中写入数值,可以使用下面汇编代码:
LDR R0,=0x0209C004 @将寄存器地址0x0209C004加载到R0中
LDR R1,=0x00000001 @将0x00000001加载到R1中
STR R1,[R0] @将R1中的值写入到寄存器地址0x0209C004中
另外,需要注意的是,LDR指令和STR指令都是按照字进行读取和写入的,也就是直接操作32bit数据,如果要按照字节或者半字操作的话,可以在LDR指令和STR指令后面加上”B”或”H”,例如按字节操作的指令为LDRB和STRB。
压栈和出栈指令
在编写代码的时候,通常会在A函数中调用B函数,当B函数执行完以后需要再回到A函数处执行,如果想要跳回到A函数继续正常执行,那就必须在跳到B函数之前将当前处理器的状态保存起来(保存R0~R15寄存器的值),当B函数执行完后,需要将前面保存的寄存器值恢复到R0~R15,保存寄存器的值操作就是现场保护,恢复寄存器的值操作就是恢复现场,在进行现场保护需要使用PUSH指令进行压栈操作,恢复现场就需要使用POP指令进行出栈操作,这些指令一次可以操作多个寄存器数据,利用当前的栈指针SP来生成地址,用法如下:
指令 | 作用 |
---|---|
PUSH |
将寄存器列表压入栈中 |
POP |
将寄存器列表出栈 |
例如,现在需要将R0~R3寄存器和R12这5个寄存器压栈,当前的SP指针指向0x80000000,处理器的堆栈向下增长,ARM汇编代码如下:
PUSH {R0~R3,R12} @将R0~R3和R12进行压栈操作
代码执行后,堆栈如下所示:
此时的堆栈指针SP指向0x7FFFFFEC,假如现在需要将LR寄存器进行压栈,ARM汇编代码如下:
PUSH {LR} @将LR寄存器进行压栈操作
代码执行后,堆栈生长如下所示:
想要将寄存器出栈的话,使用下面的ARM汇编代码:
POP {LR} @先将LR寄存器出栈
POP {R0~R3,R12} @将R0~R3,R12寄存器出栈
栈是一种先进后出的模型,出栈是从栈顶先开始,地址依次减小来提取堆栈中的数据恢复到寄存器列表中。
跳转指令
在ARM汇编中,有多种跳转操作,例如:
- 使用B、BL或BX指令直接跳转
- 直接向PC寄存器里面写入数据
在上面列出的操作中,都可以完成跳转操作,但是一般常用的还是使用跳转指令B、BL或者BX,指令用法如下:
指令 | 作用 |
---|---|
B | 跳转到label |
BX |
间接跳转,跳转到存放在Rm的地址处,并切换指令集 |
BL | 跳转到label,并将返回地址保存到LR中 |
BLX | 跳转到Rm指定的地址,并将返回地址保存到LR中,切换指令集 |
B指令
B指令会将PC寄存器的值设置为跳转的目标地址,一旦执行B指令后,ARM处理器将会立即跳到指定的目标地址,如果想调用的函数不会再返回到原来的地方执行,就可以使用B指令,使用示例如下:
_start:
ldr sp,=0x80200000 @设置堆栈指针SP
b main @跳转到main函数处执行
示例代码就是在汇编中初始化C运行环境,然后跳转到C文件的main函数处执行,main函数调用后,将不会返回到原来的位置处执行。
BL指令
BL指令在跳转之前会将当前PC寄存器的值保存到LR寄存器中,通过将LR寄存器中的值重新加载到PC中,就可以继续从跳转之前的代码处执行,这是子程序调用的一个基本常用手段,例如,ARM处理器的irq中断服务函数使用汇编编写,主要是使用汇编来实现现场保护和现场恢复、获取中断号等,具体的中断处理过程使用C函数,所以存在在汇编中调用C函数的问题,C函数的处理过程完成以后,需要返回到汇编的中断服务函数,一般是恢复现场,这时候就要使用BL指令进行跳转了,典型代码如下:
push {r0, r1} @将r0和r1进行保存
cps #0x13 @处理器进入SVC模式
bl system_irqhandler @跳转到C的中断处理函数
cps #0x12 @处理器进入IRQ模式
pop {r0, r1} @恢复r0和r1寄存器
str r0, {r1, #0x10 } @中断执行完成,写EOIR
上面代码中,使用BL指令跳转到C版本的中断处理函数system_irqhandler,函数执行完后,需要返回到原来的位置继续执行下面的汇编代码。
算术运算指令
ARM汇编中也可以进行算术运算,例如加减乘除操作,使用对应的运算指令即可,常用的运算指令用法如下:
指令 | 计算公式 | 作用 |
---|---|---|
ADD Rd, Rn, Rm | Rd = Rn + Rm | 加法运算 |
ADD Rd, Rn, #immed | Rd = Rn + #immed | |
ADC Rd, Rn, Rm | Rd = Rn + Rm + 进位 | 带进位的加法运算 |
ADC Rd, Rn, #immed | Rd = Rn + #immed + 进位 | |
SUB Rd, Rn, Rm | Rd = Rn - Rm | 减法运算 |
SUB Rd, Rn, #immed | Rd = Rn - #immed | |
SBC Rd, Rn, Rm | Rd = Rn - Rm - 借位 | 带借位的减法 |
SBC Rd, Rn, #immed | Rd = Rn - #immed -借位 | |
MUL Rd, Rn, Rm | Rd = Rn * Rm | 乘法运算 |
UDIV Rd, Rn, Rm | Rd = Rn / Rm | 无符号除法运算 |
SDIV Rd, Rn, Rm | Rd = Rn / Rm | 有符号除法运算 |
逻辑运算指令
ARM中还有一些常用的逻辑运算指令,用法如下:
指令 | 计算公式 | 作用 |
---|---|---|
AND Rd, Rn | Rd = Rd & Rn | 按位与 |
AND Rd, Rn, #immed | Rd = Rn & #immed | 按位与 |
AND Rd, Rn, Rm | Rd = Rn & Rm | 按位与 |
ORR Rd, Rn | Rd = Rd | Rn | 按位或 |
ORR Rd, Rn, #immed | Rd = Rn | #immed | 按位或 |
ORR Rd, Rn, Rm | Rd = Rn | Rm | 按位或 |
BIC Rd, Rn | Rd = Rd & (~Rn) | 位清除 |
BIC Rd, Rn, #immed | Rd = Rn & (~#immed) | 位清除 |
BIC Rd, Rn, Rm | Rd = Rn & (~Rm) | 位清除 |
对于更多更详细的ARM汇编指令,可以参考ARM官网的相关文档。