新闻中心

EEPW首页>嵌入式系统>设计应用> arm汇编编程(示例)

arm汇编编程(示例)

作者: 时间:2016-11-09 来源:网络 收藏
一、 arm的认知及基本概念

(一).arm的基本概念

本文引用地址://m.amcfsurvey.com/article/201611/317880.htm

1.什么是arm

arm是一家英国电子公司的名字,全名是AdvancedRISCMachine

这家企业设计了大量高性能、廉价、耗能低的RISC(精简指令集)处理器,ARM公司只设计芯片而不生产,它将

技术授权给世界上许多公司和厂商。目前采用arm技术知识产权内核的微处理器,即通常所说的arm微处理器

所以arm也是对一类微处理器的通称。

arm指令集体系版本号(软件)为V1~V7目前V1~V3已很少见。从V4版不再与以前的版本兼容。

arm的CPU系列(硬件)主要有ARM7~ARM11

2.典型的嵌入式处理器

arm占市场79.5%ARM

mips占市场13.9%MIPS

microSPARC占市场3.1%SUN

PowerPc占市场2.8%IBM

其它占市场0.8%

3.arm的应用范围:

工业控制:如机床、自动控制等

无线通信:如手机

网络应用:如

电子产品:如音视频播放噐、机顶盒、游戏机、数码相机、打印机

其它各领域:如军事、医疗、机器人、智能家居

4.计算机体系结构

见图:冯.诺依曼计算机体系图

冯.诺依曼体系结构

处理器使用同一个存储器,经由同一个总线传输

完成一条指令需要3个步骤:即取指令->指令译码->执行指令

指令和数据共享同一总线的结构

哈佛体系结构

将程序指令存储和数据存储分开

中央处理器首先到程序指令存储器中读取程序指令。解码后到数据地址,再到相应的数据存储器读取数据,然后执行指令

程序指令存储与数据存储分开,可以使指令和数据有不同的数据宽度。

5.复杂指令集与精简指令集

CISC复杂指令集:采用冯.诺依曼体系结构。数据线和指令线分时复用(只能通过一辆车)。

存储器操作指令多汇编程序相对简单指令结束后响应中断CPU电路丰富面积大功耗

RISC精简指令集:采用哈佛体系结构。数据线和指令线分离(同时能通过多辆车)。

对存储器操作有限汇编程序占空间大在适当地方响应中断CPU电路较少体积小功耗低

ARM采用RISC精简指令集

Thumb是ARM体系结构中一种16位的指令集。

从ARMv4T之后,的ARM处理器有一种16-bit指令模式,叫做Thumb,较短的指令码提供整体更佳的编码密度,更有效地使用有限的内存带宽。所有ARM9和后来的家族,包括XScale都纳入了Thumb技术。

即ARM有两种指令集:RISC、Thumb

6.arm的思想

1)arm体系的总思想:

在不牺牲性能的同时,尽量简化处理器。同时从体系结构上灵活支持处理器扩展。采用RISC结构。RISC处理器简化了处理器结构,减少复杂功能指令的同时,提高了处理器速度。

ARM及MIPS都是典型的RISC处理器

2)arm的流水线结构

arm处理器使用流水线来增加处理器指令流的速度,这样可以使几个操作同时进行。并使处理和存储器系统连续操作。

arm处理器分为三级:取指->译码->执行

取指:指令从存储器中取出

译码:对指令使用的寄存器进行译码

执行:从寄存器组中读取寄存器,执行移位和ALU操作,寄存器被写回到寄存器组中

3)ARM处理器支持的类型

字节8位

半字16位

字32位

所有数据操作都以字为单位

ARM指令的长度刚好是一个字,Thumb指令长度刚好是半个字

4)ARM处理器状态

ARM处理器内核使用ARM结构,该结构包含32位的ARM指令集和16位Thumb指令集,因此ARM有两种操作状态

ARM状态:32位

Thumb状态:16位

5)处理器模式

ARM处理器共有7种运行模式:

用户:正常程序工作模式,不能直接切换到其它模式

系统:用于支持操作系统的特权任务,可以直接切换到其它模式

快中断:支持高速数据传输及通道处理,FIQ异常响应时进入此模式

中断:用于通用中断处理,IRQ异常响应时进入此模式

管理:操作系统保护代码,系统复位和软件中断响应时进入此模式

中止:用于支持虚拟内存或存储器保护,用于MMU

未定义:支持硬件协处理器的软件仿真,未定义指令异常响应时进入此模式。

(二)、经典平台硬件组成

见图:arm硬件组成图

开发板一般是由一块组成的,有核心器件和外围器件接口等,但是有的是由两块板子组成,主版和核心板,主版上主要是外围接口,外围器件等,核心板上主要是核心器件,还有一些晶振电路等

1.核心板(天嵌2440)

CPU处理器S3C2440AL,主频400MHz(最高可达533MHz)

SDRAM内存板载64MBSDRAM(标准配置),32bit数据总线SDRAM时钟频率高达100MHz(支持运行133MHz)

NandFlash板载64MBNandFlash或256MBNandFlash(标准配置)

NorFlash板载2MBNorFlash(最高可升级到8MB)

CorePower专业1.25V核心电压供电

Power核心板采用3.3V供电

Powerled核心板电源指示灯

核心板接口接口型号为DC-2.0双列直插

SDRAM:随机存储器,普遍使用的内存。用作主存。

NORFlash和NANDFlash是现在市场上两种主要的非易失闪存。

NOR的特点是芯片内执行,应用程序可以直接在flash闪存内运行,不必再把代码读到系统RAM中。

NAND结构能提供极高的单元密度,可以达到高存储密度,并且写入和擦除的速度也很快。

2.主板

电源

并口线

复位

RTC电源

RS232电平转换DB9插座

音频IIS,AC97

按键、PS/2与IC接口

数码管

触摸屏

以太网

主USBHUB1转4

3.寄存器

见图:ARM模块和内核框图

寄存器是中央处理器内的组成部份。

寄存器是有限存贮容量的高速存贮部件,用来暂存指令、数据和位址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,包含的寄存器有累加器(ACC)。

IR用于存储指令

PC用于存储程序运行的地址(即当前指令在内存中的位置)

寄存器是由一个指令的输出或输入可以直接索引到的暂存器群组。所有的计算机指令都是进入寄存器后被直接读取

ARM的汇编编程,本质上就是针对CPU寄存器的编程。

//*重点需要背讼*

ARM寄存器分为2类:普通寄存器和状态寄存器

(1)通用寄存器和计数器:共32个,15个通用寄存器

R0-R7未备份寄存器

R0(a1)R1(a1)R2(a3)R3(a4)R4(v1)R5(v2)R6(v3)R7(v4)

R8-R12备份寄存器

R8(v5)R9(SB,v6)R10(SL,v7)R11(EP,v8)R12(IP)数据寄存器

R15(PC)程序计数器它的值是当前正在执行的指令在内存中的位置。

当指令执行结束后,CPU会自动将PC值加上一个单位,PC值指向下一条即将执行的指令的地址

如果通过汇编指令对PC寄存器赋值,就会完成一次程序的跳转(如从子函数跳转回主函数内)

R14(LR)链接寄存器存放子程序的返回地址

例如:在主函数内,如果调用子函数,程序会进入到子函数内执行。当子函数执行完毕后,需要回到

主函数内,所以,在子函数调用前需要将这个地址先保存起来,否则无法找到这个地址。

LR用于保存这个地址,这个地址也称为子程序返回地址。当子函数结束后,再将LR内的地址赋给PC即可。

如果子程序再调用孙程序,LR如何保存地址呢?

先把当前LR内的值压入内存的栈区,然后LR再保存孙程序的返回地址。当孙程序执行完后通过PC跳转到

子程序内,此时将栈区内的子程序返回地址取出保存在LR内。当子程序执行完后,再通过PC跳转到主函数内。

R13(SP)栈指针寄存器用于存放堆栈的栈顶地址。

SP相当于指针变量,保存的是栈顶的地址,出栈时,从SP指向的内存中取出数据,入栈时将新的内存地址

压入栈顶,而SP相当于链表的头指针(head)。

原则上说R0-R12可以保存任何数据。其中R0-R7用来临时存储数据,R8-R12系统没有用来做任何特殊用途,常用于中断

而在汇编与C语言的交互中,定制了ATPCS标准

寄存器:R4-R11用来保存局部变量

参数:参数小于等于4,用R0-R3保存参数,参数多于4,剩余的传入堆栈

函数返回:结果为32位整数,通过R0返回

结果为64位整数,通过R0,R1返回

对于位数更多的结果,通过内存传递

(2)状态寄存器:

状态寄存器用于保存程序的当前状态

CPSR当前程序状态寄存器

一个寄存器为32位,每一位数据代表不同的状态。分为三个部分(条件代码标志位、控制位、保留区位)

31322928....76543210

NZCVIFTM4M3M2M1M0

其中NZCV称为条件标志位(即保存的是条件的运算结果,真和假)

N=1表示运算结果为负数,N=0表示运算结果为正数。

Z=1表示运算结果为0,Z=0表示运算结果为非零。

C=1表示运算结果产生了进位。

V=1运算结果的符号位发生了溢出。

这4个位的组合,代表了各种条件,如下:

0000 EQ Z置位相等/等于0

0001 NE Z清0不等

0010 CS/HS C置位进位/无符号高于或等于

0011 CC/LO C清0无进位/无符号低于

0100 MI N置位负数

0101 PL N清0非负数

0110 VS V置位溢出

0111 VC V清0无溢出

1000 HI C置位且Z清0无符号高于

1001 LS C清0或Z置位无符号低于或等于

1010 GE N等于V有符号大于或等于

1011 LT N不等于V有符号小于

1100 GT Z清0且N等于V有符号大于

1101 LE Z置位或N不等于V有符号小于或等于

1110 AL 任何状态 总是(always)

1111 NV 无 从不(never)

IFT称为控制位

II=1禁用IRO中断

FF=1禁用FIQ中断

T表示CPU当前的状态,1代表正在Thumb指令集状态,0表示正在ARM指令集状态。

M0至M4表示中断类型(控制位内的模式位)

0b10000User用户中断

0b10001FIQ快速中断

0b10010IRQ声卡、调制解调器等外部设备产生的中断

0b10011Supervisor管理程序或监控程序产生的中断

0b10111Abort异常中断

0b11011Undefined未定义中断

0b11111System系统中断

SPSR保存的程序状态寄存器,结构与CPSR完全一样,用来保存CPSR的值。以便出现异常时恢复CPSR的值

(3)流水线对pc值的影响

CPU内部的组成部分:指令寄存器,指令译码器,指令执行单元(包括ALU和通用寄存器组)

CPU执行指令的步骤:取指->译码->执行

