内容简介
树莓派单板机(Raspberry Pi Single Computer)是一种极了不起的产品,用户可以以非常低的成本获得一个Linux环境并带GPIO硬件扩展的迷你计算机系统。新一代树莓派4B还提供了良好的工业物联网和AIoT支持。树莓派单板机拥有完整的生态链,软硬件资源丰富,是嵌入式系统开发和智能硬件产品创新的很好选择。
作为嵌入式系统与嵌入式智能硬件开发基础中的基础,汇编语言是许多从事信息科学和工程领域的技术人员应该掌握的一项基本技能。目前,市场上针对树莓派单板机系统介绍C、Scratch、Python等编程语言与实践方面的资源很多,但鲜有系统针对树莓派单板机ARM汇编语言编程方面的介绍。这里以袁志勇主编的《嵌入式系统原理与应用技术》(北京航空航天大学出版社2019年1月第3版)一书中ARM汇编语言编程知识为基础,采用树莓派单板机及Linux操作系统验证平台,较系统地介绍树莓派ARM汇编语言编程技术与示例。由于准备仓促,不妥之处,还请各位不吝赐教。
第5讲:ARM访存指令、ARM转移指令及树莓派ARM状态与Thumb状态切换汇编程序举例
第5讲目录
·ARM访存指令
·ARM转移指令
·树莓派ARM状态与Thumb状态切换汇编程序举例
一、ARM访存指令
访存指令分为单数据访存指令、多数据访存指令和数据交换操作指令三大类。
(1)单数据访存指令
在ARM指令中,读指令也叫加载指令,写指令也叫存储指令。
第一类单数据访存指令是指加载或存储字、加载或存储无符号字节的指令。
① LDR指令
格式:LDR{cond} Rd, addr
功能:Rd←[addr] ( 加载字数据)
② LDRB指令
格式:LDRB{cond} Rd, addr
功能:Rd←[addr] (加载无符号字节数据)
③ LDRT指令
格式:LDRT{cond} Rd, addr
功能:Rd←[addr] (以用户模式加载字数据)
④ LDRBT指令
格式:LDRBT{cond} Rd, addr
功能:Rd←[addr] (以用户模式加载无符号字节数据)
⑤ STR指令
格式:STR{cond} Rd, addr
功能:[addr]←Rd (存储字数据,Store register to memory)
⑥ STRB指令
格式:STRB{cond} Rd, addr
功能:[addr]←Rd (存储字节数据)
⑦ STRT指令
格式:STRT{cond} Rd, addr
功能:[addr]←Rd (以用户模式存储字数据)
⑧ STRBT指令
格式:STRBT{cond} Rd, addr
功能:[addr]←Rd (以用户模式存储字节数据)
带有T后缀的指令说明:
●即使处理器工作于特权模式,存储系统也将其访问看成是处理器在用户模式下。
●用于存储器保护。
●不能与前变址寻址、自动变址寻址一起使用(即不能改变基址寄存器值)。
●在用户模式下无效。
LDR/STR指令为变址寻址,由基地址和偏移地址两部分组成:基地址部分为一个基址寄存器,可以是任一通用寄存器;偏移地址部分使用较为灵活,实际上就是第2操作数,可以是立即数、寄存器移位及移位常数。
例:
ldrb r0, [r1, #+0xfff] @ 将r1+0xfff地址的字节读入r0
ldr r0, [r1, +r2]! @ 将r1+r2地址的32比特数读入r0,然后r1← r1+r2
str r0, [r1, +r2, LSL #31] @ 将r0(32bit)写到地址r1+(r2<<31)
ldr r0, [r1], #+0xfff @ 将r1地址的数读入r0,然后r1←r1+0xfff
ldr r0, [r1], +r2 @ 将r1地址的数读入r0,然后r1←r1+r2
ldr r0, [r1], +r2, LSL #31 @ 将r1地址的数读入r0,然后r1←r1+(r2<<31)
第二类单数据访存指令是指加载或存储无符号半字、加载有符号半字或加载有符号字节的指令。
① LDRH指令
格式:LDRH{cond} Rd, addr
功能:Rd←[addr] (加载无符号半字数据)
② LDRSB指令
格式:LDRSB{cond} Rd, addr
功能:Rd←[addr] (加载有符号字节数据)
③ LDRSH指令
格式:LDRSH{cond} Rd, addr
功能:Rd←[addr] (加载有符号半字数据)
④ STRH指令
格式:STRH{cond} Rd, addr
功能:[addr]←Rd (存储半字数据)
例:
LDRSB R1, [R0, R3] @ 将R0+R3地址上的字节数据读到R1,高24位用符号位扩展
LDRSH R1, [R9] @ 将R9地址上的半字数据读出到R1, 高16位用符号位扩展
LDRH R6,[R2], #2 @ 将R2地址上的半字数据读出到R6, 高16位用零扩展, 然后修改R2=R2+2
STRH R1, [R0, #2]! @ 将R1的数据保存到R0+2地址中, 只存储低2字节数据, 并且修改R0=R0+2
(2) 多数据访存指令
多数据访存指令可以实现一组(1-16)寄存器和一块(4-64字节)连续内存单元之间的数据传输。
格式:LDM|STM{cond}[type][Rn]{!}, {^}
功能:LDM指令用于从基址寄存器所指示的一片连续存储器中读取数据到寄存列表所指示的多个寄存器中,内存单元的起始地址为基址寄存器Rn的值,各寄存器由寄存器列表Regs表示,该指令一般用于多个寄存器数据的出栈操作。STM指令用于将寄存器列表所指示的多个寄存器中的值存入到由基址寄存器所指示的一片连续存储器中,该指令一般用于多个寄存器数据的进栈操作。
type表示类型,用于数据的存储与加载时有如下4种方式:
●IA (Increment After):事后递增(每次传送后地址值增加);
●IB (Increment Before):事先递增(每次传送前地址值增加);
●DA (Decrement After):事后递减(每次传送后地址值减少);
●DB (Decrement Before):事先递减(每次传送前地址值减少)
用于堆栈操作时有如下4种方式:
●FD:满递减堆栈;
●ED:空递减堆栈;
●EA:满递增堆栈;
●EA:空递增堆栈。
{!}为可选后缀,若选择该项表示数据加载或存储完毕后,将最后的地址写回到基址寄存器Rn中。
{^}为可选后缀,当指令为LDM且寄存器列表包含PC,表示除了正常的多寄存器传送外,还要将SPSR拷贝到CPSR中。当寄存器列表不包含PC,表示加载/存储的是用户模式的寄存器,而不是当前模式的寄存器。
LDM/STM指令寻址是按字对齐的,即忽略地址位[1:0]。LDM/STM的主要用途是现场保护、数据复制和参数传送等。
例:
LDMIA R0, {R5-R8} @ 将内存中[R0]到[R0+12]4个字读取到R5~R8的4个寄存器中
LDMIB R0, {R5-R8} @ 将内存中[R0+4]到[R0+16]4个字读取到R5~R8的4个寄存器中
LDMDA R0, {R5-R8} @ 将内存中[R0-12]到[R0]4个字读取到R5~R8的4个寄存器中
LDMDB R0, {R5-R8} @ 将内存中[R0-16]到[R0-4]4个字读取到R5~R8的4个寄存器中
LDMIA R0!, {R3-R9} @ 加载R0指向地址上的多字数据,保存到R3~R9中,R0值更新
STMIA R1!, {R3-R9} @ 将R3~R9的数据存储到R1指向的地址,R1值更新
STMFD SP!, {R0-R7, LR} @ 保护现场,将R0~R7、LR入栈,SP值更新
LDMFD SP!, {R0-R7, PC} @ 恢复现场,包括R0~R7 和PC(异常处理返回),SP值更新
(3) 数据交换操作指令
数据交换指令有字数据交换指令(SWP)和字节数据交换指令(SWPB)。
格式:SWP{}{B} Rd, Rm, [Rn]
功能:Rd←[Rn], [Rn]←Rm
SWP指令用于将一个存储单元(该单元地址放在寄存器Rn中)的内容读取到一个寄存器Rd中,同时将另一个寄存器Rm的内容写入到该存储单元中。 B为可选后缀,若有B,则交换字节,否则交换32位字。Rd为被加载的寄存器。Rm的数据用于存储到Rn所指的地址中。若Rm与Rd相同,则为寄存器与存储器内容进行交换。Rn为要进行数据交换的存储器地址,Rn不能与Rd和Rm相同。
例:
SWP R1, R2, [R3]
@ 将内存单元[R3]中的字读取到R1,
@ 同时将R2中的数据写入内存单元[R3]中
SWP R1, R1, [R2]
@ 将R1寄存器内容和内存单元[R2]的内容互换
SWPB R1, R2, [R0]
@ 将R0指向的存储单元的内容读取1字节数据到
@ R1中(高24位清零), 并将R2的内容写入到该内存
@单元中(最低字节有效)
二、ARM转移指令
(1) 转移指令
格式:B{cond} label
功能:PC←label
B指令跳转到指定的地址执行程序。B转移指令限制在当前指令的±32 MB的范围内。
例:无条件跳转
B label
……
label ……
例:执行10次循环
MOV R0, #10
LOOP
……
SUBS R0, R0, #1
BNE LOOP @ z=0转LOOP
(2) 带链接的转移指令
格式:BL{cond} label
功能:LR←BL后面的第一条指令地址, PC←label
BL指令先将下一条指令的地址拷贝到LR 链接寄存器中,然后跳转到指定地址运行程序。转移地址限制在当前指令的±32 MB的范围内。BL指令常用于子程序调用。
例:
BL SUB1 ; LR←BL下条指令地址, 转至子程序SUB1处
……
SUB1 ……
MOV PC, LR ; 子程序返回
例:根据不同的条件,执行不同的子程序。
CMP R1, #5
BLLT ADD11 ;有符号数 <
BLGE SUB22 ;有符号数 ≧
……
ADD11
……
SUB22
……
注意:如果R1<5,只有ADD11不改变条件码,本例才能正常工作。
例:
BL SUB1
……
SUB1 STMFD SP!, {R0-R3,R14}
……
BL SUB2
……
SUB2 ……
说明:在保存R14之前子程序不应再调用下一级的嵌套子程序。否则,新的返回地址将覆盖原来的返回地址,就无法返回到原来的调用位置。
(3) 带状态切换的转移指令
格式:BX{cond} Rm
功能:PC←Rm&0xfffffffe, T←Rm[0]&1
BX指令跳转到Rm指定的地址执行程序。若Rm的位[0]为1,则跳转时自动将CPSR中的标志T置位,即把目标地址的代码解释为Thumb代码;若Rm的位[0]为0,则跳转时自动将CPSR中的标志T复位,即把目标地址的代码解释为ARM代码。
例:
……
ADR R0, THUMBCODE+1 @ 将R0的bit[0]置1
BX R0 @ 跳转,并根据R0的bit[0]实现状态切换
.CODE 16 @表明下面是16位Thumb代码
THUMBCODE MOV R2, #2
……
ADR R0,ARMCODE @加载ARMCODE地址到R0中
BX R0
.CODE 32 @表明下面是32位ARM代码
ARMCODE MOV R4, #4
……
在上面ARM状态和Thumb状态切换程序片段中,“.CODE 16“伪操作用于选择16位Thumb指令,“.CODE 16“伪操作用于选择32位ARM指令。
三、树莓派ARM状态与Thumb状态切换汇编程序举例
这里以前面的ARM状态和Thumb状态切换程序片段为基础构建一个树莓派GNU ARM汇编源程序,程序清单如下:
1 @Filename: arm_thumb.s
2 .GLOBAL _start
3 .CODE 32
4 _start: ADR R0,_thumb+1 @ 将R0的bit[0]置1
5 BX R0
6 @ 跳转,并根据R0的bit[0]实现状态切换
7 .CODE 16 @ 16位Thumb代码
8 _thumb: MOV R2,#2
9 MOV R3,#10
10 SUB R3,R2
11 ADR R0,_arm @ 加载ARMCODE地址到R0中
12 BX R0
13 .CODE 32 @ 32位ARM代码
14 _arm: MOV R4,#4
15 MOV R5,#20
16 SUB R5,R4
17 B _start
18 .END
树莓派开始执行程序只能处于ARM状态,第3行的伪操作“.code 32”声明第4行和第5行指令是32位的ARM指令,当执行到第5行BX指令时,程序跳转到第8行MOV指令,程序由32位的ARM状态切换到16位的Thumb状态。第8-12行指令由伪操作“.code 16”声明是16位的Thumb指令,实现R3与R2两个寄存器的值相减。第12行使用BX指令跳转到第14行,程序由16位的Thumb状态切换到32位的ARM状态,第14-17行指令由伪操作“.code 32”声明是32位的ARM指令,实现R5与R4两个寄存器的值相减。
图1 ARM状态和Thumb状态切换程序示例
首先,在树莓派Linux终端命令提示符使用nano等编辑器编辑名为arm_thumb.s的ARM汇编源程序,用cat命令显示ARM汇编源文件、用as命令汇编arm_thumb.s源程序、用ld命令链接arm_thumb.o程序 (见图1)。接着可以使用GNU objdump -d arm_thumb.o命令显示ARM指令与Thumb指令的十六进制机器码 (见图1),可知ARM指令占4个字节(即32位指令),Thumb指令占两个字节(即16位指令)。最后,可用GDB调试器跟踪、查看指令的执行情况,相关命令可参阅前面几讲中的介绍 (此略)。
End of This Lecture.
(作者Email联系:[email protected])
发布时间:2020年4月3日
上一讲链接
下一讲链接