您的位置:首页 > 编程语言

arm汇编编程(示例)

2012-11-17 11:37 260 查看
一、arm的认知及基本概念

(一).arm的基本概念

1. 什么是arm

arm是一家英国电子公司的名字,全名是Advanced RISC Machine

这家企业设计了大量高性能、廉价、耗能低的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内存 板载64MB SDRAM(标准配置), 32bit数据总线SDRAM时钟频率高达100MHz(支持运行133MHz)

Nand Flash 板载64MB Nand Flash或256MB Nand Flash(标准配置)

Nor Flash 板载2MB Nor Flash(最高可升级到8MB)

CorePower 专业1.25V核心电压供电

Power 核心板采用3.3V供电

Powerled 核心板电源指示灯

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

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

NOR Flash和 NAND Flash是现在市场上两种主要的非易失闪存。

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

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

2.主板

电源

并口线

复位

RTC电源

RS232电平转换DB9插座

音频IIS,AC97

按键、PS/2与IC接口

数码管

触摸屏

以太网卡

主USB HUB 1转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位,每一位数据代表不同的状态。分为三个部分(条件代码标志位、控制位、保留区位)

31 32 29 28 .... 7 6 5 4 3 2 1 0

N Z C V I F T M4 M3 M2 M1 M0

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

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 称为控制位

I I=1 禁用IRO中断

F F=1 禁用FIQ中断

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

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

0b10000 User 用户中断

0b10001 FIQ 快速中断

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

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

0b10111 Abort 异常中断

0b11011 Undefined 未定义中断

0b11111 System 系统中断

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映象工程

ARM Executable Image 生成ELF格式映像(bin)

ARM Object Library 生成armar格式目标库文件

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

Makefile Importer Wizard 用于Vc

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

Thumb Executable image 用thumb指令生成ELF格式映像

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

选 ARM Executable Image,工程文件名2440ART

加源文件

project -> Add Files

新建

填加已有

生成目标的配置

2440ART.mcp内双击

TargetSettins: post-linker选择ArM fromELF

Language Settins:Architecture or Processor选择相应的编译器ARM920T

Arm Linker:output 内RO 0x30000000

options内Image entry point 设为0x30000000

layout 内Object 2440init.o Section Init

Listings 内Image map

Arm fromELF: output format内 Plain binary

output filename内***.bin

编译

make

调试 AXD是调试器

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

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

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

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

用file-<lode image项,加载刚才编译所的的 .axf调试文件.

execute->run to cousor 项.使程序进入用户主程序

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

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

例:

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

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

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

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

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

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

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

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

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

; SWI 0x123456 ;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)数据段

AREA DATAAREA,DATA,BIINIT,ALLGN=2

DISPBUF SPACE 200

RCVBUF SPACE 200

DATA用以指明为数据段,

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

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

3).汇编语言结构

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

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

所有指令均不能顶格写

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

;为注释

@代码行注释,同;

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

\为换行符

ENTRY 为程序的入口

END 为程序的结束

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

二、ARM的寻址方式

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

如:MOV r0,#10

操作码:即CPU指令 如MOV ADD

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

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

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

ARM指令格式:

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

31 ~ 28 27~25 24 ~ 21 20 19~16 15~12 11 ~ 0

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

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

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

对应输入的一条指令为:

<Opcode>{<cond>}{s} <Rd>,<Rn>,<Operand2>

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

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

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

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

其中条件助记符如下:

EQ 相等

NE 不相等

MI 负数

VS 溢出

PL 正数或零

HI 无符号大于

LS 无符号小于或等于

CS 无符号大于或等于

CC 无符号小于

GT 有符号大于

GE 有符号大于或等于

LE 有符号小于或等于

LT 有符号小于

AL 无条件执行

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

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

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

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

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

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

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

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

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

1.立即寻址

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

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

SUB R0,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.寄存器寻址

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

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

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

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

3.寄存器间接寻址

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

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

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

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

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

LDR 指令用于读取内存数据

STR 指令用于写入内存数据

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

4.寄存器基址寻址

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

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

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

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

1)前索引寻址

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

LDR R2,[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)后索引寻址

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

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

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

3)寄存器的值作索引

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

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

5.多寄存器寻址

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

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