取指:将指令从内存或指令cache中取入指令寄存器

译码:指令译码器对指令寄存器中的指令进行译码操作,辨识add,或是sub等操作

执行:指令执行单元根据译码的结果进行运算并保存结果

流水线操作:并发多条流水线(以3条为例)

1取指译码执行

2取指译码执行

3取指译码执行

提高时间效率

(三)、学习内容

1.汇编(对裸板机的控制,以及驱动程序控制)

2.内核移植(uboot移植、内核编译、文件系统移植、应用程序移植)

3.驱动程序编写

//^^^^^^^^^^^^^^^^^^^^^^^^^^下午^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

(四)、ADS的使用

ADS是汇编或C语言编译调试工具

可生成的文件:.axf含调试信息的可执行ELF文件

.bin可烧写的二进制映像文件

.hex可烧写的十六进制映像文件

ADS配置

在磁盘中新建一个目录D:arm,用来存储所写的代码

点击目录:File->new,创建一个可执行的ARM映象工程

ARMExecutableImage生成ELF格式映像(bin)

ARMObjectLibrary生成armar格式目标库文件

EmptyProject创建不包含任何库或源文件的工程

MakefileImporterWizard用于Vc

ThumbARMInterworkingImage用于ARM和thumb指令混合代码生成的ELF映像

ThumbExecutableimage用thumb指令生成ELF格式映像

ThumbObjectLibrary用于Thumb指令的代码生成的armar格式文件

选ARMExecutableImage,工程文件名2440ART

加源文件

project->AddFiles

新建

填加已有

生成目标的配置

2440ART.mcp内双击

TargetSettins:post-linker选择ArMfromELF

LanguageSettins:ArchitectureorProcessor选择相应的编译器ARM920T

ArmLinker:output内RO0x30000000

options内Imageentrypoint设为0x30000000

layout内Object2440init.oSectionInit

Listings内Imagemap

ArmfromELF:outputformat内Plainbinary

outputfilename内*.bin

编译

make

调试AXD是调试器

设置,debug->打开AXD调试界面,选择option->configtarget选项

选ARMUL(模拟调试器),然后选择确定.进入调试界面.

ARMUL是虚拟调试环境(虚拟开发板)

如果用开发板真实环境调试,则需要使用JTAG连开发板后,在此处选H-JTAG

用file-

execute->runtocousor项.使程序进入用户主程序

可以用F8来一条一条执行语句,也可用F10,可以设置断点.

(五).汇编语言基本结构

例:

AREAInit,CODE,READONLY;AREA定义代码段,段名Init;代码段,只读

ENTRY;伪操作,第一条指令的入口

Start;标号,一段代码的开始,用于标记,无意义,必须顶格

MOVr0,#10;将10存入r0寄存器,整型常量前面用#号

MOVr1,#3;将3存入r1寄存器,r0和r1相当于两个变量,只是名称固定,在寄存器的存储空间内

ADDr0,r0,r1;将r0内数据与r1内数据相加,相加后数据放在r0内

;Stop;停止标号,下面的代码用于停止运行程序

;MOVr0,#0x18;软件异常中断响应

;LDRr1,=0x20026;ADP停止运行,应用退出

;SWI0x123456;ARM半主机软件中断

END

1).基本概念

(2)寄存器:如R0、R1等

ARM的汇编编程,本质上就是针对CPU寄存器的编程。

(3)指令:即操作码,直接控制CPU,如MOV

包括跳转指令、数据处理指令、乘法指令、PSR访问指令、加载或存储指令、数据交换指令、移位指令等

(4)伪操作:作用于编译器,大多用于定义和控制。如AREA

包括符号定义、数据定义、控制等

(5)标号:仅是一种标识。在跳转语句中,可以指向要跳转到的标识号位置

在ARM汇编中,标号代表一个地址,段内标号的地址在汇编时确定,段外标号的地址值在连接时确定

(6)符号:即标号(代表地址)、变量名、数字常量名等。符号的命名规则如下:

a.符号由大小写字母、数字以及下划线组成;

b.除局部标号以数字开头外,其它的符号不能以数字开头;

c.符号区分大小写,且所有字符都是有意义的;

d.符号在其作用域范围你必须是唯一的;

e.符号不能与系统内部或系统预定义的符号同名;

f.符号不要与指令助记符、伪指令同名。

2).段定义

在汇编语言中,以相对独立的指令或数据序列的程序段组成程序代码

段的划分:数据段、代码段。一个汇编程序至少有一个代码段

(1)代码段

上面的例子为代码段。

AREA定义一个段,并说明所定义段的相关属性,CODE用以指明为代码段

ENTRY标识程序的入口点。END为程序结束。

(2)数据段

AREADATAAREA,DATA,BIINIT,ALLGN=2

DISPBUFSPACE200

RCVBUFSPACE200

DATA用以指明为数据段,

SPACE分配200字节的存储单元并初始化为0

指令和伪操作在后面详细描述

3).汇编语言结构

[标号][指令或伪操作]

所有标号必须在一行的顶格书写,其后不加冒号

所有指令均不能顶格写

指令助记符大小写敏感,不能大小写混合,只能全部大写或全部小写

;为注释

@代码行注释,同;

#整行注释或直接操作数前缀

为换行符

ENTRY为程序的入口

END为程序的结束

//*arm体系结构第二天*

二、ARM的寻址方式

最简单的汇编指令格式是操作码和操作数

如:MOVr0,#10

操作码:即CPU指令如MOVADD

操作数:即表示数据是在寄存器中还是在内存中,是绝对地址还是相对地址

操作数部分要解决的问题是,到哪里去获取操作数,获取操作数的方式就是寻址方式。

ARM每一条指令都是32位机器码,对应CPU的位数

ARM指令格式:

在32位中分为7个位域,每个位域分别存储不同意义的编码(二进制数

31~2827~2524~212019~1615~1211~0

-----------------------------------------------------------

|Cond|001|Opcode|S|Rn|Rd|Operand2|

-----------------------------------------------------------

对应输入的一条指令为:

{ }{s} , ,

Opcode:指令操作码用4个位存储,如MOV,ADD每一个操作码和操作数最终都是以二进制形式存在

Cond:指令的条件码用4个位来储,可省略

如:ADDr0,r0,#1;计算r0加1后的值,再写入到r0内

ADDEQr0,r0,#1;只有在CPSR寄存器条件标志位满足指定条件时,才计算r0加1后的值,再写入到r0内

其中条件助记符如下:

EQ相等

NE不相等

MI负数

VS溢出

PL正数或零

HI无符号大于

LS无符号小于或等于

CS无符号大于或等于

CC无符号小于

GT有符号大于

GE有符号大于或等于

LE有符号小于或等于

LT有符号小于

AL无条件执行

S:决定指令的操作是否影响CPSR的值,用一个位存储,省略则表示为0值,否则为1值

如SUBSR0,R0,#1;R0减1,结果放入R0,同时响影CPSR的值

SUBR0,R0,#1;R0减1,结果放入R0,不影响CPSR的值

Rd:目标寄存器编码用4位存储

Rn:第1个操作数的寄存器编码用4位存储

Operand2:第2个操作数用12位存储

寻址方式:是根据指令中给出的地址码字段来实现寻找真实操作数地址的方式,共8种寻址方式:

寄存器寻址、立即寻址、寄存器间接寻址、基址寻址、多寄存器寻址、堆栈寻址、相对寻址、寄存器移位寻址

//*寻址方式是重点需要理解背讼*

1.立即寻址

操作数是常量,用#表示常量。例

MOVR0,#0xFF000;指令省略了第1个操作数寄存器。将立即数0xFF000(第2操作数)装入R0寄存器

SUBR0,R0,#64;R0减64,结果放入R0

#表示立即数0x或&表示16进制数否则表示十进制数

立即数寻址指令中的地址码就是操作数本身,可以立即使用的操作数。

其中,#0xFF000和#64都是立即数。该立即数位于32位机器码中,占低位的12位。也就是说在ARM指令中以12位存储一个数据

那么,32位的数据(long或float)如何存储?

32位的数据以一种特殊的方式来处理

其中:高4位表示的无符号整数

低8位补0扩展为32位,然后循环右移x位来代表一个数。x=高4位整数*2

所以,不是每一个32位数都是合法的立即数,只有能通过上述构造得到的才是合法的立好数。如:

合法立即数:0xff,0x104,0xff0

不合法立即数:ox101,0x102,0xff1

2.寄存器寻址

操作数的值在寄存器中,指令执行时直接取出寄存器值来操作

例:MOVR1,R2;将R2的值存入R1在第1个操作数寄存器的位置存放R2编码

SUBR0,R1,R2;将R1的值减去R2的值,结果保存到R0在第2操作数位置,存放的是寄存器R2的编码

寄存器寻址是根据寄存器编码获取寄存器内存储的操作数

3.寄存器间接寻址

操作数从寄存器所指向的内存中取出,寄存地存储的是内存地址

例:LDRR1,[R2];将R2指向的存储单元的数据读出,保存在R1中R2相当于指针变量

STRR1,[R2];将R1的值写入到R2所指向的内存

SWPR1,R1,[R2];将寄存器R1的值和R2指定的存储单元的内容交换

[R2]表示寄存器所指向的内存,相当于*p

LDR指令用于读取内存数据

STR指令用于写入内存数据

//-----------------------------------------------------

4.寄存器基址寻址

操作数从内存址偏移后读取数据。常用于查表、数组操作、功能部件寄存器访问等。

基址:相当于首地址,地址偏移后取值作为操作数

基址寄存器:用来存放基址的寄存器

变址寻址:基址寻址也称为变址寻址

1)前索引寻址

