- 嵌入式系统及其开发应用
- 沈连丰 许波 夏玮玮等编著
- 194字
- 2020-08-27 13:29:51
第2章 ARM微处理器及其开发应用
信息产业的蓬勃发展给嵌入式微处理器带来了巨大的市场,PowerPC、Motorola 68000、ARM 系列等都是目前广为应用的嵌入式微处理器。尽管它们的功能各有不同,但在具有RISC型的处理器结构、稳定的指令系统、低电压、低功耗的工作模式等方面有着共性特点,深入分析一种微处理器基本上能够达到举一反三的目的。因此,本章将以ARM微处理器为例,首先讨论其结构,接着介绍其指令系统,然后介绍其程序设计的基础知识。
2.1 ARM微处理器概述
2.1.1 ARM微处理器的特点及应用领域
ARM是Advanced RISC Machines的简称,既代表一个公司,也是对一类微处理器的通称,还是一种技术的名称。
1991年,ARM公司成立于英国剑桥,主要出售芯片设计技术的授权。采用ARM知识产权的微处理器,即通常所说的ARM微处理器,已遍及工业控制、消费类电子产品、通信系统、计算机网络等各类产品市场,基于ARM技术的微处理器应用占据了32位RISC微处理器很大的市场份额,ARM技术正在逐步渗入人们生活的各个方面。
ARM公司是专门从事基于RISC技术芯片设计开发的公司。作为知识产权供应商,它本身不直接从事芯片生产,而是转让设计许可,由合作公司生产各具特色的芯片。许多芯片制造商从ARM 公司购买其设计的ARM 微处理器核,根据各自不同的应用领域,加入适当的外围电路,形成自己的ARM微处理器芯片从而进入市场。因此,ARM技术获得了较多第三方在工具、制造、软件等方面的支持,使其产品更容易进入市场,其性价比更具竞争力,从而被消费者所接受。
ARM微处理器采用RISC结构,除了具有嵌入式系统共有的体积小、功耗低、性价比高等特点外,还具有如下性能:
• 支持Thumb(16位)/ARM(32位)双指令集,能很好地兼容8位/16位器件;
• 大量使用寄存器,大多数数据操作都在寄存器中完成;
• 寻址方式灵活简单,指令长度固定,执行速度快、效率高。
ARM微处理器及其技术在如下领域已经得到广泛应用。
① 工业控制:在32位的RISC微控制器应用中,基于ARM核的微控制器芯片不但占据了高端市场的大部分份额,同时也不断向低端市场扩展,以其高性能和低价位向传统的8位/16位微控制器提出挑战。
② 无线通信:通信设备的功能越来越强大,ARM以其高性能和低成本的优势,在蜂窝网络(CN,Cellular Network)、无线城域网(WMAN,Wireless Metropolitan Area Network)、无线局域网(WLAN,Wireless Local Area Network)、无线个域网(WPAN,Wireless Personal Network)和个人通信网(PCN,Personal Communication Network)等方面获得越来越多的应用。
③ 网络应用:随着宽带技术的推广,采用ARM技术的各类高速宽带的处理器芯片在与Internet或其他宽带网的连接应用上凸显其竞争优势。另外,由于其在语音及视频处理上进行了优化并获得广泛支持,也对DSP的传统应用领域提出了挑战。
④ 消费类电子产品:ARM技术在目前流行的数字音频播放器、数字机顶盒和游戏机中得到广泛采用,将来会在所有的信息家电中占据重要位置。
⑤ 成像和安全产品:现在流行的数码相机和打印机中绝大部分采用ARM技术,手机中的32位SIM智能卡也采用了ARM技术。
ARM微处理器及技术还应用到其他许多领域并呈越加广泛之势。
2.1.2 ARM微处理器系列
ARM微处理器包括ARM公司和其他厂商基于ARM体系结构的处理器,目前有:ARM7系列、ARM9系列、ARM9E系列、ARM10E系列、SecurCore系列、Intel的Xscale系列和StrongARM等处理器。它们除了具有ARM体系结构的共同特点外,每一系列还都有自己的特点和应用领域,例如,ARM7、ARM9、ARM9E和ARM10为通用系列,它们分别提供一套相对独特的性能来满足不同应用领域的需求,而 SecurCore 系列专门为安全性能要求较高的应用而设计。下面简要介绍它们的特点及应用领域。
1.ARM7系列微处理器
这一系列是低功耗的32位RISC处理器,非常适合用于低成本的消费类产品,具有如下特点:
• 支持实时在线仿真器(ICE,In Circuit Emulator),开发、调试方便;
• 极低的功耗,适合应用于便携式产品;
• 三级流水线结构;
• 代码密度高并兼容16位的Thumb指令集;
• 对操作系统的支持广泛,包括Windows CE、Linux、Palm OS等;
• 指令系统与ARM9、ARM9E和ARM10E等系列兼容,便于用户产品的升级换代;
• 运算处理能力高达130MIPS,能胜任绝大多数的复杂应用。
ARM7系列微处理器包括如下几种类型的核:ARM7TDMI、ARM7TDMI-S、ARM720T、ARM7EJ等。其中,ARM7TDMI是目前使用最广泛的32位嵌入式RISC处理器,属于低端ARM处理器核。TDMI的基本含义为:
T——支持16位压缩指令集Thumb;
D——支持片上Debug;
M——内嵌硬件乘法器(Multiplier);
I——嵌入式ICE,支持片上断点和调试点。
ARM7系列微处理器目前的主要应用领域有:工业控制、Internet设备、网络和调制解调器设备、移动电话和多媒体通信等。
2.ARM9系列微处理器
这一系列除了具有ARM7的大部分功能外,还有以下特点:
• 5级整数流水线,指令执行效率更高;
• 提供1.1MIPS/MHz的哈佛结构;
• 支持32位ARM指令集和16位Thumb指令集;
• 支持32位的高速微控制器总线结构(AMBA,Advanced Microcontroller Bus Architecture)的接口;
• 全性能的MMU;
• 支持Windows CE、Linux、Palm OS等多种嵌入式操作系统和多种实时操作系统;
• 支持数据Cache和指令Cache,具有更高的指令和数据处理能力。
ARM9系列微处理器包括ARM920T、ARM922T和ARM940T等类型,以适合不同的应用场合。ARM9系列微处理器目前已广泛应用于无线设备、仪器仪表、安全系统、机顶盒、高端打印机、数字照相机和数字摄像机等产品中。
3.ARM9E系列微处理器
这一系列是ARM9的增强型,使用单一的处理器内核提供了微控制器、DSP、Java应用系统的综合解决方案,减小了芯片的面积并降低了系统的复杂度,特别适合那些需要同时使用DSP和MPU的场合。它除了具有ARM9的全部功能外,还有如下特点:
• 支持DSP指令集,适合需要高速数字信号处理的场合;
• 支持矢量浮点(VFP,Vector Floating Point)处理协处理器VFP9;
• 运算速度高达300MIPS。
ARM9E系列微处理器目前包括ARM926EJ-S、ARM946E-S和ARM966E-S等类型,已应用于许多要求功能较强的无线设备、成像设备、工业控制、存储设备、网络设备和数字式消费类产品等。
4.ARM10E系列微处理器
这一系列采用了两种先进的节能方式,故其功能更强但功耗更低。该系列除了具有ARM9、ARM9E的全部功能外,还具有如下特点:
• 6级整数流水线,指令执行效率更高;
• 支持矢量浮点处理协处理器VFP10;
• 运算速度高达400MIPS;
• 内嵌并行读/写操作部件。
本系列微处理器目前包含ARM1020E、ARM1022E和ARM1026EJ-S等类型,主要应用于ARM9E等系列尚不能达到速率和节电要求的场合。
5.SecurCore系列微处理器
这一系列专为安全需要而设计,提供了完善的32位RISC技术的安全解决方案。该系列除了具有ARM体系结构的低功耗、高性能的特点外,还提供了对信息安全解决方案的支持,在系统的信息安全方面具有如下的特点:
• 带有灵活的保护单元,以确保操作系统和应用数据的安全;
• 采用软内核技术,防止外部对其进行扫描探测;
• 可集成用户自己的安全特性和其他协处理器。
本系列微处理器目前包括SC100、SC110、SC200和SC210等类型,主要应用于对安全性要求较高的场合,如电子商务、电子政务、电子银行业务、网络和认证系统等。
6.StrongARM系列微处理器
Intel的StrongARM微处理器SA-1100系列,是采用ARM体系结构高度集成的32位RISC微处理器。它融合了Intel的设计处理技术及ARM的体系结构,已成功地应用于多家公司的掌上电脑系列产品中。
7.Xscale微处理器
这是Intel目前主要推广的一款ARM微处理器,它支持16位的Thumb指令和DSP指令集,在功耗和性价比方面具有一定的优势,已应用在数字移动电话、PDA和网络产品等产品中。
2.1.3 ARM微处理器结构
1.RISC体系结构
传统的复杂指令集计算机(CISC,Complex Instruction Set Computer)结构有其固有的缺点,即随着计算机技术的发展,必须不断引入新的复杂的指令,为支持这些新增的指令,计算机的体系结构将会越来越复杂,然而,在CISC 指令集的各种指令中,其使用频率却相差悬殊,大约有20%的指令会被反复使用,占整个程序代码的80%,而余下的80%的指令却不经常使用,在程序代码中只占20%。显然,这种结构是不太合理的。
基于上述的不合理性,1979年,美国加州大学伯克利分校提出了精简指令集计算机(RISC,Reduced Instruction Set Computer)的概念,RISC并非只是简单地减少指令,而是把着眼点放在了如何使计算机的结构更简单,运算速度更快上。为此,RISC 结构优先选取使用频率最高的简单指令,避免复杂指令;将指令长度固定,减少指令格式和寻地方式的种类;以控制逻辑为主,不用或少用微码控制。
到目前为止,还没有给出RISC体系结构严格的定义,一般认为,它应具有如下特点:
• 采用固定长度的指令格式,指令归整、简单、基本寻址方式有2~3种;
• 使用单周期指令,便于流水线操作执行;
• 大量使用寄存器,数据处理指令只对寄存器进行操作,只有加载/存储指令可以访问存储器,以提高指令的执行效率。
除此以外,ARM体系结构还采用了如下技术以尽量缩小芯片的面积和降低功耗:
• 所有的指令都可根据前面的执行结果决定是否被执行,从而提高指令的执行效率;
• 可用加载/存储指令批量传输数据,以提高数据的传输效率;
• 可在一条数据处理指令中同时完成逻辑处理和移位处理;
• 在循环处理中使用地址的自动增减来提高运行效率。
当然,和 CISC 结构相比较,尽管 RISC 有上述优点,但决不能认为 RISC 就可以取代CISC。事实上,它们各有优势,而且界限并不那么明显。现代的CPU往往采用CISC的外围,内部加入了RISC的特性。例如,超长指令集CPU融合了RISC和CISC的优势,成为未来的CPU发展方向之一。
2.ARM微处理器的寄存器结构
ARM处理器共有37个寄存器,被分为若干个组(BANK),这些寄存器包括:
• 31个通用寄存器,包括程序计数器(PC指针),均为32位;
• 6个状态寄存器,用以标识CPU的工作状态及程序的运行状态,也均为32位,目前只使用了其中的一部分。
同时,ARM 处理器又有7种不同的处理器模式,在每一种处理器模式下均有一组相应的寄存器与之对应,即在任意一种处理器模式下,可访问的寄存器包括:15个通用寄存器(R0~R14)、1~2个状态寄存器和程序计数器。在所有的寄存器中,有些是在7种处理器模式下公用的同一个物理寄存器,而有些寄存器则是在不同的处理器模式下有不同的物理寄存器。
关于ARM处理器的寄存器结构,在后面的相关章节将会详细描述。
2.1.4 ARM微处理器的应用选型
鉴于ARM微处理器的众多优点,随着国内外嵌入式应用领域的逐步发展,ARM微处理器必然会获得广泛的重视和应用。但是,由于ARM微处理器有多达十几种的内核结构,几十个芯片生产厂家,以及千变万化的内部功能配置组合,给开发人员在选择方案时带来一定的困难,因此,对ARM芯片做一些对比研究是十分必要的。以下从应用的角度来探讨选择ARM微处理器时所应考虑的主要问题。
1.ARM微处理器内核的选择
ARM微处理器包含一系列的内核结构,用户如果希望使用Windows CE或标准Linux等操作系统,则可以选择ARM720T以上带有MMU功能的型号,如:ARM720T、ARM920T、ARM922T、ARM946T、Strong-ARM等。
ARM7TDMI没有MMU,不支持Windows CE和标准Linux,但目前有uCLinux等不需要MMU支持的操作系统可运行于该硬件平台上。事实上,uCLinux已经成功移植到多种不带MMU的微处理器平台上,并在稳定性和其他方面都有上佳表现。
2.系统的工作频率
系统的工作频率在很大程度上决定了ARM微处理器的处理能力。ARM7系列微处理器的典型处理速度为0.9MIPS/MHz,常见的ARM7系列主时钟频率为20~133MHz;ARM9系列的典型处理速度为1.1MIPS/MHz,常见的ARM9系列主时钟频率为100~233MHz;ARM10系列主时钟频率最高可达700MHz。
不同芯片对时钟的处理不同,有的芯片只需要一个主时钟频率,有的芯片内部时钟控制器可以分别为ARM核和USB、UART、DSP、音频等功能部件提供不同频率的时钟。
3.芯片内存储器的容量
大多数ARM微处理器片内存储器的容量都不太大,需要用户在设计系统时外扩存储器,但也有部分芯片具有相对较大的片内存储空间。例如,ATMEL 公司的 AT91F40162就具有高达2 MB的片内程序存储空间。用户在设计时可考虑选用这种类型,以简化系统的设计。
4.片内外围电路的选择
除ARM微处理器核之外,几乎所有的ARM芯片均根据各自不同的应用领域,扩展了相关功能模块,如USB接口、LCD控制器、键盘接口、RTC、ADC和DAC、DSP协处理器等,并将其集成在芯片之中,称为片内外围电路。尽可能使用它们来完成所需的功能,既可简化系统的设计,又能提高系统的可靠性。
2.2 ARM微处理器的指令系统
2.2.1 ARM微处理器的指令分类与格式
ARM 微处理器的指令是加载/存储型的,即仅能处理寄存器中的数据,处理结果都要放回寄存器,对系统存储器的访问则需要通过专门的加载/存储指令来完成。ARM 微处理器的指令集可以分为跳转指令、数据处理指令、程序状态寄存器(PSR,Program Status Register)处理指令、加载/存储指令、协处理器指令和异常产生指令6类。在2.2.4节将对这6类指令进行详细讨论,这里先给出基本ARM指令,见表2.1,其中,CPSR(Current Program Status Register)为当前程序状态寄存器,SPSR(Saved Program Status Register)为备份的程序状态寄存器。
表2.1 基本ARM指令及功能描述
2.2.2 指令的条件域
当处理器工作在ARM状态时,几乎所有的指令均根据CPSR中条件码的状态和指令的条件域有条件地执行。当指令的执行条件满足时,指令被执行,否则指令被忽略。每一条ARM指令都包含4位的条件码,它们位于指令的最高4位[31:28]。条件码共有16种,每种条件码可用两个字符表示,这两个字符可以添加在指令助记符的后面与指令同时使用。例如,跳转指令B可以加上后缀EQ变为BEQ表示“相等则跳转”,即当CPSR中的Z标志置位时发生跳转。在16种条件标志码中,只有15种可以使用,见表2.2,第16种(1111)为系统保留,暂时不使用。
表2.2 指令的条件码
2.2.3 ARM指令的寻址方式
所谓寻址方式,就是处理器根据指令中给出的地址信息来寻找物理地址的方式。目前ARM指令系统支持如下7种常见的寻址方式。
1.立即寻址
立即寻址也叫立即数寻址,这是一种特殊的寻址方式,操作数本身在指令中给出,只要取出指令也就取到了操作数,这个操作数被称为立即数。例如:
ADD R0,R0,#1 ;R0←R0+1 ADD R0,R0,#0x3f ;R0←R0+0x3f
在以上两条指令中,第二个源操作数即为立即数,要求以“#”为前缀。对于以十六进制数表示的立即数,还要求在“#”后加上“0x”或“&”。
2.寄存器寻址
寄存器寻址就是利用寄存器中的数值作为操作数,这种寻址方式是各类微处理器经常采用的一种方式,也是一种执行效率较高的寻址方式。例如:
ADD R0,R1,R2 ;R0←R1+R2
该指令的执行效果是将寄存器R1和R2的内容相加,其结果存入寄存器R0中。
3.寄存器间接寻址
寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。例如:
ADD R0,R1,[R2] ;R0←R1+[R2] LDR R0,[R1] ;R0←[R1] STR R0,[R1] ;[R1]←R0
在第1条指令中,以寄存器R2的值作为操作数的地址,在存储器中取得一个操作数后与R1相加,结果存入寄存器R0中。第2条指令将以R1的值为地址的存储器中的数据传送到R0中。第3条指令将R0的值传送到以R1的值为地址的存储器中。
4.基址变址寻址
基址变址寻址就是将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从而得到一个操作数的有效地址。变址寻址方式常用于访问某基地址附近的地址单元。采用变址寻址方式的指令有以下几种形式:
LDR R0,[R1,#4] ;R0←[R1+4] LDR R0,[R1,#4]! ;R0←[R1+4],R1←R1+4 LDR R0,[R1],#4 ;R0←[R1],R1←R1+4 LDR R0,[R1,R2] ;R0←[R1+R2]
在第1条指令中,将寄存器R1的内容加上4形成操作数的有效地址,从而取得操作数存入寄存器R0中;在第2条指令中,将寄存器R1的内容加上4形成操作数的有效地址,从而取得操作数存入寄存器R0中,然后,R1的内容自增4字节;在第3条指令中,以寄存器R1的内容作为操作数的有效地址,从而取得操作数存入寄存器 R0中,然后,R1的内容自增4字节;在第4条指令中,将寄存器R1的内容加上寄存器R2的内容形成操作数的有效地址,从而取得操作数存入寄存器R0中。
5.多寄存器寻址
采用多寄存器寻址方式,一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令传送最多16个通用寄存器的值。例如:
LDMIA R0, {R1, R2, R3, R4} ;R1←[R0],R2←[R0+4],R3←[R0+8],R4←[R0+12]
该指令的后缀IA表示在每次执行完加载/存储操作后,R0按字长度增加,因此,指令可将连续存储单元的值传送到R1~R4中。
6.相对寻址
与基址变址寻址方式类似,相对寻址以程序计数器 PC 的当前值为基地址,指令中的地址标号作为偏移量,将两者相加之后得到操作数的有效地址。以下程序段完成子程序的调用和返回,跳转指令BL采用了相对寻址方式:
BL NEXT ;跳转到子程序NEXT处执行 … NEXT … MOV PC,LR ;从子程序返回
7.堆栈寻址
堆栈是一种数据结构,按先进后出(FILO,First In Last Out)方式工作,使用一个称做堆栈指针的专用寄存器指示当前的操作位置,堆栈指针总是指向栈顶。当堆栈指针指向最后压入堆栈的数据时,称为满堆栈(Full Stack);当堆栈指针指向下一个将要放入数据的空位置时,称为空堆栈(Empty Stack);当堆栈由低地址向高地址生成时,称为递增堆栈(Ascending Stack);当堆栈由高地址向低地址生成时,称为递减堆栈(Descending Stack)。ARM微处理器支持这4种类型的堆栈工作方式。
2.2.4 ARM指令集
本节对ARM指令集的6大类指令进行详细描述。
1. 跳转指令
跳转指令用于实现程序流程的跳转。在ARM程序中有两种方法可以实现程序流程的跳转:① 使用专门的跳转指令;② 直接向程序计数器PC写入跳转地址值,可以实现在4GB地址空间中的任意跳转。
ARM 指令集中的跳转指令可以完成从当前指令向前或向后32MB 地址空间的跳转,包括以下4条指令:
B 跳转指令 BL 带返回的跳转指令 BLX 带返回和状态切换的跳转指令 BX 带状态切换的跳转指令
下面分别加以介绍。
(1)B指令
B指令的格式为:B{条件} 目标地址
一旦遇到一个B指令,ARM 处理器将立即跳转到给定的目标地址,从那里继续执行。注意,存储在跳转指令中的实际值是相对当前PC 值的一个偏移量,而不是绝对地址。它的值由汇编器来计算(参考寻址方式中的相对寻址),是24位有符号数,左移两位后有符号数扩展为32位,表示的有效偏移为26位(前后32MB的地址空间)。例如:
B Label ;程序无条件跳转到标号Label处执行 BEQ Label ;当CPSR寄存器中Z条件码置位时,程序跳转到标号Label处执行
(2)BL指令
BL指令的格式为:BL {条件} 目标地址
BL是另一个跳转指令,跳转之前会在寄存器R14中保存PC的当前内容,因此,可以通过将R14的内容重新加载到PC中来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个基本且常用的手段。例如:
BL Label ;程序无条件跳转到标号Label处执行,同时将当前的PC值存入R14中
(3)BLX指令
BLX指令的格式为:BLX 目标地址
BLX 指令从 ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态由ARM状态切换到Thumb状态,同时将PC的当前内容保存到寄存器R14中。因此,当子程序使用Thumb指令集,而调用者使用ARM指令集时,可以通过BLX指令实现子程序的调用和处理器工作状态的切换。子程序的返回可以通过将寄存器R14值复制到PC中来完成。
(4)BX指令
BX指令的格式为:BX {条件} 目标地址
BX指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM指令,也可以是Thumb指令。
2. 数据处理指令
数据处理指令可分为数据传送指令、算术逻辑运算指令和比较指令等。数据传送指令用于在寄存器和存储器之间进行数据的双向传输;算术逻辑运算指令完成常用的算术与逻辑的运算,该类指令不但将运算结果保存在目的寄存器中,同时还更新CPSR中的相应条件标志位;比较指令不保存运算结果,只更新CPSR中相应的条件标志位。
数据处理指令包括以下16条:
MOV 数据传送指令 MVN 数据取反传送指令 CMP 比较指令 CMN 反值比较指令 TST 位测试指令 TEQ 相等测试指令 ADD 加法指令 ADC 带进位加法指令 SUB 减法指令 SBC 带借位减法指令 RSB 逆向减法指令 RSC 带借位的逆向减法指令 AND 逻辑与指令 ORR 逻辑或指令 EOR 逻辑异或指令 BIC 位清除指令
下面分别进行介绍。
(1)MOV指令
MOV指令的格式为:MOV {条件} {S} 目的寄存器,源操作数
MOV 指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器中。其中,S选项决定指令的操作是否影响CPSR中条件标志位的值。当没有S选项时,指令不更新CPSR中条件标志位的值。例如:
MOV R1,R0 ;将寄存器R0的值传送到寄存器R1中 MOV PC,R14 ;将寄存器R14的值传送到PC中,常用于子程序返回 MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位后传送到R1中
(2)MVN指令
MVN指令的格式为:MVN {条件} {S} 目的寄存器,源操作数
MVN 指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器中。与MOV指令不同之处是,在传送值之前按位取反,即把一个被取反的值传送到目的寄存器中,其中S的含义与前述相同。例如:
MVN R0,#0 ;将立即数0取反,传送到寄存器R0中,完成后R0=−1
(3)CMP指令
CMP指令的格式为:CMP {条件} 操作数1,操作数2
CMP指令用于把一个寄存器的内容与另一个寄存器的内容或立即数进行比较,同时更新CPSR 中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是操作数1与操作数2的关系(大、小、相等)。例如:
CMP R1,R0 ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的 ;标志位 CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标 ;志位
(4)CMN指令
CMN指令的格式为:CMN {条件} 操作数1,操作数2
CMN 指令用于把一个寄存器的内容与另一个寄存器的内容或立即数取反后进行比较,同时更新CPSR中条件标志位的值。该指令实际完成操作数1和操作数2相加,并根据结果更改条件标志位。例如:
CMN R1,R0 ;将寄存器R1的值与寄存器R0的值相加,并根据结果设置CPSR的 ;标志位 CMN R1,#100 ;将寄存器R1的值与立即数100相加,并根据结果设置CPSR的标 ;志位
(5)TST指令
TST指令的格式为:TST {条件} 操作数1,操作数2
TST指令用于把一个寄存器的内容与另一个寄存器的内容或立即数进行按位的与运算,并根据运算结果更新CPSR中条件标志位的值。操作数1是要测试的数据,而操作数2是一个位掩码,故可用来检测是否设置了特定的位。例如:
TST R1,#%1 ;用于测试在寄存器R1中是否设置了最低位(%表示二进制数) TST R1,#0xffe ;将寄存器R1的值与立即数0xffe按位与,并根据结果设置CPSR ;的标志位
(6)TEQ指令
TEQ指令的格式为:TEQ{条件} 操作数1,操作数2
TEQ 指令用于把一个寄存器的内容与另一个寄存器的内容或立即数进行按位的异或运算,并根据运算结果更新CPSR中条件标志位的值,常用来比较操作数1和操作数2是否相等。例如:
TEQ R1,R2 ;将寄存器R1的值与寄存器R2的值按位异或,并根据结果设置CPSR的标志位
(7)ADD指令
ADD指令的格式为:ADD {条件} {S} 目的寄存器,操作数1,操作数2
ADD指令用于把两个操作数相加,并将结果存入目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器、被移位的寄存器或立即数。例如:
ADD R0,R1,R2 ; R0 = R1+R2 ADD R0,R1,#256 ; R0 = R1+256 ADD R0,R2,R3,LSL#1 ; R0 = R2+(R3 << 1),<<表示左移
在本节(8)~(15)的各指令中,操作数1和操作数2的含义均和这里的相同。
(8)ADC指令
ADC指令的格式为:ADC {条件} {S} 目的寄存器,操作数1,操作数2
ADC指令用于把两个操作数相加,再加上CPSR中的C条件标志位的值,并将结果存入目的寄存器中。它使用一个进位标志位,这样就可以进行比32位大的数的加法。注意,不要忘记设置S后缀来更改进位标志。
下面是完成两个128位数加法的例子,两个数分别由高到低存放在寄存器 R7~R4和R11~R8中,运算结果由高到低存放在寄存器R3~R0中。
ADDS R0,R4,R8 ;加低端的字 ADCS R1,R5,R9 ;加第二个字,带进位 ADCS R2,R6,R10 ;加第三个字,带进位 ADC R3,R7,R11 ;加第四个字,带进位
(9)SUB指令
SUB指令的格式为:SUB {条件} {S} 目的寄存器,操作数1,操作数2
SUB指令用于把操作数1减去操作数2,并将结果存放到目的寄存器中。例如:
SUB R0,R1,R2 ;R0 = R1−R2 SUB R0,R1,#256 ;R0 = R1−256 SUB R0,R2,R3,LSL#1 ;R0 = R2−(R3 << 1)
(10)SBC指令
SBC指令的格式为:SBC {条件} {S} 目的寄存器,操作数1,操作数2
SBC指令用于把操作数1减去操作数2,再减去CPSR中的C条件标志位的反码,并将结果存放到目的寄存器中。该指令使用进位标志来表示借位,这样就可以进行大于32位的减法。注意,不要忘记设置S后缀来更改进位标志。例如:
SBCS R0,R1,R2 ;R0 = R1−R2−!C,并根据结果设置CPSR的进位标志位
(11)RSB指令
RSB指令的格式为:RSB {条件} {S} 目的寄存器,操作数1,操作数2
RSB指令称为逆向减法指令,用于把操作数2减去操作数1,并将结果存放到目的寄存器中。例如:
RSB R0,R1,R2 ;R0 = R2−R1 RSB R0,R1,#256 ;R0 = 256−R1 RSB R0,R2,R3,LSL#1 ;R0 = (R3 << 1) −R2
(12)RSC指令
RSC指令的格式为:RSC {条件} {S} 目的寄存器,操作数1,操作数2
RSC指令用于把操作数2减去操作数1,再减去CPSR中的C条件标志位的反码,并将结果存入目的寄存器中。该指令使用进位标志来表示借位,这样就可以进行大于32位的减法。注意,不要忘记设置S后缀来更改进位标志。例如:
RSC R0,R1,R2 ;R0 = R2- R1-!C
(13)AND指令
AND指令的格式为:AND {条件} {S} 目的寄存器,操作数1,操作数2
AND指令用于对两个操作数上进行逻辑与运算,并把结果存入目的寄存器中,常用于屏蔽操作数1的某些位。例如:
AND R0,R0,#3 ;保持R0的0、1位,其余位清零
(14)ORR指令
ORR指令的格式为:ORR {条件} {S} 目的寄存器,操作数1,操作数2
ORR指令用于对两个操作数上进行逻辑或运算,并把结果存入目的寄存器中,常用于设置操作数1的某些位。例如:
ORR R0,R0,#3 ;设置R0的0、1位,其余位保持不变
(15)EOR指令
EOR指令的格式为:EOR {条件} {S} 目的寄存器,操作数1,操作数2
EOR指令用于对两个操作数上进行逻辑异或运算,并把结果存入目的寄存器中,常用于反转操作数1的某些位。例如:
EOR R0,R0,#3 ;反转R0的0、1位,其余位保持不变
(16)BIC指令
BIC指令的格式为:BIC {条件} {S} 目的寄存器,操作数1,操作数2
BIC指令用于清除操作数1的某些位,并把结果存入目的寄存器中。操作数2为32位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。例如:
BIC R0,R0,#%1011 ;清除 R0中的位0、1和3,其余位保持不变
3. 乘法指令与乘加指令
ARM微处理器支持的乘法指令与乘加指令共有6条,可分为运算结果为32位和64位两类。与前面的数据处理指令不同,指令中的所有操作数、目的寄存器必须为通用寄存器,不能对操作数使用立即数或被移位的寄存器,同时,目的寄存器和操作数1必须是不同的寄存器。这6条指令是:
MUL 32位乘法指令 MLA 32位乘加指令 SMULL 64位有符号数乘法指令 SMLAL 64位有符号数乘加指令 UMULL 64位无符号数乘法指令 UMLAL 64位无符号数乘加指令
下面分别进行介绍。
(1)MUL指令
MUL指令的格式为:MUL {条件} {S} 目的寄存器,操作数1,操作数2
MUL指令完成操作数1与操作数2的乘法运算,并把结果存入目的寄存器中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数或无符号数。例如:
MUL R0,R1,R2 ;R0 = R1×R2 MULS R0,R1,R2 ;R0 = R1×R2,同时设置CPSR中的相关条件标志位
(2)MLA指令
MLA指令的格式为:MLA {条件} {S} 目的寄存器,操作数1,操作数2,操作数3
MLA指令完成操作数1与操作数2的乘法运算,再将乘积加上操作数3,并把结果存入目的寄存器中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数或无符号数。例如:
MLA R0, R1, R2, R3 ;R0 = R1×R2+R3 MLAS R0, R1, R2, R3 ;R0 = R1×R2+R3,同时设置CPSR中的相关条件标志位
(3)SMULL指令
SMULL指令的格式为:
SMULL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
SMULL指令完成操作数1与操作数2的乘法运算,并把结果的低32位存入目的寄存器Low中,结果的高32位存入目的寄存器High中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数。例如:
SMULL R0, R1, R2, R3 ;R0=(R2×R3)的低32位,R1=(R2×R3)的高32位
(4)SMLAL指令
SMLAL指令的格式为:
SMLAL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
SMLAL 指令完成操作数1与操作数2的乘法运算,并把结果的低32位同目的寄存器Low中的值相加后又存入目的寄存器Low中,结果的高32位同目的寄存器High中的值相加后又存入目的寄存器High中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的有符号数。对于目的寄存器Low,在指令执行前存放64位加数的低32位,指令执行后存放结果的低32位;对于目的寄存器High,在指令执行前存放64位加数的高32位,指令执行后存放结果的高32位。例如:
SMLAL R0, R1, R2, R3 ;R0=(R2×R3)的低32位+R0,R1=(R2×R3)的高32位+R1
(5)UMULL指令
UMULL指令的格式为:
UMULL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
UMULL指令完成操作数1与操作数2的乘法运算,并把结果的低32位存入目的寄存器Low中,结果的高32位存入目的寄存器High中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的无符号数。例如:
UMULL R0, R1, R2, R3 ;R0 = (R2×R3)的低32位,R1 = (R2×R3)的高32位
(6)UMLAL指令
UMLAL指令的格式为:
UMLAL {条件} {S} 目的寄存器Low,目的寄存器High,操作数1,操作数2
UMLAL指令完成操作数1与操作数2的乘法运算,并把结果的低32位同目的寄存器Low中的值相加后又存入目的寄存器Low中,结果的高32位同目的寄存器High中的值相加后又存入目的寄存器High中,同时可以根据运算结果设置CPSR中相应的条件标志位。其中,操作数1和操作数2均为32位的无符号数。对于目的寄存器Low,在指令执行前存放64位加数的低32位,指令执行后存放结果的低32位;对于目的寄存器High,在指令执行前存放64位加数的高32位,指令执行后存放结果的高32位。例如:
UMLAL R0, R1, R2, R3 ;R0=(R2×R3)的低32位+R0,R1=(R2×R3)的高32位+R1
4. 程序状态寄存器访问指令
ARM 微处理器支持程序状态寄存器访问指令,用于在程序状态寄存器和通用寄存器之间传送数据。程序状态寄存器访问指令包括以下两条:
MRS 程序状态寄存器到通用寄存器的数据传送指令
MSR 通用寄存器到程序状态寄存器的数据传送指令
下面分别进行介绍。
(1)MRS指令
MRS指令的格式为:MRS {条件} 通用寄存器,程序状态寄存器(CPSR或SPSR)
MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用于以下两种情况:① 当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器;② 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。例如:
MRS R0,CPSR ;传送CPSR的内容到R0中 MRS R0,SPSR ;传送SPSR的内容到R0中
(2)MSR指令
MSR指令的格式为:MSR {条件}程序状态寄存器(CPSR或SPSR)_<域>,操作数
MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要操作的位。32位的程序状态寄存器可分为如下4个域:
位[31:24]为条件标志位域,用f表示;
位[23:16]为状态位域,用s表示;
位[15:8]为扩展位域,用x表示;
位[7:0]为控制位域,用c表示。
该指令通常用于恢复或改变程序状态寄存器的内容。在使用时,一般要在 MSR 指令中指明将要操作的域。例如:
MSR CPSR,R0 ;传送R0的内容到CPSR中 MSR SPSR,R0 ;传送R0的内容到SPSR中 MSR CPSR_c,R0 ;传送R0的内容到SPSR中,但仅修改CPSR中的控制位域
5. 加载/存储指令
ARM 微处理器支持加载/存储指令,用于在寄存器和存储器之间传送数据。加载指令用于将存储器中的数据传送到寄存器中,存储指令则完成相反的操作,共有如下6种:
LDR 字数据加载指令 LDRB 字节数据加载指令 LDRH 半字数据加载指令 STR 字数据存储指令 STRB 字节数据存储指令 STRH 半字数据存储指令
下面分别进行介绍。
(1)LDR指令
LDR指令的格式为:LDR {条件} 目的寄存器,<存储器地址>
LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中,它从存储器中读取32位的字数据到通用寄存器中,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当做目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,且寻址方式灵活多样。例如:
LDR R0,[R1] ;将存储器地址为R1的字数据读入寄存器R0 LDR R0,[R1,R2] ;将存储器地址为R1+R2的字数据读入寄存器R0 LDR R0,[R1,#8] ;将存储器地址为R1+8的字数据读入寄存器R0 LDR R0,[R1,R2]! ;将存储器地址为R1+R2的字数据读入寄存器R0 ;并将新地址R1+R2写入R1 LDR R0,[R1,#8]! ;将存储器地址为R1+8的字数据读入寄存器R0 ;并将新地址R1+8写入R1 LDR R0,[R1],R2 ;将存储器地址为R1的字数据读入寄存器R0 ;并将新地址R1+R2写入R1 LDR R0, [R1, R2, LSL#2] ;将存储器地址为R1的字数据读入寄存器R0 ;并将新地址R1+R2×4写入R1 LDR R0, [R1, R2, LSL#2]! ;将存储器地址为R1+R2×4的字数据读入寄存器R0 ;并将新地址R1+R2×4写入R1
(2)LDRB指令
LDRB指令的格式为:LDRB {条件} 目的寄存器,<存储器地址>
LDRB指令用于从存储器中将一个8位的字节数据传送到目的寄存器中,同时将寄存器的高24位清零。该指令通常用于从存储器中读取8位的字节数据到通用寄存器中,然后对数据进行处理。当程序计数器 PC 作为目的寄存器时,指令从存储器中读取的字数据被当做目的地址,从而可以实现程序流程的跳转。例如:
LDRB R0,[R1] ;将存储器地址为R1的字节数据读入寄存器R0,并将R0的 ;高24位清零 LDRB R0,[R1, #8] ;将存储器地址为R1+8的字节数据读入寄存器R0,并将R0的 ;高24位清零
(3)LDRH指令
LDRH指令的格式为:LDRH {条件} 目的寄存器,<存储器地址>
LDRH 指令用于从存储器中将一个16位的半字数据传送到目的寄存器中,同时将寄存器的高16位清零。该指令通常用于从存储器中读取16位的半字数据到通用寄存器中,然后对数据进行处理。当程序计数器 PC 作为目的寄存器时,指令从存储器中读取的字数据被当做目的地址,从而可以实现程序流程的跳转。例如:
LDRH R0,[R1] ;将存储器地址为R1的半字数据读入寄存器R0,并将R0的 ;高16位清零 LDRH R0,[R1,#8] ;将存储器地址为R1+8的半字数据读入寄存器R0,并将R0的 ;高16位清零 ;高16位清零
(4)STR指令
STR指令的格式为:STR{条件} 源寄存器,<存储器地址>
STR 指令用于从源寄存器中将一个32位的字数据传送到存储器中,在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。例如:
LDRH R0,[R1,R2] ;将存储器地址为R1+R2的半字数据读入寄存器R0,并将R0的
STR R0,[R1],#8 ;将R0中的字数据写入以R1为地址的存储器 ;并将新地址R1+8写入R1 STR R0,[R1,#8] ;将R0中的字数据写入以R1+8为地址的存储器
(5)STRB指令
STRB指令的格式为:STRB {条件} 源寄存器,<存储器地址>
STRB指令用于从源寄存器中将一个8位的字节数据传送到存储器中,该字节数据为源寄存器中的低8位。例如:
STRB R0,[R1] ;将R0中的字节数据写入以R1为地址的存储器 STRB R0,[R1,#8] ;将R0中的字节数据写入以R1+8为地址的存储器
(6)STRH指令
STRH指令的格式为:STRH{条件}源寄存器,<存储器地址>
STRH指令用于从源寄存器中将一个16位的半字数据传送到存储器中,该半字数据为源寄存器中的低16位。例如:
STRH R0,[R1] ;将R0中的半字数据写入以R1为地址的存储器 STRH R0,[R1,#8] ;将R0中的半字数据写入以R1+8为地址的存储器
6. 批量数据加载/存储指令
ARM 微处理器所支持的批量数据加载/存储指令能够一次在一片连续的存储器单元和多个寄存器之间传送数据。批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器中,批量数据存储指令则完成相反的操作。加载和存储指令分别为:
LDM 批量数据加载指令 STM 批量数据存储指令
格式为:LDM(或STM){条件} {类型} 基址寄存器{!},寄存器列表{∧ }
LDM(或 STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据。该指令的常见用途是将多个寄存器的内容入栈或出栈。其中,{类型}分为以下几种情况:
IA 每次传送后地址加1; IB 每次传送前地址加1; DA 每次传送后地址减1; DB 每次传送前地址减1; FD 满递减堆栈; ED 空递减堆栈; FA 满递增堆栈; EA 空递增堆栈。
{!}为可选后缀,若使用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。基址寄存器不允许为R15,寄存器列表可以为R0~R15的任意组合。
{∧}也为可选后缀,当指令为 LDM 且寄存器列表中包含 R15时,使用该后缀表示:除了正常的数据传送之外,还将SPSR复制到CPSR中。同时,该后缀还表示传入或传出的是用户模式下的寄存器而不是当前模式下的寄存器。例如:
STMFD R13!, {R0, R4-R12, LR} ;将寄存器列表中的(R0, R4到R12, LR)存入堆栈 LDMFD R13!, {R0, R4-R12, PC} ;将堆栈内容恢复到寄存器(R0, R4到R12, LR)中
7. 数据交换指令
ARM 微处理器所支持的数据交换指令能在存储器和寄存器之间交换数据,包含字数据交换指令SWP和字节数据交换指令SWPB两条指令,分别介绍如下。
(1)SWP指令
SWP指令的格式为:SWP {条件} 目的寄存器,源寄存器1,[源寄存器2]
SWP指令用于将源寄存器2所指向的存储器中的字数据传送到目的寄存器中,同时将源寄存器1中的字数据传送到源寄存器2所指向的存储器中。显然,当源寄存器1和目的寄存器为同一个寄存器时,指令交换该寄存器和存储器的内容。例如:
SWP R0,R1,[R2] ;将R2所指向的存储器中的字数据传送到R0中,同时将R1 ;中的字数据传送到R2所指向的存储单元 SWP R0,R0,[R1] ;该指令完成将R1所指向的存储器中的字数据与R0中的字数 ;据交换
(2)SWPB指令
SWPB指令的格式为:SWPB {条件} 目的寄存器,源寄存器1,[源寄存器2]
SWPB指令用于将源寄存器2所指向的存储器中的字节数据传送到目的寄存器中,目的寄存器的高24位清零,同时将源寄存器1中的字节数据传送到源寄存器2所指向的存储器中。当源寄存器1和目的寄存器为同一寄存器时,指令交换该寄存器和存储器的内容。例如:
SWPB R0,R1,[R2] ;将R2所指向的存储器中的字节数据传送到R0,R0的高 ;24位清零,同时将R1中的低8位数据传送到R2所指向 ;的存储单元中 SWPB R0,R0,[R1] ;该指令完成将R1所指向的存储器中的字节数据与R0中的 ;低8位数据的交换
8. 移位操作
ARM微处理器内嵌的桶型移位器(Barrel Shifter),支持数据的各种移位操作,移位操作在ARM指令集中不作为单独的指令使用,它只能作为指令格式中的一个字段,在汇编语言中表示为指令中的选项。例如,当数据处理指令的第二个操作数为寄存器时,就可以加入移位操作选项对它进行各种移位操作。移位操作包括如下6种类型:
LSL 逻辑左移 ASL 算术左移 LSR 逻辑右移 ASR 算术右移 ROR 循环右移 RRX 带扩展的循环右移
其中,ASL和LSL是等价的。下面分别介绍各种移位操作。
(1)LSL(或ASL)操作
LSL(或ASL)操作的格式为:通用寄存器,LSL(或ASL)操作数
LSL(或ASL)对通用寄存器中的内容进行逻辑(或算术)左移操作,按操作数所指定的数量向左移位,低位用零来填充。其中,操作数可以是通用寄存器,也可以是立即数(0~31)。例如:
MOV R0, R1, LSL#2 ;将R1中的内容左移两位后传送到R0中
(2)LSR操作
LSR操作的格式为:通用寄存器,LSR 操作数
LSR对通用寄存器中的内容进行右移操作,按操作数所指定的数量向右移位,左端用零来填充。其中,操作数可以是通用寄存器,也可以是立即数(0~31)。例如:
MOV R0, R1, LSR#2 ;将R1的内容右移两位后传送到R0中,左端用零来填充
(3)ASR操作
ASR操作的格式为:通用寄存器,ASR 操作数
ASR对通用寄存器中的内容进行右移的操作,按操作数所指定的数量向右移位,左端用第31位的值来填充。操作数可以是通用寄存器,也可以是立即数(0~31)。例如:
MOV R0, R1, ASR#2 ;将R1的内容右移两位后送给R0中,左端用第31位的值来 ;填充
(4)ROR操作
ROR操作的格式为:通用寄存器,ROR 操作数
ROR对通用寄存器中的内容进行循环右移操作,按操作数所指定的数量向右循环移位,左端用右端移出的位来填充。其中,操作数可以是通用寄存器,也可以是立即数(0~31)。当进行32位的循环右移操作时,通用寄存器中的值不改变。例如:
MOV R0, R1, ROR#2 ;将R1中的内容循环右移两位后传送到R0中
(5)RRX操作
RRX操作的格式为:通用寄存器,RRX 操作数
RRX对通用寄存器中的内容进行带扩展的循环右移操作,按操作数所指定的数量向右循环移位,左端用进位标志位C来填充。操作数可以是通用寄存器,也可以是立即数(0~31)。例如:
MOV R0, R1, RRX#2 ;将R1的内容进行带扩展的循环右移两位后传送到R0中
9. 协处理器指令
ARM微处理器可支持多达16个协处理器,用于各种协处理操作。在程序执行的过程中,每个协处理器只执行针对自身的协处理指令,忽略ARM处理器和其他协处理器的指令。
ARM的协处理器指令主要用于:初始化ARM协处理器的数据处理操作,在ARM处理器的寄存器和协处理器的寄存器之间传送数据,在ARM协处理器的寄存器和存储器之间传送数据等,共包括以下5条:
CDP 协处理器数操作指令 LDC 协处理器数据加载指令 STC 协处理器数据存储指令 MCR ARM处理器寄存器到协处理器寄存器的数据传送指令 MRC 协处理器寄存器到ARM处理器寄存器的数据传送指令
分别说明如下。
(1)CDP指令
CDP指令的格式为:
CDP{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2
CDP指令用于ARM处理器通知ARM协处理器执行特定的操作。若协处理器不能成功完成特定的操作,则产生未定义指令异常。其中,协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器和源寄存器均为协处理器的寄存器。指令不涉及ARM处理器的寄存器和存储器。例如:
CDP P3,2,C12,C10,C3,4 ;该指令完成协处理器P3的初始化
(2)LDC指令
LDC指令的格式为:LDC{条件}{L} 协处理器编码,目的寄存器,[源寄存器]
LDC指令用于将源寄存器所指向的存储器中的字数据传送到目的寄存器中。若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指令为长读取操作,如用于双精度数据的传输。例如:
LDC P3,C4,[R0] ;将ARM处理器的寄存器R0所指向的存储器中的字数据传送到协处 ;理器P3的寄存器C4中
(3)STC指令
STC指令的格式为:STC{条件}{L} 协处理器编码,源寄存器,[目的寄存器]
STC指令用于将源寄存器中的字数据传送到目的寄存器所指向的存储器中。若协处理器不能成功完成传送操作,则产生未定义指令异常。其中,{L}选项表示指令为长读取操作,如用于双精度数据的传输。例如:
STC P3,C4,[R0] ;将协处理器P3的寄存器C4中的字数据传送到ARM处理器的寄存 ;器R0所指向的存储器中
(4)MCR指令
MCR指令的格式为:
MCR{条件} 协处理器编码,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2, 协处理器操作码2
MCR指令用于将ARM处理器寄存器中的数据传送到协处理器寄存器中。若协处理器不能成功完成操作,则产生未定义指令异常。其中,协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,源寄存器为ARM处理器的寄存器,目的寄存器1和目的寄存器2均为协处理器的寄存器。例如:
MCR P3,3,R0,C4,C5,6 ;将ARM处理器的寄存器R0中的数据传送到协处理器P3的 ;寄存器C4和C5中
(5)MRC指令
MRC指令的格式为:
MRC{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2
MRC指令用于将协处理器寄存器中的数据传送到ARM处理器寄存器中。若协处理器不能成功完成操作,则产生未定义指令异常。其中,协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。例如:
MRC P3,3,R0,C4,C5,6 ;该指令将协处理器P3的寄存器中的数据传送到ARM处理器的 ;寄存器中
10. 异常产生指令
异常产生指令有SWI(软件中断指令)和BKPT(断点中断指令)两条指令,分别说明如下。
(1)SWI指令
SWI指令的格式为:SWI{条件} 24位的立即数
SWI指令用于产生软件中断,以便用户程序调用操作系统的系统例程。操作系统在SWI的异常处理程序中提供相应的系统服务,指令中24位的立即数用于指定用户程序调用系统例程的类型,相关参数通过通用寄存器传递。当指令中24位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器 R0的内容决定,同时,参数通过其他通用寄存器传递。例如:
SWI 0x02 ;该指令调用操作系统编号为02的系统例程
(2)BKPT指令
BKPT指令的格式为:BKPT 16位的立即数
BKPT指令产生软件断点中断,可用于程序的调试。
2.2.5 Thumb指令及应用
为兼容数据总线宽度为16位的应用系统,ARM 体系结构除了支持执行效率很高的32位ARM指令集以外,同时支持16位的Thumb指令集。Thumb指令集是ARM指令集的一个子集,允许指令编码为16位的长度。与等价的32位代码相比较,Thumb指令集在保留32位代码的优势同时,大大地节省了系统的存储空间。所有的Thumb指令都有对应的ARM指令,而且Thumb的编程模型也对应于ARM的编程模型。在应用程序的编写过程中,只要遵循一定调用的规则,Thumb 子程序和ARM 子程序就可以互相调用。当处理器在执行 ARM程序段时,称ARM处理器处于ARM工作状态;当处理器在执行Thumb程序段时,称ARM处理器处于Thumb工作状态。与ARM指令集相比:Thumb指令集中的数据处理指令的操作数仍然是32位的,指令地址也为32位,但Thumb指令集为实现16位的指令长度,舍弃了ARM指令集的一些特性,例如,大多数的Thumb指令是无条件执行的,而几乎所有的ARM指令都是有条件执行的;大多数的Thumb数据处理指令的目的寄存器与其中一个源寄存器相同。由于Thumb指令的长度为16位,即只用ARM指令一半的位数来实现同样的功能,因此,要实现特定的程序功能,所需的Thumb指令的条数比ARM指令多。
在一般情况下,Thumb指令与ARM指令的时间效率和空间效率具有如下关系:
• Thumb代码所需的存储空间为ARM代码的60%~70%;
• Thumb代码使用的指令数比ARM代码多30%~40%;
• 若使用32位的存储器,则ARM代码比Thumb代码快约40%;
• 若使用16位的存储器,则Thumb代码比ARM代码快40%~50%;
• 与ARM代码相比较,使用Thumb代码时,存储器的功耗会降低30%。
显然,ARM指令集和Thumb指令集各有特点。若对系统的性能有较高要求,则应使用32位的存储系统和ARM指令集;若对系统的成本及功耗有较高要求,则应使用16位的存储系统和Thumb指令集;若两者结合,则可发挥各自的优点,获得更好的效果。
2.3 ARM汇编程序设计基础
2.3.1 ARM汇编器所支持的伪指令
在ARM汇编语言程序里,有一些特殊指令助记符,它们与指令系统的助记符不同,没有相对应的操作码。通常,称这些特殊指令助记符为伪指令,它们所完成的操作称为伪操作。伪指令在源程序中的作用是为完成汇编程序做各种准备工作,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命就完成了。在ARM的汇编程序中,有如下几种伪指令。
1. 符号定义伪指令
符号定义(Symbol Definition)伪指令用于定义ARM汇编程序中的变量、对变量赋值以及定义寄存器的别名等操作。常见的符号定义伪指令有如下几种:
• 用于定义全局变量的GBLA、GBLL和GBLS伪指令;
• 用于定义局部变量的LCLA、LCLL和LCLS伪指令;
• 用于对变量赋值的SETA、SETL、SETS伪指令;
• 为通用寄存器列表定义名称的RLIST伪指令。
(1)GBLA、GBLL和GBLS伪指令
语法格式:GBLA(GBLL或GBLS)全局变量名
GBLA、GBLL和GBLS伪指令用于定义一个ARM程序中的全局变量,并将其初始化。其中:
√ GBLA用于定义一个全局的数字变量,并初始化为0;
√ GBLL用于定义一个全局的逻辑变量,并初始化为F(假);
√ GBLS用于定义一个全局的字符串变量,并初始化为空。
由于它们用于定义全局变量,故在整个程序范围内变量名必须唯一。例如:
GBLA Test1 ;定义一个全局的数字变量,变量名为Test1 Test1 SETA 0xaa ;将该变量赋值为0xaa GBLL Test2 ;定义一个全局的逻辑变量,变量名为Test2 Test2 SETL {TRUE} ;将该变量赋值为真 GBLS Test3 ;定义一个全局的字符串变量,变量名为Test3 Test3 SETS "Testing" ;将该变量赋值为“Testing”
(2)LCLA、LCLL和LCLS伪指令
语法格式:LCLA(LCLL或LCLS)局部变量名
这3条伪指令均用于定义ARM程序中的局部变量,并将其初始化。其中:
√ LCLA用于定义一个局部的数字变量,并初始化为0;
√ LCLL用于定义一个局部的逻辑变量,并初始化为F(假);
√ LCLS用于定义一个局部的字符串变量,并初始化为空。
由于它们用于声明局部变量,因此,在其作用范围内,变量名必须唯一。例如:
LCLA Test4 ;声明一个局部的数字变量,变量名为Test4 Test3 SETA 0xaa ;将该变量赋值为0xaa LCLL Test5 ;声明一个局部的逻辑变量,变量名为Test5 Test4 SETL {TRUE} ;将该变量赋值为真 LCLS Test6 ;定义一个局部的字符串变量,变量名为Test6 Test6 SETS "Testing" ;将该变量赋值为"Testing"
(3)SETA、SETL和SETS伪指令
语法格式:变量名 SETA(SETL或SETS) 表达式
这3条伪指令用于给已经定义的全局变量或局部变量赋值:SETA用于给一个数学变量赋值,SETL用于给一个逻辑变量赋值,SETS用于给一个字符串变量赋值。其中,变量名为已经定义过的全局变量或局部变量,表达式为将要赋给变量的值。例如:
LCLA Test3 ;声明一个局部的数字变量,变量名为Test3 Test3 SETA 0xaa ;将该变量赋值为0xaa LCLL Test4 ;声明一个局部的逻辑变量,变量名为Test4 Test4 SETL {TRUE} ;将该变量赋值为真
(4)RLIST伪指令
语法格式:名称 RLIST {寄存器列表}
RLIST伪指令可用于对一个通用寄存器列表定义名称。使用它定义的名称可在ARM指令LDM/STM指令中使用,这时列表中的寄存器访问次序为:根据寄存器的编号由低到高,而与列表中的寄存器排列次序无关。例如:
RegList RLIST {R0-R5, R8, R10};将寄存器列表名称定义为RegList,可在ARM ;指令LDM/STM中通过该名称访问寄存器列表
2. 数据定义伪指令
数据定义(Data Definition)伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。常见的数据定义伪指令有:
DCB ;分配一片连续的字节存储单元并用指定数据初始化 DCW(DCWU) ;分配一片连续的半字存储单元并用指定数据初始化 DCD(DCDU) ;分配一片连续的字存储单元并用指定数据初始化 DCFD(DCFDU) ;为双精度浮点数分配一片连续字存储单元并用指定数据初始化 DCFS(DCFSU) ;为单精度浮点数分配一片连续字存储单元并用指定数据初始化 DCQ(DCQU) ;分配一片以8字节为单位的连续存储单元并用指定数据初始化 SPACE ;分配一片连续的存储单元 MAP ;定义一个结构化的内存表首地址 FIELD ;定义一个结构化的内存表数据域
分别说明如下。
(1)DCB
语法格式:标号 DCB 表达式
其中,表达式可以为0~255的数字或字符串,DCB也可用“=”代替。例如:
Str DCB "This is a test!" ;分配一片连续的字节存储单元并初始化
(2)DCW(或DCWU)
语法格式:标号 DCW(或DCWU)表达式
其中,表达式可以为程序标号或数字表达式。
用DCW分配的字存储单元是半字对齐的,而用DCWU分配的字存储单元并不严格半字对齐。例如:
DataTest DCW 1,2,3 ;分配一片连续的半字存储单元并初始化
(3)DCD(或DCDU)
语法格式:标号 DCD(或DCDU)表达式
其中,表达式可以为程序标号或数字表达式,DCD也可用“&”代替。用DCD分配的字存储单元是字对齐的,而用DCDU分配的字存储单元并不严格字对齐。例如:
DataTest DCD 4,5,6 ;分配一片连续的字存储单元并初始化
(4)DCFD(或DCFDU)
语法格式:标号 DCFD(或DCFDU) 表达式
每个双精度的浮点数占两个字单元。用 DCFD 分配的字存储单元是字对齐的,而用DCFDU分配的字存储单元并不严格字对齐。例如:
FdataTest DCFD 2E115, −5E7 ;分配一片连续字存储单元并初始化为指定双精度数
(5)DCFS(或DCFSU)
语法格式:标号 DCFS(或DCFSU)表达式
每个单精度的浮点数占据一个字单元。用 DCFS 分配的字存储单元是字对齐的,而用DCFSU分配的字存储单元并不严格字对齐。例如:
FdataTest DCFS 2E5, −5E−7 ;分配一片连续字存储单元并初始化为指定单精度数
(6)DCQ(或DCQU)
语法格式:标号 DCQ(或DCQU)表达式
用DCQ分配的存储单元是字对齐的,而用DCQU分配的存储单元并不严格字对齐。例如:
DataTest DCQ 100 ;分配一片连续的存储单元并初始化为指定的值
(7)SPACE
语法格式:标号 SPACE 表达式
其中,表达式为要分配的字节数,SPACE也可用“%”代替。例如:
DataSpace SPACE 100 ;分配连续100字节的存储单元并初始化为0
(8)MAP
语法格式:MAP 表达式{,基址寄存器}
MAP也可用“^”代替。其中,表达式可以为程序中的标号或数学表达式。基址寄存器为可选项,当基址寄存器选项不存在时,表达式的值即为内存表的首地址;当该选项存在时,内存表的首地址为表达式的值与基址寄存器的和。MAP通常与FIELD配合使用来定义结构化的内存表。例如:
MAP 0x100,R0 ;定义结构化内存表首地址的值为0x100+R0
(9)FILED
语法格式:标号 FIELD 表达式
FILED 也可用“#”代替,表达式的值为当前数据域在内存表中所占的字节数。FIELD常与MAP配合使用来定义结构化的内存表,MAP定义内存表的首地址,FIELD定义内存表中的各个数据域,并可为每个数据域指定一个标号供其他指令引用。注意,MAP 和 FIELD伪指令仅用于定义数据结构,并不实际分配存储单元。例如:
MAP 0x100 ;定义结构化内存表首地址的值为0x100 A FIELD 16 ;定义A的长度为16字节,位置为0x100 B FIELD 32 ;定义B的长度为32字节,位置为0x110 S FIELD 256 ;定义S的长度为256字节,位置为0x130
3. 汇编控制伪指令
汇编控制(Assembly Control)伪指令用于控制汇编程序的执行流程,常用的汇编控制伪指令包括:IF、ELSE、ENDIF,WHILE、WEND,MACRO、MEND,MEXIT 等,分别说明如下。
(1)IF、ELSE、ENDIF
语法格式:
IF 逻辑表达式 指令序列1 ELSE 指令序列2 ENDIF
IF、ELSE、ENDIF伪指令能根据条件的成立与否决定是否执行某个指令序列。若IF后面的逻辑表达式为真,则执行指令序列1,否则执行指令序列2。其中,ELSE及指令序列2可以没有。此时,若IF后面的逻辑表达式为真,则执行指令序列1,否则继续执行后面的指令。IF、ELSE、ENDIF伪指令可以嵌套使用。例如:
GBLL Test ;声明一个全局的逻辑变量,变量名为Test … IF Test = TRUE 指令序列1 ELSE 指令序列2 ENDIF
(2)WHILE、WEND
语法格式:
WHILE 逻辑表达式 指令序列 WEND
WHILE、WEND 伪指令能根据条件的成立与否决定是否循环执行某个指令序列。若WHILE 后面的逻辑表达式为真,则执行指令序列。该指令序列执行完毕后,再判断逻辑表达式的值,若为真则继续执行,一直到逻辑表达式的值为假结束。WHILE、WEND 伪指令可以嵌套使用。例如:
GBLA Counter ;声明一个全局的数学变量,变量名为Counter Counter SETA 3 ;由变量Counter控制循环次数 … WHILE Counter < 10 指令序列 WEND
(3)MACRO、MEND
语法格式:
$标号 宏名 $参数1,$参数2,… 指令序列 MEND
MACRO、MEND伪指令可以将一段代码定义为一个整体,称为宏指令,然后就可以在程序中通过宏指令多次调用该段代码。其中,$标号在宏指令被展开时,会被替换为用户定义的符号。宏指令可以使用一个或多个参数,当宏指令被展开时,这些参数被相应的值替换。宏指令的使用方式和功能与子程序有些相似,子程序可以提供模块化的程序设计、节省存储空间并提高运行速度,但在使用子程序结构时需要保护现场,从而增加了系统的开销,因此,在代码较短且需要传递的参数较多时,可以使用宏指令代替子程序。包含在 MACRO 和MEND之间的指令序列称为宏定义体。在宏定义体的第一行应声明宏的原型(包含宏名、所需的参数),然后就可以在汇编程序中通过宏名来调用该指令序列。在源程序被编译时,汇编器将宏调用展开,用宏定义中的指令序列代替程序中的宏调用,并将实际参数的值传递给宏定义中的形式参数。MACRO、MEND伪指令可以嵌套使用。
(4)MEXIT
语法格式:MEXIT
MEXIT用于从宏定义中跳转出去。
4. 其他常用的伪指令
在汇编程序中还经常使用如下伪指令:
AREA ALIGN CODE16、CODE32 ENTRY END EQU EXPORT(或GLOBAL) IMPORT EXTERN GET(或INCLUDE) INCBIN RN ROUT
分别介绍如下。
(1)AREA
语法格式:AREA 段名 属性1,属性2,…
AREA伪指令用于定义一个代码段或数据段。其中,若“段名”以数字开头,则该段名需要用“|”括起来,如:|1_test|。属性字段表示该代码段(或数据段)的相关属性,多个属性用逗号分隔。常用的属性如下。
CODE属性:用于定义代码段,默认为READONLY。
DATA属性:用于定义数据段,默认为READWRITE。
READONLY属性:指定本段为只读,代码段默认为READONLY。
READWRITE属性:指定本段为可读可写,数据段的默认属性为READWRITE。
ALIGN 属性:使用方式为“ALIGN 表达式”。在默认时,ELF(可执行连接文件)的代码段和数据段是按字节对齐的,表达式的取值范围为0~31,相应的对齐方式为按照2表达式次幂所取值的字节数进行对齐。
COMMON属性:定义一个通用的段,不包含任何的用户代码和数据,各源文件中同名的COMMON段共享同一段存储单元。
一个汇编语言程序至少要包含一个段,当程序太长时,也可以将程序分为多个代码段和数据段。例如:
AREA Init, CODE, READONLY ;定义了一个代码段,段名为Init,属性为只读
(2)ALIGN
语法格式:ALIGN {表达式{,偏移量}}
ALIGN伪指令可通过添加填充字节的方式,使当前位置满足一定的对齐方式。其中,表达式的值用于指定对齐方式,可能的取值为2的幂,如1、2、4、8、16等;若未指定表达式,则将当前位置对齐到下一个字的位置。偏移量也为一个数字表达式,若使用该字段,则当前位置的对齐方式为:2的表达式次幂+偏移量。例如:
AREA Init,CODE,READONLY,ALIEN=3 ;指定后面的指令为8字节对齐 指令序列 END
(3)CODE16和CODE32
语法格式:CODE16(或CODE32)
CODE16或CODE32伪指令通知编译器,其后的指令序列分别对应16位的Thumb指令或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 ;程序结束
(4)ENTRY
语法格式:ENTRY
ENTRY 伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个ENTRY。当有多个ENTRY时,程序的真正入口点由链接器指定。但在一个源文件里最多只能有一个ENTRY(可以没有)。例如:
AREA Init,CODE,READONLY ENTRY ;指定应用程序的入口点 …
(5)END
语法格式:END
END伪指令用于通知编译器已经到了源程序的结尾。
(6)EQU
语法格式:名称 EQU 表达式{,类型}
EQU 伪指令用于为程序中的常量、标号等定义一个等效的字符名称,类似于 C 语言中的#define。其中,EQU可用“*”代替。“名称”为EQU伪指令定义的字符名称。当“表达式”为32位的常量时,可以指定表达式的数据类型,有以下三种类型:CODE16、CODE32和DATA。例如:
Test EQU 50 ;定义标号Test的值为50 Addr EQU 0x55, CODE32 ;定义Addr的值为0x55且该处为32位的ARM指令
(7)EXPORT(或GLOBAL)
语法格式:EXPORT 标号{[WEAK]}
EXPORT 伪指令用于在程序中声明一个全局的标号,该标号可在其他文件中引用。EXPORT可用GLOBAL代替。标号在程序中区分大小写,[WEAK]选项声明其他的同名标号优先于该标号被引用。例如:
AREA Init,CODE,READONLY EXPORT Stest ;声明一个可全局引用的标号Stest … END
(8)IMPORT
语法格式:IMPORT 标号{[WEAK]}
IMPORT伪指令用于通知编译器,要使用的标号是在其他源文件中定义的,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。“标号”在程序中区分大小写。[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息。在多数情况下,将该标号置为0。若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。例如:
AREA Init,CODE,READONLY IMPORT Main ;通知编译器当前文件要引用标号Main,但它是在其他源文件中定义的 … END
(9)EXTERN
语法格式:EXTERN 标号{[WEAK]}
EXTERN伪指令用于通知编译器,要使用的标号是在其他的源文件中定义的,但要在当前源文件中引用,如果当前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。“标号”在程序中区分大小写。[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给出错误信息,在多数情况下,将该标号置为0。若该标号为B或BL指令引用,则将B或BL指令置为NOP操作。例如:
AREA Init,CODE,READONLY EXTERN Main ;通知编译器当前文件要引用标号Main,但它是在其他源文件中定义的 … END
(10)GET(或INCLUDE)
语法格式:GET 文件名
GET伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可以使用INCLUDE代替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
(11)INCBIN
语法格式:INCBIN 文件名
INCBIN 伪指令用于将一个目标文件或数据文件包含到当前的源文件中,被包含的文件不作任何变动直接存放在当前文件中,编译器从其后开始继续处理。例如:
AREA Init,CODE,READONLY INCBIN a1.dat ;通知编译器当前源文件包含文件a1.dat INCBIN C:\a2.txt ;通知编译器当前源文件包含文件C:\a2.txt … END
(12)RN
语法格式:名称 RN 表达式
RN 伪指令用于给一个寄存器定义一个别名。采用这种方式可以方便程序员记忆该寄存器的功能。其中,名称为给寄存器定义的别名,表达式为寄存器的编码。例如:
Temp RN R0 ;将R0定义一个别名Temp
(13)ROUT
语法格式:{名称} ROUT
ROUT伪指令用于给一个局部变量定义作用范围。在程序中未使用该伪指令时,局部变量的作用范围为所在的AREA,而使用ROUT后,局部变量的作为范围为当前ROUT和下一个ROUT之间。
2.3.2 汇编语言的语句格式
ARM(Thumb)汇编语言的语句格式为:{标号} {指令或伪指令} {;注释}
在汇编语言程序设计中,指令的助记符中可以全部用大写或全部用小写,但不允许在一条指令中大、小写混用。同时,如果一条语句太长,可将该长语句分为若干行来书写,在行的末尾用“\”表示下一行与本行为同一条语句。
1. 汇编语言程序中常用的符号
在汇编语言程序设计中,经常使用各种符号代替地址、变量和常量等,以增加程序的可读性。尽管符号的命名由编程者决定,但并不是任意的,必须遵循以下的约定:
• 符号区分大小写,同名的大、小写符号会被编译器认为是两个不同的符号;
• 符号在其作用范围内必须唯一;
• 自定义的符号名不能与系统的保留字相同;
• 符号名不应与指令或伪指令同名。
下面就程序中的变量、常量和变量代换分别进行说明。
(1)程序中的变量
程序中的变量是指其值在程序的运行过程中可以改变的量。ARM(Thumb)汇编程序所支持的变量包括数字变量、逻辑变量和字符串变量。数字变量用于在程序的运行中保存数字值,但要注意,数字值的大小不应超出数字变量所能表示的范围。逻辑变量用于在程序的运行中保存逻辑值。逻辑值只有两种取值情况:真或假。字符串变量用于在程序的运行中保存一个字符串,但要注意,字符串的长度不应超出字符串变量所能表示的范围。在ARM(Thumb)汇编语言程序设计中,可使用GBLA、GBLL、GBLS伪指令声明全局变量,使用LCLA、LCLL、LCLS伪指令声明局部变量,使用SETA、SETL和SETS对其进行初始化。
(2)程序中的常量
程序中的常量是指其值在程序的运行过程中不能被改变的量。ARM(Thumb)汇编程序所支持的常量包括数字常量、逻辑常量和字符串常量。数字常量一般为32位的整数。作为无符号数时,其取值范围为0~232−1;作为有符号数时,其取值范围为−231~231−1。逻辑常量只有两种取值情况:真或假。字符串常量为一个固定的字符串,一般用于程序运行时的信息提示。
(3)程序中的变量代换
程序中的变量可通过代换操作取得一个常量。代换操作符为“$”。如果在数字变量前面有一个代换操作符“$”,编译器会将该数字变量的值转换为由十六进制数组成的字符串,并用该字符串替换“$”后的数字变量。如果在逻辑变量前面有一个代换操作符“$”,编译器会将该逻辑变量替换为它的取值(真或假);如果在字符串变量前面有一个代换操作符“$”,编译器会将该字符串变量的值替换“$”后的字符串变量。例如:
LCLS S1 ;定义局部字符串变量S1和S2 LCLS S2 S1 SETS "Test!" S2 SETS "This is a $S1" ;字符串变量S2的值为“This is a Test!”
2. 汇编语言程序中的表达式和运算符
在汇编语言程序设计中,也经常使用各种表达式,表达式一般由变量、常量、运算符和括号构成。常用的表达式有数字表达式、逻辑表达式和字符串表达式,其运算次序遵循如下的优先级:
• 优先级相同的双目运算符的运算顺序为从左到右;
• 相邻的单目运算符的运算顺序为从右到左,且单目运算符的优先级高于其他运算符;
• 括号运算符的优先级最高。
(1)数字表达式及运算符
数字表达式一般由数字常量、数字变量、数字运算符和括号构成。与数字表达式相关的运算符有以下3种。
① 算术运算符:+、−、×、/、MOD,分别代表加、减、乘、除和取余数运算。
② 移位运算符:ROL、ROR、SHL、SHR。
以X和Y表示两个数字表达式,以上移位运算符代表的运算如下:
X:ROL:Y ;表示将X循环左移Y位 X:ROR:Y ;表示将X循环右移Y位 X:SHL:Y ;表示将X左移Y位 X:SHR:Y ;表示将X右移Y位
③ 按位逻辑运算符:AND、OR、NOT、EOR。
以X和Y表示两个数字表达式,以上按位逻辑运算符代表的运算如下:
X:AND:Y ;表示将X和Y按位作逻辑与的操作 X:OR:Y ;表示将X和Y按位作逻辑或的操作 X:NOT:Y ;表示将X和Y按位作逻辑非的操作 X:EOR:Y ;表示将X和Y按位作逻辑异或的操作
(2)逻辑表达式及运算符
逻辑表达式一般由逻辑量、逻辑运算符和括号构成,其表达式的运算结果为真或假。与逻辑表达式相关的运算符有:=、>、<、>=、<= 、/=、<>,LAND、LOR、LNOT、LEOR。若以X和Y表示两个逻辑表达式,则有:
X = Y ;表示X等于Y X > Y ;表示X大于Y X < Y ;表示X小于Y X >= Y ;表示X大于等于Y X <= Y ;表示X小于等于Y X /= Y ;表示X不等于Y X <> Y ;表示X不等于Y X:LAND:Y ;表示将X和Y 作逻辑与操作 X:LOR:Y ;表示将X和Y作逻辑或操作 X:LEOR:Y ;表示将X和Y作逻辑异或操作
(3)字符串表达式及运算符
字符串表达式一般由字符串常量、字符串变量、运算符和括号构成。编译器所支持的字符串最大长度为512字节。常用的与字符串表达式相关的运算符如下。
① LEN运算符。它返回字符串的长度(字符数),以X表示字符串表达式,其语法格式为:
:LEN:X
② CHR运算符。它将0~255之间的整数转换为一个字符,以M表示某一个整数,其语法格式为:
:CHR:M
③ STR 运算符。它将一个数字表达式或逻辑表达式转换为一个字符串。对于数字表达式,STR运算符将其转换为一个由十六进制数组成的字符串;对于逻辑表达式,STR运算符将其转换为字符串T或F,其语法格式为:
:STR:X
其中,X为一个数字表达式或逻辑表达式。
④ LEFT运算符。它返回某个字符串左端的一个子串,其语法格式为:
X:LEFT:Y
其中,X为源字符串,Y为一个整数,表示要返回的字符个数。
⑤ RIGHT运算符。与LEFT运算符相对应,RIGHT运算符返回某个字符串右端的一个子串,其语法格式为:
X:RIGHT:Y
其中,X为源字符串,Y为一个整数,表示要返回的字符个数。
⑥ CC运算符。 用于将两个字符串连接成一个字符串,其语法格式为:
X:CC:Y
其中,X为源字符串1,Y为源字符串2,CC运算符将Y连接到X的后面。
(4)与寄存器和程序计数器(PC)相关的表达式及运算符
这一类表达式及运算符主要有BASE和INDEX两种,分别介绍如下。
① BASE 运算符
它返回基于寄存器的表达式中寄存器的编号,其语法格式为:
:BASE:X
其中,X为与寄存器相关的表达式。
② INDEX运算符
它返回基于寄存器的表达式中相对于其基址寄存器的偏移量,其语法格式为:
:INDEX:X
其中,X为与寄存器相关的表达式。
(5)其他常用运算符
① ?运算符
它返回某代码行所生成的可执行代码的长度,例如:
?X
表明返回定义符号X的代码行所生成的可执行代码的字节数。
② DEF运算符
它判断是否定义某个符号,例如:
:DEF:X
表明如果符号X已经定义,则结果为真,否则为假。
2.3.3 汇编语言的程序结构
1. 汇编语言的程序结构
在ARM(Thumb)汇编语言程序中常以程序段为单位组织代码。段是相对独立的指令或数据序列,具有特定的名称,可分为代码段和数据段,代码段的内容为执行代码,数据段存放代码运行时需要用到的数据。一个汇编程序至少应该有一个代码段。当程序较长时,可以分割为多个代码段和数据段,多个段在程序编译链接时最终形成一个可执行的映像文件。
可执行映像文件通常由以下几部分构成:
• 一个或多个代码段,代码段的属性为只读;
• 零个或多个包含初始化数据的数据段,数据段的属性为可读/写;
• 零个或多个不包含初始化数据的数据段,数据段的属性为可读/写。
链接器根据系统默认或用户设定的规则,将各个段安排在存储器中的相应位置。因此源程序中段之间的相对位置与可执行的映像文件中段的相对位置一般不会相同。
下面是一个体现汇编语言源程序基本结构的例子。
AREA Init,CODE,READONLY ENTRY Start LDR R0,=0x3FF5000 LDR R1,0xFF STR R1,[R0] LDR R0,=0x3FF5008 LDR R1,0x01 STR R1,[R0] … END
在汇编语言程序中,用AREA伪指令定义一个段,并说明所定义段的相关属性。本例定义一个名为 Init 的代码段,属性为只读。ENTRY 伪指令标识程序的入口点,接下来为指令序列,程序的末尾为 END 伪指令,该伪指令告诉编译器源文件的结束,每一个汇编程序段都必须有一条END伪指令,指示代码段的结束。
2. 汇编语言的子程序调用
在ARM汇编语言程序中,子程序的调用一般是通过BL指令来实现的,其格式为:
BL 子程序名
该指令在执行时完成如下操作:将子程序的返回地址存放在链接寄存器LR中,同时将程序计数器 PC 指向子程序的入口点,当子程序执行完毕需要返回调用处时,只需要将存放在 LR 中的返回地址重新复制给程序计数器 PC 即可。在调用子程序的同时,也可以完成参数的传递和从子程序返回运算的结果,通常可以使用寄存器R0~R3完成。
以下是使用BL指令调用子程序的汇编语言源程序的例子。
AREA Init,CODE,READONLY ENTRY Start LDR R0,=0x3FF5000 LDR R1,0xFF STR R1,[R0] LDR R0,=0x3FF5008 LDR R1,0x01 STR R1,[R0] BL PRINT_TEXT … PRINT_TEXT … MOV PC,BL … END
2.4 ARM微处理器的编程模型
2.4.1 ARM微处理器的工作状态
从编程的角度看,ARM 微处理器的工作状态一般有两种:① ARM 状态,此时处理器执行32位的字对齐(地址的低两位为0)的ARM指令;② Thumb状态,此时处理器执行16位的半字对齐(地址的最低位为0)的Thumb指令。在程序的执行过程中,微处理器可以随时在两种工作状态之间切换且不影响处理器的工作模式和相应寄存器中的内容。另外,在两种工作状态下也都支持字节处理,因此ARM微处理器支持字节(8位)、半字(16位)、字(32位)三种数据类型。
ARM指令集和Thumb指令集均有切换处理器状态的指令,但ARM微处理器在开始执行代码时,应该处于ARM状态。
当操作数寄存器的状态位(位0)为1时,可以采用执行BX指令的方法,使微处理器从ARM状态切换到Thumb状态。此外,当处理器处于Thumb状态时发生异常(如IRQ、FIQ、Undef、Abort、SWI等),则异常处理返回时,自动切换到Thumb状态。
当操作数寄存器的状态位为0时,执行BX指令时可以使微处理器从Thumb状态切换到ARM状态。此外,在处理器进行异常处理时,把PC指针放入异常模式链接寄存器中,并从异常向量地址开始执行程序,也可以使处理器切换到ARM状态。
2.4.2 ARM体系结构的存储器格式
ARM体系结构将存储器看做从0地址开始的字节的线性组合。第0~3字节放置第一个存储的字数据,第4~7字节放置第二个存储的字数据,依次排列。作为32位的微处理器, ARM体系结构所支持的最大寻址空间约为4GB(232B)。ARM体系结构可以用两种方法存储字数据,称为大端格式和小端格式,分别说明如下。
(1)大端格式
在这种格式中,字数据的高字节存储在低地址中,低字节存放在高地址中,如图2.1所示。
(2)小端格式
与大端格式相反,这种格式在低地址中存放字数据的低字节、高地址中存放字数据的高字节,如图2.2所示。
图2.1 以大端格式存储字数据
图2.2 以小端格式存储字数据
2.4.3 处理器模式
ARM微处理器支持以下7种运行模式。
① 用户模式(usr):正常的程序执行状态;
② 快速中断模式(fiq):用于高速数据传输或通道处理;
③ 外部中断模式(irq):用于通用的中断处理;
④ 管理模式(svc):操作系统使用的保护模式;
⑤ 数据访问终止模式(abt):数据或指令预取终止时进入该模式,用于虚拟存储及存储保护;
⑥ 系统模式(sys):运行具有特权的操作系统任务;
⑦ 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。
ARM微处理器的运行模式可以通过软件改变,也可以通过外部中断或异常处理改变。
大多数的应用程序运行在用户模式下,这时,某些被保护的系统资源是不能被访问的。其余的6种模式统称为非用户模式或特权模式(Privileged Modes),其中除去系统模式以外的5种又称为异常模式(Exception Modes),常用于处理中断或异常及需要访问受保护的系统资源等情况。
2.4.4 寄存器组织
ARM微处理器共有37个32位的寄存器,其中31个为通用寄存器,6个为状态寄存器。这些寄存器不能被同时访问,具体哪些寄存器可被编程访问取决于微处理器的工作状态及具体的运行模式。但在任何时候,通用寄存器 R14~R0、程序计数器 PC、一个或两个状态寄存器都是可访问的。
1. ARM状态下的寄存器组织
ARM状态下的寄存器组织如图2.3所示,它包含通用寄存器R0~R15和当前程序状态寄存器CPSR及备份的程序状态寄存器SPSR。其中,R0~R7称为未分组寄存器,R8~R14称为分组寄存器,R15称为程序计数器,分别说明如下。
(1)未分组寄存器R0~R7
在所有的运行模式下,未分组寄存器都指向同一个物理寄存器,它们未被系统用做特殊的用途,因此,在中断或异常处理进行模式转换时,由于不同的处理器运行模式均使用相同的物理寄存器,可能会造成寄存器中数据的破坏,这一点在程序设计时应引起注意。
图2.3 ARM状态下的寄存器组织
(2)分组寄存器R8~R14
对于分组寄存器,每一次所访问的物理寄存器与处理器当前的运行模式有关。
对于R8~R12来说,每个寄存器对应两个不同的物理寄存器:当使用fiq模式时,访问寄存器R8_fiq~R12_fiq;当使用除fiq模式以外的其他模式时,访问寄存器R8_usr~R12_usr。
对于R13、R14来说,每个寄存器对应6个不同的物理寄存器,其中的一个由用户模式与系统模式公用,另外5个物理寄存器对应于其他5种不同的运行模式。采用R13_<mode>和R14_<mode>记号来区分不同的物理寄存器,其中,mode为以下几种模式之一:usr、fiq、irq、svc、abt、und。
寄存器R13在ARM指令中常用做堆栈指针,但这只是一种习惯用法,用户也可使用其他的寄存器作为堆栈指针;而在Thumb 指令集中,某些指令强制性要求使用R13作为堆栈指针。
由于处理器的每种运行模式均有自己独立的物理寄存器R13,在用户应用程序的初始化部分,一般都要初始化每种模式下的R13,使其指向该运行模式的栈空间。这样,当程序的运行进入异常模式时,可以将需要保护的寄存器放入R13所指向的堆栈,而当程序从异常模式返回时,则从对应的堆栈中恢复,采用这种方式可以保证异常发生后程序的正常执行。
R14也称做子程序链接寄存器(SLR,Subroutine Link Register)或简称链接寄存器(LR, Link Register)。当执行BL子程序调用指令时,R14中得到R15(程序计数器PC)的备份。在其他情况下,R14用做通用寄存器。与之类似,当发生中断或异常时,对应的分组寄存器R14_svc、R14_irq、R14_fiq、R14_abt和R14_und用来保存R15的返回值。
在每一种运行模式下,都可用R14保存子程序的返回地址。当用BL或BLX指令调用子程序时,将PC的当前值复制给R14,执行完子程序后,又将R14的值复制回PC,即可完成子程序的调用返回。以上的描述可用指令完成。
① 执行以下任意一条指令:
MOV PC,LR BX LR
② 在子程序入口处使用以下指令将R14存入堆栈:
STMFD SP!, {<Regs>, LR}
③ 对应地,使用以下指令可以完成子程序的返回:
LDMFD SP!, {<Regs>,PC}
R14也可用做通用寄存器。
(3)程序计数器PC(R15)
寄存器R15用做程序计数器。在ARM状态下,位[1:0]为0,位[31:2]用于保存PC;在Thumb状态下,位[0]为0,位[31:1]用于保存PC。虽然可以用做通用寄存器,但是有些指令在使用R15时有一些特殊限制,若不注意,执行的结果将是不可预料的。在ARM状态下, PC的0位和1位是0;在Thumb状态下,PC的0位是0。
R15虽然也可用做通用寄存器,但一般不这么使用,因为对R15的使用有一些特殊的限制,如果违反了这些限制,则程序的执行结果是未知的。由于ARM体系结构采用了多级流水线技术,对于ARM指令集而言,PC总是指向当前指令的下两条指令的地址,即PC的值为当前指令的地址值加8字节。
在ARM状态下,任一时刻都可以访问以上所讨论的16个通用寄存器和一到两个状态寄存器。在非用户模式(特权模式)下,可访问到特定模式分组寄存器。在图2.3中也能够清楚看到在每一种运行模式下,哪一些寄存器是可以访问的。
(4)当前程序状态寄存器CPSR和备份的程序状态寄存器SPSR
由图2.3可见,CPSR可在任何运行模式下被访问。当异常发生时,SPSR用于保存CPSR的当前值;从异常退出,时则可由SPSR来恢复CPSR。
由于用户模式和系统模式不属于异常模式,它们没有 SPSR,因此,在这两种模式下访问SPSR,结果将是未知的。有关SPSR的详细讨论将在后面给出。
2. Thumb状态下的寄存器组织
Thumb状态下的寄存器集是ARM状态下寄存器集的一个子集,程序可以直接访问8个通用寄存器R7~R0、程序计数器PC、堆栈指针SP、链接寄存器LR和当前程序状态寄存器CPSR。同时,在每一种特权模式下都有一组SP、LR和SPSR。图2.4给出了Thumb状态下的寄存器组织。
图2.4 Thumb状态下的寄存器组织
Thumb状态下的寄存器组织与ARM状态下的寄存器组织之间的相应关系为:两种状态下的R0~R7、CPSR、SPSR是相同的;Thumb状态下的SP对应于ARM状态下的R13,LR对应于R14,PC对应于R15。如图2.5所示。
图2.5 Thumb状态下的寄存器组织与ARM状态下的寄存器组织的对应关系
在Thumb状态下,高位寄存器R8~R15并不是标准寄存器集的一部分,但可以使用汇编语言程序受限制地访问这些寄存器,将其用做快速的暂存器。使用带特殊变量的MOV指令,数据可以在低位寄存器和高位寄存器之间进行传送;高位寄存器的值可以使用 CMP 和ADD指令进行比较或加上低位寄存器中的值。
3. 程序状态寄存器
ARM体系结构包含1个当前程序状态寄存器(CPSR)和5个备份的程序状态寄存器(SPSR)。SPSR用来进行异常处理,其功能包括:保存ALU中的当前操作信息,控制允许和禁止中断,设置处理器的运行模式等。程序状态寄存器的格式如图2.6所示,可以看出,它分为条件码标致位、保留位和控制位3部分,下面分别进行讨论。
图2.6 程序状态寄存器格式
(1)条件码标志位
N、Z、C、V均为条件码标志位(CCF,Condition Code Flags)。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行。在ARM状态下,绝大多数的指令都是有条件执行的;在Thumb状态下,仅有分支指令是有条件执行的。条件码标志位的具体含义见表2.3。
表2.3 条件码标志的具体含义
(2)控制位
SPSR的低8位(包括I、F、T和M[4:0])称为控制位,当发生异常时,这些位可以被改变;如果处理器运行于特权模式,这些位也可以由程序修改。
I、F称为中断禁止位。当I=1时,禁止IRQ中断;当F=1时,禁止FIQ中断。
T反映处理器的运行状态。对于ARM体系结构v5及以上版本的T系列处理器,当该位为1时,程序运行于Thumb状态;否则运行于ARM状态。对于ARM体系结构v5及以上版本的非T系列处理器,当该位为1时,执行下一条指令以引起为定义的指令异常;当该位为0时,表示运行于ARM状态。
M0、M1、M2、M3、M4是运行模式位,具体含义见表2.4。
表2.4 运行模式位M [4:0]的具体含义
由表2.4可知,并不是所有运行模式位的组合都是有效的,其他组合结果会导致处理器进入一个不可恢复的状态。
(3)保留位
SPSR中的其余位为保留位,当改变SPSR中的条件码标志位或者控制位时,保留位不应被改变,在程序中也不要使用保留位来存储数据。保留位将用于ARM版本的扩展。
2.4.5 异常
当正常的程序执行流程发生暂时的停止时,称为异常(Exceptions)。例如,处理一个外部的中断请求。在处理异常之前,当前处理器的状态必须保留,这样当异常处理完成之后,当前程序可以继续执行。处理器允许多个异常同时发生,它们将会按固定的优先级进行处理。ARM体系结构中的异常,与8位/16位体系结构的中断有很大的相似之处,但异常与中断的概念并不完全等同。
1. ARM体系结构所支持的异常类型
ARM体系结构所支持的异常及具体含义见表4.5。
表2.5 ARM体系结构所支持的异常
2. 对异常的响应
当一个异常出现以后,ARM微处理器会执行以下操作。
① 将下一条指令的地址存入相应的链接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行。若异常是从ARM 状态进入的,则LR寄存器中保存的是下一条指令的地址(当前PC+4或PC+8,与异常的类型有关);若异常是从Thumb状态进入的,则在 LR 寄存器中保存当前 PC 的偏移量。这样,异常处理程序就不需要确定异常是从何种状态进入的。例如,对于软件中断异常SWI,指令“MOV PC,R14_svc”总是返回到下一条指令,不管SWI是在ARM状态下执行还是在Thumb状态下执行。
② 将CPSR复制到相应的SPSR中。
③ 根据异常类型,强制设置CPSR的运行模式位。
④ 强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处。
还可以设置中断禁止位,以禁止中断发生。
异常发生时,如果处理器处于Thumb状态,则当异常向量地址加载入PC时,处理器自动切换到ARM状态下。
ARM微处理器对异常的响应过程用伪码可以描述为:
R14_<Exception_Mode> = Return Link SPSR_<Exception_Mode> = CPSR CPSR[4:0] = Exception Mode Number CPSR[5] = 0 ;当运行于ARM工作状态时 If <Exception_Mode> == Reset or FIQ then ;当响应FIQ异常时,禁止新的 ;FIQ异常 CPSR[6] = 1 CPSR[7] = 1 PC = Exception Vector Address
3. 从异常返回
异常处理完毕之后,ARM微处理器会执行以下操作从异常返回。
① 将链接寄存器LR的值减去相应的偏移量后送到PC中。
② 将SPSR复制回CPSR中。
③ 若在进入异常处理时设置了中断禁止位,则要在此清除。
可以认为,应用程序总是从复位异常处理程序开始执行的,因此复位异常处理程序不需要返回。
4. 各类异常的具体描述
现在对表2.5中的几个主要异常给出具体描述。
(1)快速中断请求
快速中断请求(FIQ,Fast Interrupt Request)异常是为了支持数据传输或者通道处理而设计的。在ARM状态下,系统有足够的私有寄存器,从而可以避免对寄存器保存的需求,并减小了系统上下文切换的开销。
若将CPSR的F位置为1,则会禁止FIQ中断;若将CPSR的F位清零,处理器会在指令执行时检查FIQ的输入。注意,只有在特权模式下才能改变F位的状态。
可由外部通过对处理器上的 nFIQ 引脚输入低电平产生 FIQ。不管在 ARM 状态还是在Thumb状态下进入FIQ模式,FIQ处理程序均会执行以下指令从FIQ模式返回:
SUBS PC, R14_fiq , #4
该指令将寄存器R14_fiq的值减去4后,复制到程序计数器PC中,从而实现从异常处理程序中的返回,同时将SPSR_mode寄存器的内容复制到当前程序状态寄存器CPSR中。
(2)外部中断请求
外部中断请求(IRQ,Interrupt Request)异常属于正常的中断请求,可通过对处理器的nIRQ引脚输入低电平产生。IRQ的优先级低于FIQ,当程序执行进入FIQ异常时,IRQ可能会被屏蔽。
若将CPSR的I位置为1,则会禁止IRQ中断;若将CPSR的I位清零,处理器会在指令执行完之前检查IRQ的输入。注意,只有在特权模式下才能改变I位的状态。
不管在ARM状态还是在Thumb状态下进入IRQ模式,IRQ处理程序均会执行以下指令从IRQ模式返回:
SUBS PC , R14_irq , #4
该指令将寄存器R14_irq的值减去4后,复制到程序计数器PC中,从而实现从异常处理程序中的返回,同时将SPSR_mode寄存器的内容复制到CPSR中。
(3)中止
产生中止(ABORT)异常意味着对存储器的访问失败。ARM微处理器在存储器访问周期内检查是否发生中止异常。
中止异常包括两种类型:① 指令预取中止,发生在指令预取时;② 数据中止,发生在数据访问时。
当指令预取访问存储器失败时,存储器系统向ARM处理器发出存储器中止信号,预取的指令被记为无效。但只有当处理器试图执行无效指令时,指令预取中止异常才会发生。如果指令未被执行,例如,在指令流水线中发生了跳转,则预取指令中止不会发生。
若数据中止发生,则系统的响应与指令的类型有关。
当确定了中止的原因后,ABORT处理程序均会执行以下指令从中止模式返回,无论ARM状态还是Thumb状态:
SUBS PC, R14_abt, #4 ;指令预取中止 SUBS PC, R14_abt, #8 ;数据中止
以上指令恢复PC(从R14_abt)和CPSR(从SPSR_abt)的值,并重新执行中止的指令。
(4)软件中断
软件中断(SWI, Software Interrupt)指令用于进入管理模式,请求执行特定的管理功能。软件中断处理程序执行以下指令从SWI模式返回,无论ARM状态还是Thumb状态:
MOV PC , R14_svc
以上指令恢复PC(从R14_svc)和CPSR(从SPSR_svc)的值,并返回到SWI的下一条指令。
(5)未定义指令
当ARM处理器遇到不能处理的指令时,会产生未定义指令(Undefined Instruction)异常。采用这种机制,可以通过软件仿真扩展ARM或Thumb指令集。
在仿真未定义指令后,处理器执行以下程序返回,无论ARM状态还是Thumb状态:
MOVS PC, R14_und
以上指令恢复PC(从R14_und)和CPSR(从SPSR_und)的值,并返回到未定义指令后的下一条指令。
表2.6总结了进入异常处理时保存在相应R14中的PC值,以及在退出异常处理时推荐使用的指令。表2.7是异常向量(Exception Vectors)表,给出了异常对应的地址和进入模式;当多个异常同时发生时,系统将根据固定的优先级决定异常的处理次序,表2.8给出了异常优先级(Exception Priorities)。
表2.6 异常进入/退出
注意:① PC应是具有预取中止的BL/SWI/未定义指令所取的地址。
② PC是从FIQ或IRQ取得的不能执行的指令的地址。
③ PC是产生数据中止的加载或存储指令的地址。
④ 系统复位时,保存在R14_svc中的值是不可预知的。
表2.7 异常向量表
表2.8 异常优先级
5. 应用程序中的异常处理
当系统运行时,异常可能会随时发生,为保证在ARM处理器发生异常时不至于处于未知状态,在应用程序的设计中,首先要进行异常处理。采用的方式是,在异常向量表中的特定位置放置一条跳转指令,跳转到异常处理程序。当ARM处理器发生异常时,程序计数器PC 会被强制设置为对应的异常向量,从而跳转到异常处理程序。当异常处理完成以后,返回到主程序继续执行。
2.5 ARM存储器设计
1. 存储器容量和速度
现代的微处理器能以非常高的速率执行指令,为了充分开发这种潜能,处理器必须和容量大速度快的存储器相连接。不幸的是,存储器的存储空间越大,其存储速度会越慢。因此,很难设计出既有足够大存储空间,又有足够快存储速度来满足高性能处理器的存储器。多数情况是把速度快、存储量小的存储器和速度慢、存储量大的存储器结合起来,做成一个复合式的存储器,尽量让它体现出快速、大存储量的性能。高速、小存储量的存储器作为高速缓存,用来自动保存最常用的处理器指令和数据。高速缓存的有效性依赖于程序的空间定位和时间定位特性。两层存储器原理可以扩展到具有多个层次的存储器层次结构。这样,计算机的磁盘备份存储也可以看做这种结构的一部分。在适合的存储器管理下,程序的大小不仅仅取决于计算机主存储器的大小,还受到比它大很多的硬盘的限制。
典型的计算机分级存储体系包括几个层次,每个层次都有一个特有的容量和速度。处理器寄存器可被视为分级存储体系的顶层。一个RISC 处理器通常有几十个32位的寄存器,每个记录的存取时间只有几ns。片上高速缓存容量为8~32KB,存取时间在10ns左右。高性能台式机可有几百KB的二级高速缓存,存取时间为几十ns。主存储器有几兆到几百兆的动态RAM存储空间,存取时间大约在100ns左右。备份存储器,通常在一个硬盘上有数百MB到数百GB的空间,存取时间几十ms。即使在系统里没有二级高速缓存,主存储器和备份存储器之间的性能差别比任何其他相邻的两层之间的差别也要大。
寄存器中的数据由编译器或者汇编器直接控制,但是其他各层次的数据通常被自动管理。高速缓存对于应用程序是不可见的,它在硬件的控制下,把一块或者一页指令和数据在不同层次间搬移。主存储器和备份存储器的分页由操作系统控制,其他的对于应用程序都是透明的。既然主存储器和备份存储器之间的性能差别这么大,就需要许多精妙的算法来决定数据在各层间的搬移的时机。
嵌入式系统通常没有备份存储器,因此不使用分页存储。但是,很多嵌入式系统的混合高速缓存和ARM CPU的芯片采用某种程度的缓存组织结构。因此,对于嵌入式系统的设计,关注高速缓存问题的细节还是很有必要的。快速存储器平均每比特价格比慢速存储器要昂贵,所以,分层存储器追求的是性能逼近最快速存储器,但平均每比特的代价却接近最慢速的存储器。
2. 片上存储器
在许多嵌入式系统中,简单的片上RAM是首选的高速缓存,原因如下。① 简单、便宜、功耗低。在随后的内容中可以看到,高速缓存带来了很大的逻辑设计开销,而逻辑设计又是使它有效工作所必需的,如果没有现成的合适的缓存,它的设计代价将是很高的。② 稳定性好。高速缓存的表现复杂,这使得在特定条件下运行时很难预测它会工作得怎么样,尤其是很难保证中断响应时间,而片上RAM的稳定性通常比另外扩充的要好。
使用片上RAM作为高速缓存往往需要程序员外在的管理。缓存通常是对程序员透明的,当程序被很好地定义且在程序员的控制下时,片上RAM能被有效地用做软件控制的高速缓存,但当应用不能预测时,这种控制任务会很困难。因此,在那些应用未知的通用系统中,外扩的高速缓存变成了首选。
片上RAM的一个重要优点在于,当程序员知道将来的处理负荷时,它允许程序员分配空间。高速缓存只取决于自身过去的程序行为,因此,不能提前为即将到来的任务做好准备。再者,当即将到来的任务对实时性要求非常严格时,这种不同就会非常重要。
系统设计人员必须考虑所有因素,采用合适的方法设计专用系统。无论用哪种片上存储,都要非常小心,它必须足够快,能够让处理器不间断工作,又必须足够大,能够存下重要的程序,但又不能太快(导致功耗过大)或者太大(导致占用太多芯片空间)。
3. 高速缓存
目前每个 DRAM 器件芯片的存储容量大约为数百兆字节,随机存取时工作频率为几十兆赫兹,但微处理器每秒能发出几千万个记忆请求,也就是说,处理器运行速度比存储器读/写速度快很多,要发挥系统的潜能就必须解决高速缓存的问题。
高速缓存器目前还是容量小、速度快的存储器件,它保存最近用过的存储值。它的操作对于程序员是透明的,自动地决定什么值保留下来,什么值被覆盖。近来在同样的芯片上它被作为处理器而实现。缓存工作器的工作原理是基于程序访问的局部性的,就是说,在特定情况下,程序在同一数据段(比如堆栈)多次执行相同指令(如在循环中)。高速缓存可以用多种方式生成。在最高层一个处理器可以有如下两种组织方式之一:① 统一高速缓存,这种缓存由指令和数据公用,如图2.7所示;② Harvard结构缓存,这种高速缓存的指令和数据分开,如图2.8所示。两种组织方式各有特点。统一缓存能够根据当前程序需求自动调整缓存中用于存储指令的比例,性能比固定分配的要好。另一方面,分离的缓存允许在单一的时钟周期里载入和存储指令。
既然处理器以它的时钟频率来运行,那么当处理器需要的数据存储在缓存中时,整个系统的性能就在很大程度上取决于不能满足缓存的存储器所占的比例。在缓存中的一次存取称为一次命中(Hit),不在缓存中的一次存取称为一次遗漏(Miss)。所有存储器的存取满足缓存的比例称为命中率,通常表示为百分比,不满足缓存的比例称为遗漏率。要让一个处理器发挥其潜能,应精心设计缓存,使其遗漏率很小。遗漏率取决于缓存的一些参数,包括它的大小和组织方法。缓存的组织方法有三种:直接寻址(Direct-mapped)、固定相联(Set-associative)和完全相联(Fully associative),关于它们的详细介绍请读者参阅相关文献。
图2.7 统一的指令和数据缓存
图2.8 数据和指令分开的缓存
4. 高速缓存设计实例
第一个将ARM微处理器与片上高速缓存集成在一起的是1989年设计的ARMS,在以后的许多ARM内核中都保留和发展了这种设计。图2.9给出了ARMS高速缓存的组织方式。图中,虚拟地址(Virtual Address)的最低两位[1:0]作为字节地址(Byte Addresses)用于选中32-bit字的一个字节,接下来的两位[3:2]从高速缓存串中选出一个字,再下面两位[5:4]选择4个tag CAM即64入口的标识内容可寻址存储器(CAM,Content Addressable Memory)中的一个(图2.9中选中左数第3个),虚拟地址的其他位[31:6]用来检测数据是否在被选中的那个高速缓存单元中,显然,检查结果要么是遗漏,要么是数据和该高速缓存的地址一起命中。
ARMS已被设计成微处理器内核,在ARM6、ARM7、ARM9和ARM10中使用,具有高速、节电的特点。通常采用两个时钟操作:快的时钟确定处理器周期,当它进行高速缓存操作或向写缓冲写数据时使用;当处理器访问外部存储器时使用低速时钟,提供给内核的时钟动态地在这两个时钟源之间切换,而它们可能是相互异步的。
图2.9 ARMS高速缓存组织方式
2.6 基于ARM的嵌入式系统开发
选用ARM处理器开发嵌入式系统时,选择合适的开发工具可以加快开发进度,节省开发成本。因此,一套含有编辑、编译、汇编、链接和调试的软件及工程管理和函数库的集成开发环境(IDE,Integrated Development Environment)一般来说是必不可少的,至于嵌入式实时操作系统、评估板等其他开发工具则可以根据应用软件规模和开发计划选用。
使用 IDE 开发基于 ARM 的应用软件,包括编辑、编译、汇编、链接等工作全部在 PC上完成,调试工作则需要配合其他的模块或产品。目前常见的调试工具和方法有:指令集模拟器(Instruction Set Emulator)、驻留监控软件(Resident Monitors)、JTAG仿真器(Joint Test Action Group Emulator)、在线仿真器(On line Emulator)等,在本书的后面章节将对它们进行详细讨论。
嵌入式系统的设计包含硬件系统和软件系统两部分,这两部分的设计是互相关联、密不可分的,经常需要在它们之间进行权衡与折中,这就要求设计者具有较深厚的硬件和软件基础,并具有熟练应用的能力,这也许是嵌入式系统设计与其他纯粹的软件设计或硬件设计最大的区别。
Linux操作系统和C语言在嵌入式系统的软件设计中具有重要的意义,本书在后面章节中将贯穿对它们的详细讨论和应用。
评估开发板的设计以兼顾学习与应用为出发点,在保证完成ARM技术学习开发的同时,考虑了系统的扩展、电路板的面积、散热、电磁兼容性及安装等问题,因此,评估开发板也可作为嵌入式系统主板,直接应用在一些实际系统中。
2.7 本章小结
本章对ARM微处理器结构、指令系统、汇编程序的设计要点、编程模型和存储器的设计等给出了详细讨论,最后以一个ARM Linux评估开发板作为设计开发实例。它们是学习后面内容的基础。本书的重点是讨论嵌入式系统的开发应用,在后面各章将给出大量的实验和开发实例,它们主要是基于ARM微处理器的,因此在学习后面各章内容时再反过来读读本章,可能会对其有更深的理解。
思考题
1. ARM微处理器的特点是什么?它的应用领域有哪些?
2. ARM微处理器系列有哪些?它的发展趋势是什么?
3. 什么是RISC体系结构?
4. 选用ARM微处理器应该注意哪些问题?
5. ARM指令系统支持几种常见的寻址方式?
6. ARM指令集可以分为哪几类?
7. 什么是符号定义伪指令?常见的符号定义伪指令有哪几种?
8. 什么是汇编控制伪指令?
9. ARM微处理器的工作状态有几个?
10. ARM体系结构的存储器格式有哪些?