STMIA R1!,{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(堆栈指针)指向一块存储区

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

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

SP指向的是栈顶

7.相对寻址

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

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

例:

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

...

LOOP

MOV R6 #1

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

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

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

8.寄存器移位寻址

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

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

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

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

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

三.ARM指令集

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

(一)、数据处理指令

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

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

1. 数据传输指令

1) MOV 数据传送指令

格式:MOV Rd,<op1>

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

例:

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

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

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

2) MVN 数据取反传送指令

格式:MVN <Rd>,<op1>;

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

例:

MVN R0,#0 ;R0=-1

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

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

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

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

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

2.算术指令

1) ADD 加法指令

格式:ADD{<cond>}{S} <Rd>,<Rn>,<op2>;

功能:Rd=Rn+op2

例:

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

ADD R0,R1,R2 ;R0=R1+R2

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

2) ADC 带进位加法指令

格式:ADC{<cond>}{S} <Rd>,<Rn>,<op2>;

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

例如:

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

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

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

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

ADC R1,R3,R5 ;高32位相加

3) SUB 减法指令

格式:SUB{<cond>}{S} <Rd>,<Rn>,<op2>;

功能:Rd=Rn-op2

例:

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

SUB R0,R1,R2 ;R0=R1-R2

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

4) SBC 带借位减法指令

格式:SBC{<cond>}{S} <Rd>,<Rn>,<op2>;

功能:Rd=Rn-op2-!carry

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

例:

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

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

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

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

SBC R1,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) MUL 32位乘法指令

格式:MUL{<cond>}{S} <Rd>,<Rn>,<op2>;

功能:Rd=Rn×op2

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

例:

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

2) MLA 32位乘加指令

格式:MLA{<cond>}{S} <Rd>,<Rn>,<op2>,<op3>;

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

例:

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

3) SMULL 64位有符号数乘法指令

格式:SMULL{<cond>}{S} <Rdl>,<Rdh>,<Rn>,<op2>;

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

例:

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

4) SMLAL 64位有符号数乘加指令

格式:SMLAL{<cond>}{S} <Rdl>,<Rdh>,<Rn>,<op2>;

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

例:

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

5) UMULL 64位无符号数乘法指令

格式:UMULL{<cond>}{S} <Rdl>,<Rdh>,<Rn>,<op2>;

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

例:

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

6) UMLAL 64位无符号数乘加指令

格式:UMLAL {<cond>}{S} <Rdl>,<Rdh>,<Rn>,<op2>;

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

例:

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

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

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

1) AND 逻辑与指令

格式:AND{<cond>}{S} <Rd>,<Rn>,<op2>;

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

例:

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

2) ORR 逻辑或指令

格式:ORR{<cond>}{S} <Rd>,<Rn>,<op2>;

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

例:

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

3) EOR 逻辑异或指令

格式:EOR{<cond>}{S} <Rd>,<Rn>,<op2>;

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

例:

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

4) BIC 位清除指令

格式:BIC{<cond>}{S} <Rd>,<Rn>,<op2>;

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

例:

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

5. 比较指令

1) CMP 比较指令

格式:CMP{<cond>} <Rn>,<op1>;

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

例:

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

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

2) CMN 反值比较指令

格式:CMN{<cond>} <Rn>,<op1>;

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

例:

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

3) TST 位测试指令

格式:TST{<cond>} <Rn>,<op1>;

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

例:

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

4) TEQ 相等测试指令

格式:TEQ{<cond>} <Rn>,<op1>;

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

例:

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

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

1) LSL(或ASL) 左移

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

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

例:

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

2) LSR操作 右移

格式: 寄存器 LSR 操作数

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

例:

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

3)其它移位

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

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

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

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

(二)、跳转指令

1) B 跳转指令

格式:B{<cond>} <addr>;

功能:跳转到addr地址。

例:

B exit; 程序跳转到标号exit处

2) BL 带返回的跳转指令

格式:BL{<cond>} <addr>;

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

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

例:

BL func; 调用子程序func



func



MOV R15,R14; 子程序返回

3) 其它跳转指令

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

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

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

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

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

(三)、程序状态指令

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

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

格式:MRS{<cond>} <Rd>,CPSR/SPSR;

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