例:LDRR2,[R3,#4];读取R3+4地址上的存储单元的内容,放入R2

LDRR2,[R3,#4]!;读取R3+4地址上的存储单元的内容,放入R2,然后R3内的地址变为R3+4,即R3=R3+4

[R3,#4]表示地址偏移后取值,相当于*(p+4)或p[4],R3内保存的地址不变

[R3,#4]!表示地址偏移后取值,相当于*(p+4)或p[4],!表示回写,即R3=R3-4,R3的值发生了改变

2)后索引寻址

例:LDRR2,[R3],#4;先读取R3地址上的存储单元的内容,放入R2,然后R3内的地址变为R3+4,即R3=R3+4

[R3],#4类似于*p++只不过自加的不是1,而是指定的4

[R3,#4]!类似于*++p只不过自加的不是1,而是指定的4

3)寄存器的值作索引

例:LDRR2,[R3,R0];R0内存储索引值,R3内存地址,读取R3+R0地址上的存储单元的内容,放入R2

[R3,R0]表示地址偏移后取值,相当于*(p+i)或p[i]

5.多寄存器寻址

一次可传送多个寄存器的值,也称为块拷贝寻址

例:LDMIAR1!,{R2-R7,R12};将R1指向的存储单元中的数据读写到R2~R7、R12中,然后R1自加1

STMIAR1!,{R2-R7,R12};将寄存器R2~R7、R12的值保存到R1指向的存储单元中,然后R1自加1

其中R1是基址寄存器,用来存基址,R2-R7、R12用来存数据赋值编号小的寄存器与低地址相对应,与寄存器列表顺序无关

!为可选后缀,表示改变R1的值,则当数据传送完毕之后,将最后的地址写入基址寄存器

基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。

这里R1没有写成[R1]!,是因为这个位不是操作数位,而是寄存器位

LDMIA和STMIA是块拷贝指令,LDMIA是从R1所指向的内存中读数据,STMIA是向R1所指向的内存写入数据

R1指向的是连续地址空间

6.寄存器堆栈寻址

是按特定顺序存取存储区,按后进先出原则,使用专门的寄存器SP(堆栈指针)指向一块存储区

例:LDMIASP!,{R2-R7,R12};将栈内的数据,读写到R2~R7、R12中,然后下一个地址成为栈顶

STMIASP!,{R2-R7,R12};将寄存器R2~R7、R12的值保存到SP指向的栈中

SP指向的是栈顶

7.相对寻址

即读取指令本身在内存中的地址。是相对于PC内指令地址偏移后的地址。

由程序计数器PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。

例:

BLOOP;B指令用于跳转到标号LOOP指令处执行代码

...

LOOP

MOVR6#1

其中LOOP仅仅是标号,而不是地址,不是CPU指令,所以在指令的机器码中没有标号的机器码,而是

计算出了从B指令到MOV指令之间内存地址的差值,这个差值相当于PC的偏移量,即相当于:addpc,pc,#偏移量

B指令引起了PC寄存器值的变化,PC内永远保存将要运行指令在内存中的地址。

8.寄存器移位寻址

操作数在被使用前进行移位运算

例:MOVR0,R2,LSL#3;R2的值左移3位,结果放入R0,;即是R0=R2×8

LSL是移位指令,用于将前面寄存器的数据左移

//^^^^^^^^^^^^^^^^^^^^^^^^^^下午^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ARM汇编语言语句由指令、伪指令、伪操作、宏指令组成

三.ARM指令集

共包括6种类型:数据处理指令、跳转指令、程序状态指令、加载存取储指令、协处理指令、软中断指令

(一)、数据处理指令

数据处理指令,只能对寄存器内容进行操作,而不能对内存进行操作,所有数据处理指令均可使用S后缀,并影响状态标志

包括:数据传送指令、算述指令、乘法指令、逻辑运算指令、比较指令、移位指令

1.数据传输指令

1)MOV数据传送指令

格式:MOVRd,

功能:Rd=操作数,操作数可以是寄存器、被移位的寄存器或立即数。

例:

MOVR0,#0xFF000;立即寻址,将立即数0xFF000(第2操作数)装入R0寄存器

MOVR1,R2;寄存器寻址,将R2的值存入R1

MOVR0,R2,LSL#3;移位寻址,R2的值左移3位,结果放入R0

2)MVN数据取反传送指令

格式:MVN , ;

功能:将操作数传送到目的寄存器Rd中,但该值在传送前被按位取反,即Rd=!op1;

例:

MVNR0,#0;R0=-1

mvn意为“取反传输”,它把源寄存器的每一位取反,将得到的结果写入结果寄存器。

movs和mvns指令对pc寄存器赋值时有特殊含义,表示要求在赋值的同时从spsr中恢复cpsr。

mov和mvn指令,编译器会进行智能的转化。比如指令“movr1,0xffffff00”中的立即数是非法的。

在编译时,编译器将其转化为“mvnr1,0xff”,这样就不违背立即数的要求。所以对于mov和mvn指令,

可以认为:合法的立即数反码也是合法的立即数。

2.算术指令

1)ADD加法指令

格式:ADD{ }{S} , , ;

功能:Rd=Rn+op2

例:

ADDR0,R1,#5;R0=R1+5

ADDR0,R1,R2;R0=R1+R2

ADDR0,R1,R2,LSL#5;R0=R1+R2左移5位

2)ADC带进位加法指令

格式:ADC{ }{S} , , ;

功能:Rd=Rn+op2+carry,carry为进位标志值。该指令用于实现超过32位的数的加法。

例如:

第一个64位操作数存放在寄存器R2,R3中;

第二个64位操作数存放在寄存器R4,R5中;

64位结果存放在R0,R1中。

ADDSR0,R2,R4;低32位相加,S表示结果影响条件标志位的值

ADCR1,R3,R5;高32位相加

3)SUB减法指令

格式:SUB{ }{S} , , ;

功能:Rd=Rn-op2

例:

SUBR0,R1,#5;R0=R1-5

SUBR0,R1,R2;R0=R1-R2

SUBR0,R1,R2,LSL#5;R0=R1-R2左移5位

4)SBC带借位减法指令

格式:SBC{ }{S} , , ;

功能:Rd=Rn-op2-!carry

SUB和SBC生成进位标志的方式不同于常规,如果需要借位则清除进位标志,所以指令要对进位标志进行一个非操作。

例:

第一个64位操作数存放在寄存器R2,R3中;

第二个64位操作数存放在寄存器R4,R5中;

64位结果存放在R0,R1中。

SUBSR0,R2,R4;低32位相减,S表示结果影响条件标志位的值

SBCR1,R3,R5;高32位相减

5)其它减法指令

RSB反向减法指令,同SUB指令,但倒换了两操作数的前后位置,即Rd=op2-Rn。

RSC带借位的反向减法指令,同SBC指令,但倒换了两操作数的前后位置,即Rd=op2-Rn-!carry。

rsb r0,r1,r2 /*r0=r2–r1*/

rsc r0,r1,r2 /*r0=r2–r1+carry–1*/

adds和adcs在进位时将cpsr的C标志置1;否则置0。

subs和sbcs在产生借位时将cpsr的C标志置0;否则置1。

3.乘法指令

1)MUL32位乘法指令

格式:MUL{ }{S} , , ;

功能:Rd=Rn×op2

该指令根据S标志,决定操作是否影响CPSR的值;其中op2必须为寄存器。Rn和op2的值为32位的有符号数或无符号数。

例:

MULSR0,R1,R2;R0=R1×R2,结果影响寄存器CPSR的值

2)MLA32位乘加指令

格式:MLA{ }{S} , , , ;

功能:Rd=Rn×op2+op3op2和op3必须为寄存器。Rn、op2和op3的值为32位的有符号数或无符号数。

例:

MLAR0,R1,R2,R3;R0=R1×R2+R3

3)SMULL64位有符号数乘法指令

格式:SMULL{ }{S} , , , ;

功能:RdhRdl=Rn×op2Rdh、Rdl和op2均为寄存器。Rn和op2的值为32位的有符号数。

例:

SMULLR0,R1,R2,R3;R0=R2×R3的低32位R1=R2×R3的高32位

4)SMLAL64位有符号数乘加指令

格式:SMLAL{ }{S} , , , ;

功能:RdhRdl=Rn×op2+RdhRdl;Rdh、Rdl和op2均为寄存器。Rn和op2的值为32位的有符号数,RdhRdl的值为64位的加数。

例:

SMLALR0,R1,R2,R3;R0=R2×R3的低32位+R0R1=R2×R3的高32位+R1

5)UMULL64位无符号数乘法指令

格式:UMULL{ }{S} , , , ;

功能:同SMULL指令,但指令中Rn和op2的值为32位的无符号数。

例:

UMULLR0,R1,R2,R3;R0=R2×R3的低32位R1=R2×R3的高32位其中R2,R3的值为无符号数

6)UMLAL64位无符号数乘加指令

格式:UMLAL{ }{S} , , , ;

功能:同SMLAL指令,但指令中Rn,op2的值为32位的无符号数,RdhRdl的值为64位无符号数。

例:

UMLALR0,R1,R2,R3;R0=R2×R3的低32位+R0R1=R2×R3的高32位+R1

;其中R2,R3的值为32位无符号数R1,R0的值为64位无符号数

4.逻辑指令:and、orr、eor和bic

1)AND逻辑与指令

格式:AND{ }{S} , , ;

功能:Rd=RnANDop2一般用于清除Rn的特定几位。

例:

ANDR0,R0,#5;保持R0的第0位和第2位,其余位清0

2)ORR逻辑或指令

格式:ORR{ }{S} , , ;

功能:Rd=RnORop2一般用于设置Rn的特定几位。

例:

ORRR0,R0,#5;R0的第0位和第2位设置为1,其余位不变

3)EOR逻辑异或指令

格式:EOR{ }{S} , , ;

功能:Rd=RnEORop2一般用于将Rn的特定几位取反。

例:

EORR0,R0,#5;R0的第0位和第2位取反,其余位不变

4)BIC位清除指令

格式:BIC{ }{S} , , ;

功能:Rd=RnAND(!op2)用于清除寄存器Rn中的某些位,并把结果存放到目的寄存器Rd中

例:

BICR0,R0,#5;R0中第0位和第2位清0,其余位不变

5.比较指令

1)CMP比较指令

格式:CMP{ } , ;

功能:Rn-op1,根据结果更新CPSR中条件标志位的值。

例:

CMPR0,#5;计算R0-5,根据结果设置条件标志位

ADDGTR0,R0,#5;ADD为加法指令,GT为判断条件标志位是否大于5,如果R0>5,则执行ADD指令

2)CMN反值比较指令

格式:CMN{ } , ;

功能:同CMP指令,但寄存器Rn的值是和op1取负的值进行比较。

例:

CMNR0,#5;把R0与-5进行比较

3)TST位测试指令

格式:TST{ } , ;

功能:RnANDop1按位与后,更新CPSR中条件标志位,用于检查寄存器Rn是否设置了op1中相应的位。

例:

TSTR0,#5;测试R0中第0位和第2位是否为1

4)TEQ相等测试指令

格式:TEQ{ } , ;

功能:RnEORop1按位异或后,更新CPSR中条件标志位,用于检查寄存器Rn的值是否和op1所表示的值相等。

例:

TEQR0,#5;判断R0的值是否和5相等

6.移位指令(位运算指令)

1)LSL(或ASL)左移

格式:寄存器,LSL(或ASL)操作数

功能:将寄存器内的数据左移,操作数是移位的位数在0-31之间

例:

MOVR0,R1,LSL#2;将R1中的内容左移两位后传送到R0中。

2)LSR操作右移

格式:寄存器LSR操作数

功能:将寄存嚣内的数据右移

例:

MOVR0,R1,LSR#2;将R1中的内容右移两位后传送到R0中,左端用零来填充。

3)其它移位

ASR右移,左端用第31位值来填充

