查看: 1636  |  回复: 0
[转载]常用ARM汇编指令及ATPCS规则
我不会单片机
12
主题
7
回复
发表于2018-11-26 15:50:40 | 只看该作者
1# 电梯直达
在嵌入式开发中,汇编程序常常用于非常关键的地方,比如系统启动时的初始化,进出中断时的环境保存、恢复,对性能要求非常苛刻的函数等。

只在必要情况下使用汇编指令,只涉及几条汇编指令。

1.相对跳转指令:b、bl

这两条指令的不同之处在于bl指令除了跳转之外,还将返回地址(bl的下一条指令的地址)保存在lr寄存器中。

这两条指令的可跳转范围是当前指令的前后32M:-32M~+32M。它们是位置无关的指令。使用示例:

b fun1
......
fun1:
bl fun2
......
fun2:
......
2.数据传送指令mov,地址读取伪指令ldr

mov指令可以把一个寄存器的值赋给另一个寄存器,或者把一个常数赋给寄存器。例子如下:

mov r1, r2 /* r1=r2 */
mov r1, #4096 /* r1=4096 */
mov指令传送的常数必须能用"立即数"来表示。

当不知道一个数能否用"立即数"来表示时,可以使用ldr命令来赋值。ldr是伪指令,它不是真实存在的指令,编译器会把它扩展成真正的指令;如果该常数能用"立即数"来表示,则使用mov指令;否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。例子如下:

ldr r1, =4097 /* r1=4097 */
ldr本意为"大范围的地址读取伪指令",上面的例子使用它来将常数赋给寄存器r1。下面的例子是获得代码的绝对地址:

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} <addressing_mode>  <rn>{!}  <register list>{^}
stm {cond} <addressing_mode>  <rn>{!}  <register list>{^}
其中{cond}表示指令的执行条件。
<addressing_mode>表示地址变化模式,有以下4种方式:

ia(Increment After)     事后递增方式
ib(Increment Before)     事先递增方式
da(Decrement After)     事后递减方式
db(Decrement Before)     事先递减方式
<rn>中保存内存的地址,如果后面加上了感叹号,指令执行后,rn的值会更新:等于下一个内存单元的地址。

<register list>表示寄存器列表,对于ldm指令,从<rn>所对应的内存块中取出数据,写入这些寄存器;对于stm指令,把这些寄存器的值,写入<rn>所对应的内存块中。

{^}有两种含义:如果<register list>中有pc寄存器,它表示指令执行后,spsr寄存器的值将自动复制到cpsr寄存器中——这常用于从中断处理函数中返回;如果<register list>中没有pc寄存器,{^}表示操作的是用户模式下的寄存器,而不是当前特权模式的寄存
器。

指令中寄存器列表和内存单元的对应关系为:编号低的寄存器对应内存中的低地址单元,编号高的寄存器对应内存中的高地址单元。

ldm和stm指令示例如下:

//中断入口函数
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,即寄存器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 main
.text
.global _start
_start:
".extern"定义一个外部符号(可以是变量也可以是函数),上面的代码表示本文件中引用的main是一个外部函数。

".text"表示下面的语句都属于代码段。

".global"将本文件中的某个程序标号定义为全局的,比如上面的代码表示_start个全局函数。

7.汇编指令的执行条件

大多数ARM指令都可以条件执行,即根据cpsr寄存器中的条件标志位决定是否执行该指令:如果条件不满足,该指令相当于一条nop指令。

每条ARM指令包含4位的条件码域,这表明可以定义16个执行条件。可以将这些执行条件的助记符附加在汇编指令后,比如moveq、 movgt等。这16个条件码和它们的助记符、含义如下表所示:

表. 指令的条件码

条件码 助记符 含义 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 从不执行  
表中的cpsr条件标志位N、Z、C、V分别表示:Negative、Zero、Cary、oVerflow。影响条件标志位的因素比较多,比如比较指令cmp、cmn、teq及tst等。

2.ARM-THUMB子程序调用规则ATPCS

为了使C语言程序和汇编程序之间能够互相调用,必须为子程序间的调用制定规则,在ARM处理器中,这个规则被称为ATPCS;ARM程序和Thumb程序中子程序调用的规则。基本的ATPCS规则包括寄存器使用规则、数据栈使用规则、参数传递规则。

1.寄存器使用规则

ARM处理器中有r0~r15共16个寄存器,它们的用途有一些约定的习惯,并依具这些用途定义了别名,如下表所示:

ATPCS中各寄存器的使用规则及其名称:

寄存器 别名 使用规则
R15 pc 程序计数器
R14 lr 链接寄存器
R13 sp 数据栈寄存器
R12 ip 子程序内部调用的scratch寄存器
R11 v8 ARM状态局部变量寄存器8
R10 v7、s1 ARM状态局部变量寄存器7
R9 v6、sb ARM状态局部变量寄存器6
R8 v5 ARM状态局部变量寄存器5
R7 v4 ARM状态局部变量寄存器4
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
寄存器的使用规则总结如下:

子程序间通过寄存器r0~r3来传递参数,这时可以使用它们的别名a0~a3。被调用的子程序返回前无需恢复r0~r3的内容。

在子程序中,使用r4~r11来保存局部变量,这时可以使用它们的别名v1~v8。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器则不必进行这些操作。在Thumb程序中,通常只能使用寄存器r4~r7来保存局部变量。

寄存器r12用作子程序间scratch寄存器,别名为ip。

寄存器r13用作数据栈指针,别名为sp。在子程序中寄存器r13不能用作其他用途。它的值在进入、退出子程序时必须相等。

寄存器r14被称为连接寄存器,别名为lr。它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将lr值保存到数据栈中),r14可以用作其他用途。

寄存器r15是程序计数器,别名为pc。它不能用作其他用途。

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个,剩余的参数通过数据栈来传递。

对于一般的返回结果,通常使用a0~a3来传递。示例:

假设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 //4.复制长度=16K
bl CopyCode2SDRAM //调用C函数CopyCode2SDRAM
cmp a0, #0 //判断函数返回值
第1行将r0设为0x30000000,则CopyCode2SDRAM函数执行时,它的第一个参数buf的指向的内存地址为0x30000000。

第2行将r1设为0,CopyCode2SDRAM函数的第二个参数start_addr等于0。

第3行将r2设为16*1024,CopyCode2SDRAM函数的第三个参数start_addr等于16*1024。

第5行判断返回值。

完毕!
--------------------- 
作者:晴天_QQ 
来源:CSDN 
原文:https://blog.csdn.net/caihaitao2000/article/details/84196054 
版权声明:本文为博主原创文章,转载请附上博文链接!

主题

回复
  • 温馨提示: 标题不合格、重复发帖、发布广告贴,将会被删除帖子或禁止发言。 详情请参考: 社区发帖规则
  • 您当前输入了 0 个文字。还可以输入 8000 个文字。 已添加复制上传图片功能,该功能目前仅支持chrome和火狐

禁言/删除

X
请选择禁言时长:
是否清除头像:
禁言/删除备注:
昵 称:
 
温馨提示:昵称只能设置一次,设置后无法修改。
只支持中文、英文和数字。

举报

X
请选择举报类型:
请输入详细内容:

顶部