例:

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

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

格式:MSR{<cond>} CPSR/SPSR_<field>,<op1>;

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

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

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

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

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

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

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

例:

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

MSR CPSR_fsxc,#5; CPSR的值修改为5

(四)、加载存储指令

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

中的数据存储到内存中

1.单数据访存指令

1)单数据加载指令

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

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

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

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

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

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

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

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

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

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

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

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

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

各指令的区别:

(1) LDR 字数据加载指令

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2)单数据存储指令

格式:STR(或STR、STRB、STRBT、STRH、STRT) <Rd>,<addr>;

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

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

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

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

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

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

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

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

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

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

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

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

(1) STR 字数据存储指令

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

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

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

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

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

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

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

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

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

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

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

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

2.多数据访存指令

1) 批量数据加载指令

格式:LDM{<cond>}{<type>} <Rn>{!},<regs>{^};

功能:从一片连续的内存单元读取数据到各个寄存器中,内存单元的起始地址为基址寄存器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{<cond>}{<type>} <Rn>{!},<regs>{^};

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

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

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

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

3.数据交换指令

1) 字数据交换指令

格式:SWP <Rd>,<op1>,[<op2>];

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

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

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

2) 字节数据交换指令

格式:SWPB <Rd>,<op1>,[<op2>];

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

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

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

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

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

(五)、协处理指令

1) CDP协处理器操作指令

格式:CDP{<cond>}<p>,<opcode1>,<CRd>,<CRm>,<CRn>,<opcode2>;

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

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

指令中:

P为协处理器编号;

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

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

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

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

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

格式:LDC {<cond>}{L}<p>,<CRd>,<addr>;

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

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

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

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

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

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

格式:STC{<cond>}{L}<p>,<CRd>,<addr>;

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

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

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

格式:MCR{<cond>}<p>,<op1>,<Rd>,<CRn>,<CRm>{,op2};

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

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

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

格式:MRC {<cond>}<p>,<op1>,<Rd>,<CRn>,<CRm>{,op2};

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

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

(六)、异常中断指令

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

1) SWI 软件中断指令

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

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

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

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

例:SWI 0X05; 调用编号为05的系统例程。

2) BKPT 断点中断指令

格式:BKPT 16位的立即数;

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

指令操作的伪代码:

(七)、信号量操作指令

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

(八)、ARM程序常见结构

1.子函数和主函数

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

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

ENTRY ;程序入口

Start

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

MOV R1,#10

BL ADD_SUM ;调用子程序ADD_SUM

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

ADD_SUM

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

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

OVER

END

运行过程

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

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

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

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

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

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

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

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

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

2.条件跳转语句

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

ENTRY ;程序入口

Start

MOV R0,#2 ;将R0赋初值2

MOV R1,#5 ;将R1赋初值5

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

CMP R5,#10

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

WAIT

CMP R0,R1

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

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

DOEQUAL

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

BNE WAIT ;若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)条件助记符

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

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

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

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

(4)位运算

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

3.循环语句

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

ENTRY ;程序入口

Start

MOV R1,#0 ;将R1赋初值0

LOOP

ADD R1,R1,#1

CMP R1,#10

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

END

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

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

AREA SORT,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

STRLO R3,[R0, # -4]

STRLO R2,[R0]

SUBS R7,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 ;宏名

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

MEND

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

ENTRY ;程序入口

Start

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

MOV R1,#10

BL ADD_NUM ;调用子程序ADD_NUM

BL SUB_NUM ;调用子程序SUB_NUM

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

EXPORT ADD_NUM

ADD_NUM

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

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

EXPORT SUB_NUM

SUB_NUM

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

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

OVER

END

例:有参数宏

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

MACRO ;宏定义

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

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

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

MOV R1,$dat2

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

MEND ;宏定义结束

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

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

IMPORT FADD1

MOV R0,#3

MOV R1,#3

BL FADD1

2.符号定义伪操作

1)定义常量(EQU)

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

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

其中 EQU 可用 * 代替。

标号名称:为常量名。

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

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

CODE16 、 CODE32 和 DATA

示例:EQU的使用

X EQU 45 ;即#define X 45,必须顶格

Y EQU 64

stack_top EQU 0x30200000,CODE32