ROR右移,循环右移,左端用右端移出的位来填充

RRX右移,循环右移,左端用进位标志位C来填充

//*arm体系结构第三天*

(二)、跳转指令

1)B跳转指令

格式:B{ };

功能:跳转到addr地址。

例:

Bexit;程序跳转到标号exit处

2)BL带返回的跳转指令

格式:BL{ };

功能:同B指令,但BL指令执行跳转操作的同时,还将PC(寄存器R15)的值保存到LR寄存器(寄存器R14)中。

该指令用于实现子程序调用,程序的返回可通过把LR寄存器的值到PC寄存器中来实现。

例:

BLfunc;调用子程序func

func

MOVR15,R14;子程序返回

3)其它跳转指令

BLX带返回和状态切换的跳转指令,用于子程序调用和程序Thumb状态的切换。

BX带状态切换的跳转指令,处理器跳转到目标地址处,目标地址处的指令可以是ARM指令,也可以是Thumb指令。

跳转指令用于实现程序的跳转和程序状态的切换。

ARM程序设计中,实现程序跳转有两种方式:跳转指令、直接向程序寄存器PC中写入目标地址值。

//----------------------------------------------------------------------------------------

(三)、程序状态指令

用于状态寄存器和通用寄存器间传送数据。总共有两条指令:MRS和MSR。两者结合可用来修改程序状态寄存器的值。

1)MRS程序状态寄存器到通用寄存器的数据传送指令

格式:MRS{ } ,CPSR/SPSR;

功能:用于将程序状态寄存器的内容传送到目标寄存器Rd中。

例:

MRSR0,CPSR;状态寄存器CPSR的值存入寄存器R0中

2)MSR通用寄存器到程序状态寄存器的数据传送指令

格式:MSR{ }CPSR/SPSR_ , ;

功能:用于将寄存器Rd的值传送到程序状态寄存器中。

:用来设置状态寄存器中需要操作的位。

32位的状态寄存器可以分为4个域:

位[31:24]为条件标志位域,用f表示。

位[23:16]为状态位域,用s表示。

位[15:8]为扩展位域,用x表示。

位[7:0]为控制位域,用c表示。

例:

MSRCPSR_f,R0;用R0的值修改CPSR的条件标志域

MSRCPSR_fsxc,#5;CPSR的值修改为5

(四)、加载存储指令

该集合的指令使用频繁,当数据存放在内存中时,必须先把数据从内存装载到寄存器,执行完后再把寄存器

中的数据存储到内存中

1.单数据访存指令

1)单数据加载指令

格式:LDR(或LDRB、LDRBT、LDRH、LDRSB、LDRSH、LDRT、STR、STRB、STRBT、STRH、STRT) ,;

功能:内存地址中的数据装载到目标寄存器Rd中,同时还可以把合成的有效地址写回到基址寄存器。

寻址方式:Rn:基址寄存器。Rm:变址寄存器。Index:偏移量,12位的无符号数。

LDRRd,[Rn];把内存中地址为Rn的字数据装入寄存器Rd中

LDRRd,[Rn,Rm];将内存中地址为Rn+Rm的字数据装入寄存器Rd中

LDRRd,[Rn,#index];将内存中地址为Rn+index的字数据装入Rd中

LDRRd,[Rn,Rm,LSL#5];将内存中地址为Rn+Rm×32的字数据装入Rd

LDRRd,[Rn,Rm]!;将内存中地址为Rn+Rm的字数据装入Rd,并将新地址Rn+Rm写入Rn

LDRRd,[Rn,#index]!;将内存中地址为Rn+index的字数据装入Rd,并将新地址Rn+index写入Rn

LDRRd,[Rn,Rm,LSL#5]!;将内存中地址为Rn+Rm×32的字数据装入Rd,并将新地址Rn+Rm×32写入Rn

LDRRd,[Rn],Rm;将内存中地址为Rn的字数据装入寄存器Rd,并将新地址Rn+Rm写入Rn

LDRRd,[Rn],#index;将内存中地址为Rn的字数据装入寄存器Rd,并将新地址Rn+index写入Rn

LDRRd,[Rn],Rm,LSL#5;将内存中地址为Rn的字数据装入寄存器Rd,并将新地址Rn+Rm×32写入Rn

各指令的区别:

(1)LDR字数据加载指令

将内存地址中的字数据装载到目标寄存器Rd中

例:LDRR0,[R1,R2,LSL#5]!;将内存中地址为R1+R2×32的字数据装入寄存器R0,并将新地址R1+R2×32写入R1

(2)LDRB字节数据加载指令

同LDR,只是从内存读取一个8位的字节数据而不是一个32位的字数据,并将Rd的高24位清0。

例:LDRBR0,[R1];将内存中起始地址为R1的一个字节数据装入R0中

(3)LDRBT用户模式的字节数据加载指令

同LDRB指令,但无论处理器处于何种模式,都将该指令当作一般用户模式下的内存操作。

(4)LDRH半字数据加载指令

同LDR指令,但该指令只是从内存读取一个16位的半字数据而不是一个32位的字数据,并将Rd的高16位清0。

例:LDRHR0,[R1];将内存中起始地址为R1的一个半字数据装入R0中

(5)LDRSB有符号的字节数据加载指令

同LDRB指令,但该指令将寄存器Rd的高24位设置成所装载的字节数据符号位的值。

例:LDRSBR0,[R1];将内存中起始地址为R1的一个字节数据装入R0中,R0的高24位设置成该字节数据的符号位

(6)LDRSH有符号的半字数据加载指令

同LDRH指令,但该指令将寄存器Rd的高16位设置成所装载的半字数据符号位的值。

例:LDRSHR0,[R1];将内存中起始地址为R1的一个16位半字数据装入R0中,R0的高16位设置成该半字数据的符号位

(7)LDRT用户模式的字数据加载指令

同LDR指令,但无论处理器处于何种模式,都将该指令当作一般用户模式下的内存操作。有效地址必须是字对齐的

2)单数据存储指令

格式:STR(或STR、STRB、STRBT、STRH、STRT) ,;

功能:将寄存器数据写入到内存中

寻址方式:Rn:基址寄存器。Rm:变址寄存器。Index:偏移量,12位的无符号数。

STRRd,[Rn];将寄存器Rd中的字数据写入到内存中地址为Rn内存中

STRRd,[Rn,Rm];将寄存器Rd中的字数据写入到内存中地址为Rn+Rm的内存中

STRRd,[Rn,#index];将寄存器Rd中的字数据写入到内存中地址为Rn+index内存中

STRRd,[Rn,Rm,LSL#5];将寄存器Rd中的字数据写入到内存中地址为Rn+Rm×32内存中

STRRd,[Rn,Rm]!;将寄存器Rd中的字数据写入到内存中地址为Rn+Rm的内存中

STRRd,[Rn,#index]!;将寄存器Rd中的字数据写入到内存中地址为Rn+index的内存中,并将新地址Rn+index写入Rn

STRRd,[Rn,Rm,LSL#5]!;将寄存器Rd中的字数据写入到内存中地址为Rn+Rm×32的内存中,并将新地址Rn+Rm×32写入Rn

STRRd,[Rn],Rm;将寄存器Rd中的字数据写入到内存中地址为Rn的内存中,并将新地址Rn+Rm写入Rn

STRRd,[Rn],#index;将寄存器Rd中的字数据写入到内存中地址为Rn的内存中,并将新地址Rn+index写入Rn

STRRd,[Rn],Rm,LSL#5;将寄存器Rd中的字数据写入到内存中地址为Rn的内存中,并将新地址Rn+Rm×32写入Rn

(1)STR字数据存储指令

把寄存器Rd中的字数据(32位)保存到addr所表示的内存地址中,同时还可以把合成的有效地址写回到基址寄存器。

例:STRR0,[R1,#5]!;把R0中的字数据保存到以R1+5为地址的内存中,然后R1=R1+5

(2)STRB字节数据存储指令

把寄存器Rd中的低8位字节数据保存到addr所表示的内存地址中。

例:STRBR0,[R1];将寄存器R0中的低8位数据存入R1表示的内存地址中

(3)STRBT用户模式的字节数据存储指令

同STRB指令,但无论处理器处于何种模式,该指令都将被当作一般用户模式下的内存操作。

(4)STRH半字数据存储指令

把寄存器Rd中的低16位半字数据保存到addr所表示的内存地址中,而且addr所表示的地址必须是半字对齐的。

例:STRHR0,[R1];将寄存器R0中的低16位数据存入R1表示的内存地址中

(5)STRT用户模式的字数据存储指令

同STR指令,但无论处理器处于何种模式,该指令都将被当作一般用户模式下的内存操作。

2.多数据访存指令

1)批量数据加载指令

格式:LDM{ }{ } {!}, {^};

功能:从一片连续的内存单元读取数据到各个寄存器中,内存单元的起始地址为基址寄存器Rn的值,各个寄存器由寄存

器列表regs表示。

该指令一般用于多个寄存器数据的出栈。

type字段种类:

IA:每次传送后地址加1。

IB:每次传送前地址加1。

DA:每次传送后地址减1。

DB:每次传送前地址减1。

FD:满递减堆栈。

ED:空递减堆栈。

FA:满递增堆栈。

EA:空递增堆栈。

堆栈寻址的命令LDMFA/STMFA、LDMEA/STMEA、LDMFD/STMFD、LDMED/STMED。

LDM和STM表示多寄存器寻址,即一次可以传送多个寄存器值。

LDM:一次装载多个,这里用来出栈。

STM:一次存储多个,这里用来入栈。

F/E表示指针指向的位置

F:full满堆栈,表示堆栈指针指向最后一个入栈的有效数据项。

E:empty空堆栈,表示堆栈指针指向下一个要放入的空地址。

A/D表示堆栈的生长方式

A:堆栈向高地址生长,即递增堆栈。

D:堆栈向低地址生长,即递减堆栈。

注意:有一个约定,编号低的寄存器在存储数据或者加载数据时对应于存储器的低地址。

FD、ED、FA和EA指定是满栈还是空栈,是升序栈还是降序栈,用于堆栈寻址。

一个满栈的栈指针指向上次写的最后一个数据单元.

空栈的栈指针指向第一个空闲单元。

一个降序栈是在内存中反向增长而升序栈在内存中正向增长。

{!}:若选用了此后缀,则当指令执行完毕后,将最后的地址写入基址寄存器。

{^}:当regs中不包含PC时,该后缀用于指示指令所用的寄存器为用户模式下的寄存器,

否则指示指令执行时,将寄存器SPSR的值到CPSR中。

2)批量数据存储指令

格式:STM{ }{ } {!}, {^};

功能:将各个寄存器的值存入一片连续的内存单元中,内存单元的起始地址为基址寄存器Rn的值

各个寄存器由寄存器列表regs表示。该指令一般用于多个寄存器数据的入栈。

{^}:指示指令所用的寄存器为用户模式下的寄存器。其他参数用法同LDM指令。

例:STMEAR13!,{R0-R12,PC};将寄存器R0~R12以及程序计数器PC的值保存到R13指示的堆栈中

3.数据交换指令

1)字数据交换指令

格式:SWP , ,[ ];

功能:Rd=[op2],[op2]=op1

从op2所表示的内存装载一个字并把这个字放置到目的寄存器Rd中,然后把寄存器op1的内容存储到同一内存地址中。

例:SWPR0,R1,[R2];将R2所表示的内存单元中的字数据装载到R0,然后将R1中的字数据保存到R2所表示的内存单元中

2)字节数据交换指令

格式:SWPB , ,[ ];

功能:从op2所表示的内存装载一个字节并把这个字节放置到目的寄存器Rd的低8位中,Rd的高24位设置为0;

然后将寄存器op1的低8位数据存储到同一内存地址中。

例:SWPBR0,R1,[R2];将R2所表示的内存单元中的一个字节数据装载到R0的低8位,然后将R1中的低8位字节

数据保存到R2所表示的内存单元中

//^^^^^^^^^^^^^^^^^^^^^^^^^^下午^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

(五)、协处理指令

1)CDP协处理器操作指令

格式:CDP{ }

, , , , , ;

功能:用于传递指令给协处理器p,要求其在寄存器CRn和CRm上,进行操作opcode1,并把结果存放到CRd中,

可以使用opcode2提供与操作有关的补充信息。指令中的所有寄存器均为协处理器的寄存器,操作由协处理器完成。

指令中:

P为协处理器编号;

CRd为目的寄存器的协处理器寄存器;

CRm和CRn为存放操作数的协处理器寄存器;

Opcode1和opcode2为协处理器即将执行的操作。

例:CDPp5,5,c0,c1,c2,9;该指令用于通知协处理器p5,在c1和c2上执行操作5和9,并将结果存放到c0中。

2)LDC协处理器数据读取指令

格式:LDC{ }{L}

, ,;

功能:将addr表示的内存地址中的连续数据传送到目的寄存器CRd中。

L表示指令为长读取操作,比如用于双精度数据的传输;

目的寄存器CRd为协处理器的寄存器;

addr的寻址方式同LDR指令,其寄存器为ARM处理器的寄存器。

例:LDCp5,c1,[R1+5]:该指令用于将R1+5所对应的存储单元中的数据,传送到协处理器p5的寄存器c1中。

3)STC协处理器数据存储指令

格式:STC{ }{L}

, ,;

功能:将寄存器CRd的值传送到addr表示的内存地址中。指令中各参数用法同LDC。

例如:STCp5,c1,[R1+5];该指令用于将协处理器p5中寄存器c1的数据传送到R1+5所对应的存储单元中。

4)MCRARM寄存器到协处理器寄存器的数据传送指令

格式:MCR{ }

, , , , {,op2};

功能:将ARM处理器的寄存器Rd中的数据传送到协处理器p的寄存器CRn,CRm中;op1,op2为协处理器将要执行的操作。

例:MCRp5,5,R1,C1,C2,9;该指令将R1中的数据传送到协处理器p5的寄存器C1,C2中,协处理器执行操作5和9。

MRC协处理器寄存器到ARM寄存器的数据传送指令

格式:MRC{ }

, , , , {,op2};

功能:将协处理器p的寄存器CRn,CRm的数据传送到ARM处理器的寄存器Rd中;op1,op2为协处理器将要执行的操作。

例:MRCp5,5,R1,C1,C2,9;该指令将寄存器C1,C2中的数据传送到R1中,协处理器p5协处理器执行操作5和9。

(六)、异常中断指令

异常中断产生指令:用于系统调用和调试。

1)SWI软件中断指令

