《深入浅出嵌入式底层软件开发》—2. 基本寻址方式与基本指令
2.1 基本寻址方式
1) 寄存器寻址
2)立即数寻址
立即数寻址指令中的地址码部分就是操作数本身,也就是说,数据就包含在指令中,由于立即数在32位机器码中,不可能用32位来表示立即数(因为还有操作码),事实上,ARM机器指令中,仅用了低12位来表示立即数,但这并不是表示立即数的范围为-2048—2047,它可以表示,只不过它只能表示其中的个数字而已,ARM是这样用12bit来表示立即数的:将12bit划分为两部分——高4位和低8位,将低8位补0扩展为32位,然后循环右移X位(X=高4位表示的无符号整数X2)
3)寄存器间接寻址
寄存器中存放的是操作数的地址指针,如:
LDR R0,[R2] 表示将R2中存放的数作为内存地址,取出该内存中的数,放到R0中
4)基址寻址
基址寻址就是将基址寄存器的内容与指令中给出的偏移量相加,形成操作数的有效地址,如:
LDR R1,[R2,#0x0C],将R2的值+0x0C形成内存地址,读取内存中该地址上的内容放入R1
LDR R0, [R1, #0x04]! ,表示将R1的值加上4后作为内存地址,并且指令结束时,R1本身也要加4
LDR R0, [R1],#0x04, 表示将R1的值作为内存地址,并且指令接受时,R1本身要加4
5)多寄存器寻址
多寄存器寻址一次可传送几个寄存器值,允许一条指令传送16个寄存器的任何子集或所有寄存器,如:
LDMIA R1!,{R2-R4,R6},它将R1的值作为内存地址,到该地址处取得值后加载到R2中,然后将R1的值加1(地址相当于+4),再去新地址取值放到R3,然后再加1,以此类推;类似指令工4个:ldmia、ldmib、ldmda、ldmdb(ia是先办事后增加,ib是先增加后办事,da是先办事后减小,db是先减小后办事)
寄存器列表{R2-R4,R6}中的顺序不要紧,寄存器与内存地址对应关系是:编号小德寄存器与内存的低地址对应
6)堆栈寻址
由于ARM指令集没有专门的出栈和入栈指令,所以ARM汇编程序采用SP作为栈指针,以stm指令完成入栈操作,以ldm指令完成出栈操作
如果采用的是空递减(空递增、满递减、满递增)堆栈的话,入栈操作则使用指令STMED(STMEA、STMFD、STMFA),出栈操作则使用指令LDMED(LDMEA、LDMFD、LDMFA).
7) 寄存器移位寻址
当第二个操作数是寄存器移位方式时,第二个寄存器操作数在与第一个操作数结合之前,选择进行移位操作,如:
MOV R0,R2,LSL #3表示将R2的值逻辑左移3位,结果放入R0,即R0=R2*8
移位有几种方式:LSL、LSR、ASR、ROR、RRX
8)相对寻址
相对寻址是基址寻址的一种变通,由程序计数器PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址,如:
B LOOP
...
LOOP MOV R6, #1
该指令的意思是跳转到标号LOOP所代表的指令处,B LOOP这条指令的机器码是:高8bit是操作码相关内容,低24bit是一个常数,表示从B指令到MOV指令之间的内存地址的差值,B LOOP 相当于 add pc,pc,#偏移量常数,B指令跳转范围是当前指令的先后32MB
2.2 基本指令
1)单寄存器加载指令
加载子指令:LDR r0,[r1],将内存中的一个字(4字节)加载到寄存器r0中
加载字节指令:LDRB r0,[r1],将内存中的1字节加载到寄存器r0中
2)单寄存器存储指令
存储字指令:STR r0,[r1],将r0中的值存储到内存的4字节中
存储字节指令:STRB r0,[r1],将r0的低8bit存储到1字节中
3)分支指令
B、BL、BX
B和BL功能相同,不同之处在于BL跳转的同时还要返回地址(BL指令的下一条指令的地址)保存到lr中
4)数据处理指令
MOV r0,r1:将r1的值赋给r0
ADD(SUB) r0, r1, r2: 将r1的值加上(减去)r2的值,结果放到r0中
AND(ORR,EOR) r0,r1,r2 :将r1的值与(或、异或)r2的值,结果放到r0中
CMP r1,r2: 比较r1与r2的值的大小,执行r1-r2的操作,如果结果为负数,CPSR的N位置1,Z位置0;结果为0,N位置0,Z位置1,结果为正,NZ位都置0
2.3 伪指令
伪指令有4条:ldr、adr、adrl、nop
1)当我们要实现 mov r0,#10000(不合法)这样的功能时,可以用ldr r0,=10000来实现,它并不是ldr指令,而是一条伪指令,编译器会将它替换成
ldr r0, [pc, # -4]
DCD 10000
这里为什么是 -4而不是+4,是因为流水线的原因,当执行到这条指令时,PC的值是当前指令+8,所以-4就是 DCD 10000所在的地址
从指令位置到文字池的偏移量必须小于4KB
除了“ldr 寄存器,=常数”这种形式外,还有“ldr 寄存器, =标号”这种形式也经常被使用,如:
“ldr pc, = InitStack"这条伪指令的作用是将标号InitStack所代表的地址赋予PC,但是,为什么不用”bl InitStack" 呢?这是因为bl指令的跳转范围是+-32MB,超过32MB时,BL就不适用了。
2)adr伪指令将基于PC相对偏移的地址值读取到寄存器中,作用与ldr伪指令的作用相同,都是将标号所代表的地址赋予寄存器,不过二者的实现机制是完全不同的:ldr采用绝对地址,adr采用相对地址,adr 伪指令要求标号与adr伪指令必须在同一个段中,而ldr伪指令没有这要求
3)nop伪指令汇编时被替换成“MOV R0,R0”指令,主要用于延时操作。