AREA Example,CODE,READONLY

CODE32

ENTRY

Start

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

MOV R0,#X ;将X替换为45

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

MOV R0,#Y

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

ADD R0,R0,R1

STR R0,[SP]

END

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

2)定义变量

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

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

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

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

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

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

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

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

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

GBLA count ;定义全局变量

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

AREA Example,CODE,READONLY

CODE32

ENTRY

Start

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

ADD R0,R0,#2

B Start

END

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

示例:变量与内存地址

GBLA globv

globv SETA 23

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

ENTRY ;程序入口

Start

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

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

ADD R1,R1,#2

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

MOV R0,#0

OVER

END

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

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

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

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

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

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

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

示例:

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

num SETA 0xaa ;将该变量赋值为 0xaa

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

isOk SETL ;将该变量赋值为真

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

str1 SETS "Testing" ;将该变量赋值为"Testing"

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

MACRO

MOV_START ;宏名

LCLA x ;定义局部变量

LCLA y

x SETA 12 ;必须顶格写

y SETA 24

MOV R0,#2

MOV R1,#3

ADD R0,R0,R1

MEND

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

ENTRY ;程序入口

Start

MOV_START

MOV R0,#0

OVER

END

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

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

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

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

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

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

(4)变量代换 $

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

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

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

如:

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

LCLS Y2

Y1 SETS "WORLD!"

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

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

格式:名称 RN 表达式

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

示例:

Temp RN R0 ;将R0 定义一个别名Temp

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

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

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

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

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

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

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

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

6) 定义协处理器(CP)

格式: 名称 CP 协处理器名

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

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

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

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

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

示例:

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

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

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

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

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

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

1) 按类型分配内存

格式:标号 伪指令 表达式

标号

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

伪指令:如下

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

Str DCB "This is a test!" ;分配一片连续的字节存储单元并初始化。

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

DataTest DCW 1,2,3

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

DataTest DCD 4,5,6

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

FDataTest DCFD 2E115,-5E7

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

FDataTest DCFS 2E5,-5E-7

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

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

2)申请连续内存

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

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

格式: 标号 SPACE 表达式

表达式为要分配的字节数

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

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

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

示例:

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

ENTRY ;程序入口

Start

BL funcl

funcl

LDR R0,=0x12345678

ADD R1,R1,R0

MOV PC,LR

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

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

END

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

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

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

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

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

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

例:

Datastruc SPACE 280 ;分配280个字节单元

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

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

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

格式:标号 FILED 表达式

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

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

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

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

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

示例:

Datastruc SPACE 280 ;分配280个字节单元

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

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

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

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

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

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

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

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

4.汇编控制伪操作

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

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

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

例:条件编译

AREA Example,CODE,READONLY

CODE32

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

GBLA count ;定义全局变量

count SETA 20

ENTRY

Start

IF count < Data_in ;条件编译

MOV R0,#3

ELSE

MOV R1,#24

ENDIF

MOV R1,#12

ADD R0,R0,R1

END

例:循环编译

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

Counter SETA 3 ;由变量Counter 控制循环次数

……

WHILE Counter < 10

指令序列

IF continue

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 段共享同一段存储单元

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

使用示例:

AREA Init , CODE , READONLY

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

2)、 ENTRY

格式:ENTRY

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

3)、 END

格式:END

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

4)、 CODE16、CODE32

格式:CODE16 (或 CODE32 )

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

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

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

示例:

AREA Init , CODE , READONLY

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

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

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

……

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

NEXT LDR R3,=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 伪指令

示例:

AREA Init , CODE , READONLY

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

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

END

9)、 INCBIN

格式:INCBIN 文件名

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

示例:

AREA Init , CODE , READONLY

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

INCBIN C:\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 的表达式次幂+偏移量。

示例:

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

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

1)ADR及ADRL

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

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

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

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

例:

查表

ADR R0,D_TAB ;加载转换表地址

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

……

D_TAB

DCB 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92

2)LDR

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

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

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

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

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

MOV R0,#0

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

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

3)NOP

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

例:延时子程序

Delay

NOP ;空操作

NOP

NOP

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

BNE Delay

MOV PC,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做返回值)

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

ENTRY ;程序入口