格式:SWI{ }24位的立即数;

功能:用于产生软件中断,以使用户程序调用操作系统的系统例程。

指令中24位的立即数指定用户程序调用系统例程的类型,其参数通过通用寄存器传递。当24位的立即数

被忽略时,系统例程类型由寄存器R0指定,其参数通过其他通用寄存器传递。

例:SWI0X05;调用编号为05的系统例程。

2)BKPT断点中断指令

格式:BKPT16位的立即数;

功能:用于产生软件断点中断,以便软件调试时使用。16位的立即数用于保存软件调试中额外的断点信息。

指令操作的伪代码:

(七)、信号量操作指令

信号量操作指令:用于进程间的同步互斥,提供对信号量的原子操作。

(八)、ARM程序常见结构

1.子函数和主函数

使用BL指令进行调用,该指令会把返回的PC值保存在LR

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

MOVR0,#0;设置实参,将传递给子程骗子的实参存放在r0和r1内

MOVR1,#10

BLADD_SUM;调用子程序ADD_SUM

BOVER;跳转到OVER标号处,进入结尾

ADD_SUM

ADDR0,R0,R1;实现两数相加

MOVPC,LR;子程序返回,R0内为返回的结果

OVER

END

运行过程

(1)程序从ENTRY后面指令处开始运行(即主函数)

R0和R1先准备好数据令子函数运算时使用

(2)BL为跳转指令,用于调用子函数后面为函数标号。

在调用函数的过程中,自动将PC内的值保存到LR(R14)内备份,PC内当前的值为下一条要执行的指令地址

(即BOVER指令地址),在子函数结束前将该地址恢复到PC内

(3)B为跳转指令,用于跳转到指定的标号后,此处跳转到程序结尾

(4)MOVPC,LR是子函数内的最后一条语句,用于将LR内保存的地址恢复到PC内

PC(R15)程序计数器存储要执行的指令在内存中的地址

PC的值=当前正在执行指令在内存中的地址+8

2.条件跳转语句

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

MOVR0,#2;将R0赋初值2

MOVR1,#5;将R1赋初值5

ADDR5,R0,R1;将R0和R1内的值相加并存入R5

CMPR5,#10

BEQDOEQUAL;若R5为10,则跳转到DOEQUAL标签处

WAIT

CMPR0,R1

ADDHIR2,R0,#10;若R0>R1则R2=R0+10

ADDLSR2,R1,#5;若R1<=R2则R2=R1+5

DOEQUAL

ANDSR1,R1,#0x80;R1=R1&0x80,并设置相应标志位

BNEWAIT;若R1的d7位为1则跳转到WAIT标签

OVER

END

运行过程

(1)程序从ENTRY后面指令处开始运行(即主函数)

R0和R1赋初值2和5

将R0与R1相加后存入R5内

(2)CMP用于比较两个数据,指令格式如下:

CMP操作数1,操作数2

CMP用于把一个寄存器的内容和另一个寄存器或立即数进行比较,同时

更新CPSR中条件标志位的值。标志位表示操作数1和操作数2的关系。然后执行后面的语句

(3)条件助记符

BEQB为跳转指令,EQ为条件相等,读取CPSR内的条件标志位,如相等则跳转到所指定的标号处

BNEB为跳转指令,NE为不相等(0),如不相等则跳转到所指定的标号处

ADDHIADD为相加指令,HI为无符号大于,如大于则执行相加

ADDLSADD为相加指令,LS为无符号小于或等于,如小于或等于则相加

(4)位运算

ANDSAND按位与,0x80取出第7位,S为将该位与的值写入标志位

3.循环语句

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

MOVR1,#0;将R1赋初值0

LOOP

ADDR1,R1,#1

CMPR1,#10

BCCLOOP;R1小于10则执行跳转到LOOP处执行循环,即R1从0到10后退出循环

END

例:编写一具有完整汇编格式的程序,实现冒泡法排序功能。

设无符号字数据存放在从0x400004开始的区域,字数据的数目字存放在0x400000中。

AREASORT,CODE,READONLY

ENTRY

START

MOV R1,#0x400000

LP

SUBS R1,R1,#1

BEQ EXIT

MOV R7,R1

LDR R0,=0x400004

LP1

LDR R2,[R0],#4

LDR R3,[R0]

CMP R2,R3

STRLOR3,[R0,#-4]

STRLOR2,[R0]

SUBSR7,R7,#1

BNE LP1

B LP

EXIT

END

练习:

1.编写1+2+3+…+100的汇编程序。

2.实现子函数,该函数返回两个参数中的最大值,在主函数内调用。

//*arm体系结构第四天*

四.伪操作和宏指令

伪指令——是汇编语言程序里的特殊指令助记符,在汇编时被合适的机器指令替代。

伪操作——为汇编程序所用,在源程序进行汇编时由汇编程序处理,只在汇编过程起作用,不参与程序运行。

宏指令——通过伪操作定义的一段独立的代码。在调用它时将宏体插入到源程序中。也就是常说的宏。

说明:所有的伪指令、伪操作和宏指令,均与具体的开发工具中的编译器有关

1.宏定义(MACRO、MEND)

格式:MACRO

{$标号名}宏名{$参数1,$参数2,……}

指令序列

MEND

MACRO、MEND伪指令可以将一段代码定义为一个整体,称为宏指令,在程序中通过宏指令多次调用该段代码。

{}为可选项

$标号在宏指令被展开时,标号会被替换为用户定义的符号

在宏定义体的第一行应声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来调用该指令序列

写在代码段或数据段前面

MEXIT跳出宏

例:没有参数的宏(实现子函数返回)

MACRO

MOV_PC_LR;宏名

MOVPC,LR;子程序返回,R0内为返回的结果

MEND

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

MOVR0,#0;设置实参,将传递给子程骗子的实参存放在r0和r1内

MOVR1,#10

BLADD_NUM;调用子程序ADD_NUM

BLSUB_NUM;调用子程序SUB_NUM

BOVER;跳转到OVER标号处,进入结尾

EXPORTADD_NUM

ADD_NUM

ADDR0,R0,R1;实现两数相加

MOV_PC_LR;调用宏,代表子函数结束

EXPORTSUB_NUM

SUB_NUM

SUBR0,R1,R0;实现两数相减

MOV_PC_LR;调用宏,代表子函数结束

OVER

END

例:有参数宏

宏定义从MACRO伪指令开始,到MEND结束,并可以使用参数。类似于C的#define

MACRO;宏定义

CALL$Function,$dat1,$dat2;宏名称为CALL,带3个参数

IMPORT$Function;声明外部子程序宏开始

MOVR0,$dat1;设置子程序参数,R0=$dat1

MOVR1,$dat2

BL$Function;调用子程序宏最后一句

MEND;宏定义结束

CALLFADD1,#3,#2;宏调用,后面是三个参数

汇编预处理后,宏调用将被展开,程序清单如下:

IMPORTFADD1

MOVR0,#3

MOVR1,#3

BLFADD1

2.符号定义伪操作

1)定义常量(EQU)

