转自http://blog.csdn.net/yam_killer/article/details/7901363
ARM9汇编指令总结
1、 LDR指令
Arm指令集中,LDR既可以做为加载指令,也可以作为伪指令。
1) LDR pc, =MyHandleIRQ ;表示将MyHandleIRQ符号放入pc寄存器中
eg:
COUNT EQU 0x40003100
……
LDR R1,=COUNT
MOV R0,#0
STR R0,[R1]
COUNT是我们定义的一个变量,地址为0x40003100。这中定义方法在汇编语言中是很常见的,如果使用过单片机的话,应该都熟悉这种用法。
LDR R1,=COUNT是将COUNT这个变量的地址,也就是0x40003100放到R1中。
MOV R0,#0是将立即数0放到R0中。最后一句STR R0,[R1]是一个典型的存储指令,将R0中的值放到以R1中的值为地址的存储单元去。实际就是将0放到地址为0x40003100的存储单元中去。可见这三条指令是为了完成对变量COUNT赋值。用三条指令来完成对一个变量的赋值,看起来有点不太舒服。这可能跟ARM的采用RISC有关。
2) LDR PC,MyHandleIRQ ;表示将读取存储器中MyHandleIRQ符号所表示的地址中的值,及需要多读一次存储器。
eg:
;将COUNT的值赋给R0
LDR R1,=COUNT
LDR R0,[R1] ;此为加载指令
LDR R1,=COUNT这条伪指令,是怎样完成将COUNT的地址赋给R1,有兴趣的可以看它编译后的结果。这条指令实际上会编译成一条LDR指令和一条DCD伪指令。
3)、认真阅读下列代码,体会LDR的两种用法
在代码中:
start:
ldr pc,=MyHandleReset ;jump to HandleReset
ldr pc,=MyHandleUndef ;jump to HandleUndef
ldr pc,=MyHandleSWI ;jump to HandleSWI
ldr pc,=MyHandleIabort ;jump to HandleIabort
ldr pc,=MyHandleDabort ;jump to HandleDabort
nop
ldr pc,=MyHandleIRQ ;jump to HandleIRQ <=之前出错的一行
ldr pc,=MyHandleFIQ ;jump to HandleFIQ
;MyHandleIRQ: .word OS_CPU_IRQ_ISR
MyHandleIRQ:
sub lr, lr, #4 ; to calculate the return address
stmdb sp!, {r0-r12,lr}
ldr lr, =int_return ; restore the return address
ldr pc, =int_handle ; call for the interrupt handler
在“之前出错的一行”处,如果改成“ldr pc,MyHandleIRQ”当中断来临时,无法进行中断处理。
另一种情况是正确的,注意体会:
start:
ldr pc,=MyHandleReset ;jump to HandleReset
ldr pc,=MyHandleUndef ;jump to HandleUndef
ldr pc,=MyHandleSWI ;jump to HandleSWI
ldr pc,=MyHandleIabort ;jump to HandleIabort
ldr pc,=MyHandleDabort ;jump to HandleDabort
nop
ldr pc,MyHandleIRQ ;jump to HandleIRQ <=之前出错的一行
ldr pc,=MyHandleFIQ ;jump to HandleFIQ
MyHandleIRQ: .word OS_CPU_IRQ_ISR
;MyHandleIRQ:
; sub lr, lr, #4 ; to calculate the return address
; stmdb sp!, {r0-r12,lr}
; ldr lr, =int_return ; restore the return address
; ldr pc, =int_handle ; call for the interrupt handler
因为当中断来临时,还需要去MyHandleIRQ处把OS_CPU_IRQ_ISR取出,即多取一次存储器。
2、 STR指令
STR Rd , addressing ;[addressing]←Rd (store)
STR r0, [r1, #0x10] ;r1+0x10这个是所用的实际地址值,但是不会写入r1,在此句之后,r1值不变
STR r0, [r1], #0x10 ;r1+0x10这个是所用的实际地址值,这个值会写入r1,此句之后,r1=r1+0x10
STR用来存取内存,从rom到ram,而mov是ram里用的
3、 LDM指令和STM指令
批量加载/存储指令可以实现在一组寄存器和一块连续的内存单元之间传输数据。LDM为加载多个寄存器,STM为存储多个寄存器。允许一条指令传送16个寄存器的任何子集或所有寄存器。指令格式如下:
LDM{cond}<模式> Rn{!},reglist{^}
STM{cond}<模式> Rn{!},reglist{^}
LDM /STM 的主要用途是现场保护、数据复制、参数传送等。其模式有8种,如下所列:(前面4种用于数据块的传输,后面4种是堆栈操作)。
(1) IA:每次传送后地址加4
(2) IB:每次传送前地址加4
(3) DA:每次传送后地址减4
(4) DB:每次传送前地址减4
(5) FD:满递减堆栈
(6) ED:空递增堆栈
(7) FA:满递增堆栈
(8) EA:空递增堆栈
其中,寄存器Rn为基址寄存器,装有传送数据的初始地址,Rn不允许为R15;后缀“!”表示最后的地址写回到Rn中;寄存器列表reglist可包含多于一个寄存器或寄存器范围,使用“,”分开,如{R1,R2,R6-R9},寄存器排列由小到大排列;“^”后缀不允许在用户模式呈系统模式下使用,若在LDM指令用寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,将SPSR拷贝到CPSR中,这可用于异常处理返回;使用“^”后缀进行数据传送且寄存器列表不包含PC时,加载/存储的是用户模式的寄存器,而不是当前模式的寄存器。
地址对准――这些指令忽略地址的位[1:0]。在进行数据复制时,先设置好源数据指针,然后使用块拷贝寻址指令LDMIA/STMIA、LDMIB/STMIB、LDMDA/STMDA、LDMDB /STMDB 进行读取和存储。而进行堆栈操作时,则要先设置堆栈指针,一般使用SP然后使用堆栈寻址指令STMFD/LDMFD、STMED。LDMED、STMFA/LDMFA、STMEA/LDMEA实现堆栈操作。
在堆栈请求格式中,FD,ED,FA,EA定义了前/后向索引和上/下位,F,E表示堆栈满或者空。A和D定义堆栈是递增还是递减,如果递增,STM将向上,LDM向下,如果递减,则相反。当LDM/STM没有用于堆栈,而只是简单地表示地址前向增加,后向增加,前向减少,后向减少时,由IA,IB,DA,DB控制。
寄存器的任意子集或者所有的寄存器都可以被指定,唯一的限制是寄存器列表不应该为空。任何时候R15被存储到MEM中,存储的值时指令地址加12。传输地址是由Rn中的内容和前/后向索引位,上/下位决定的,寄存器的传输按照从低向高的顺序。如果寄存器列表中有R15,则R15在最后一个被传输。序号低得寄存器对应于存储器的低地址。
详解
1)、STMFD SP!,{R0,R1,R2,R14} ;满递减入栈,R13为基址地址
效果图如下:
2)、LDMFD SP!,{R0,R1,R2,R14} ;满递减出栈,R13为基址地址
效果图:
3)、STMED SP!,{R0,R1,R2,R14} ;满递减入栈,R13为基址地址
效果图:
4)、LDMED SP! , {R0,R1,R2,R14} ;空递减出栈,R13为基址地址
效果图:
5)STMFA SP!,{R0,R1,R2,R14} ;满递增入栈,R13为基址地址
效果图:
6)、LDMFA SP!,{R0,R1,R2,R14} ;满递增出栈,R13为基址地址
效果图:
7)、STMEA SP!,{R0,R1,R2,R14} ;空递增入栈,R13为基址地址
效果图
8)、LDMEA SP!,{R0,R1,R2,R14} ;空递增出栈,R13为基址地址
效果图
9)、STMIA R0!,{R1,R2,R3,R14} ;先传后增,寄存器 to RAM
效果图
10)、LDMIA R0!,{R1,R2,R3,R14} ; 先传后增,RAM to寄存器
效果图
11)、STMIB R0!,{R0,R1,R2,R14} ;先增后传,寄存器 to RAM
效果图:
12)、LDMIB R0!, {R1,R2,R3,R14} ;先增后传,RAM to寄存器
效果图:
13)、STMDA R0! , {R1,R2,R3,R14} ;先传后减,寄存器 to RAM
效果图:
14)、LDMDA RO!,{R1,R2,R3,R14} ;先传后减,RAM to寄存器
效果图:
15)、STMDB R0!,{R1,R2,R3,R14} ;先减后传,寄存器 to RAM
效果图
16)、LDMDB R0! , {R1,R2,R3,R14} ;先减后传, RAM to寄存器
效果图:
使用LDM/STM进行数据复制例程如下:
…
LDR R0,=SrcData ;设置源数据地址
LDR R1,=DstData ;设置目标地址
LDMIA R0,{R2-R9} ;加载8字数据到寄存器R2~R9
STMIA R1,{R2-R9} ;存储寄存器R2~R9到目标地址
使用LDM/STM进行现场寄存器保护,常在子程序中或异常处理使用:
SENDBYTE
STMFD SP!,{R0-R7,LR} ;寄存器入堆
…
BL DELAY ;调用DELAY子程序
…
LDMFD SP!,{R0-R7,PC} ;恢复寄存器,并返回
4、B、BL、BX、BLX和BXJ指令
跳转、带链接跳转、跳转并切换指令集、带链接跳转并切换指令集、跳转并转换到 Jazelle 状态。
B或BL指令引起处理器转移到“子程序名”处开始执行。两者的不同之处在于BL指令在转移到子程序执行之前,将其下一条指令的地址拷贝到R14(LR,链接寄存器)。由于BL指令保存了下条指令的地址,因此使用指令“MOV PC ,LR”即可实现子程序的返回。而B指令则无法实现子程序的返回,只能实现单纯的跳转。用户在编程的时候,可根据具体应用选用合适的子程序调用语句。
BX 和 BLX 指令可将处理器的状态从 ARM 更改为 Thumb,或从 Thumb 更改为 ARM。
BLX label 无论何种情况,始终会更改处理器的状态。
BX Rm 和 BLX Rm 可从 Rm 的位 [0] 推算出目标状态:如果 Rm 的位 [0] 为 0,则处理器的状态会更改为(或保持在)ARM 状态
如果 Rm 的位 [0] 为 1,则处理器的状态会更改为(或保持在)Thumb 状态。
BXJ 指令会将处理器的状态更改为 Jazelle。
通过 B BL BLX BX 可以完成在当前指令向前或者向后32MB的地址空间的跳转,机器级指令 B 和 BL 对当前指令有地址范围限制。 但是,即使label 超出范围,仍可以使用这些指令。通常您并不知道链接器会将 label 放在何处。必要时链接器会添加代码,以允许进行更长的跳转。 所添加的代码称为中间代码。
为什么跳转范围是32MB呢?这要从B、BL的机器码格式说起。
Cond | 1 | 0 | 1 | L | offset |
[31:28]位是条件码;[27:24]位为“1010”时,表示B跳转指令,为“1011”时,表示BL跳转指令;[23:0]表示偏移地址。使用B或BL跳转时,下一条指令的地址是这样计算的:将指令中24位带符号的补码立即数扩展为32(扩展其符号位);将此32位数左移两位;将得到的值加到pc寄存器中,即得到跳转的目标地址。既由于偏移地址为24位所以,跳转范围为32M。
BL的经典用法如下:
bl NEXT ; 跳转到NEXT
……
NEXT
……
mov pc, lr ; 从子程序返回。