Start

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

MOV R2,#3

MOV R1,#2

MOV R0,#1

BL func1 ;调用子程序ADD_SUM

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

func1

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

ADD R0,R0,R2

ADD R0,R0,R3

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

OVER

END

相当于如下C语言

int func1(int a,int b,int c,int d){

return a+b+c+d;

}

int main(){

func1(1,2,3,4);

}

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

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

ENTRY ;程序入口

Start

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

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

MOV IP,#2

MOV LR,#3

MOV R4,#4

MOV R1,#5

MOV R2,#6

MOV R3,#7

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

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

MOV R2,LR

MOV R1,IP

BL func1 ;调用子程序funcl R0是返回结果

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

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

func1

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

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

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

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

ADD R0,R0,R1 ;从此行开始相当于return a+b+c+d+e+f+g;

ADD R0,R0,R2

ADD R0,R0,R3

ADD R0,R0,IP

ADD R0,R0,LR

ADD R0,R0,R4

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

OVER

END

下面是栈顶

相当于如下C语言

int func1(int a,int b,int c,int d,int e,int f,int g){

return a+b+c+d+e+f+g;

}

int main(){

int a=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代码

int sum5(int a, int b ,int c, int d)

{

return (a+b+c+d);

}

//汇编代码

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

IMPORT sum5 ;

ENTRY ;程序入口

Start

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

MOV R2,#3

MOV R1,#2

MOV R0,#1

BL sum5 ;调用子程序sum5

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

OVER

END

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

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

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

//C代码

int i=3;

int sum5(int a, int b ,int c, int d)

{

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

}

//汇编代码

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

IMPORT sum5

IMPORT i

ENTRY ;程序入口

Start

LDR R1,i ;将i读入R1内

MOV R0,#2

ADD R0,R0,R1

STR R0,i ;将寄存器值写入i内

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

MOV R2,#3

MOV R1,#2

MOV R0,#1

BL sum5 ;调用子程序ADD_SUM

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

OVER

END

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

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

//汇编代码

EXPORT func1 ;func1为子函数名

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

func1

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

ADD R0,R0,R2

ADD R0,R0,R3

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

END

//C代码

extern int func1(int a,int b,int c,int d);

int main(int argc,char **argv){

int a=1,b=2,c=3,d=4;

int z=func1(a,b,c,d);

printf("%d",z);

return 0;

}

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

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

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

//汇编代码

EXPORT func1

EXPORT tmp

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

tmp ;全局变量名

DCD 0x0005 ;全局变量创建内存空间及赋初值

func1 ;子函数名

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

ADD R0,R0,R2

ADD R0,R0,R3

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

END

//C代码

extern int func1(int a,int b,int c,int d);

extern int tmp;

int main(int argc,char **argv){

int a=1,b=2,c=3,d=4;

int z=func1(a,b,c,tmp);

printf("%d",z);

return 0;

}

5. 在C语言中内嵌汇编

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

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

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

__asm{

//内嵌汇编

}

例:在C语言中嵌入汇编

int f(){ //C函数

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

MRS R0,CPSR

ORR R0,R0,#0x80

MSR CPSR_c,R0

}

}

int main(int argc,char **argv){

int a;

int z=f(a);

printf("%d",z);

return 0;

}

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

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

2)常量前的#号可以省略

3)只有指令B可以使用C程序中的标号,指令BL不可以使用

4)不支持汇编语言中用于内存分配的伪操作

5)内嵌汇编不支持通过“.”指示符或PC获取当前指令地址

6)不支持LDR Rn,=expression 伪指令,而使用MOV Rn,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-Flash ARM V4.08i

2 连接好开发板 开发板->jlink->pc(usb)

3 将开发板置为NorFlash启动

4 打开菜单 file->open project->选择TQ2440.jflash

5 填加配置选项

将Flash.csv复制到 安装目录的\ETC\JFlash内

打开菜单 options->project settings

在弹出对话框内选 flash 后,点击按钮select flash device

在弹出对话框内选择 EN29LV160AB

6.连接开发板

重启开发板,然后点击菜单 target->connect 查看联接信息

(3)烧写方法

将j-link连接好后,在菜单 file->open内选择要烧写的程序。

如:烧写u-boot.bin,