格式:标号名称EQU表达式{,类型}

用于为程序中的常量、标号等定义一个等效的字符名称,类似于C语言中的#define。

其中EQU可用*代替。

标号名称:为常量名。

表达式:寄存器的地址值、程序中的标号、32位地址常量、32位常量

当表达式为32位的常量时,可以指定表达式的数据类型,可以有以下三种类型:

CODE16、CODE32和DATA

示例:EQU的使用

XEQU45;即#defineX45,必须顶格

YEQU64

stack_topEQU0x30200000,CODE32

AREAExample,CODE,READONLY

CODE32

ENTRY

Start

LDRSP,=stack_top;stack_top内的值0x30200000是地址,=stack_top是取stack_top常量地址(即指针的指针)

MOVR0,#X;将X替换为45

STRR0,[SP];将R0内的45存入到SP所指向的内存中(SP此时是指针的指针)

MOVR0,#Y

LDRR1,[SP];从内存中读取数据到R1内

ADDR0,R0,R1

STRR0,[SP]

END

注:X,Y,stack_top为标号,必须顶格写,大多写在代码段外面

2)定义变量

常量:数字常量,有三种表示方式:十进制数、十六进制数、字符串常量、布尔常量(如testnoSETS{FALSE})

变量:数字变量、逻辑变量、字符串变量

(1)*GBLA、GBLL、GBLS定义全局变量

格式:GBLA(GBLL、GBLS)全局变量名

GBLA伪指令用于定义一个全局的数字变量,并初始化为0;

GBLL伪指令用于定义一个全局的逻辑变量,并初始化为F(假);

GBLS伪指令用于定义一个全局的字符串变量,并初始化为空;

由于以上三条伪指令用于定义全局变量,因此在整个程序范围内变量名必须唯一。

示例:全局变量的定义与赋值

GBLAcount;定义全局变量

countSETA2;给全局变量赋值为2,必须顶格

AREAExample,CODE,READONLY

CODE32

ENTRY

Start

MOVR0,#count;将count内的值写入R0内

ADDR0,R0,#2

BStart

END

注:在赋值过程中,全局变量名必须顶格写,全局变量常在代码段外定义和赋值

示例:变量与内存地址

GBLAglobv

globvSETA23

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

LDRR0,=globv;golbv是全局变量,将内存地址读入到R0内

LDRR1,[R0];将内存数据值读入到R1内

ADDR1,R1,#2

STRR1,[R0];将修改后数据再赋给变量

MOVR0,#0

OVER

END

注:#取变量值=取变量地址[R0]读取R0内地址所指向的数据值

(2)*LCLA、LCLL、LCLS定义局部变量

格式:LCLA(LCLL或LCLS)局部变量名

LCLA伪指令用于定义一个局部的数字变量,并初始化为0;

LCLL伪指令用于定义一个局部的逻辑变量,并初始化为F(假);

LCLS伪指令用于定义一个局部的字符串变量,并初始化为空;

以上三条伪指令必须写在宏定义内,用于声明局部变量,宏结束,局部变量不再起作用

示例:

LCLAnum;声明一个局部的数字变量,变量名为num

numSETA0xaa;将该变量赋值为0xaa

LCLLisOk;声明一个局部的逻辑变量,变量名为isOk

isOkSETL;将该变量赋值为真

LCLSstr1;定义一个局部的字符串变量,变量名为str1

str1SETS"Testing";将该变量赋值为"Testing"

示例:局部变量的定义与赋值

MACRO

MOV_START;宏名

LCLAx;定义局部变量

LCLAy

xSETA12;必须顶格写

ySETA24

MOVR0,#2

MOVR1,#3

ADDR0,R0,R1

MEND

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

MOV_START

MOVR0,#0

OVER

END

注:在赋值过程中,局部变量名必须顶格写,局部变量必须在宏定义内使用

(3)*SETA、SETL和SETS用于给一个已经定义的全局变量或局部变量赋值。

SETA伪指令用于给一个数学变量赋值;

SETL伪指令用于给一个逻辑变量赋值;

SETS伪指令用于给一个字符串变量赋值;

其中,变量名为已经定义过的全局变量或局部变量,表达式为将要赋给变量的值。

(4)变量代换$

$在数字变量前,将变值转换为十六进制字符串

$在逻辑变量前,将变量转换为真或假

$在字符串变量前,替换后面变量的字符串

如:

LCLSY1;定义局部字符串变量Y1和Y2

LCLSY2

Y1SETS"WORLD!"

Y2SETS"LELLO,$Y1";将字符串Y2的值替换$Y1,形成新的字符串

3)、定义一个寄存器(RN)

格式:名称RN表达式

RN伪指令用于给一个寄存器定义一个别名。

示例:

TempRNR0;将R0定义一个别名Temp

4)定义寄存器列表(RLIST)

格式:名称RLIST{寄存器列表}

用于对一个通用寄存器列表定义名称,使用该伪指令定义的名称可在ARM指令LDM/STM中使用。

在LDM/STM指令中,寄存器列表中的寄存器访问次序总是先访问编号较低的寄存器,再访问编号较高的寄存器,而不管寄存器列表中各寄存器的排列顺序。

示例:RegListRLIST{R0-R5,R8,R10};将寄存器列表名称定义为RegList,用于多寄存器寻址(后面详解)

5)定义协处理器寄存器(CN)

格式:名称CN协处理器的寄存器编号

示例:PowerCN6;将协处理器的寄存器6名称定义为Power

6)定义协处理器(CP)

格式:名称CP协处理器名

示例:DmuCP6;将协处理器6名称定义为Dmu

7)定义浮点或精度寄存器(DN,SN,FN)

格式:名称DN双精度寄存器编号;DN为双精度VFP寄存器定义名称

格式:名称SN单精度寄存器编号;SN为单精度VFP寄存器定义名称

格式:名称FN浮点寄存器编号;FN为浮点寄存器定义名称

示例:

heightDN6;将VFP双精度寄存器6名称定义为height

widthSN16;将VFP单精度寄存器16名称定义为width

heightFN6;将浮点寄存器6名称定义为height

//^^^^^^^^^^^^^^^^^^^^^^^^^^下午^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

3.数据定义伪操作(申请内存)

数据定义伪指令用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。

1)按类型分配内存

格式:标号伪指令表达式

标号

表达式:初始化的值,表达式可以为程序标号或字符、数字表达式

伪指令:如下

(1)DCB用于分配一片连续的字节存储单元(字符数组),可用=号代替

StrDCB"Thisisatest!";分配一片连续的字节存储单元并初始化。

(2)DCW(或DCWU)用于分配一片连续的半字存储单元(16位短整型数组),DCW半字对齐,DCWU不严格半字对齐。

DataTestDCW1,2,3

(3)DCD(或DCDU)用于分配一片连续的字存储单元(32位,整型数组),DCD可用&代替,DCD字对齐的

DataTestDCD4,5,6

(4)DCFD(或DCFDU)为双精度的浮点数分配一片连续的字存储单元,每个双精度的浮点数占据两个字单元。

FDataTestDCFD2E115,-5E7

(5)DCFS(或DCFSU)为单精度的浮点数分配一片连续的字存储单元,每个单精度的浮点数占据一个字单元。

FDataTestDCFS2E5,-5E-7

(6)DCQ(或DCQU)用于分配一片以8个字节为单位的连续存储区域(每8字节为一个数据的数组)

DataTestDCQ100;分配一片连续的存储单元并初始化为指定的值100。

2)申请连续内存

(1)申请一个连续内存(SPACE)

用于分配一片连续的存储区域并初始化为0,可用%代替

格式:标号SPACE表达式

表达式为要分配的字节数

例:DataSpaceSPACE100;分配连续100字节的存储单元并初始化为0。

(2)声明一个数据缓冲池的开始(LTORG)

通常,把数据缓冲池放在代码段的最后面,下一个代码段之前,或END之前

示例:

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

BLfuncl

funcl

LDRR0,=0x12345678

ADDR1,R1,R0

MOVPC,LR

LTORG;定义缓冲池0x12345678LTORG根据LDR确定内存地址

dataSPACE4200;从当前位置开始分配4200字节内存

END

(3)定义一个结构化的内存表首地址(MAP)

格式:MAP表达式{,基址寄存器}

用于定义一个结构化的内存表的首地址。可用^代替。

表达式可以为程序中的标号或数学表达式,基址寄存器为可选项.

当基址寄存器选项不存在时,表达式的值即为内存表的首地址,

当该选项存在时,内存表的首地址为表达式的值与基址寄存器的和。

例:

DatastrucSPACE280;分配280个字节单元

MAPDatastruc;内存表的首地址为Datastruc内存块

(4)定义一个结构化内存表的数据域(FILED

用于定义一个结构化内存表中的数据域。可用#代替

格式:标号FILED表达式

FIELD伪指令常与MAP伪指令配合使用来定义结构化的内存表。表达式的值为当前数据域所占的字节数。

标号为数据域(字段、成员变量)名

MAP伪指令定义内存表的首地址,

FIELD伪指令定义内存表中的各个数据域,并可以为每个数据域指定一个标号供其他的指令引用。(3)内存首地址(MAP)

MAP伪指令通常与FIELD伪指令配合使用来定义结构化的内存表。

示例:

DatastrucSPACE280;分配280个字节单元

MAPDatastruc;内存表的首地址为Datastruc内存块

constaFIELD4;字段consta长度4字节,相对地址0

constabFIELD4;字段constab长度4字节,相对地址4

xFIELD8;字段x长度8字节,相对地址8

yFIELD8;字段y长度8字节,相对地址16

stringFIELD256;字段string长度256字节,相对地址24

LDRR6,[R9,consta];引用内存表中的数据域

注意:MAP伪操作和FIELD伪操作仅仅是定义数据结构,他们并不实际分配内存单元,而SPACE用于分配内存

4.汇编控制伪操作

用于控制汇编程序的执行流程,常用的汇编控制伪指令包括以下几条:

(1)IF逻辑表达式...ELSE...ENDIF条件控制

(2)WHILE逻辑表达式...WEND循环控制

例:条件编译

AREAExample,CODE,READONLY

CODE32

Data_in*100;定义标号Data_in的值为100在ENTRY入口之前

GBLAcount;定义全局变量

countSETA20

ENTRY

Start

IFcount

MOVR0,#3

ELSE

MOVR1,#24

ENDIF

MOVR1,#12

ADDR0,R0,R1

END

例:循环编译

GBLACounter;声明一个全局的数学变量,变量名为Counter

CounterSETA3;由变量Counter控制循环次数

……

WHILECounter<10

指令序列

IFcontinue

MEXIT;退出宏

ENDIF

WEND

5.其他常用的伪指令

1)、AREA

格式:AREA段名属性1,属性2,……

AREA伪指令用于定义一个代码段或数据段。其中,段名若以数字开头,则该段名需用"|"括起来,如|1_test|。

属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号?

—CODE属性:用于定义代码段,默认为READONLY。

—DATA属性:用于定义数据段,默认为READWRITE。

—READONLY属性:指定本段为只读,代码段默认为READONLY。

—READWRITE属性:指定本段为可读可写,数据段的默认属性为READWRITE。

—ALIGN属性:使用方式为ALIGN表达式。在默认时,ELF(可执行连接文件)的代码段和数据段是按字对齐的

—COMMON属性:定义一个通用的段,不包含任何的用户代码和数据。各源文件中同名的COMMON段共享同一段存储单元

一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。

使用示例:

AREAInit,CODE,READONLY

该伪指令定义了一个代码段,段名为Init,属性为只读

2)、ENTRY

格式:ENTRY

用于指定汇编程序的入口点。一个源文件里最多只能有一个ENTRY(可以没有)。

3)、END

格式:END

用于通知编译器已经到了源程序的结尾。

4)、CODE16、CODE32

格式:CODE16(或CODE32)

CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。

CODE32伪指令通知编译器,其后的指令序列为32位的ARM指令。

在使用ARM指令和Thumb指令混合编程的代码里,可用这两条伪指令进行切换

示例:

AREAInit,CODE,READONLY

CODE32;通知编译器其后的指令为32位的ARM指令

LDRR0,=NEXT+1;将跳转地址放入寄存器R0

BXR0;程序跳转到新的位置执行,并将处理器切换到Thumb工作状态

……

CODE16;通知编译器其后的指令为16位的Thumb指令

NEXTLDRR3,=0x3FF

……

END;程序结束

5)、EXPORT(或GLOBAL)

格式:EXPORT标号

export伪指令用于在程序中声明一个全局的标号,该标号可在其他的文件中引用。

6)、IMPORT

格式:IMPORT标号

IMPORT伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用

如果当前源文件实际并未引用该标号,该标号也会被加入到当前源文件的符号表中。

7)、EXTERN

格式:EXTERN标号

EXTERN伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用

如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。

8)、GET(或INCLUDE)

格式:GET文件名

GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。

汇编程序中常用的方法是在某源文件中定义一些宏指令,用EQU定义常量的符号名称,用MAP和FIELD定义结构化的数据类型,然后用GET伪指令将这个源文件包含到其他的源文件中。使用方法与C语言中的"include"相似。

GET伪指令只能用于包含源文件,包含目标文件需要使用INCBIN伪指令

示例:

AREAInit,CODE,READONLY

GETa1.s;通知编译器当前源文件包含源文件a1.s

GETC:a2.s;通知编译器当前源文件包含源文件C:a2.s……

END

9)、INCBIN

格式:INCBIN文件名

INCBIN伪指令用于将一个目标文件或数据文件包含到当前的源文件中

示例:

AREAInit,CODE,READONLY

INCBINa1.dat;通知编译器当前源文件包含文件a1.dat

INCBINC:a2.txt;通知编译器当前源文件包含文件C:a2.txt……

END

10)、ROUT

格式:{名称}ROUT

ROUT伪指令用于给一个局部变量定义作用范围。

在程序中未使用该伪指令时,局部变量的作用范围为所在的AREA,

而使用ROUT后,局部变量的作为范围为当前ROUT和下一个ROUT之间。

11)、ALIGN

格式:ALIGN{表达式{,偏移量}}

ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对其方式

表达式的值用于指定对齐方式,可能的取值为2的幂,如1、2、4、8、16等。

偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。

示例:

AREAInit,CODE,READONLY,ALIEN=3;指定后面的指令为8字节对齐。

五.ARM汇编伪指令(读取内存地址)

1)ADR及ADRL

将PC相对偏移的地址或基于寄存器相对偏移的地址值读取到寄存器中

格式:ADR(ADRL)寄存器,地址表达式

ADR小范围的地址读取伪指令,

ADRL中等范围的地址读取伪指令

例:

查表

ADRR0,D_TAB;加载转换表地址

LDRBR1,[R0,R2];使用R2作为参数,进行查表

……

D_TAB

DCB0xC0,0xF9,0xA4,0xB0,0x99,0x92

2)LDR

用于加载32位立即数或一个地址值到指定的寄存器,大范围的地址读取伪指令.

LDR通常都是作加载指令,但是它也可以作伪指令。作用是装载一个32bit常数和一个地址到寄存器。

格式:LDR寄存器,=地址表达式

COUNTEQU0x56000054;COUNT是一个变量,地址为0x56000054。

LDRR1,=COUNT;将COUNT这个变量的值(地址),也就是0x56000054放到R1中。

MOVR0,#0

STRR0,[R1];是一个典型的存储指令,将R0中的值放到以R1中的值为地址的存储单元去

;这三条指令是为了完成对变量COUNT赋值。

3)NOP

空操作伪指令,可用于延时操作

例:延时子程序

Delay

NOP;空操作

NOP

NOP

SUBSR1,R1,#1;循环次数减1

BNEDelay

MOVPC,LR

六、Thumb指令集

有兴趣的自学

//*arm体系结构第五天*

七、C语言与汇编混合编程

完全使用汇编语言来编写程序会非常的繁琐

通常,只是使用汇编程序来完成少量必须由汇编程序才能完成的工作,而其它工作则由C语言程序来完成。

(一)、ATPCS规则

混合编程中,双方都须遵守ATPCS规则。这些基本规则包括:

子程序调用过程中寄存器的使用规则、数据栈的使用规则和参数的传递规则。

1.寄存器使用规则

寄存器:R4-R11用来保存局部变量

R0-R3(a1-a4)用于保存参数/返回结果/scratch(临时寄存器)

R4-R11(v1-v8)用于保存ARM状态局部变量

R12(IP)子程序内部调用的scratch

R13(SP)数据栈指针

R14(LR)连接寄存器

R15(PC)程序计数器

R7又可称为wr用于Thumb状态工作寄存器

R9又可称为sb在支持RWPI的ATPCS中为静态基址寄存器

R10又可称为sl在支持PWPI的ATPCS中为数据栈限制指针

R11又可称为fp用于帧指针

2.数据栈使用规则

ATPCS标准规定,数据栈为FD(满递减类型),并且对数据栈的操作是8字节对齐。在进行出栈和入栈操作,则

必须使用ldmfd和strnfd指令(或ldmia和stmdb)

3.参数的传递规则

参数:参数小于等于4,用R0-R3保存参数,参数多于4,剩余的传入堆栈

函数返回:结果为32位整数,通过R0返回

结果为64位整数,通过R0,R1返回

对于位数更多的结果,通过内存传递

例:参数传递及结果返回(r0-r3做参数,r0做返回值)

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

MOVR3,#4;设置实参,将参数写入R0-R3

MOVR2,#3

MOVR1,#2

MOVR0,#1

BLfunc1;调用子程序ADD_SUM

BOVER;跳转到OVER标号处,进入结尾

func1

ADDR0,R0,R1;实现两数相加

ADDR0,R0,R2

ADDR0,R0,R3

MOVPC,LR;子程序返回,R0内为返回的结果

OVER

END

相当于如下C语言

intfunc1(inta,intb,intc,intd){

returna+b+c+d;

}

intmain(){

func1(1,2,3,4);

}

例:多于4个参数,前4个通过寄存器R0-R3传递,其它参数通过数据栈传递

AREAExample,CODE,READONLY;声明代码段Example

ENTRY;程序入口

Start

STMFDSP!,{R1-R4,LR};先将R1-R4,及LR内原有数据压入栈,需要使用这五个寄存器

MOVR0,#1;准备好7个寄存嚣存入7个数据LR,IP,R4作临时寄存器使用

MOVIP,#2

MOVLR,#3

MOVR4,#4

MOVR1,#5

MOVR2,#6

MOVR3,#7

STMFDSP!,{R1-R3};先将R1-R3数据从前向后入栈,然后将IP,LR,R4内的数据装入R1-R3

MOVR3,R4;其中IP,LR,R4是临时使用的寄存器

MOVR2,LR

MOVR1,IP

BLfunc1;调用子程序funclR0是返回结果

LDMFDSP!,{R1-R4,PC};从栈中取出最初的数据,恢复原始值

BOVER;跳转到OVER标号处,进入结尾

func1

STMFDSP!,{R4,LR};当调用函数时,LR和R4都已发生了变化,其中LR是指令地址所以也压入栈

LDRR4,[SP,#0x10];目前共压入5个数据,每一个数据占两个字节,当前栈顶偏移10为前5个数据7

ADDLR,SP,#8;将前第4个数据的地址(栈项+偏移)赋给LR

LDMIALR,{IP,LR};连续将LR地址处的两个数据取出写入IP和LR内,从右向左写,LDMIA即出栈指令

ADDR0,R0,R1;从此行开始相当于returna+b+c+d+e+f+g;

ADDR0,R0,R2

ADDR0,R0,R3

ADDR0,R0,IP

ADDR0,R0,LR

ADDR0,R0,R4

LDMFDSP!,{R4,PC};从栈内取数据加载入R4和PC,PC跳转回主函数

OVER

END

下面是栈顶

相当于如下C语言

intfunc1(inta,intb,intc,intd,inte,intf,intg){

returna+b+c+d+e+f+g;

}

intmain(){

inta=1,b=2,c=3,d=4,e=5,f=6,g=7;

func1(a,b,c,d,e,f,g);

}

(二)、C和ARM汇编程序间的相互调用

1.汇编程序调用C子程序

为保证程序调用时参数正确传递,必须遵守ATPCS。

在C程序中函数不能定义为static函数。在汇编程序中需要在汇编语言中使用IMPORT伪操作来声明C子函数

//C代码

intsum5(inta,intb,intc,intd)

{

return(a+b+c+d);

}

//汇编代码

AREAExample,CODE,READONLY;声明代码段Example

IMPORTsum5;

ENTRY;程序入口

Start

MOVR3,#4;设置实参,将参数写入R0-R3

MOVR2,#3

MOVR1,#2

MOVR0,#1

BLsum5;调用子程序sum5

BOVER;跳转到OVER标号处,进入结尾

OVER

END

2.汇编程序访问全局C变量

汇编程序中可以通过C全局变量的地址来间接访问C语言中定义的全局变量

在编编程序中用IMPORT引入C全局变量,该C全局变量的名称在汇编程序中被认为是一个标号。通过ldr和str指令访问该编号所代表的地址

//C代码

inti=3;

intsum5(inta,intb,intc,intd)

{

return(a+b+c+d+i);

}

//汇编代码

AREAExample,CODE,READONLY;声明代码段Example

IMPORTsum5

IMPORTi

ENTRY;程序入口

Start

LDRR1,i;将i读入R1内

MOVR0,#2

ADDR0,R0,R1

STRR0,i;将寄存器值写入i内

MOVR3,#4;设置实参,将参数写入R0-R3

MOVR2,#3

MOVR1,#2

MOVR0,#1

BLsum5;调用子程序ADD_SUM

BOVER;跳转到OVER标号处,进入结尾

OVER

END

3.在C语言中调用汇编子程序

为保证程序调用时参数的正确传递,在汇编程序中需要使用EXPORT伪操作来声明汇编子程序,同时在C语言中使用extern扩展声明汇编子程序。

//汇编代码

EXPORTfunc1;func1为子函数名

AREAExample,CODE,READONLY;声明代码段Example

func1

ADDR0,R0,R1;实现两数相加

ADDR0,R0,R2

ADDR0,R0,R3

MOVPC,LR;子程序返回,R0内为返回的结果

END

//C代码

externintfunc1(inta,intb,intc,intd);

intmain(intargc,charargv){

inta=1,b=2,c=3,d=4;

intz=func1(a,b,c,d);

printf("%d",z);

return0;

}

4.在C语言中调用汇编全局变量

汇编中用DCD为全局变量分配空间并赋值,并定义一个标号代表该存储位置。

在汇编中用EXPORT导出标号(这个标号就是全局变量),在C程序中用extern扩展声明名该变量

//汇编代码

EXPORTfunc1

EXPORTtmp

AREAExample,CODE,READONLY;声明代码段Example

tmp;全局变量名

DCD0x0005;全局变量创建内存空间及赋初值

func1;子函数名

ADDR0,R0,R1;实现两数相加

ADDR0,R0,R2

ADDR0,R0,R3

MOVPC,LR;子程序返回,R0内为返回的结果

END

//C代码

externintfunc1(inta,intb,intc,intd);

externinttmp;

intmain(intargc,charargv){

inta=1,b=2,c=3,d=4;

intz=func1(a,b,c,tmp);

printf("%d",z);

return0;

}

5.在C语言中内嵌汇编

有些操作C语言程序无法实现,如改变CPSP寄存器值,初始化堆栈指针寄存器SP等,这些只能由汇编来完成。

但出于编程简洁等一些因素,有时需要在C源代码中实现上述操作,此时就需要在C中嵌入少量汇编代码。

内嵌的汇编不能直接引用C的变量定义,必须通过ATPCS进行,语法格式如下:

__asm{

//内嵌汇编

}

例:在C语言中嵌入汇编

intf(){//C函数

__asm{//内嵌汇编,禁用中断例子

MRSR0,CPSR

ORRR0,R0,#0x80

MSRCPSR_c,R0

}

}

intmain(intargc,charargv){

inta;

intz=f(a);

printf("%d",z);

return0;

}

出地完整性考虑,内嵌汇编相对于一般汇编的不同特点如下:

1)操作数可以是寄存器、常量或C表达式。可以是char、short、或int类型,而且是无符号数进行操作

2)常量前的#号可以省略

3)只有指令B可以使用C程序中的标号,指令BL不可以使用

4)不支持汇编语言中用于内存分配的伪操作

5)内嵌汇编不支持通过“.”指示符或PC获取当前指令地址

6)不支持LDRRn,=expression伪指令,而使用MOVRn,expression指令向寄存器赋值

7)不支持标号表达式

8)不支持ADR和ADRL伪指令

9)不支持BX和BLX指令

10)不可以向PC赋值

11)使用0x前缀替代&表示十六进制数

12)不使用寄存寻址变量

13)ldm和stm指令的寄存器列表只允许物理寄存器

14)必须小心使用物理寄存器,如R0-R3,LR和PC

//------------------------------------------

(三)、裸机硬件的控制方法

1.裸板开发环境搭建

1)J-Link

(1)安装Setup_JLinkARM_V408i.exe

(2)连接开发板

1打开桌面快捷J-FlashARMV4.08i

2连接好开发板开发板->jlink->pc(usb)

3将开发板置为NorFlash启动

4打开菜单file->openproject->选择TQ2440.jflash

5填加配置选项

将Flash.csv到安装目录的ETCJFlash内

打开菜单options->projectsettings

在弹出对话框内选flash后,点击按钮selectflashdevice

在弹出对话框内选择EN29LV160AB

6.连接开发板

重启开发板,然后点击菜单target->connect查看联接信息

(3)烧写方法

将j-link连接好后,在菜单file->open内选择要烧写的程序。

如:烧写u-boot.bin,

然后在烧写地址对话框内输入烧写地址,u-boot的地址设为0

再点击菜单target->program进行烧写

(4)调试方法

连接JLINK和开发板。

打开程序J-LINKARMV4.08i->J-linkGDBServer

设置信息JTAGspeed为500KH

所有选项勾选

设置AXD调试环境options->configuretarget填加JlinkARM目录下的JLinkRDI.dll

然后在AXD内选则JLinkRDI.dll选项,同时点击右侧configure按钮

在弹出对话框内General标签:JTAGspeed设为4000Khz

flash标签:去掉Enableflashprogramming选项

Breakpoints:去掉Usesoftwarebreakpoints选项

CPU标签:勾选Allowinstructionsetsimulation

Littleendian

Resetstrategy内选

Hardware,haltafterreset(normal)和1000ms

在AXD内,通过File->LoadImage载入要调试的axf文件

(四)、软件控制硬件的编程原理

每一种硬件,在其控制器芯片上都有物理寄存器(不是CPU寄存器,是硬件上的寄存器)

这些寄存器分为三类:命令寄存器、状态寄存器、数据寄存器

程序控制硬件的方法是,通过汇编str指令向命令寄存器写入指令即可完成对硬件的配置操作或执行物理操作。

通过汇编ldr指令从数据寄存器中获取数据,或从状态寄存器获取状态。

程序控制硬件,其实质就是对硬件的寄存器进行读写操作。

程序中需要解决的问题:

1)硬件寄存器的内存地址是多少?

2)向哪一个寄存器写入什么值?

如想解决上述两个问题,需要熟练查看硬件的手册,阅读硬件连线原理图。

数电的基本知识:

1).电路符号

2).基本原理

以LED灯为例讲解如何使用硬件手册,和原理图

在开发板上有4个LED灯,如果想点亮,必须先看硬件连线图

1.查看开发板LED管脚连线

TQ2400开发板有两块电路板:核心板、主板

LED灯在主板上,所以查看《TQ2440底板原理图.pdf》,找到LED模块(LED测试)

从电路图可看到如果让LED1灯点亮,必须在nLED_1连线上输出低电屏(即通电)

如何让LED1上输出低电屏,需查看CPU相关引脚连线

2.查看TQ2440核心板原理图

CPU在核心板上,所以查看《TQ2440_V2核心板原理图.pdf》

找到nLED1连线,如下

nLED1在CUP的GPB5引脚上

nLED2GPB6

nLED3GPB7

nLED4GPB8

如何让CPU的GPB5为低电屏,需查看CPU引脚模式

3.查看CPU用户手册

TQ2440的微处理器芯片为S3C2440,所以查看《S3C2440.pdf》芯片手册,找到GPB5的管脚模式

1)控制寄存器

GPB5[11:10]00=Input01=Output

10=nXBACK11=Reserved

GPBCON控制寄存器

GPBDAT数据寄存器

GPBUP上拉使能寄存器

[11:10]是所在位,每个管脚都是占两个位

其中Input是输入值

Output输出值

reserved保留值

nXBACKnXBACK/GPB5

即,如果将GPB5设为低电屏,则需要将控制寄存器设置为输出模式。

即需要CPU寄存器GPBCON的11和10位设置为01即11位为010位为1

2)数据寄存器

GPB[10:0][10:0]当端口配置为输入端口时,相应位为引脚状态。

当端口配置为输出端口时,相应位为引脚状态。

当端口配置为功能引脚时,将读取到未定义值

所以,当GPBCON为输入状态时,GPBDAT的相应位5则应为0才会输出低电屏

3)查看寄存器地址

GPBCON0x56000010R/W端口配置寄存器复位值0x0

GPBDAT0x56000014R/W端口数据寄存器复位值-

所以,需要将地址位0x56000010的寄存器作为GPBCON寄存器

地址位0x56000014的寄存器作为GPBDAT寄存器

4.代码编写

//ledtest.c

#defineGPBCON(*(volatileunsignedlong*)0x56000010)

#defineGPBDAT(*(volatileunsignedlong*)0x56000014)

//volatile影响编译器编译的结果,表示变量是随时可能发生变化的

//0x56000010是地址,强行转换为unsignedlong*类型,

//然后前面加*代表这个地址所指向的寄存器变量

#defineLEDS(1<<5|1<<6|1<<7|1<<8)//即11110000

#defineDELAYVAL(0xFFFF)

externintdelay(inttime);//声明汇编函数

intmain(){

intval;

inti=0;

GPBCON=(1<<16|1<<14|1<<12|1<<10);//即010101010000000000即B5B6B7B8的状态寄存器

val=val+1;

for(i=0;i<4;i++){

GPBDAT=(GPBDAT&(~LEDS))|(1<<6|1<<7|1<<8);

delay(DELAYVAL);

GPBDAT=(GPBDAT&(~LEDS))|(1<<5|1<<7|1<<8);

delay(DELAYVAL);

GPBDAT=(GPBDAT&(~LEDS))|(1<<5|1<<6|1<<8);

delay(DELAYVAL);

GPBDAT=(GPBDAT&(~LEDS))|(1<<5|1<<6|1<<7);

delay(DELAYVAL);

}

return0;

}

//delay.s

;汇编指令延时程序

EXPORTdelay

AREADELAY,CODE,READONLY

;下面是延时子程序

delay

subR0,R0,#1;r0=r0-1

cmpR0,#0x0;将r0与0比较

bnedelay;比较的结果不为0,则继续调用delay

movPC,LR;返回

END

5.调试代码

2440ART.mcp内双击

TargetSettins:post-linker选择ArMfromELF

LanguageSettins:ArchitectureorProcessor选择相应的编译器ARM920T

ArmLinker:output内RO0x30000000

options内Imageentrypoint设为0x30000000

layout内Object2440init.oSectionInit

Listings内Imagemap

ArmfromELF:outputformat内Plainbinary

outputfilename内*.bin

编译

make

调试AXD是调试器

设置,debug->打开AXD调试界面,选择option->configtarget选项

选ARMUL(模拟调试器),然后选择确定.进入调试界面.

ARMUL是虚拟调试环境(虚拟开发板)

如果用开发板真实环境调试,则需要使用JTAG连开发板后,在此处选H-JTAG

用file-

execute->runtocousor项.使程序进入用户主程序

可以用F8来一条一条执行语句,也可用F10,可以设置断点.



关键词:arm汇编编

评论


技术专区

关闭