自己的一些简单的总结,也是最常用的ARM汇编指令,之后也会不断的补充完善。
1. 汇编系统预定义的段名
.text @代码段
.data @初始化数据段
.bss @未初始化数据段
需要注意的是,源程序中.bss段应该在.text之前。
2.定义入口点
汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标志指明其它入口点。
.text
.global _start
_start:
3 .word用法
word expression就是在当前位置放一个word型的值,这个值就是expression
举例来说,
_rWTCON:
.word 0x15300000
就是在当前地址,即_rWTCON处放一个值0x15300000
4 .equ赋值操作,相当于c语言的宏定义
.equ MEM_CTRL_BASE, 0x48000000 //注意要加,号
5. 逻辑指令
AND―――――逻辑"与"操作指令
参考:
http://blog.csdn.net/denlee/article/details/2501182
在嵌入式开发中,汇编程序常常用于非常关键的地方,比如系统启动时的初始化,进出中断时的环境保存、恢复,对性能要求非常苛刻的函数等。
1、相对跳转指令: b 、 bl
不同之处在于: bl 指令除了跳转之外,还将返回地址( bl 的下一条指令的地址)保存在lr 寄存器中。
跳转范围:当前指令的前后32M。
它们是与位置无关的指令。
示例:
b fun1
......
fun1:
bl fun2
......
fun2:
......
2、数据传送指令: mov ,地址读取伪指令: ldr
mov 指令可以把一个寄存器的值赋给另一个寄存器,或者把一个常数赋给寄存器。
例:
mov r1, r2
mov r1, #4096
mov 指令传送的常数必须能用立即数来表示。
当不知道一个数能否用立即数来表示时,可以使用 ldr 命令来赋值。 ldr 是伪指令,它不是真实存在的指令,编译器会把它扩展成真正的指令:如果该常数能用立即数来表示,则使用 mov 指令;否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。
例:
ldr r1, =4097
ldr 本意为“大范围的地址读取伪指令”,以下是获得代码的绝对地址:
例:
ldr r1, =label
label:
......
3、内存访问指令: ldr 、 str 、 ldm 、 stm
ldr 指令既可能是大范围的地址读取伪指令,也可能是内存访问指令。当它的第二个参数前面有 “ = ” 时,表示伪指令,否则表示内存访问指令。
ldr 指令是从内存中读取数据到寄存器,str 指令把寄存器的值存储到内存中,它们操作的数据都是32位的。
例:
ldr r1, [r2, #4] // 将地址为r2+4的内存单元数据读取到r1中
ldr r1, [r2] // 将地址为r2的内存单元数据读取到r1中
ldr r1, [r2], #4 // 将地址为r2的内存单元数据读取到r1中,然后r2=r2+4
str r1, [r2, #4] // 将r1的数据保存到地址为r2+4的内存单元中
str r1, [r2] // 将r1的数据保存到地址为r2的内存单元中
str r1, [r2], #4 // 将r1的数据保存到地址为r2的内存单元中,然后r2=r2+4
ldm 和 stm 属于批量内存访问指令,只用一条指令就可以读写多个数据。格式为:
ldm {cond}
stm {cond}
其中,{cond} 表示指令的执行条件有:
条件码(cond) |
助记符 |
含义 |
cpsr中条件标志位 |
0000 |
eq |
相等 |
Z = 1 |
0001 |
ne |
不相等 |
Z = 0 |
0010 |
cs/hs |
无符号数大于/等于 |
C = 1 |
0011 |
cc/lo |
无符号数小于 |
C = 0 |
0100 |
mi |
负数 |
N = 1 |
0101 |
pl |
非负数 |
N = 0 |
0110 |
vs |
上溢出 |
V = 1 |
0111 |
vc |
没有上溢出 |
V = 0 |
1000 |
hi |
无符号数大于 |
C = 1 或 Z = 0 |
1001 |
ls |
无符号数小于等于 |
C = 0 或 Z = 1 |
1010 |
ge |
带符号数大于等于 |
N = 1, V = 1 或 N = 0, V = 0 |
1011 |
lt |
带符号数小于 |
N = 1, V = 0 或 N = 0, V = 1 |
1100 |
gt |
带符号数大于 |
Z = 0 且 N = V |
1101 |
le |
带符号数小于/等于 |
Z = 1 或 N! = V |
1110 |
al |
无条件执行 |
- |
1111 |
nv |
从不执行 |
- |
大多数ARM指令都可以条件执行,即根据cpsr寄存器中的条件标志位决定是否执行该指令:如果条件不满足,该指令相当于一条nop指令。
每条ARM指令包含4位的条件码域,这表明可以定义16个执行条件。
cpsr条件标志位N、Z、C、V分别表示Negative、Zero、Carry、oVerflow。
ia (Increment After) :事后递增方式。
ib (Increment Before) :事先递增方式。
da (Decrement After) :事后递减方式。
db (Decrement Before) :事先递减方式。
{^} 有两种含义:
如果
如果
例:
HandleIRQ: @中断入口函数
sub lr, lr, #4 @计算返回地址
stmdb sp!, { r0 - r12, lr } @保存使用的寄存器
@r0 - r12, lr被保存在sp表示的内存中
@“!”使得指令执行后sp = sp - 14 * 4
ldr lr, =int_return @设置调用IRQ_Handle函数后的返回地址
ldr pc, =IRQ_Handle @调用中断分发函数
int_return:
ldmia sp!, { r0 - r12, pc }^ @中断返回,“^”表示将spsr的值复制到cpsr
@于是从irq模式返回被中断的工作模式
@“!”使得指令执行后sp = sp + 14 * 4
4、加减指令: add 、 sub
例:
add r1, r2, #1 // r1 = r2 + 1
sub r1, r2, #1 // r1 = r2 - 1
5、程序状态寄存器的访问指令: msr 、 mrs
ARM处理器有一个程序状态寄存器(cpsr),它用来控制处理器的工作模式、设置中断的总开关。
例:
msr cpsr, r0 // 复制r0到cpsr中
mrs r0, cpsr // 复制cpsr到r0中
6、其他伪指令
.extern : 定义一个外部符号(可以是变量也可以是函数)
.text : 表示现在的语句都属于代码段
.global : 将本文件中的某个程序标号定义为全局的
ARM-THUMB子程序调用规则:ATPCS
为了使C语言程序和汇编程序之间能够互相调用,必须为子程序间的调用制定规则,在ARM处理器中,这个规则被称为 ATPCS :ARM程序和THUMB程序中子程序调用的规则。基本的ATPCS规则包括寄存器使用规则、数据栈使用规则、参数传递规则。
1、寄存器使用规则
子程序间通过寄存器 r0 ~ r3 来传递参数,这时可以使用它们的别名 a1 ~ a4 。被调用的子程序返回前无需恢复 r0 ~ r3 的内容。
在子程序中,使用 r4 ~ r11 来保存局部变量,这时可以使用它们的别名 v1 ~ v8 。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器,则不必进行这些操作。在THUMB程序中,通常只能使用寄存器 r4 ~ r7 来保存局部变量。
寄存器 r12 用作子程序间scratch寄存器,别名为ip。
寄存器 r13 用作数据栈指针,别名为 sp 。在子程序中寄存器 r13 不能用作其他用途。它的值在进入、退出子程序时必须相等。
寄存器 r14 称为连接寄存器,别名为 lr 。它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将 lr 值保存到数据栈中), r14 可以用作其他用途。
寄存器 r15 是程序计数器,别名为 pc 。它不能用作其他用途。
寄存器 |
别名 |
使用规则 |
r15 |
pc |
程序计数器 |
r14 |
lr |
连接寄存器 |
r13 |
sp |
数据栈指针 |
r12 |
ip |
子 程序内部调用的scratch寄存器 |
r11 |
v8 |
ARM状态局部变量寄存器8 |
r10 |
v7、s1 |
ARM状态局部变量寄存器7、在支持数据栈检查的ATPCS中为数据栈限制指针 |
r9 |
v6、sb |
ARM状态局部变量寄存器6、在支持RWPI的ATPCS中为静态基址寄存器 |
r8 |
v5 |
ARM状态局部变量寄存器5 |
r7 |
v4、wr |
ARM状态局部变量寄存器4、THUMB状态工作寄存器 |
r6 |
v3 |
ARM状态局部变量寄存器3 |
r5 |
v2 |
ARM状态局部变量寄存器2 |
r4 |
v1 |
ARM状态局部变量寄存器1 |
r3 |
a4 |
参数/结果/scratch寄存器4 |
r2 |
a3 |
参数/结果/scratch寄存器3 |
r1 |
a2 |
参数/结果/scratch寄存器2 |
r0 |
a1 |
参数/结果/scratch寄存器1 |
2、数据栈使用规则
数据栈有两个增长方向:向内存地址减小的方向增长时,称为 DESCENDING栈 ;向内存地址增加的方向增长时,称为 ASCENDING栈 。
所谓数据栈的增长就是移动栈指针。当栈指针指向栈顶元素(最后一个入栈的数据)时,称为 FULL栈 ;当栈指针指向栈顶元素(最后一个入栈的数据)相邻的一个空的数据单元时,称为 EMPTY栈 。
则数据栈可以分为4种:
FD:Full Descending 满递减
ED:Empty Descending 空递减
FA :Full Ascending 满递增
EA:Empty Ascending 空递增
ATPCS规定数据栈为FD类型,并且对数据栈的操作是8字节对齐的。使用 stmdb / ldmia 批量内存访问指令来操作FD数据栈。
使用stmdb命令往数据栈中保存内容时,先递减sp指针,再保存数据,使用ldmia命令从数据栈中恢复数据时,先获得数据,再递增sp指针,sp指针总是指向栈顶元素,这刚好是FD栈的定义。
3、参数传递规则
一般地,当参数个数不超过 4 个时,使用 r0 ~ r3 这4个寄存器来传递参数;如果参数个数超过 4 个,剩余的参数通过数据栈来传递。
对于一般的返回结果,通常使用 r0 ~ r3 来传递。
例:
假设CopyCode2SDRAM函数是用C语言实现的,它的数据原型如下:
int CopyCode2SDRAM( unsigned char *buf, unsigned long start_addr, int size )
在汇编代码中,使用下面的代码调用它,并判断返回值:
ldr r0, =0x30000000 @1. 目标地址 = 0x30000000,这是SDRAM的起始地址
mov r1, #0 @2. 源地址 = 0
mov r2, #16*1024 @3. 复制长度 = 16K
bl CopyCode2SDRAM @调用C函数CopyCode2SDRAM
cmp a0, #0 @判断函数返回值