然后在烧写地址对话框内输入烧写地址,u-boot的地址设为0

再点击菜单 target->program进行烧写

(4)调试方法

连接JLINK和开发板。

打开程序 J-LINK ARM V4.08i->J-link GDB Server

设置信息 JTAG speed 为500KH

所有选项勾选

设置AXD调试环境 options->configure target填加JlinkARM目录下的JLinkRDI.dll

然后在AXD内选则JLinkRDI.dll选项,同时点击右侧configure按钮

在弹出对话框内General标签:JTAG speed设为4000Khz

flash标签 : 去掉 Enable flash programming选项

Breakpoints: 去掉 Use software breakpoints选项

CPU标签 :勾选 Allowinstruction set simulation

Little endian

Reset strategy内选

Hardware,halt after reset(normal)和1000ms

在AXD内,通过File->Load Image载入要调试的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引脚上

nLED2 GPB6

nLED3 GPB7

nLED4 GPB8

如何让CPU的GPB5为低电屏,需查看CPU引脚模式

3.查看CPU用户手册

TQ2440的微处理器芯片为S3C2440,所以查看《S3C2440.pdf》芯片手册,找到 GPB5的管脚模式

1)控制寄存器

GPB5 [11:10] 00 = Input 01 = Output

10 = nXBACK 11 = Reserved

GPBCON 控制寄存器

GPBDAT 数据寄存器

GPBUP 上拉使能寄存器

[11:10] 是所在位,每个管脚都是占两个位

其中 Input 是输入值

Output 输出值

reserved 保留值

nXBACK nXBACK/GPB5

即,如果将GPB5设为低电屏,则需要将控制寄存器设置为输出模式。

即需要CPU寄存器GPBCON的 11和10位设置为 01 即11位为0 10位为1

2)数据寄存器

GPB[10:0] [10:0] 当端口配置为输入端口时,相应位为引脚状态。

当端口配置为输出端口时,相应位为引脚状态。

当端口配置为功能引脚时,将读取到未定义值

所以,当GPBCON为输入状态时,GPBDAT的相应位5则应为0才会输出低电屏

3)查看寄存器地址

GPBCON 0x56000010 R/W 端口配置寄存器 复位值 0x0

GPBDAT 0x56000014 R/W 端口数据寄存器 复位值 -

所以,需要将地址位0x56000010的寄存器作为GPBCON寄存器

地址位0x56000014的寄存器作为GPBDAT寄存器

4.代码编写

//ledtest.c

#define GPBCON (*(volatile unsigned long *)0x56000010)

#define GPBDAT (*(volatile unsigned long *)0x56000014)

//volatile 影响编译器编译的结果,表示变量是随时可能发生变化的

//0x56000010是地址,强行转换为unsigned long *类型,

//然后前面加*代表这个地址所指向的寄存器变量

#define LEDS (1<<5 | 1<< 6 | 1<< 7| 1<<8) //即 11110000

#define DELAYVAL (0xFFFF)

extern int delay(int time); //声明汇编函数

int main(){

int val;

int i=0;

GPBCON = (1<< 16 | 1<< 14 | 1<< 12 | 1<< 10); //即010101010000000000 即B5 B6 B7 B8的状态寄存器

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);

}

return 0;

}

//delay.s

;汇编指令延时程序

EXPORT delay

AREA DELAY,CODE,READONLY

;下面是延时子程序

delay

sub R0,R0,#1 ;r0=r0-1

cmp R0,#0x0 ;将r0与0比较

bne delay ;比较的结果不为0,则继续调用delay

mov PC,LR ;返回

END

5.调试代码

2440ART.mcp内双击

TargetSettins: post-linker选择ArM fromELF

Language Settins:Architecture or Processor选择相应的编译器ARM920T

Arm Linker:output 内RO 0x30000000

options内Image entry point 设为0x30000000

layout 内Object 2440init.o Section Init

Listings 内Image map

Arm fromELF: output format内 Plain binary

output filename内***.bin

编译

make

调试 AXD是调试器

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

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

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

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

用file-<lode image项,加载刚才编译所的的 .axf调试文件.

execute->run to cousor 项.使程序进入用户主程序

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

arm汇编编程(示例)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: