当前位置:首页 >> 信息与通信 >>

嵌入式系统原理与应用


嵌入式系统原理与应用

大连理工大学软件学院 嵌入式教研室 周宽久

一、

关于三星 S3C44B0X 目标板的 uClinux Bootloader

摘要: S3C44B0X 是常用的一款基于 ARM7TDMI 内核的 RISC 处理器。 uClinux 操作系统可以很 好地支持此类无 MMU

的处理器。本文介绍了在 S3C44B0X 目标板上移植 uClinux 的重要步骤 之一:Bootloader 开发的概念和过程。 关键词: S3C44B0X;uClinux;Bootloader;ARM Kernel XIP uClinux 是为控制领域设计的嵌入式 Linux 操作系统,它沿袭了主流 Linux 的大部分特性, 并进行了一定幅度的裁减。其设计主要针对没有内存管理单元(MMU)的微处理器,例如基于 ARM7TDMI 内核的 S3C44B0X。 嵌入式 Linux 系统通常由三部份组成: Bootloader、 Kernel 和 File System。 其中 Bootloader 是在系统启动之后、Kernel 运行之前所执行的第一段代码,其任务是为调用 Kernel 准备必 要的软硬件环境。由此可见,Bootloader 是非常依赖于硬件和操作系统的。所谓依赖于硬 件,是指 Bootloader 的实现与处理器体系架构和板级硬件资源密切相关;所谓依赖于操作 系统,是指不同操作系统的内核对调用方式和运行环境有不同的要求。 理论上,uClinux 在引导时并非一定需要一个独立于 Kernel Image 的 Bootloader Image。 然而将 Bootloader 与 Kernel 分开设计能够使软件架构更加清晰, 也有助于灵活地支持多种 引导方式,实现必要的辅助功能。uClinux Bootloader 的主要任务可概括如下: ● ● ● ● ● 引导和初始化 加载 uClinux Kernel 设置内核启动参数 调用 uClinux Kernel 辅助功能:文件下载、Flash 烧写、人机界面等

对于常见架构的处理器,一般都能找到现成的 Bootloader,但其结构往往较为复杂,且仍 需要针对具体的目标板进行移植。 当然, 也可以选择自行开发 Bootloader。 由于 Bootloader Image 在物理上独立于 Kernel Image,因此不一定跟随 Linux 选用 GNU 作为开发工具。对于 ARM 处理器,完全可以使用 ADS 或 RVDS 等集成环境来开发 Bootloader。 1.引导和初始化 1.1 硬件初始化阶段一 S3C44B0X 在上电或复位后,程序从位于地址 0x0 的 Reset Exception Vector 处开始执行, 因此需要在这里放置 Bootloader 的第一条指令: ResetHandler, b 跳转到标号 ResetHandler 处进行第一阶段的硬件初始化, 主要内容为: WDT, 关 关中断, 配置 PLL 和时钟, 初始化 Memory Controller。这里比较重要的是配置 PLL 的输出频率,S3C44B0X 最高能够支持 66MHz;如果 目标板上使用 DRAM/SDRAM,应当据此计算刷新频率等相关参数。

1.2 建立异常向量表 ARM7TDMI 内核规定:包括 Reset Exception Vector 在内的异常向量表的基地址是 0x0,所 以存放 Bootloader 的 Flash 基地址也必须是 0x0;而 S3C44B0X 处理器又不支持 Remap,这 意味着一旦发生中断,程序就要跳转到 Flash 中的异常向量表(中断属于异常的一种)。 uClinux 会在 RAM 里建立自己的二级异常向量表(基地址缺省为 0x0C000000);所以编写 Bootloader 时,0x0 处的一级异常向量表只需简单地包含向二级异常向量表的跳转: b ResetHandler ;Reset Handler ldr pc,=0x0c000004 ;Undefined Instruction Handler ldr pc,=0x0c000008 ;Software Interrupt Handler ldr pc,=0x0c00000c ;Prefetch Abort Handler ldr pc,=0x0c000010 ;Data Abort Handler b . ;Reserved ldr pc,=0x0c000018 ;IRQ Handler ldr pc,=0x0c00001c ;FIQ Handler 如果在 Bootloader 运行过程中不必响应中断,那么上面的配置已能满足要求。如果某些 Bootloader 功能要求使用中断(例如用 Timer Interrupt 实现精确定时),那么 Bootloader 必须在同样的位置建立自己的二级异常向量表, 以便同 uClinux 保持一致。 这张表应存放在 Flash 中,并由 Bootloader 复制到 RAM 地址 0x0C000000 处。 1.3 初始化各种处理器模式 ARM7TDMI 内核支持 7 种处理器模式:User,FIQ,IRQ,Supervisor,Abort,System 和 Undefined。Bootloader 需要依次切换到每种模式,初始化该模式的程序状态寄存器(CPSR) 和堆栈指针(SP)。S3C44B0X 在上电或复位后处于 Supervisor 模式;本步骤中应该在最后切 换回 Supervisor 模式,即 Bootloader 后续部份仍将运行在 Supervisor 模式下。 1.4 section 重定位 对于 ADS 或 RVDS 等开发工具,一个 ARM 程序通常由 RO、RW 和 ZI 三个 section 组成,其中 RO 是代码和常量,RW 是已初始化的全局变量,ZI 是未初始化的全局变量(在 GNU 中对应的 概念是 TEXT、DATA 和 BSS)。RO 代码既可以在 Flash 中运行,也可以在 RAM 中运行。考虑到 Bootloader 可能需要烧写 Flash,而烧写时处理器无法从 Flash 中读取指令,因此应将 RO 和 RW 复制到 RAM 中,并将 ZI 清零。RO 复制完毕之后,程序就可以跳转到 RAM 中运行。若 不考虑烧写 Flash,则 Bootloader 不必复制 RO,程序始终在 Flash 中运行。 1.5 填写中断向量表 中断向量表一般位于 RAM 地址的最高端,存放着各个 ISR 的入口地址。由于 IRQ Exception 为全部中断所共用,因此必须在 IRQ Exception 服务例程中根据中断状态寄存器来判断中断 源并调用相应的 ISR。各个 ISR 的入口地址需要在这一步里填写。

另外,S3C44B0X 的中断控制器支持 Vectored 和 Non-Vectored 两种中断处理模式,其中前 者是 Samsung 自行开发的模式,并不被大多数 ARM 处理器所支持。考虑到代码的可移植性, 上面只讨论了 Non-Vectored Mode。 1.6 硬件初始化阶段二 遵循“必要”原则继续对硬件资源进行初始化,包括 S3C44B0X 内置的 GPIO、Cache、中断 控制器和 UART 等。Bootloader 中暂未用到的设备可以留待使用之前再进行初始化。 S3C44B0X 内置有数据/指令合一的 8KB Cache, 且允许按照地址范围设置两个 Non-Cacheable 的区间。合理的配置是打开对 RAM 地址区间的 Cache,关闭对其它地址区间的 Cache,以避 免可能存在的 Cache 一致性问题。 1.7 建立人机界面 引导过程的最后一步是在串行终端上建立人机交互界面。常见的做法是先等待固定的时间, 若未接收到用户输入,则直接从 Flash 中加载或调用 uClinux Kernel;若接收到用户输入, 则显示菜单模式或命令行模式的交互界面,并等待进一步的命令。 2. 加载 uClinux Kernel Bootloader 是否需要执行加载操作,取决于 uClinux Kernel Image 的类型。根据不同的配 置方式,可以生成以下几种 uClinux Kernel Image: 2.1 非压缩,非 XIP XIP(eXecute In Place)是指在存放代码的位置上就地运行程序;而非 XIP 就是指在运行之 前需要对代码进行重定位。该类型的 uClinux Kernel Image 以非压缩格式存放在 Flash 中, 需由 Bootloader 加载到 RAM 然后调用。该类型在开发调试阶段最为常用。 2.2 非压缩,XIP 该类型的 uClinux Kernel Image 以非压缩格式存放在 Flash 中,不需加载,由 Bootloader 直接调用。 复制 Data 段和清零 BSS 段的工作由 Kernel 自行完成。 该类型常用于 RAM 空间非 常有限的系统中,缺点是程序在 Flash 中运行的速度稍慢。 2.3 RAM 自解压 压缩格式的 uClinux Kernel Image 由开头的一段自解压代码和其后的压缩数据组成。由于 是以压缩格式存放, 因此 Kernel 只能以非 XIP 方式运行。 自解压的 uClinux Kernel Image RAM 存放在 Flash 中,由 Bootloader 加载到 RAM 中的临时空间,然后调用自解压代码。Kernel 被解压到最终的目标空间然后运行;压缩镜像所占据的临时空间在随后由 uClinux 回收利 用。该类型占用 Flash 较少,且运行速度较快,在最终产品中更为常见。 2.4 ROM 自解压

解压缩代码也能够以 XIP 的方式在 Flash 中运行。ROM 自解压的 uClinux Kernel Image 存 放在 Flash 中,不需加载,由 Bootloader 直接调用其自解压代码,将 uClinux Kernel 解压 到最终的目标空间并运行之。与 RAM 自解压相比,ROM 自解压并不真正节省 RAM,而且解压 缩的速度较慢,因此实用价值不大。 3. 设置内核启动参数 Linux 2.4 版本以后的内核都期望以标记列表(tagged list)的形式来接收启动参数。每个 标记存放在一个 tag 结构中,每个 tag 结构由标识被传递参数的 tag_header 结构以及随后 的参数值组成。通常由 Bootloader 设置的启动参数有:ATAG_MEM、ATAG_CMDLINE、 ATAG_SERIAL 等。启动参数的标记列表以 ATAG_CORE 开始,以 ATAG_NONE 结束,代码示例如 下。其中 0x0C000100 是内核启动参数在 RAM 中的基地址,Bootloader 应当将要传递的启动 参数复制到该处 RAM 中;指针 params 的类型是 struct tag。宏 tag_next()以指向当前标记 的指针为参数,计算下一个标记的起始地址。 params = (struct tag *)0x0C000100; params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size(tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next(params); ...... params->hdr.tag = ATAG_NONE; params->hdr.size = 0; 对应地,在 Linux 内核源码 arch/armnommu/mach-s3c44b0/arch.c 中设置内核启动参数在 RAM 中的基地址: MACHINE_START (S3C44B0, "44B0") ...... BOOT_PARAMS (0x0C000100) ...... MACHINE_END 4. 调用 uClinux Kernel Bootloader 调用 Kernel 的方法是直接跳转到其第一条指令处。对于 ARM 处理器,在跳转时 应当满足下列条件:r0=0;r1=Machine ID;禁止 IRQ 和 FIQ;处理器运行在 Supervisor 模式;关闭 MMU;关闭 Data Cache。 对于 S3C44B0X,它没有 MMU,其 Cache 是指令与数据合一的,因此只能全部关闭。 S3C44B0X 的 Machine ID 各种 ARM 处理器的 Machine ID 均由 www.arm.linux.org.uk 分配; 是 178。据此,用 C 代码实现的 Kernel 调用示例如下,其中 r0 和 r1 的值通过参数传递:

void (*CallKernel)(int zero, int mach) = (void (*)(int, int))KERNEL_ADDR; CallKernel(0, 178); 5. 辅助功能 完整的 Bootloader 还应该允许更新 Flash 中存放的 uClinux Kernel Image 以及 Bootloader 自身。为此,必要的辅助功能包括:从主机下载文件到目标板的 RAM;用 RAM 中的数据烧写 Flash;以及实现上述操作所需的人机交互接口,这里就不赘述了。 参考文献: [1] 李善平,刘文峰等,Linux 与嵌入式系统,清华大学出版社,2003 年 1 月 [2] 詹荣开,嵌入式系统 Boot Loader 技术内幕,2003 年 12 月 [3] ARM Architecture Reference Manual, ARM, 2000 [4] S3C44B0X RISC Microprocessor User's Manual, Samsung, 2002

二、 基于 ARM 微处理器的 Linux 驱动程序设计
设备驱动程序是操作系统内核和机器硬件之间的接口,它为应用程序屏蔽硬件的细节,一般来说,Linux 的设备驱动程序需要完成如下功能:

·设备初始化、释放;

·提供各类设备服务;

·负责内核和设备之间的数据交换;

·检测和处理设备工作过程中出现的错误。 Linux 下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得 Windows 的设 备操作犹如文件一般。在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样 对硬件设备进行操作,如 open ()、close ()、read ()、write () 等。 Linux 主要将设备分为二类: 字符设备和块设备。 字符设备是指设备发送和接收数据以字符的形式进行; 而块设备则以整个数据缓冲区的形式进行。在对字符设备发出读/写请求时,实际的硬件 I/O 一般就紧接着 发生了;而块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就 返回请求的数据,如果不能,就调用请求函数来进行实际的 I/O 操作。块设备主要针对磁盘等慢速设备。 1.内存分配 内存分配 由于 Linux 驱动程序在内核中运行,因此在设备驱动程序需要申请/释放内存时,不能使用用户级的 malloc/free 函数,而需由内核级的函数 kmalloc/kfree () 来实现,kmalloc()函数的原型为:

void kmalloc (size_t size ,int priority);
参数 size 为申请分配内存的字节数,kmalloc 最多只能开辟 128k 的内存;参数 priority 说明若 kmalloc() 不能马上分配内存时用户进程要采用的动作:GFP_KERNEL 表示等待,即等 kmalloc()函数将一些内存安 排到交换区来满足你的内存需要,GFP_ATOMIC 表示不等待,如不能立即分配到内存则返回 0 值;函数 的返回值指向已分配内存的起始地址,出错时,返回 0。

kmalloc ()分配的内存需用 kfree()函数来释放,kfree ()被定义为:

# define kfree (n) kfree_s( (n) ,0)
其中 kfree_s () 函数原型为:

void kfree_s (void * ptr ,int size);
参数 ptr 为 kmalloc()返回的已分配内存的指针,size 是要释放内存的字节数,若为 0 时,由内核自动确定 内存的大小。 2.中断 中断 许多设备涉及到中断操作,因此,在这样的设备的驱动程序中需要对硬件产生的中断请求提供中断服务程 序。 与注册基本入口点一样, 驱动程序也要请求内核将特定的中断请求和中断服务程序联系在一起。 Linux 在 中,用 request_irq()函数来实现请求:

int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);
参数 irq 为要中断请求号,参数 handler 为指向中断服务程序的指针,参数 type 用来确定是正常中断还是 快速中断(正常中断指中断服务子程序返回后,内核可以执行调度程序来确定将运行哪一个进程;而快速 中断是指中断服务子程序返回后,立即执行被中断程序,正常中断 type 取值为 0 ,快速中断 type 取值为 SA_INTERRUPT),参数 name 是设备驱动程序的名称。 3.字符设备驱动 字符设备驱动 我们必须为字符设备提供一个初始化函数,该函数用来完成对所控设备的初始化工作,并调用 register_chrdev() 函数注册字符设备。假设有一字符设备"exampledev",则其 init 函数为:

void exampledev_init(void) { if (register_chrdev(MAJOR_NUM, " exampledev &exampledev_fops)) TRACE_TXT("Device exampledev driver registered error"); else

",

TRACE_TXT("Device exampledev driver registered successfully"); …//设备初始化 }
其中, register_chrdev 函数中的参数 MAJOR_NUM 为主设备号,"exampledev"为设备名, exampledev_fops 为包含基本函数入口点的结构体,类型为 file_operations。当执行 exampledev_init 时,它将调用内核函数 register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备地址表中,在用户进程对该设备执 行系统调用时提供入口地址。 较早版本内核的 file_operations 结构体定义为(代码及图示):

struct file_operations { int (*lseek)(); int (*read)(); int (*write)(); int (*readdir)(); int (*select)(); int (*ioctl)(); int (*mmap)(); int (*open)(); void(*release)(); int (*fsync)(); int (*fasync)(); int (*check_media_change)(); void(*revalidate)(); };

随着内核功能的加强,file_operations 结构体也变得更加庞大。但是大多数的驱动程序只是利用了其中的 一部分,对于驱动程序中无需提供的功能,只需要把相应位置的值设为 NULL。对于字符设备来说,要提 供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()等。 对设备特殊文件进行 open()系统调用时,将调用驱动程序的 open () 函数:

open()函数

int (*open)(struct inode * inode,struct file *filp);
其中参数 inode 为设备特殊文件的 inode (索引结点) 结构的指针,参数 filp 是指向这一设备的文件结构的 指 针 。 open() 的 主 要 任 务 是 确 定 硬 件 处 在 就 绪 状 态 、 验 证 次 设 备 号 的 合 法 性 ( 次 设 备 号 可 以 用 MINOR(inode-> i_rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码(0 表示成功,负数表示 存在错误) 等; 当最后一个打开设备的用户进程执行 close ()系统调用时, 内核将调用驱动程序的 release ()

release()函数 函数:

void (*release) (struct inode * inode,struct file *filp) ;
release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。 当对设备特殊文件进行 read() 系统调用时,将调用驱动程序 read() 函数:

read()函数

ssize_t (*read) (struct file * filp, char * buf, size_t count, loff_t * offp);
参数 buf 是指向用户空间缓冲区的指针,由用户进程给出,count 为用户进程要求读取的字节数,也 由用户给出。 read() 函数的功能就是从硬设备或内核内存中读取或复制 count 个字节到 buf 指定的缓冲区中。在复 制数据时要注意,驱动程序运行在内核中,而 buf 指定的缓冲区在用户内存区中,是不能直接在内核中访 问使用的, 因此, 必须使用特殊的复制函数来完成复制工作, 这些函数在 include/asm/uaccess.h 中被声明:

unsigned long copy_to_user (void * to, void * from, unsigned long len);
此外,put_user()函数用于内核空间和用户空间的单值交互(如 char、int、long)。 write( ) 函数 当设备特殊文件进行 write () 系统调用时,将调用驱动程序的 write () 函数:

ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

write ()的功能是将参数 buf 指定的缓冲区中的 count 个字节内容复制到硬件或内核内存中,和 read() 一样,复制工作也需要由特殊函数来完成:

unsigned long copy_from_user(void *to, const void *from, unsigned long n);
此外,get_user()函数用于内核空间和用户空间的单值交互(如 char、int、long)。 ioctl() 函数 数原型为: 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函

int (*ioctl) (struct inode * inode,struct file * filp,unsigned int cmd,unsigned long arg);
参数 cmd 为设备驱动程序要执行的命令的代码,由用户自定义,参数 arg 为相应的命令提供参数,类 型可以是整型、指针等。 同样,在驱动程序中,这些函数的定义也必须符合命名规则,按照本文约定,设备"exampledev"的驱 动 程 序 的 这 些 函 数 应 分 别 命 名 为 exampledev_open 、 exampledev_ release 、 exampledev_read 、 exampledev_write、exampledev_ioctl,因此设备"exampledev"的基本入口点结构变量 exampledev_fops 赋值如下(对较早版本的内核):

struct file_operations exampledev_fops { NULL , exampledev_read , exampledev_write , NULL , NULL , exampledev_ioctl , NULL , exampledev_open , exampledev_release , NULL , NULL , NULL , NULL } ;
就目前而言,由于 file_operations 结构体已经很庞大,我们更适合用 GNU 扩展的 C 语法来初始化 exampledev_fops:

struct file_operations exampledev_fops = {

read: exampledev _read, write: exampledev _write, ioctl: exampledev_ioctl , open: exampledev_open , release : exampledev_release , };
看看第一章电路板硬件原理图,板上包含四个用户可编程的发光二极管(LED),这些 LED 连接在 ARM 处理器的可编程 I/O 口(GPIO)上,现在来编写这些 LED 的驱动:

#include <linux/config.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/miscdevice.h> #include <linux/sched.h> #include <linux/delay.h> #include <asm/hardware.h> #define DEVICE_NAME "leds" /*定义 led 设备的名字*/ #define LED_MAJOR 231 /*定义 led 设备的主设备号*/ static unsigned long led_table[] = { /*I/O 方式 led 设备对应的硬件资源*/ GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6, }; /*使用 ioctl 控制 led*/ static int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case 0: case 1: if (arg > 4) { return -EINVAL; } write_gpio_bit(led_table[arg], !cmd); default: return -EINVAL; } } static struct file_operations leds_fops = {

owner: THIS_MODULE, ioctl: leds_ioctl, }; static devfs_handle_t devfs_handle; static int __init leds_init(void) { int ret; int i; /*在内核中注册设备*/ ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &leds_fops); if (ret < 0) { printk(DEVICE_NAME " can't register major number\n"); return ret; } devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, LED_MAJOR, 0, S_IFCHR | S_IRUSR | S_IWUSR, &leds_fops, NULL); /*使用宏进行端口初始化,set_gpio_ctrl 和 write_gpio_bit 均为 宏定义*/ for (i = 0; i < 8; i++) { set_gpio_ctrl(led_table[i] | GPIO_PULLUP_EN | GPIO_MODE_OUT); write_gpio_bit(led_table[i], 1); } printk(DEVICE_NAME " initialized\n"); return 0; } static void __exit leds_exit(void) { devfs_unregister(devfs_handle); unregister_chrdev(LED_MAJOR, DEVICE_NAME); } module_init(leds_init); module_exit(leds_exit);
使用命令方式编译 led 驱动模块:

#arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include -DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c

以上命令将生成 leds.o 文件,把该文件复制到板子的/lib 目录下,使用以下命令就可以安装 leds 驱动 模块:

#insmod /lib/ leds.o
删除该模块的命令是:

#rmmod leds
4.块设备驱动 块设备驱动 块设备驱动程序的编写是一个浩繁的工程,其难度远超过字符设备,上千行的代码往往只能搞定一个 简单的块设备,而数十行代码就可能搞定一个字符设备。因此,非得有相当的基本功才能完成此项工作。 下面先给出一个实例,即 mtdblock 块设备的驱动。我们通过分析此实例中的代码来说明块设备驱动程序的 写法(由于篇幅的关系,大量的代码被省略,只保留了必要的主干):

#include <linux/config.h> #include <linux/devfs_fs_kernel.h> static void mtd_notify_add(struct mtd_info* mtd); static void mtd_notify_remove(struct mtd_info* mtd); static struct mtd_notifier notifier = { mtd_notify_add, mtd_notify_remove, NULL }; static devfs_handle_t devfs_dir_handle = NULL; static devfs_handle_t devfs_rw_handle[MAX_MTD_DEVICES]; static struct mtdblk_dev { struct mtd_info *mtd; /* Locked */ int count; struct semaphore cache_sem; unsigned char *cache_data; unsigned long cache_offset; unsigned int cache_size; enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; } *mtdblks[MAX_MTD_DEVICES]; static spinlock_t mtdblks_lock; /* this lock is used just in kernels >= 2.5.x */ static spinlock_t mtdblock_lock; static int mtd_sizes[MAX_MTD_DEVICES];

static int mtd_blksizes[MAX_MTD_DEVICES]; static void erase_callback(struct erase_info *done) { wait_queue_head_t *wait_q = (wait_queue_head_t *)done->priv; wake_up(wait_q); } static int erase_write (struct mtd_info *mtd, unsigned long pos, int len, const char *buf) { struct erase_info erase; DECLARE_WAITQUEUE(wait, current); wait_queue_head_t wait_q; size_t retlen; int ret; /* * First, let's erase the flash block. */ init_waitqueue_head(&wait_q); erase.mtd = mtd; erase.callback = erase_callback; erase.addr = pos; erase.len = len; erase.priv = (u_long)&wait_q; set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&wait_q, &wait); ret = MTD_ERASE(mtd, &erase); if (ret) { set_current_state(TASK_RUNNING); remove_wait_queue(&wait_q, &wait); printk (KERN_WARNING "mtdblock: erase of region [0x%lx, 0x%x] " "on \"%s\" failed\n", pos, len, mtd->name); return ret; } schedule(); /* Wait for erase to finish. */ remove_wait_queue(&wait_q, &wait);

/* * Next, writhe data to flash. */ ret = MTD_WRITE (mtd, pos, len, &retlen, buf); if (ret) return ret; if (retlen != len) return -EIO; return 0; } static int write_cached_data (struct mtdblk_dev *mtdblk) { struct mtd_info *mtd = mtdblk->mtd; int ret; if (mtdblk->cache_state != STATE_DIRTY) return 0; DEBUG(MTD_DEBUG_LEVEL2, "mtdblock: writing cached data for \"%s\" " "at 0x%lx, size 0x%x\n", mtd->name, mtdblk->cache_offset, mtdblk->cache_size); ret = erase_write (mtd, mtdblk->cache_offset, mtdblk->cache_size, mtdblk->cache_data); if (ret) return ret; mtdblk->cache_state = STATE_EMPTY; return 0; } static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, int len, const char *buf) { … } static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, int len, char *buf)

{ … } static int mtdblock_open(struct inode *inode, struct file *file) { … } static release_t mtdblock_release(struct inode *inode, struct file *file) { int dev; struct mtdblk_dev *mtdblk; DEBUG(MTD_DEBUG_LEVEL1, "mtdblock_release\n"); if (inode == NULL) release_return(-ENODEV); dev = minor(inode->i_rdev); mtdblk = mtdblks[dev]; down(&mtdblk->cache_sem); write_cached_data(mtdblk); up(&mtdblk->cache_sem); spin_lock(&mtdblks_lock); if (!--mtdblk->count) { /* It was the last usage. Free the device */ mtdblks[dev] = NULL; spin_unlock(&mtdblks_lock); if (mtdblk->mtd->sync) mtdblk->mtd->sync(mtdblk->mtd); put_mtd_device(mtdblk->mtd); vfree(mtdblk->cache_data); kfree(mtdblk); } else { spin_unlock(&mtdblks_lock); } DEBUG(MTD_DEBUG_LEVEL1, "ok\n"); BLK_DEC_USE_COUNT; release_return(0);

} /* * This is a special request_fn because it is executed in a process context * to be able to sleep independently of the caller. The * io_request_lock (for <2.5) or queue_lock (for >=2.5) is held upon entry * and exit. The head of our request queue is considered active so there is * no need to dequeue requests before we are done. */ static void handle_mtdblock_request(void) { struct request *req; struct mtdblk_dev *mtdblk; unsigned int res; for (;;) { INIT_REQUEST; req = CURRENT; spin_unlock_irq(QUEUE_LOCK(QUEUE)); mtdblk = mtdblks[minor(req->rq_dev)]; res = 0; if (minor(req->rq_dev) >= MAX_MTD_DEVICES) panic("%s : minor out of bound", __FUNCTION__); if (!IS_REQ_CMD(req)) goto end_req; if ((req->sector (mtdblk->mtd->size >> 9)) goto end_req; // Handle the request switch (rq_data_dir(req)) { int err; case READ: down(&mtdblk->cache_sem); err = do_cached_read (mtdblk, req->sector << 9, req->current_nr_sectors << 9, + req->current_nr_sectors) >

req->buffer); up(&mtdblk->cache_sem); if (!err) res = 1; break; case WRITE: // Read only device if ( !(mtdblk->mtd->flags & MTD_WRITEABLE) ) break; // Do the write down(&mtdblk->cache_sem); err = do_cached_write (mtdblk, 9,req->current_nr_sectors << 9, req->buffer); up(&mtdblk->cache_sem); if (!err) res = 1; break; } end_req: spin_lock_irq(QUEUE_LOCK(QUEUE)); end_request(res); } } static volatile int leaving = 0; static DECLARE_MUTEX_LOCKED(thread_sem); static DECLARE_WAIT_QUEUE_HEAD(thr_wq); int mtdblock_thread(void *dummy) { … } #define RQFUNC_ARG request_queue_t *q static void mtdblock_request(RQFUNC_ARG) { /* Don't do anything, except wake the thread if necessary */ wake_up(&thr_wq); }

req->sector

<<

static int mtdblock_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) { struct mtdblk_dev *mtdblk; mtdblk = mtdblks[minor(inode->i_rdev)]; switch (cmd) { case BLKGETSIZE: /* Return device size */ return put_user((mtdblk->mtd->size >> 9), (unsigned long *) arg); case BLKFLSBUF: if(!capable(CAP_SYS_ADMIN)) return -EACCES; fsync_dev(inode->i_rdev); invalidate_buffers(inode->i_rdev); down(&mtdblk->cache_sem); write_cached_data(mtdblk); up(&mtdblk->cache_sem); if (mtdblk->mtd->sync) mtdblk->mtd->sync(mtdblk->mtd); return 0; default: return -EINVAL; } } static struct block_device_operations mtd_fops = { owner: THIS_MODULE, open: mtdblock_open, release: mtdblock_release, ioctl: mtdblock_ioctl }; static void mtd_notify_add(struct mtd_info* mtd) { … } static void mtd_notify_remove(struct mtd_info* mtd) { if (!mtd || mtd->type == MTD_ABSENT) return;

devfs_unregister(devfs_rw_handle[mtd->index]); } int __init init_mtdblock(void) { int i; spin_lock_init(&mtdblks_lock); /* this lock is used just in kernels >= 2.5.x */ spin_lock_init(&mtdblock_lock); #ifdef CONFIG_DEVFS_FS if (devfs_register_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME, &mtd_fops)) { printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", MTD_BLOCK_MAJOR); return -EAGAIN; } devfs_dir_handle = devfs_mk_dir(NULL, DEVICE_NAME, NULL); register_mtd_user(&notifier); #else if (register_blkdev(MAJOR_NR,DEVICE_NAME,&mtd_fops)) { printk(KERN_NOTICE "Can't allocate major number %d for Memory Technology Devices.\n", MTD_BLOCK_MAJOR); return -EAGAIN; } #endif /* We fill it in at open() time. */ for (i=0; i< MAX_MTD_DEVICES; i++) { mtd_sizes[i] = 0; mtd_blksizes[i] = BLOCK_SIZE; } init_waitqueue_head(&thr_wq); /* Allow the block size to default to BLOCK_SIZE. */ blksize_size[MAJOR_NR] = mtd_blksizes; blk_size[MAJOR_NR] = mtd_sizes; BLK_INIT_QUEUE(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request,

&mtdblock_lock); kernel_thread (mtdblock_thread, CLONE_FS|CLONE_FILES|CLONE_SIGHAND); return 0; } NULL,

static void __exit cleanup_mtdblock(void) { leaving = 1; wake_up(&thr_wq); down(&thread_sem); #ifdef CONFIG_DEVFS_FS unregister_mtd_user(&notifier); devfs_unregister(devfs_dir_handle); devfs_unregister_blkdev(MTD_BLOCK_MAJOR, DEVICE_NAME); #else unregister_blkdev(MAJOR_NR,DEVICE_NAME); #endif blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR)); blksize_size[MAJOR_NR] = NULL; blk_size[MAJOR_NR] = NULL; } module_init(init_mtdblock); module_exit(cleanup_mtdblock);
从上述源代码中我们发现,块设备也以与字符设备 register_chrdev、unregister_ chrdev 函数类似的 方法进行设备的注册与释放:

int register_blkdev(unsigned int major, const char *name, struct block_device_operations *bdops); int unregister_blkdev(unsigned int major, const char *name);
但 是 , register_chrdev 使 用 一 个 向 file_operations 结 构 的 指 针 , 而 register_blkdev 则 使 用 block_device_operations 结构的指针,其中定义的 open、release 和 ioctl 方法和字符设备的对应方法相 同,但未定义 read 或者 write 操作。这是因为,所有涉及到块设备的 I/O 通常由系统进行缓冲处理。 块驱动程序最终必须提供完成实际块 I/O 操作的机制,在 Linux 当中,用于这些 I/O 操作的方法称 为"request(请求)"。在块设备的注册过程中,需要初始化 request 队列,这一动作通过 blk_init_queue 来完成,blk_init_queue 函数建立队列,并将该驱动程序的 request 函数关联到队列。在模块的清除阶段, 应调用 blk_cleanup_queue 函数。 本例中相关的代码为:

BLK_INIT_QUEUE(BLK_DEFAULT_QUEUE(MAJOR_NR), &mtdblock_request, &mtdblock_lock); blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
每个设备有一个默认使用的请求队列,必要时,可使用 BLK_DEFAULT_QUEUE(major) 宏得到该默 认队列。这个宏在 blk_dev_struct 结构形成的全局数组(该数组名为 blk_dev)中搜索得到对应的默认队 列。blk_dev 数组由内核维护,并可通过主设备号索引。blk_dev_struct 接口定义如下:

struct blk_dev_struct { /* * queue_proc has to be atomic */ request_queue_t request_queue; queue_proc *queue; void *data; };
request_queue 成员包含了初始化之后的 I/O 请求队列,data 成员可由驱动程序使用,以便保存一 些私有数据。 request_queue 定义为:

struct request_queue { /* * the queue request freelist, one for reads and one for writes */ struct request_list rq[2]; /* * Together with queue_head for cacheline sharing */ struct list_head queue_head; elevator_t elevator; request_fn_proc * request_fn; merge_request_fn * back_merge_fn; merge_request_fn * front_merge_fn; merge_requests_fn * merge_requests_fn; make_request_fn * make_request_fn; plug_device_fn * plug_device_fn; /* * The queue owner gets to use this for whatever they like.

* ll_rw_blk doesn't touch it. */ void * queuedata; /* * This is used to remove the plug when tq_disk runs. */ struct tq_struct plug_tq; /* * Boolean that indicates whether this queue is plugged or not. */ char plugged; /* * Boolean that indicates whether current_request is active or * not. */ char head_active; /* * Is meant to protect the queue in the future instead of * io_request_lock */ spinlock_t queue_lock; /* * Tasks wait here for free request */ wait_queue_head_t wait_for_request; };
下图表征了 blk_dev、blk_dev_struct 和 request_queue 的关系:

下图则表征了块设备的注册和释放过程:

5.小结 小结 本章讲述了 Linux 设备驱动程序的入口函数及驱动程序中的内存申请、中断等,并分别以实例讲述了字符 设备及块设备的驱动开发方法。

三、两种嵌入式操作系统的比较 详细内容:ARM 的希望:两种嵌入式操作系统的比较

摘 要: 嵌入式操作系统是嵌入式系统应用的核心。本文通过对两种典型的开源嵌入式 操作系统的对比,分析和总结了嵌入式操作系统应用中的若干问题,归纳了嵌入式操作系 统的选型依据。

关键词: 嵌入式;操作系统;mC/OS;mClinux 引言 随着现代计算机技术的飞速发展和互联网技术的广泛应用,从 PC 时代过渡 到了以个人数字助理、手持个人电脑和信息家电为代表的 3C(计算机、通信、消 费电子)一体的后 PC 时代。后 PC 时代里,嵌入式系统扮演了越来越重要的角色, 被广泛应用于信息电器、移动计算机设备、网络设备和工控仿真等领域。嵌入式 系统的开发也成为近年 IT 行业的技术热点。 完成简单功能的嵌入式系统一般不需要操作系统,如以前许多 MCS51 系列单 片机组成的小系统就只是利用软件实现简单的控制环路。但是随着所谓后 PC 时 代的来临,嵌入式系统设计日趋复杂,嵌入式操作系统就必不可少了。 一般而言,嵌入式操作系统不同于一般意义的计算机操作系统,它有占用空 间小、执行效率高、方便进行个性化定制和软件要求固化存储等特点。 从八十年代起,国际上就有一些 IT 组织、公司,开始进行商用嵌入式系统和 专用操作系统的研发。这其中涌现了一些著名的嵌入式系统,如 Microsoft 公司 的 WinCE 和 WindRiverSystem 公司的 VxWorks 就分别是非实时和实时嵌入式操作 系统的代表。但是商用产品的造价都十分昂贵,用于一般用途会提高产品成本从 而失去竞争力。 mC/OS 和 uClinux 操作系统是两种性能优良源码公开且被广泛应用的的免费 嵌入式操作系统,可以作为研究实时操作系统和非实时操作系统的典范。本文通 过对 mC/OS 和 mClinux 的对比,分析和总结了嵌入式操作系统应用中的若干重要 问题,归纳了嵌入式系统开发中操作系统的选型依据。 两种开源嵌入式操作系统介绍 mC/OS 和 mClinux 操作系统,是当前得到广泛应用的两种免费且公开源码的 嵌入式操作系统。mC/OS 适合小型控制系统,具有执行效率高、占用空间小、实 时性能优良和可扩展性强等特点,最小内核可编译至 2k。mClinux 则是继承标准 Linux 的优良特性,针对嵌入式处理器的特点设计的一种操作系统,具有内嵌网 络协议、支持多种文件系统,开发者可利用标准 Linux 先验知识等优势。其编译 后目标文件可控制在几百 K 量级。 mC/OS 是一种免费公开源代码、结构小巧、具有可剥夺实时内核的实时操作 系统。其内核提供任务调度与管理、时间管理、任务间同步与通信、内存管理和 中断服务等功能。

mClinux 是一种优秀的嵌入式 Linux 版本。mClinux 是 Micro-Conrol-Linux 的缩写。同标准 Linux 相比,它集成了标准 Linux 操作系统的稳定性、强大网络 功能和出色的文件系统等主要优点。但是由于没有 MMU(内存管理单元),其多任 务的实现需要一定技巧。 两种嵌入式操作系统主要性能比较 嵌入式操作系统是嵌入式系统软硬件资源的控制中心,它以尽量合理的有效 方法组织多个用户共享嵌入式系统的各种资源。 其中用户指的是系统程序之上的 所有软件。所谓合理有效的方法,指的就是操作系统如何协调并充分利用硬件资 源来实现多任务。复杂的操作系统都支持文件系统,方便组织文件并易于对其规 范化操作。 嵌入式操作系统还有一个特点就是针对不同的平台,系统不是直接可用的, 一般需要经过针对专门平台的移植操作系统才能正常工作。 进程调度、 文件系统支持和系统移植是在嵌入式操作系统实际应用中最常见 的问题,下文就从这几个角度入手对 mC/OS 和 mClinux 进行分析比较。 进程调度 任务调度主要是协调任务对计算机系统内资源(如内存、I/O 设备、CPU)的 争夺使用。进程调度又称为 CPU 调度,其根本任务是按照某种原则为处于就绪状 态的进程分配 CPU。由于嵌入式系统中内存和 I/O 设备一般都和 CPU 同时归属于 某进程,所以任务调度和进程调度概念相近,很多场合不加区分,下文中提到的任 务其实就是进程的概念。 进程调度可分为“剥夺型调度”和“非剥夺型调度”两种基本方式。所谓 “非剥夺型调度”是指:一旦某个进程被调度执行,则该进程一直执行下去直至 该进程结束,或由于某种原因自行放弃 CPU 进入等待状态,才将 CPU 重新分配给其 他进程。所谓“剥夺型调度”是指:一旦就绪状态中出现优先权更高的进程,或 者运行的进程已用满了规定的时间片时,便立即剥夺当前进程的运行(将其放回 就绪状态),把 CPU 分配给其他进程。 作为实时操作系统,mC/OS 是采用的可剥夺型实时多任务内核。可剥夺型的 实时内核在任何时候都运行就绪了的最高优先级的任务。mC/OS 中最多可以支持 64 个任务,分别对应优先级 0~63,其中 0 为最高优先级。调度工作的内容可以分 为两部分:最高优先级任务的寻找和任务切换。 其最高优先级任务的寻找是通过建立就绪任务表来实现的。mC/OS 中的每一 个任务都有独立的堆栈空间,并有一个称为任务控制块 TCB(Task Control Block) 数据结构,其中第一个成员变量就是保存的任务堆栈指针。任务调度模块首先用 变量 OSTCBHighRdy 记录当前最高级就绪任务的 TCB 地址,然后调用 OS_TASK_SW() 函数来进行任务切换。

mClinux 的进程调度沿用了 Linux 的传统,系统每隔一定时间挂起进程,同时 系统产生快速和周期性的时钟计时中断,并通过调度函数(定时器处理函数)决定 进程什么时候拥有它的时间片。然后进行相关进程切换,这是通过父进程调用 fork 函数生成子进程来实现的。 mClinux 系统 fork 调用完成后,要么子进程代替父进程执行(此时父进程已 经 sleep),直到子进程调用 exit 退出;要么调用 exec 执行一个新的进程,这个时 候产生可执行文件的加载,即使这个进程只是父进程的拷贝,这个过程也不可避 免。当子进程执行 exit 或 exec 后,子进程使用 wakeup 把父进程唤醒,使父进程 继续往下执行。 mClinux 由于没有 MMU 管理存储器,其对内存的访问是直接的,所有程序中访 问的地址都是实际的物理地址。操作系统队内存空间没有保护,各个进程实际上 共享一个运行空间。这就需要实现多进程时进行数据保护,也导致了用户程序使 用的空间可能占用到系统内核空间,这些问题在编程时都需要多加注意,否则容 易导致系统崩溃。 由上述分析可以得知,mC/OS 内核是针对实时系统的要求设计实现的,相对 简单,可以满足较高的实时性要求。而 mClinux 则在结构上继承了标准 Linux 的 多任务实现方式,仅针对嵌入式处理器特点进行改良。其要实现实时性效果则需 要使系统在实时内核的控制下运行,RT-Linux 就是可以实现这一个功能的一种 实时内核。 文件系统 所谓文件系统是指负责存取和管理文件信息的机构,也可以说是负责文件的 建立、撤销、组织、读写、修改、复制及对文件管理所需要的资源(如目录表、 存储介质等)实施管理的软件部分。 mC/OS 是面向中小型嵌入式系统的,如果包含全部功能(信号量、消息邮箱、 消息队列及相关函数),编译后的 mC/OS 内核仅有 6~10KB,所以系统本身并没有对 文件系统的支持。但是 mC/OS 具有良好的扩展性能,如果需要的话也可自行加入 文件系统的内容。 mClinux 则是继承了 Linux 完善的文件系统性能。其采用的是 romfs 文件系 统,这种文件系统相对于一般的 ext2 文件系统要求更少的空间。 空间的节约来自 于两个方面,首先内核支持 romfs 文件系统比支持 ext2 文件系统需要更少的代码, 其次 romfs 文件系统相对简单,在建立文件系统超级块(superblock)需要更少的 存储空间。Romfs 文件系统不支持动态擦写保存,对于系统需要动态保存的数据 采用虚拟 ram 盘的方法进行处理(ram 盘将采用 ext2 文件系统)。 mClinux 还继承了 Linux 网络操作系统的优势,可以很方便的支持网络文件 系统且内嵌 TCP/IP 协议,这为 mClinux 开发网络接入设备提供了便利。

由两种操作系统对文件系统的支持可知,在复杂的需要较多文件处理的嵌入 式系统中 mClinux 是一个不错的选择。而 mC/OS 则主要适合一些控制系统。 操作系统的移植 嵌入式操作系统移植的目的是指使操作系统能在某个微处理器或微控制器 上运行。mC/OS 和 mClinux 都是源码公开的操作系统,且其结构化设计便于把与 处理器相关的部分分离出来,所以被移植到新的处理器上是可能的。 以下对两种系统的移植分别予以说明。 (1)mC/OS 的移植 要移植 mC/OS,目标处理器必须满足以下要求; ·处理器的 C 编译器能产生可重入代码,且用 C 语言就可以打开和关闭中 断; ·处理器支持中断,并能产生定时中断; ·处理器支持足够的 RAM(几 K 字节),作为多任务环境下的任务堆栈; ·处理器有将堆栈指针和其他 CPU 寄存器读出和存储到堆栈或内存中的指 令。 在理解了处理器和 C 编译器的技术细节后,mC/OS 的移植只需要修改与处理 器相关的代码就可以了。具体有如下内容: ·OS_CPU.H 中需要设置一个常量来标识堆栈增长方向; ·OS_CPU.H 中需要声明几个用于开关中断和任务切换的宏; ·OS_CPU.H 中需要针对具体处理器的字长重新定义一系列数据类型; ·OS_CPU_A.ASM 需要改写 4 个汇编语言的函数; ·OS_CPU_C.C 需要用 C 语言编写 6 个简单函数; ·修改主头文件 INCLUDE.H,将上面的三个文件和其他自己的头文件加入。 (2)mClinux 的移植 由于 mClinux 其实是 Linux 针对嵌入式系统的一种改良,其结构比较复杂, 相对 mC/OS,mClinux 的移植也复杂得多。一般而言要移植 mClinux,目标处理器 除了应满足上述 mC/OS 应满足的条件外,还需要具有足够容量(几百 K 字节以上) 外部 ROM 和 RAM。

mClinux 的移植大致可以分为 3 个层次: ·结构层次的移植,如果待移植处理器的结构不同于任何已经支持的处理器 结构,则需要修改 linux/arch 目录下相关处理器结构的文件。 虽然 mClinux 内核 代码的大部分是独立于处理器和其体系结构的,但是其最低级的代码也是特定于 各个系统的。这主要表现在它们的中断处理上下文、内存映射的维护、任务上下 文和初始化过程都是独特的。 这些例行程序位于 linux/arch/目录下。 由于 Linux 所支持体系结构的种类繁多,所以对一个新型的体系,其低级例程可以模仿与其 相似的体系例程编写。 ·平台层次的移植,如果待移植处理器是某种 mClinux 已支持体系的分支处 理器,则需要在相关体系结构目录下建立相应目录并编写相应代码。如 MC68EZ328 就是基于无 MMU 的 m68k 内核的。此时的移植需要创建 linux/arch/m68knommu/platform/ MC68EZ328 目录并在其下编写跟踪程序(实现 用户程序到内核函数的接口等功能)、中断控制调度程序和向量初始化程序等。 ·板级移植,如果你所用处理器已被 mClinux 支持的话,就只需要板级移植 了。板级移植需要在 linux/arch/?platform/中建立一个相应板的目录,再在其 中建立相应的启动代码 crt0_rom.s 或 crt0_ram.s 和链接描述文档 rom.ld 或 ram.ld 就可以了。板级移植还包括驱动程序的编写和环境变量设置等内容。 结语 通过对 mC/OS 和 mClinux 的比较,可以看出这两种操作系统在应用方面各有 优劣。mC/OS 占用空间少,执行效率高,实时性能优良,且针对新处理器的移植相 对简单。mClinux 则占用空间相对较大,实时性能一般,针对新处理器的移植相对 复杂。但是,mCLinux 具有对多种文件系统的支持能力、内嵌了 TCP/IP 协议,可 以借鉴 Linux 丰富的资源,对一些复杂的应用,mClinux 具有相当优势。 例如 CISCO 公司的 2500/3000/4000 路由器就是基于 mClinux 操作系统开发的。
总之,操作系统的选择是由嵌入式系统的需求决定的。简单的说就是,小型控制系统可充 分利用 mC/OS 小巧且实时性强的优势,如果开发 PDA 和互联网连接终端等较为复杂的系统 则 mClinux 是不错的选择。 本章简介 ARM 微处理器编程模型的一些基本概念,包括工作状态切换、数据的存储格 式、处理器异常等,通过对本章的阅读,希望读者能了解 ARM 微处理器的基本工作原理和 一些与程序设计相关的基本技术细节,为以后的程序设计打下基础。 本章的主要内容: - ARM 微处理器的工作状态 - ARM 体系结构的存储器格式 - ARM 微处理器的工作模式 - ARM 体系结构的寄存器组织

- ARM 微处理器的异常状态 在开始本章之前,首先对字(Word) 、半字(Half-Word) 、字节(Byte)的概念作一个说明: 字(Word) :在 ARM 体系结构中,字的长度为 32 位,而在 8 位/16 位处理器体系结构 中,字的长度一般为 16 位,请读者在阅读时注意区分。 半字(Half-Word) :在 ARM 体系结构中,半字的长度为 16 位,与 8 位/16 位处理器 体系结构中字的长度一致。 字节(Byte) :在 ARM 体系结构和 8 位/16 位处理器体系结构中,字节的长度均为 8 位。

2.1 ARM 微处理器的工作状态
从编程的角度看,ARM 微处理器的工作状态一般有两种,并可在两种状态之间切换: - 第一种为 ARM 状态,此时处理器执行 32 位的字对齐的 ARM 指令; - 第二种为 Thumb 状态,此时处理器执行 16 位的、半字对齐的 Thumb 指令。 当 ARM 微处理器执行 32 位的 ARM 指令集时,工作在 ARM 状态;当 ARM 微处理 器执行 16 位的 Thumb 指令集时,工作在 Thumb 状态。在程序的执行过程中,微处理器可 以随时在两种工作状态之间切换, 并且, 处理器工作状态的转变并不影响处理器的工作模式 和相应寄存器中的内容。 状态切换方法: ARM 指令集和 Thumb 指令集均有切换处理器状态的指令, 并可在两种工作状态之间切 换,但 ARM 微处理器在开始执行代码时,应该处于 ARM 状态。 进入 Thumb 状态:当操作数寄存器的状态位(位 0)为 1 时,可以采用执行 BX 指令的 方法,使微处理器从 ARM 状态切换到 Thumb 状态。此外,当处理器处于 Thumb 状态时发 生异常(如 IRQ、FIQ、Undef、Abort、SWI 等) ,则异常处理返回时,自动切换到 Thumb 状态。 进入 ARM 状态:当操作数寄存器的状态位为 0 时,执行 BX 指令时可以使微处理器从 Thumb 状态切换到 ARM 状态。此外,在处理器进行异常处理时,把 PC 指针放入异常模式 链接寄存器中,并从异常向量地址开始执行程序,也可以使处理器切换到 ARM 状态。

2.2 ARM 体系结构的存储器格式 体系结构的存储器格式
ARM 体系结构将存储器看作是从零地址开始的字节的线性组合。从零字节到三字节放 置第一个存储的字数据,从第四个字节到第七个字节放置第二个存储的字数据,依次排列。 作为 32 位的微处理器,ARM 体系结构所支持的最大寻址空间为 4GB(232 字节) 。 ARM 体系结构可以用两种方法存储字数据,称之为大端格式和小端格式,具体说明如 下: 大端格式: 在这种格式中, 字数据的高字节存储在低地址中, 而字数据的低字节则存放在高地址中, 如图 2.1 所示:

小端格式: 与大端存储格式相反,在小端存储格式中,低地址中存放的是字数据的低字节,高地址 存放的是字数据的高字节。如图 2.2 所示:

2.3 指令长度及数据类型
ARM 微处理器的指令长度可以是 32 位(在 ARM 状态下) ,也可以为 16 位(在 Thumb 状态下) 。 ARM 微处理器中支持字节(8 位) 、半字(16 位) 、字(32 位)三种数据类型,其中, 字需要 4 字节对齐(地址的低两位为 0) 、半字需要 2 字节对齐(地址的最低位为 0) 。

2.4 处理器模式
ARM 微处理器支持 7 种运行模式,分别为: ─ 用户模式(usr) : ARM 处理器正常的程序执行状态

─ 快速中断模式(fiq) 用于高速数据传输或通道处理 : ─ 外部中断模式(irq) 用于通用的中断处理 : ─ 管理模式(svc) : 操作系统使用的保护模式

─ 数据访问终止模式(abt): 当数据或指令预取终止时进入该模式,可用于虚拟存储 及存储保护。 ─ 系统模式(sys) : 运行具有特权的操作系统任务。

─ 未定义指令中止模式(und) :当未定义的指令执行时进入该模式,可用于支持硬 件协处理器的软件仿真。 ARM 微处理器的运行模式可以通过软件改变,也可以通过外部中断或异常处理改变。 大多数的应用程序运行在用户模式下, 当处理器运行在用户模式下时, 某些被保护的系 统资源是不能被访问的。

除用户模式以外,其余的所有 6 种模式称之为非用户模式,或特权模式(Privileged Modes) ;其中除去用户模式和系统模式以外的 5 种又称为异常模式(Exception Modes) ,常 用于处理中断或异常,以及需要访问受保护的系统资源等情况。

2.5 寄存器组织
ARM 微处理器共有 37 个 32 位寄存器,其中 31 个为通用寄存器,6 个为状态寄存器。 但是这些寄存器不能被同时访问, 具体哪些寄存器是可编程访问的, 取决微处理器的工作状 态及具体的运行模式。但在任何时候,通用寄存器 R14~R0、程序计数器 PC、一个或两个 状态寄存器都是可访问的。

2.5.1 ARM 状态下的寄存器组织
通用寄存器:

通用寄存器包括 R0~R15,可以分为三类: ─ 未分组寄存器 R0~R7; ─ 分组寄存器 R8~R14 ─ 程序计数器 PC(R15) 未分组寄存器 R0~R7: 在所有的运行模式下,未分组寄存器都指向同一个物理寄存器,他们未被系统用作特殊 的用途,因此,在中断或异常处理进行运行模式转换时,由于不同的处理器运行模式均使用 相同的物理寄存器, 可能会造成寄存器中数据的破坏, 这一点在进行程序设计时应引起注意。 分组寄存器 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 也称作子程序连接寄存器(Subroutine Link Register)或连接寄存器 LR。当执行 BL 子程序调用指令时,R14 中得到 R15(程序计数器 PC)的备份。其他情况下,R14 用作 通用寄存器。 与之类似, 当发生中断或异常时, 对应的分组寄存器 R14_svc、 R14_irq、 R14_fiq、 R14_abt 和 R14_und 用来保存 R15 的返回值。 寄存器 R14 常用在如下的情况: 在每一种运行模式下,都可用 R14 保存子程序的返回地址,当用 BL 或 BLX 指令调用子程 序时,将 PC 的当前值拷贝给 R14,执行完子程序后,又将 R14 的值拷贝回 PC,即可完成 子程序的调用返回。以上的描述可用指令完成: 1、执行以下任意一条指令:
MOV BX PC,LR LR

2、在子程序入口处使用以下指令将 R14 存入堆栈:
STMFD SP!,{<Regs>,LR}

对应的,使用以下指令可以完成子程序返回:
LDMFD SP!,{<Regs>,PC}

R14 也可作为通用寄存器。 程序计数器 PC(R15) 寄存器 R15 用作程序计数器(PC) 。在 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 说明在每一 种运行模式下,哪一些寄存器是可以访问的。 寄存器 R16: 寄存器 R16 用作 CPSR(Current Program Status Register,当前程序状态寄存器),CPSR 可在任何运行模式下被访问,它包括条件标志位、中断禁止位、当前处理器模式标志位,以 及其他一些相关的控制和状态位。 每一种运行模式下又都有一个专用的物理状态寄存器,称为 SPSR(Saved Program Status Register,备份的程序状态寄存器) ,当异常发生时,SPSR 用于保存 CPSR 的当前值, 从异常退出时则可由 SPSR 来恢复 CPSR。 由于用户模式和系统模式不属于异常模式,他们没有 SPSR,当在这两种模式下访问 SPSR,结果是未知的。

2.5.2 Thumb 状态下的寄存器组织
Thumb 状态下的寄存器集是 ARM 状态下寄存器集的一个子集, 程序可以直接访问 8 个 通用寄存器(R7~R0) 、程序计数器(PC) 、堆栈指针(SP) 、连接寄存器(LR)和 CPSR。 同时,在每一种特权模式下都有一组 SP、LR 和 SPSR。图 2.4 表明 Thumb 状态下的寄存器 组织。

Thumb 状态下的寄存器组织与 ARM 状态下的寄存器组织的关系: ─ Thumb 状态下和 ARM 状态下的 R0~R7 是相同的。 ─ Thumb 状态下和 ARM 状态下的 CPSR 和所有的 SPSR 是相同的。 ─ Thumb 状态下的 SP 对应于 ARM 状态下的 R13。 ─ Thumb 状态下的 LR 对应于 ARM 状态下的 R14。 ─ Thumb 状态下的程序计数器对应于 ARM 状态下 R15 以上的对应关系如图 2.5 所示:

访问 THUMB 状态下的高位寄存器(Hi-registers) : 在 Thumb 状态下,高位寄存器 R8~R15 并不是标准寄存器集的一部分,但可使用汇编 语言程序受限制的访问这些寄存器, 将其用作快速的暂存器。 使用带特殊变量的 MOV 指令, 数据可以在低位寄存器和高位寄存器之间进行传送; 高位寄存器的值可以使用 CMP 和 ADD 指令进行比较或加上低位寄存器中的值。

2.5.3 程序状态寄存器
ARM 体系结构包含一个当前程序状态寄存器(CPSR)和五个备份的程序状态寄存器 (SPSRs) 。备份的程序状态寄存器用来进行异常处理,其功能包括: ─ 保存 ALU 中的当前操作信息 ─ 控制允许和禁止中断 ─ 设置处理器的运行模式 程序状态寄存器的每一位的安排如图 2.6 所示:

条件码标志(Condition Code Flags) N、Z、C、V 均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并 且可以决定某条指令是否被执行。 在 ARM 状态下,绝大多数的指令都是有条件执行的。 在 Thumb 状态下,仅有分支指令是有条件执行的。 条件码标志各位的具体含义如表 2-1 所示:
表 2-1 标志位 N Z C 条件码标志的具体含义 含 义

当用两个补码表示的带符号数进行运算时,N=1 表示运算的结果为负数;N=0 表示 运算的结果为正数或零; Z=1 表示运算的结果为零;Z=0 表示运算的结果为非零; 可以有 4 种方法设置 C 的值: ─ 加法运算(包括比较指令 CMN):当运算结果产生了进位时(无符号数溢出), C=1,否则 C=0。 ─ 减法运算(包括比较指令 CMP):当运算时产生了借位(无符号数溢出),C=0, 否则 C=1。 ─ 对于包含移位操作的非加/减运算指令,C 为移出值的最后一位。 ─ 对于其他的非加/减运算指令,C 的值通常不改变。

V

可以有 2 种方法设置 V 的值: ─ 对于加/减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数 时,V=1 表示符号位溢出。 ─ 对于其他的非加/减运算指令,V 的值通常不改变。

Q

在 ARM v5 及以上版本的 E 系列处理器中,用 Q 标志位指示增强的 DSP 运算指令是 否发生了溢出。在其他版本的处理器中,Q 标志位无定义。

控制位

PSR 的低 8 位(包括 I、F、T 和 M[4:0])称为控制位,当发生异常时这些位可以被改 变。如果处理器运行特权模式,这些位也可以由程序修改。 ─ 中断禁止位 I、F: I=1 F=1 禁止 IRQ 中断; 禁止 FIQ 中断。

─ T 标志位:该位反映处理器的运行状态。 对于 ARM 体系结构 v5 及以上的版本的 T 系列处理器,当该位为 1 时,程序运行 于 Thumb 状态,否则运行于 ARM 状态。 对于 ARM 体系结构 v5 及以上的版本的非 T 系列处理器,当该位为 1 时,执行下 一条指令以引起为定义的指令异常;当该位为 0 时,表示运行于 ARM 状态。 ─ 运行模式位 M[4:0]:M0、M1、M2、M3、M4 是模式位。这些位决定了处理器的 运行模式。具体含义如表 2-2 所示:
表 2-2 运行模式位 M[4:0]的具体含义 处理器模式 用户模式 FIQ 模式 IRQ 模式 管理模式 中止模式 未定义模式 系统模式 可访问的寄存器 PC,CPSR,R0-R14 PC,CPSR, SPSR_fiq,R14_fiq-R8_fiq, R7~R0 PC,CPSR, SPSR_irq,R14_irq,R13_irq,R12~R0 PC,CPSR, SPSR_svc,R14_svc,R13_svc,,R12~R0, PC,CPSR, SPSR_abt,R14_abt,R13_abt, R12~R0, PC,CPSR, SPSR_und,R14_und,R13_und, R12~R0, PC,CPSR(ARM v4 及以上版本), R14~R0

M[4:0] 0b10000 0b10001 0b10010 0b10011 0b10111 0b11011 0b11111

由表 2-2 可知,并不是所有的运行模式位的组合都是有效地,其他的组合结果会导致处 理器进入一个不可恢复的状态。 保留位 PSR 中的其余位为保留位,当改变 PSR 中的条件码标志位或者控制位时,保留位不要 被改变,在程序中也不要使用保留位来存储数据。保留位将用于 ARM 版本的扩展。

2.6 异常(Exceptions) 异常( )
当正常的程序执行流程发生暂时的停止时,称之为异常,例如处理一个外部的中断请求。在 处理异常之前,当前处理器的状态必须保留,这样当异常处理完成之后,当前程序可以继续 执行。处理器允许多个异常同时发生,它们将会按固定的优先级进行处理。 ARM 体系结构中的异常,与 8 位/16 位体系结构的中断有很大的相似之处,但异常与中断

的概念并不完全等同。

2.6.1 ARM 体系结构所支持的异常类型
ARM 体系结构所支持的异常及具体含义如表 2-3 所示。
表 2-3 ARM 体系结构所支持的异常 异常类型 复位 未定义指令 软件中断 指令预取中止 具体含义 当处理器的复位电平有效时,产生复位异常,程序跳转到复位异常处理 程序处执行。 当 ARM 处理器或协处理器遇到不能处理的指令时,产生未定义指令异 常。可使用该异常机制进行软件仿真。 该异常由执行 SWI 指令产生,可用于用户模式下的程序调用特权操作 指令。可使用该异常机制实现系统功能调用。 若处理器预取指令的地址不存在,或该地址不允许当前指令访问,存储 器会向处理器发出中止信号,但当预取的指令被执行时,才会产生指令 预取中止异常。 数据中止 IRQ(外部中断请 求) FIQ(快速中断请 求) 若处理器数据访问指令的地址不存在,或该地址不允许当前指令访问 时,产生数据中止异常。 当处理器的外部中断请求引脚有效, CPSR 中的 I 位为 0 时, 且 产生 IRQ 异常。系统的外设可通过该异常请求中断服务。 当处理器的快速中断请求引脚有效, CPSR 中的 F 位为 0 时, 且 产生 FIQ 异常。

2.6.2 对异常的响应 对异常的响应
当一个异常出现以后,ARM 微处理器会执行以下几步操作: 1、 将下一条指令的地址存入相应连接寄存器 LR, 以便程序在处理异常返回时能从正确 的位置重新开始执行。若异常是从 ARM 状态进入,LR 寄存器中保存的是下一条指令的地 址(当前 PC+4 或 PC+8,与异常的类型有关) ;若异常是从 Thumb 状态进入,则在 LR 寄 存器中保存当前 PC 的偏移量, 这样, 异常处理程序就不需要确定异常是从何种状态进入的。 例如:在软件中断异常 SWI,指令 MOV PC,R14_svc 总是返回到下一条指令,不管 SWI 是在 ARM 状态执行,还是在 Thumb 状态执行。 2、将 CPSR 复制到相应的 SPSR 中。 3、根据异常类型,强制设置 CPSR 的运行模式位。 4、强制 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

2.6.3 从异常返回
异常处理完毕之后,ARM 微处理器会执行以下几步操作从异常返回: 1、将连接寄存器 LR 的值减去相应的偏移量后送到 PC 中。 2、将 SPSR 复制回 CPSR 中。 3、若在进入异常处理时设置了中断禁止位,要在此清除。 可以认为应用程序总是从复位异常处理程序开始执行的,因此复位异常处理程序不需要返 回。

2.6.4 各类异常的具体描述
FIQ(Fast Interrupt Request) FIQ 异常是为了支持数据传输或者通道处理而设计的。在 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 中。 IRQ(Interrupt Request) IRQ 异常属于正常的中断请求,可通过对处理器的 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 中。 ABORT(中止) 产生中止异常意味着对存储器的访问失败。ARM 微处理器在存储器访问周期内检查是 否发生中止异常。 中止异常包括两种类型: ─ 指令预取中止:发生在指令预取时。 ─ 数据中止:发生在数据访问时。 当指令预取访问存储器失败时,存储器系统向 ARM 处理器发出存储器中止(Abort)信号, 预取的指令被记为无效, 但只有当处理器试图执行无效指令时, 指令预取中止异常才会发生, 如果指令未被执行,例如在指令流水线中发生了跳转,则预取指令中止不会发生。 若数据中止发生,系统的响应与指令的类型有关。 当确定了中止的原因后, Abort 处理程序均会执行以下指令从中止模式返回, 无论是在 ARM 状态还是 Thumb 状态:
SUBS PC, R14_abt, #4 SUBS PC, R14_abt, #8 ;指令预取中止 ;数据中止

以上指令恢复 PC(从 R14_abt)和 CPSR(从 SPSR_abt)的值,并重新执行中止的指令。 Software Interruupt(软件中断) 软件中断指令(SWI)用于进入管理模式,常用于请求执行特定的管理功能。软件中断 处理程序执行以下指令从 SWI 模式返回,无论是在 ARM 状态还是 Thumb 状态:
MOV PC , R14_svc

以上指令恢复 PC(从 R14_svc)和 CPSR(从 SPSR_svc)的值,并返回到 SWI 的下一条指 令。 Undefined Instruction(未定义指令) 当 ARM 处理器遇到不能处理的指令时,会产生未定义指令异常。采用这种机制,可以 通过软件仿真扩展 ARM 或 Thumb 指令集。 在仿真未定义指令后,处理器执行以下程序返回,无论是在 ARM 状态还是 Thumb 状态:
MOVS PC, R14_und

以上指令恢复 PC(从 R14_und)和 CPSR(从 SPSR_und)的值,并返回到未定义指令后的 下一条指令。

2.6.5 异常进入 退出小节 异常进入/退出小节
表 2-4 总结了进入异常处理时保存在相应 R14 中的 PC 值,及在退出异常处理时推荐使 用的指令。
表 2-4 异常进入/退出 返回指令 以前的状态 ARM R14_x BL SWI UDEF FIQ IRQ PABT DABT RESET 注意: 1、在此 PC 应是具有预取中止的 BL/SWI/未定义指令所取的地址。 2、在此 PC 是从 FIQ 或 IRQ 取得不能执行的指令的地址。 3、在此 PC 是产生数据中止的加载或存储指令的地址。 4、系统复位时,保存在 R14_svc 中的值是不可预知的。 MOV PC,R14 MOVS PC,R14_svc MOVS PC,R14_und SUBS PC,R14_fiq,#4 SUBS PC,R14_irq,#4 SUBS PC,R14_abt,#4 SUBS PC,R14_abt,#8 NA PC+4 PC+4 PC+4 PC+4 PC+4 PC+4 PC+8 - Thumb R14_x PC+2 PC+2 PC+2 PC+4 PC+4 PC+4 PC+8 - 1 1 1 2 2 1 3 4 注意

2.6.6 异常向量(Exception Vectors) 异常向量( )
表 2-5 显示异常向量地址。

表 2-5 异常向量表 地 址 0x0000,0000 0x0000,0004 0x0000,0008 0x0000,000C 0x0000,0010 0x0000,0014 0x0000,0018 0x0000,001C 异 常 复位 未定义指令 软件中断 中止(预取指令) 中止(数据) 保留 IRQ FIQ 进入模式 管理模式 未定义模式 管理模式 中止模式 中止模式 保留 IRQ FIQ

2.6.7 异常优先级(Exception Priorities) 异常优先级( )
当多个异常同时发生时, 系统根据固定的优先级决定异常的处理次序。 异常优先级由高 到低的排列次序如表 2-6 所示。
表 2-6 异常优先级 异 常 复位 数据中止 FIQ IRQ 预取指令中止 未定义指令、SWI

优先级 1(最高) 2 3 4 5 6(最低)

2.6.8 应用程序中的异常处理
当系统运行时,异常可能会随时发生,为保证在 ARM 处理器发生异常时不至于处于未知状 态,在应用程序的设计中,首先要进行异常处理,采用的方式是在异常向量表中的特定位置 放置一条跳转指令,跳转到异常处理程序,当 ARM 处理器发生异常时,程序计数器 PC 会 被强制设置为对应的异常向量,从而跳转到异常处理程序,当异常处理完成以后,返回到主 程序继续执行。

2.7 本章小节
本章对 ARM 微处理器的体系结构、寄存器的组织、处理器的工作状态、运行模式以及处理 器异常等内容进行了描述,这些内容也是 ARM 体系结构的基本内容,是系统软、硬件设计 的基础。

第 1 章 ARM 微处理器概述

本章简介 ARM 微处理器的一些基本概念、应用领域及特点,引导读者进入 ARM 技术的殿堂。

本章主要内容:

- ARM 及相关技术简介

- ARM 微处理器的应用领域及特点

- ARM 微处理器系列

- ARM 微处理器的体系结构

- ARM 微处理器的应用选型

1.1 ARM-Advanced RISC Machines -
ARM(Advanced RISC Machines),既可以认为是一个公司的名字,也可以认为是对一类微处理器的通称,还可以认为是一种技术的名字。

1991 年 ARM 公司成立于英国剑桥,主要出售芯片设计技术的授权。目前,采用 ARM 技术知识产权(IP)核的微处理器,即我们通常所说的 ARM 微处理器,已遍及工业控制、消费类电子产品、通信系统、网络系统、无线系统等各类产品市场,基于 ARM 技术的微处理器应用约占据 了 32 位 RISC 微处理器 75%以上的市场份额,ARM 技术正在逐步渗入到我们生活的各个方面。

ARM 公司是专门从事基于 RISC 技术芯片设计开发的公司,作为知识产权供应商,本身不直接从事芯片生产,靠转让设计许可由合作公司生产 各具特色的芯片,世界各大半导体生产商从 ARM 公司购买其设计的 ARM 微处理器核,根据各自不同的应用领域,加入适当的外围电路,从 而形成自己的 ARM 微处理器芯片进入市场。目前,全世界有几十家大的半导体公司都使用 ARM 公司的授权,因此既使得 ARM 技术获得更多 的第三方工具、制造、软件的支持,又使整个系统成本降低,使产品更容易进入市场被消费者所接受,更具有竞争力。

1.2 ARM 微处理器的应用领域及特点 1.2.1 ARM 微处理器的应用领域

到目前为止,ARM 微处理器及技术的应用几乎已经深入到各个领域:

1、工业控制领域:作为 32 的 RISC 架构,基于 ARM 核的微控制器芯片不但占据了高端微控制器市场的大部分市场份额,同时也逐渐向低端 微控制器应用领域扩展,ARM 微控制器的低功耗、高性价比,向传统的 8 位/16 位微控制器提出了挑战。

2、无线通讯领域:目前已有超过 85%的无线通讯设备采用了 ARM 技术, ARM 以其高性能和低成本,在该领域的地位日益巩固。

3、网络应用:随着宽带技术的推广,采用 ARM 技术的 ADSL 芯片正逐步获得竞争优势。此外,ARM 在语音及视频处理上行了优化,并获得 广泛支持,也对 DSP 的应用领域提出了挑战。

4、消费类电子产品:ARM 技术在目前流行的数字音频播放器、数字机顶盒和游戏机中得到广泛采用。

5、成像和安全产品:现在流行的数码相机和打印机中绝大部分采用 ARM 技术。手机中的 32 位 SIM 智能卡也采用了 ARM 技术。

除此以外,ARM 微处理器及技术还应用到许多不同的领域,并会在将来取得更加广泛的应用。 1.2.2 ARM 微处理器的特点

采用 RISC 架构的 ARM 微处理器一般具有如下特点:

1、体积小、低功耗、低成本、高性能;

2、支持 Thumb(16 位)/ARM(32 位)双指令集,能很好的兼容 8 位/16 位器件;

3、大量使用寄存器,指令执行速度更快;

4、大多数数据操作都在寄存器中完成;

5、寻址方式灵活简单,执行效率高;

6、指令长度固定;

1.3 ARM 微处理器系列
ARM 微处理器目前包括下面几个系列,以及其它厂商基于 ARM 体系结构的处理器,除了具有 ARM 体系结构的共同特点以外,每一个系列的

ARM 微处理器都有各自的特点和应用领域。

- ARM7 系列

- ARM9 系列

- ARM9E 系列

- ARM10E 系列

- SecurCore 系列

- Inter 的 Xscale

- Inter 的 StrongARM

其中, ARM7、 ARM9、 ARM9E 和 ARM10 为 4 个通用处理器系列, 每一个系列提供一套相对独特的性能来满足不同应用领域的需求。 SecurCore 系列专门为安全要求较高的应用而设计。

以下我们来详细了解一下各种处理器的特点及应用领域。

1.3.1 ARM7 微处理器系列
ARM7 系列微处理器为低功耗的 32 位 RISC 处理器,最适合用于对价位和功耗要求较高的消费类应用。ARM7 微处理器系列具有如下特点:

- 具有嵌入式 ICE-RT 逻辑,调试开发方便。

- 极低的功耗,适合对功耗要求较高的应用,如便携式产品。

- 能够提供 0.9MIPS/MHz 的三级流水线结构。

- 代码密度高并兼容 16 位的 Thumb 指令集。

- 对操作系统的支持广泛,包括 Windows CE、Linux、Palm OS 等。

- 指令系统与 ARM9 系列、ARM9E 系列和 ARM10E 系列兼容,便于用户的产品升级换代。

- 主频最高可达 130MIPS,高速的运算处理能力能胜任绝大多数的复杂应用。

ARM7 系列微处理器的主要应用领域为:工业控制、Internet 设备、网络和调制解调器设备、移动电话等多种多媒体和嵌入式应用。

ARM7 系列微处理器包括如下几种类型的核:ARM7TDMI、ARM7TDMI-S、

ARM720T、ARM7EJ。其中,ARM7TMDI 是目前使用最广泛的 32 位嵌入式 RISC 处理器,属低端 ARM 处理器核。TDMI 的基本含义为:

T: 支持 16 为压缩指令集 Thumb;

D: 支持片上 Debug;

M:内嵌硬件乘法器(Multiplier)

I: 嵌入式 ICE,支持片上断点和调试点;

本书所介绍的 Samsung 公司的 S3C4510B 即属于该系列的处理器。

1.3.2 ARM9 微处理器系列
ARM9 系列微处理器在高性能和低功耗特性方面提供最佳的性能。具有以下特点:

- 5 级整数流水线,指令执行效率更高。

- 提供 1.1MIPS/MHz 的哈佛结构。

- 支持 32 位 ARM 指令集和 16 位 Thumb 指令集。

- 支持 32 位的高速 AMBA 总线接口。

- 全性能的 MMU,支持 Windows CE、Linux、Palm OS 等多种主流嵌入式操作系统。

- MPU 支持实时操作系统。

- 支持数据 Cache 和指令 Cache,具有更高的指令和数据处理能力。

ARM9 系列微处理器主要应用于无线设备、仪器仪表、安全系统、机顶盒、高端打印机、数字照相机和数字摄像机等。

ARM9 系列微处理器包含 ARM920T、ARM922T 和 ARM940T 三种类型,以适用于不同的应用场合。

1.3.3 ARM9E 微处理器系列
ARM9E 系列微处理器为可综合处理器,使用单一的处理器内核提供了微控制器、DSP、Java 应用系统的解决方案,极大的减少了芯片的面积 和系统的复杂程度。ARM9E 系列微处理器提供了增强的 DSP 处理能力,很适合于那些需要同时使用 DSP 和微控制器的应用场合。

ARM9E 系列微处理器的主要特点如下:

- 支持 DSP 指令集,适合于需要高速数字信号处理的场合。

- 5 级整数流水线,指令执行效率更高。

- 支持 32 位 ARM 指令集和 16 位 Thumb 指令集。

- 支持 32 位的高速 AMBA 总线接口。

- 支持 VFP9 浮点处理协处理器。

- 全性能的 MMU,支持 Windows CE、Linux、Palm OS 等多种主流嵌入式操作系统。

- MPU 支持实时操作系统。

- 支持数据 Cache 和指令 Cache,具有更高的指令和数据处理能力。

- 主频最高可达 300MIPS。

ARM9 系列微处理器主要应用于下一代无线设备、数字消费品、成像设备、工业控制、存储设备和网络设备等领域。

ARM9E 系列微处理器包含 ARM926EJ-S、ARM946E-S 和 ARM966E-S 三种类型,以适用于不同的应用场合。

1.3.4 ARM10E 微处理器系列
ARM10E 系列微处理器具有高性能、低功耗的特点,由于采用了新的体系结构,与同等的 ARM9 器件相比较,在同样的时钟频率下,性能提高 了近 50%,同时,ARM10E 系列微处理器采用了两种先进的节能方式,使其功耗极低。

ARM10E 系列微处理器的主要特点如下:

- 支持 DSP 指令集,适合于需要高速数字信号处理的场合。

- 6 级整数流水线,指令执行效率更高。

- 支持 32 位 ARM 指令集和 16 位 Thumb 指令集。

- 支持 32 位的高速 AMBA 总线接口。

- 支持 VFP10 浮点处理协处理器。

- 全性能的 MMU,支持 Windows CE、Linux、Palm OS 等多种主流嵌入式操作系统。

- 支持数据 Cache 和指令 Cache,具有更高的指令和数据处理能力

- 主频最高可达 400MIPS。

- 内嵌并行读/写操作部件。

ARM10E 系列微处理器主要应用于下一代无线设备、数字消费品、成像设备、工业控制、通信和信息系统等领域。

ARM10E 系列微处理器包含 ARM1020E、ARM1022E 和 ARM1026EJ-S 三种类型,以适用于不同的应用场合。

1.3.5 SecurCore 微处理器系列
SecurCore 系列微处理器专为安全需要而设计,提供了完善的 32 位 RISC 技术的安全解决方案,因此,SecurCore 系列微处理器除了具有 ARM

体系结构的低功耗、高性能的特点外,还具有其独特的优势,即提供了对安全解决方案的支持。

SecurCore 系列微处理器除了具有 ARM 体系结构各种主要特点外,还在系统安全方面具有如下的特点:

- 带有灵活的保护单元,以确保操作系统和应用数据的安全。

- 采用软内核技术,防止外部对其进行扫描探测。

- 可集成用户自己的安全特性和其他协处理器。

SecurCore 系列微处理器主要应用于一些对安全性要求较高的应用产品及应用系统,如电子商务、电子政务、电子银行业务、网络和认证系统等 领域。

SecurCore 系列微处理器包含 SecurCore SC100、SecurCore SC110、SecurCore SC200 和 SecurCore SC210 四种类型,以适用于不同的应用场合。

1.3.6 StrongARM 微处理器系列
Inter StrongARM SA-1100 处理器是采用 ARM 体系结构高度集成的 32 位 RISC 微处理器。 它融合了 Inter 公司的设计和处理技术以及 ARM 体系 结构的电源效率,采用在软件上兼容 ARMv4 体系结构、同时采用具有 Intel 技术优点的体系结构。

Intel StrongARM 处理器是便携式通讯产品和消费类电子产品的理想选择,已成功应用于多家公司的掌上电脑系列产品。

1.3.7 Xscale 处理器
Xscale 处理器是基于 ARMv5TE 体系结构的解决方案, 是一款全性能、 高性价比、 低功耗的处理器。 它支持 16 位的 Thumb 指令和 DSP 指令集, 已使用在数字移动电话、个人数字助理和网络产品等场合。

Xscale 处理器是 Inter 目前主要推广的一款 ARM 微处理器。

1.4 ARM 微处理器结构 1.4.1 RISC 体系结构
传统的 CISC(Complex Instruction Set Computer,复杂指令集计算机)结构有其固有的缺点,即随着计算机技术的发展而不断引入新的复杂的指

令集,为支持这些新增的指令,计算机的体系结构会越来越复杂,然而,在 CISC 指令集的各种指令中,其使用频率却相差悬殊,大约有 20% 的指令会被反复使用,占整个程序代码的 80%。而余下的 80%的指令却不经常使用,在程序设计中只占 20%,显然,这种结构是不太合理的。

基于以上的不合理性,1979 年美国加州大学伯克利分校提出了 RISC(Reduced Instruction Set Computer,精简指令集计算机)的概念,RISC 并 非只是简单地去减少指令, 而是把着眼点放在了如何使计算机的结构更加简单合理地提高运算速度上。 RISC 结构优先选取使用频最高的简单指 令,避免复杂指令;将指令长度固定,指令格式和寻地方式种类减少;以控制逻辑为主,不用或少用微码控制等措施来达到上述目的。

到目前为止,RISC 体系结构也还没有严格的定义,一般认为,RISC 体系结构应具有如下特点:

- 采用固定长度的指令格式,指令归整、简单、基本寻址方式有 2~3 种。

- 使用单周期指令,便于流水线操作执行。

- 大量使用寄存器,数据处理指令只对寄存器进行操作,只有加载/ 存储指令可以访问存储器,以提高指令的执行效率。

除此以外,ARM 体系结构还采用了一些特别的技术,在保证高性能的前提下尽量缩小芯片的面积,并降低功耗:

- 所有的指令都可根据前面的执行结果决定是否被执行,从而提高指令的执行效率。

- 可用加载/存储指令批量传输数据,以提高数据的传输效率。

- 可在一条数据处理指令中同时完成逻辑处理和移位处理。

- 在循环处理中使用地址的自动增减来提高运行效率。

当然,和 CISC 架构相比较,尽管 RISC 架构有上述的优点,但决不能认为 RISC 架构就可以取代 CISC 架构,事实上,RISC 和 CISC 各有优势, 而且界限并不那么明显。现代的 CPU 往往采用 CISC 的外围,内部加入了 RISC 的特性,如超长指令集 CPU 就是融合了 RISC 和 CISC 的优势, 成为未来的 CPU 发展方向之一。

1.4.2 ARM 微处理器的寄存器结构
ARM 处理器共有 37 个寄存器,被分为若干个组(BANK),这些寄存器包括:

- 31 个通用寄存器,包括程序计数器(PC 指针),均为 32 位的寄存器。

- 6 个状态寄存器,用以标识 CPU 的工作状态及程序的运行状态,均为 32 位,目前只使用了其中的一部分。

同时,ARM 处理器又有 7 种不同的处理器模式,在每一种处理器模式下均有一组相应的寄存器与之对应。即在任意一种处理器模式下,可访 问的寄存器包括 15 个通用寄存器(R0~R14)、一至二个状态寄存器和程序计数器。在所有的寄存器中,有些是在 7 种处理器模式下共用的同 一个物理寄存器,而有些寄存器则是在不同的处理器模式下有不同的物理寄存器。

关于 ARM 处理器的寄存器结构,在后面的相关章节将会详细描述。

1.4.3 ARM 微处理器的指令结构
ARM 微处理器的在较新的体系结构中支持两种指令集:ARM 指令集和 Thumb 指令集。其中,ARM 指令为 32 位的长度,Thumb 指令为 16 位 长度。Thumb 指令集为 ARM 指令集的功能子集,但与等价的 ARM 代码相比较,可节省 30%~40%以上的存储空间,同时具备 32 位代码的所 有优点。

关于 ARM 处理器的指令结构,在后面的相关章节将会详细描述。

1.5 ARM 微处理器的应用选型
鉴于 ARM 微处理器的众多优点,随着国内外嵌入式应用领域的逐步发展,ARM 微处理器必然会获得广泛的重视和应用。但是,由于 ARM 微 处理器有多达十几种的内核结构,几十个芯片生产厂家,以及千变万化的内部功能配置组合,给开发人员在选择方案时带来一定的困难,所以, 对 ARM 芯片做一些对比研究是十分必要的。

以下从应用的角度出发,对在选择 ARM 微处理器时所应考虑的主要问题做一些简要的探讨。

ARM 微处理器内核的选择

从前面所介绍的内容可知,ARM 微处理器包含一系列的内核结构,以适应不同的应用领域,用户如果希望使用 WinCE 或标准 Linux 等操作系 统以减少软件开发时间, 就需要选择 ARM720T 以上带有 MMU (Memory Management Unit) 功能的 ARM 芯片, ARM720T、 ARM920T、 ARM922T、 ARM946T、Strong-ARM 都带有 MMU 功能。而 ARM7TDMI 则没有 MMU,不支持 Windows CE 和标准 Linux,但目前有 uCLinux 等不需要 MMU 支持的操作系统可运行于 ARM7TDMI 硬件平台之上。事实上,uCLinux 已经成功移植到多种不带 MMU 的微处理器平台上,并在稳定性

和其他方面都有上佳表现。

本书所讨论的 S3C4510B 即为一款不带 MMU 的 ARM 微处理器,可在其上运行 uCLinux 操作系统。

系统的工作频率

系统的工作频率在很大程度上决定了 ARM 微处理器的处理能力。ARM7 系列微处理器的典型处理速度为 0.9MIPS/MHz,常见的 ARM7 芯片系 统主时钟为 20MHz-133MHz,ARM9 系列微处理器的典型处理速度为 1.1MIPS/MHz,常见的 ARM9 的系统主时钟频率为 100MHz-233MHz, ARM10 最高可以达到 700MHz。不同芯片对时钟的处理不同,有的芯片只需要一个主时钟频率,有的芯片内部时钟控制器可以分别为 ARM 核 和 USB、UART、DSP、音频等功能部件提供不同频率的时钟。

芯片内存储器的容量

大多数的 ARM 微处理器片内存储器的容量都不太大,需要用户在设计系统时外扩存储器,但也有部分芯片具有相对较大的片内存储空间,如 ATMEL 的 AT91F40162 就具有高达 2MB 的片内程序存储空间,用户在设计时可考虑选用这种类型,以简化系统的设计。

片内外围电路的选择

除 ARM 微处理器核以外,几乎所有的 ARM 芯片均根据各自不同的应用领域,扩展了相关功能模块,并集成在芯片之中,我们称之为片内外 围电路,如 USB 接口、IIS 接口、LCD 控制器、键盘接口、RTC、ADC 和 DAC、DSP 协处理器等,设计者应分析系统的需求,尽可能采用片 内外围电路完成所需的功能,这样既可简化系统的设计,同时提高系统的可靠性。

1.6 本章小节
本章对 ARM 微处理器、ARM 技术的基本概念做了一些简单的介绍,希望读者通过对本章的阅读,能对 ARM 微处理器、ARM 技术有一个总 体上的认识。

关于我们 | 帮助中心 | 服务条款 | 友情链接 | 联系方式

电话:010-86867985 业务联系邮箱:myembed@yahoo.com.cn ?MSN:liqinyhx609@hotmail.com ?QQ:6711677

技术支持邮箱:myembed@yahoo.com.cn

Copyright@2005:嵌入式资讯网 京 ICP 备 05033261 号

第 3 章 ARM 微处理器的指令系统

本章介绍 ARM 指令集、Thumb 指令集,以及各类指令对应的寻址方式,通过对本章的阅读,希望读者能 了解 ARM 微处理器所支持的指令集及具体的使用方法。

本章的主要内容有:

- ARM 指令集、Thumb 指令集概述。

- ARM 指令集的分类与具体应用。

- Thumb 指令集简介及应用场合。 3.1 ARM 微处理器的指令集概述 3.1.1 ARM 微处理器的指令的分类与格式

ARM 微处理器的指令集是加载/存储型的,也即指令集仅能处理寄存器中的数据,而且处理结果都要放回 寄存器中,而对系统存储器的访问则需要通过专门的加载/存储指令来完成。

ARM 微处理器的指令集可以分为跳转指令、数据处理指令、程序状态寄存器(PSR)处理指令、加载/存储 指令、 协处理器指令和异常产生指令六大类, 具体的指令及功能如表 3-1 所示 (表中指令为基本 ARM 指令, 不包括派生的 ARM 指令)。

表 3-1 ARM 指令及功能描述

助记符 ADC ADD AND B BIC BL BLX BX CDP CMN

指令功能描述 带进位加法指令 加法指令 逻辑与指令 跳转指令 位清零指令 带返回的跳转指令 带返回和状态切换的跳转指令 带状态切换的跳转指令 协处理器数据操作指令 比较反值指令

CMP EOR LDC LDM LDR MCR MLA MOV MRC MRS MSR MUL MLA MVN ORR RSB RSC SBC STC STM STR SUB SWI SWP TEQ TST

比较指令 异或指令 存储器到协处理器的数据传输指令 加载多个寄存器指令 存储器到寄存器的数据传输指令 从 ARM 寄存器到协处理器寄存器的数据传输指令 乘加运算指令 数据传送指令 从协处理器寄存器到 ARM 寄存器的数据传输指令 传送 CPSR 或 SPSR 的内容到通用寄存器指令 传送通用寄存器到 CPSR 或 SPSR 的指令 32 位乘法指令 32 位乘加指令 数据取反传送指令 逻辑或指令 逆向减法指令 带借位的逆向减法指令 带借位减法指令 协处理器寄存器写入存储器指令 批量内存字写入指令 寄存器到存储器的数据传输指令 减法指令 软件中断指令 交换指令 相等测试指令 位测试指令

3.1.2 指令的条件域

当处理器工作在 ARM 状态时,几乎所有的指令均根据 CPSR 中条件码的状态和指令的条件域有条件的执 行。当指令的执行条件满足时,指令被执行,否则指令被忽略。

每一条 ARM 指令包含 4 位的条件码,位于指令的最高 4 位[31:28]。条件码共有 16 种,每种条件码可用两 个字符表示,这两个字符可以添加在指令助记符的后面和指令同时使用。例如,跳转指令 B 可以加上后缀 EQ 变为 BEQ 表示“相等则跳转”,即当 CPSR 中的 Z 标志置位时发生跳转。

在 16 种条件标志码中,只有 15 种可以使用,如表 3-2 所示,第 16 种(1111)为系统保留,暂时不能使用。

表 3-2 指令的条件码

条件码 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110

助记符后缀 EQ NE CS CC MI PL VS VC HI LS GE LT GT LE AL

标 志 Z 置位 Z 清零 C 置位 C 清零 N 置位 N 清零 V 置位 V 清零 C 置位 Z 清零 C 清零 Z 置位 N 等于 V N 不等于 V Z 置位或(N 不等于 V) 忽略

含 义 相等 不相等 无符号数大于或等于 无符号数小于 负数 正数或零 溢出 未溢出 无符号数大于 无符号数小于或等于 带符号数大于或等于 带符号数小于 带符号数小于或等于 无条件执行

Z 清零且(N 等于 V) 带符号数大于

3.2 ARM 指令的寻址方式

所谓寻址方式就是处理器根据指令中给出的地址信息来寻找物理地址的方式。目前 ARM 指令系统支持如 下几种常见的寻址方式。 3.2.1 立即寻址

立即寻址也叫立即数寻址,这是一种特殊的寻址方式,操作数本身就在指令中给出,只要取出指令也就取 到了操作数。这个操作数被称为立即数,对应的寻址方式也就叫做立即寻址。例如以下指令:

ADD R0,R0,#1 ;R0←R0+1

ADD R0,R0,#0x3f ;R0←R0+0x3f

在以上两条指令中,第二个源操作数即为立即数,要求以“#”为前缀,对于以十六进制表示的立即数,还 要求在“#”后加上“0x”或“&”。 3.2.2 寄存器寻址

寄存器寻址就是利用寄存器中的数值作为操作数,这种寻址方式是各类微处理器经常采用的一种方式,也 是一种执行效率较高的寻址方式。以下指令:

ADD R0,R1,R2 ;R0←R1+R2

该指令的执行效果是将寄存器 R1 和 R2 的内容相加,其结果存放在寄存器 R0 中。 3.2.2 寄存器间接寻址

寄存器间接寻址就是以寄存器中的值作为操作数的地址,而操作数本身存放在存储器中。例如以下指令:

ADD R0,R1,[R2] ;R0←R1+[R2]

LDR R0,[R1] ;R0←[R1]

STR R0,[R1] ;[R1]←R0

在第一条指令中,以寄存器 R2 的值作为操作数的地址,在存储器中取得一个操作数后与 R1 相加,结果存 入寄存器 R0 中。

第二条指令将以 R1 的值为地址的存储器中的数据传送到 R0 中。

第三条指令将 R0 的值传送到以 R1 的值为地址的存储器中。 3.2.3 基址变址寻址

基址变址寻址就是将寄存器(该寄存器一般称作基址寄存器)的内容与指令中给出的地址偏移量相加,从 而得到一个操作数的有效地址。变址寻址方式常用于访问某基地址附近的地址单元。采用变址寻址方式的 指令常见有以下几种形式,如下所示:

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]

在第一条指令中,将寄存器 R1 的内容加上 4 形成操作数的有效地址,从而取得操作数存入寄存器 R0 中。

在第二条指令中,将寄存器 R1 的内容加上 4 形成操作数的有效地址,从而取得操作数存入寄存器 R0 中, 然后,R1 的内容自增 4 个字节。

在第三条指令中,以寄存器 R1 的内容作为操作数的有效地址,从而取得操作数存入寄存器 R0 中,然后, R1 的内容自增 4 个字节。

在第四条指令中,将寄存器 R1 的内容加上寄存器 R2 的内容形成操作数的有效地址,从而取得操作数存入 寄存器 R0 中。 3.2.4 多寄存器寻址

采用多寄存器寻址方式,一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令完成传送 最多 16 个通用寄存器的值。以下指令:

LDMIA R0,{R1,R2,R3,R4} ;R1←[R0]

;R2←[R0+4]

;R3←[R0+8]

;R4←[R0+12]

该指令的后缀 IA 表示在每次执行完加载/存储操作后,R0 按字长度增加,因此,指令可将连续存储单元的 值传送到 R1~R4。 3.2.5 相对寻址

与基址变址寻址方式相类似,相对寻址以程序计数器 PC 的当前值为基地址,指令中的地址标号作为偏移 量,将两者相加之后得到操作数的有效地址。以下程序段完成子程序的调用和返回,跳转指令 BL 采用了 相对寻址方式:

BL NEXT ;跳转到子程序 NEXT 处执行

……

NEXT

……

MOV PC,LR ;从子程序返回 3.2.6 堆栈寻址

堆栈是一种数据结构,按先进后出(First In Last Out,FILO)的方式工作,使用一个称作堆栈指针的专用 寄存器指示当前的操作位置,堆栈指针总是指向栈顶。

当堆栈指针指向最后压入堆栈的数据时,称为满堆栈(Full Stack),而当堆栈指针指向下一个将要放入数 据的空位置时,称为空堆栈(Empty Stack)。

同时,根据堆栈的生成方式,又可以分为递增堆栈(Ascending Stack)和递减堆栈(Decending Stack),当 堆栈由低地址向高地址生成时,称为递增堆栈,当堆栈由高地址向低地址生成时,称为递减堆栈。这样就 有四种类型的堆栈工作方式,ARM 微处理器支持这四种类型的堆栈工作方式,即:

- 满递增堆栈:堆栈指针指向最后压入的数据,且由低地址向高地址生成。

- 满递减堆栈:堆栈指针指向最后压入的数据,且由高地址向低地址生成。

- 空递增堆栈:堆栈指针指向下一个将要放入数据的空位置,且由低地址向高地址生成。

- 空递减堆栈:堆栈指针指向下一个将要放入数据的空位置,且由高地址向低地址生成。 3.3 ARM 指令集

本节对 ARM 指令集的六大类指令进行详细的描述。 3.3.1 跳转指令

跳转指令用于实现程序流程的跳转,在 ARM 程序中有两种方法可以实现程序流程的跳转:

— 使用专门的跳转指令。

— 直接向程序计数器 PC 写入跳转地址值。

通过向程序计数器 PC 写入跳转地址值,可以实现在 4GB 的地址空间中的任意跳转,在跳转之前结合使用

MOV LR,PC

等类似指令,可以保存将来的返回地址值,从而实现在 4GB 连续的线性地址空间的子程序调用。

ARM 指令集中的跳转指令可以完成从当前指令向前或向后的 32MB 的地址空间的跳转, 包括以下 4 条指令:

— B 跳转指令

— BL 带返回的跳转指令

— BLX 带返回和状态切换的跳转指令

— BX 带状态切换的跳转指令

1、 B 指令

B 指令的格式为:

B{条件} 目标地址

B 指令是最简单的跳转指令。一旦遇到一个 B 指令,ARM 处理器将立即跳转到给定的目标地址,从那里 继续执行。注意存储在跳转指令中的实际值是相对当前 PC 值的一个偏移量,而不是一个绝对地址,它的 值由汇编器来计算 (参考寻址方式中的相对寻址) 它是 24 位有符号数, 。 左移两位后有符号扩展为 32 位, 表示的有效偏移为 26 位(前后 32MB 的地址空间)。以下指令:

B Label ;程序无条件跳转到标号 Label 处执行

CMP R1,#0 ;当 CPSR 寄存器中的 Z 条件码置位时,程序跳转到标号 Label 处执行

BEQ 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 指令。

3.3.2 数据处理指令

数据处理指令可分为数据传送指令、算术逻辑运算指令和比较指令等。

数据传送指令用于在寄存器和存储器之间进行数据的双向传输。

算术逻辑运算指令完成常用的算术与逻辑的运算,该类指令不但将运算结果保存在目的寄存器中,同时更 新 CPSR 中的相应条件标志位。

比较指令不保存运算结果,只更新 CPSR 中相应的条件标志位。

数据处理指令包括:

— 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 决定指令的操作是 否影响 CPSR 中条件标志位的值,当没有 S 时指令不更新 CPSR 中条件标志位的值。

指令示例:

MVN R0,#0 ;将立即数 0 取反传送到寄存器 R0 中,完成后 R0=-1

3、 CMP 指令

CMP 指令的格式为:

CMP{条件} 操作数 1,操作数 2

CMP 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新 CPSR 中条件标志 位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是操作数 1 与操作 数 2 的关系(大、小、相等),例如,当操作数 1 大于操作操作数 2,则此后的有 GT 后缀的指令将可以执行。

指令示例:

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、 ADC 指令

ADC 指令的格式为:

ADC{条件}{S} 目的寄存器,操作数 1,操作数 2

ADC 指令用于把两个操作数相加,再加上 CPSR 中的 C 条件标志位的值,并将结果存放到目的寄存器中。 它使用一个进位标志位,这样就可以做比 32 位大的数的加法,注意不要忘记设置 S 后缀来更改进位标志。 操作数 1 应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。

以下指令序列完成两个 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,并将结果存放到目的寄存器中。操作数 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 条件标志位的反码,并将结果存放到目的寄 存器中。操作数 1 应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指 令使用进位标志来表示借位,这样就可以做大于 32 位的减法,注意不要忘记设置 S 后缀来更改进位标志。 该指令可用于有符号数或无符号数的减法运算。

指令示例:

SUBS R0,R1,R2 ; R0 = R1 - R2 - !C,并根据结果设置 CPSR 的进位标志位

11、RSB 指令

RSB 指令的格式为:

RSB{条件}{S} 目的寄存器,操作数 1,操作数 2

RSB 指令称为逆向减法指令,用于把操作数 2 减去操作数 1,并将结果存放到目的寄存器中。操作数 1 应 是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令可用于有符号数或 无符号数的减法运算。

指令示例:

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 条件标志位的反码,并将结果存放到目的寄 存器中。操作数 1 应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指 令使用进位标志来表示借位,这样就可以做大于 32 位的减法,注意不要忘记设置 S 后缀来更改进位标志。 该指令可用于有符号数或无符号数的减法运算。

指令示例:

RSC R0,R1,R2 ; R0 = R2 – R1 - !C

13、AND 指令

AND 指令的格式为:

AND{条件}{S} 目的寄存器,操作数 1,操作数 2

AND 指令用于在两个操作数上进行逻辑与运算, 并把结果放置到目的寄存器中。 操作数 1 应是一个寄存器, 操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于屏蔽操作数 1 的某些位。

指令示例:

AND R0,R0,#3 ; 该指令保持 R0 的 0、1 位,其余位清零。

14、ORR 指令

ORR 指令的格式为:

ORR{条件}{S} 目的寄存器,操作数 1,操作数 2

ORR 指令用于在两个操作数上进行逻辑或运算, 并把结果放置到目的寄存器中。 操作数 1 应是一个寄存器, 操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数 1 的某些位。

指令示例:

ORR R0,R0,#3 ; 该指令设置 R0 的 0、1 位,其余位保持不变。

15、EOR 指令

EOR 指令的格式为:

EOR{条件}{S} 目的寄存器,操作数 1,操作数 2

EOR 指令用于在两个操作数上进行逻辑异或运算,并把结果放置到目的寄存器中。操作数 1 应是一个寄存 器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于反转操作数 1 的某些位。

指令示例:

EOR R0,R0,#3 ; 该指令反转 R0 的 0、1 位,其余位保持不变。

16、BIC 指令

BIC 指令的格式为:

BIC{条件}{S} 目的寄存器,操作数 1,操作数 2

BIC 指令用于清除操作数 1 的某些位,并把结果放置到目的寄存器中。操作数 1 应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。操作数 2 为 32 位的掩码,如果在掩码中设置了某 一位,则清除这一位。未设置的掩码位保持不变。

指令示例:

BIC R0,R0,#%1011 ; 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变。

3.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 3.3.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 中的控制位域

3.3.5 加载 存储指令 加载/存储指令

ARM 微处理器支持加载/存储指令用于在寄存器和存储器之间传送数据,加载指令用于将存储器中的数据 传送到寄存器,存储指令则完成相反的操作。常用的加载存储指令如下:

— 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+R2×4 的字数据读入寄存器 R0,并将新地址 R1+ R2×4 写入 R1。

LDR R0,[R1],R2,LSL#2 ;将存储器地址为 R1 的字数据读入寄存器 R0,并将新地址 R1+R2×4 写入 R1。

2、LDRB 指令

LDRB 指令的格式为:

LDR{条件}B 目的寄存器,<存储器地址>

LDRB 指令用于从存储器中将一个 8 位的字节数据传送到目的寄存器中,同时将寄存器的高 24 位清零。该 指令通常用于从存储器中读取 8 位的字节数据到通用寄存器,然后对数据进行处理。当程序计数器 PC 作 为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。

指令示例:

LDRB R0,[R1] ;将存储器地址为 R1 的字节数据读入寄存器 R0,并将 R0 的高 24 位清零。

LDRB R0,[R1,#8] ;将存储器地址为 R1+8 的字节数据读入寄存器 R0,并将 R0 的高 24 位清零。

3、LDRH 指令

LDRH 指令的格式为:

LDR{条件}H 目的寄存器,<存储器地址>

LDRH 指令用于从存储器中将一个 16 位的半字数据传送到目的寄存器中,同时将寄存器的高 16 位清零。 该指令通常用于从存储器中读取 16 位的半字数据到通用寄存器,然后对数据进行处理。当程序计数器 PC 作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。

指令示例:

LDRH R0,[R1] ;将存储器地址为 R1 的半字数据读入寄存器 R0,并将 R0 的高 16 位清零。

LDRH R0,[R1,#8] ;将存储器地址为 R1+8 的半字数据读入寄存器 R0,并将 R0 的高 16 位清零。

LDRH R0,[R1,R2] ;将存储器地址为 R1+R2 的半字数据读入寄存器 R0,并将 R0 的高 16 位清零。

4、STR 指令

STR 指令的格式为:

STR{条件} 源寄存器,<存储器地址>

STR 指令用于从源寄存器中将一个 32 位的字数据传送到存储器中。 该指令在程序设计中比较常用, 且寻址 方式灵活多样,使用方式可参考指令 LDR。

指令示例:

STR R0,[R1],#8 ;将 R0 中的字数据写入以 R1 为地址的存储器中,并将新地址 R1+8 写入 R1。

STR R0,[R1,#8] ;将 R0 中的字数据写入以 R1+8 为地址的存储器中。

5、STRB 指令

STRB 指令的格式为:

STR{条件}B 源寄存器,<存储器地址>

STRB 指令用于从源寄存器中将一个 8 位的字节数据传送到存储器中。该字节数据为源寄存器中的低 8 位。

指令示例:

STRB R0,[R1] ;将寄存器 R0 中的字节数据写入以 R1 为地址的存储器中。

STRB R0,[R1,#8] ;将寄存器 R0 中的字节数据写入以 R1+8 为地址的存储器中。

6、STRH 指令

STRH 指令的格式为:

STR{条件}H 源寄存器,<存储器地址>

STRH 指令用于从源寄存器中将一个 16 位的半字数据传送到存储器中。该半字数据为源寄存器中的低 16 位。

指令示例:

STRH R0,[R1] ;将寄存器 R0 中的半字数据写入以 R1 为地址的存储器中。

STRH R0,[R1,#8] ;将寄存器 R0 中的半字数据写入以 R1+8 为地址的存储器中。

3.3.6 批量数据加载 存储指令 批量数据加载/存储指令

ARM 微处理器所支持批量数据加载/存储指令可以一次在一片连续的存储器单元和多个寄存器之间传送数 据,批量加载指令用于将一片连续的存储器中的数据传送到多个寄存器,批量数据存储指令则完成相反的 操作。常用的加载存储指令如下:

— LDM 批量数据加载指令

— STM 批量数据存储指令

LDM(或 STM)指令

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

3.3.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 指令的格式为:

SWP{条件}B 目的寄存器,源寄存器 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 位数据交换。 3.3.8 移位指令(操作)

ARM 微处理器内嵌的桶型移位器(Barrel Shifter),支持数据的各种移位操作,移位操作在 ARM 指令集 中不作为单独的指令使用,它只能作为指令格式中是一个字段,在汇编语言中表示为指令中的选项。例如, 数据处理指令的第二个操作数为寄存器时,就可以加入移位操作选项对它进行各种移位操作。移位操作包 括如下 6 种类型,ASL 和 LSL 是等价的,可以自由互换:

— LSL 逻辑左移

— ASL 算术左移

— LSR 逻辑右移

— ASR 算术右移

— ROR 循环右移

— RRX 带扩展的循环右移

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 中。

3.3.9 协处理器指令

ARM 微处理器可支持多达 16 个协处理器,用于各种协处理操作,在程序执行的过程中,每个协处理器只 执行针对自身的协处理指令,忽略 ARM 处理器和其他协处理器的指令。

ARM 的协处理器指令主要用于 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 处理器寄存器中。

3.3.10 异常产生指令

ARM 微处理器所支持的异常指令有如下两条:

— SWI 软件中断指令

— BKPT 断点中断指令

1、SWI 指令

SWI 指令的格式为:

SWI{条件} 24 位的立即数

SWI 指令用于产生软件中断,以便用户程序能调用操作系统的系统例程。操作系统在 SWI 的异常处理程序 中提供相应的系统服务,指令中 24 位的立即数指定用户程序调用系统例程的类型,相关参数通过通用寄存 器传递,当指令中 24 位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器 R0 的内容决定, 同时,参数通过其他通用寄存器传递。

指令示例:

SWI 0x02 ;该指令调用操作系统编号位 02 的系统例程。

2、BKPT 指令

BKPT 指令的格式为:

BKPT 16 位的立即数

BKPT 指令产生软件断点中断,可用于程序的调试。 3.4 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 指令集。当然,若 两者结合使用,充分发挥其各自的优点,会取得更好的效果。

3.5 本章小节

本章系统的介绍了 ARM 指令集中的基本指令,以及各指令的应用场合及方法,由基本指令还可以派生出 一些新的指令,但使用方法与基本指令类似。与常见的如 X86 体系结构的汇编指令相比较,ARM 指令系 统无论是从指令集本身,还是从寻址方式上,都相对复杂一些。

Thumb 指令集作为 ARM 指令集的一个子集,其使用方法与 ARM 指令集类似,在此未作详细的描述,但 这并不意味着 Thumb 指令集不如 ARM 指令集重要,事实上,他们各自有其自己的应用场合。

第 4 章 ARM 程序设计基础

ARM 编译器一般都支持汇编语言的程序设计和 C/C++语言的程序设计,以及两者的混合编程。本章介绍 ARM 程序设计的一些基本概念,如 ARM 汇编语言的伪指令、汇编语言的语句格式和汇编语言的程序结构 等,同时介绍 C/C++和汇编语言的混合编程等问题。

本章的主要内容:

- ARM 编译器所支持的伪指令

- 汇编语言的语句格式

- 汇编语言的程序结构

- 相关的程序示例

4.1 ARM 汇编器所支持的伪指令

在 ARM 汇编语言程序里,有一些特殊指令助记符,这些助记符与指令系统的助记符不同,没有相对应的 操作码,通常称这些特殊指令助记符为伪指令,他们所完成的操作称为伪操作。伪指令在源程序中的作用 是为完成汇编程序作各种准备工作的,这些伪指令仅在汇编过程中起作用,一旦汇编结束,伪指令的使命 就完成。

在 ARM 的汇编程序中,有如下几种伪指令:符号定义伪指令、数据定义伪指令、汇编控制伪指令、宏指 令以及其他伪指令。

4.1.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) 局部变量名

LCLA、LCLL 和 LCLS 伪指令用于定义一个 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) 表达式

伪指令 SETA、SETL、SETS 用于给一个已经定义的全局变量或局部变量赋值。

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

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

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

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

使用示例:

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

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

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

Test4 SETL {TRUE} ;将该变量赋值为真

4、 RLIST

语法格式:

名称 RLIST {寄存器列表}

RLIST 伪指令可用于对一个通用寄存器列表定义名称, 使用该伪指令定义的名称可在 ARM 指令 LDM/STM 中使用。在 LDM/STM 指令中,列表中的寄存器访问次序为根据寄存器的编号由低到高,而与列表中的寄 存器排列次序无关。

使用示例:

RegList RLIST {R0-R5,R8,R10} ;将寄存器列表名称定义为 RegList,可在 ARM 指令 LDM/STM 中通 过该名称访问寄存器列表。

4.1.2 数据定义(Data Definition)伪指令 数据定义( )

数据定义伪指令一般用于为特定的数据分配存储单元,同时可完成已分配存储单元的初始化。常见的数据 定义伪指令有如下几种:

— DCB 用于分配一片连续的字节存储单元并用指定的数据初始化。

— DCW(DCWU) 用于分配一片连续的半字存储单元并用指定的数据初始化。

— DCD(DCDU) 用于分配一片连续的字存储单元并用指定的数据初始化。

— DCFD(DCFDU)用于为双精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。

— DCFS(DCFSU) 用于为单精度的浮点数分配一片连续的字存储单元并用指定的数据初始化。

— DCQ(DCQU) 用于分配一片以 8 字节为单位的连续的存储单元并用指定的数据初始化。

— SPACE 用于分配一片连续的存储单元

— MAP 用于定义一个结构化的内存表首地址

— FIELD 用于定义一个结构化的内存表的数据域

1、 DCB

语法格式:

标号 DCB 表达式

DCB 伪指令用于分配一片连续的字节存储单元并用伪指令中指定的表达式初始化。 其中, 表达式可以为 0~ 255 的数字或字符串。DCB 也可用“=”代替。

使用示例:

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

2、 DCW(或 DCWU)

语法格式:

标号 DCW(或 DCWU) 表达式

DCW(或 DCWU)伪指令用于分配一片连续的半字存储单元并用伪指令中指定的表达式初始化。其中,表 达式可以为程序标号或数字表达式。。

用 DCW 分配的字存储单元是半字对齐的,而用 DCWU 分配的字存储单元并不严格半字对齐。

使用示例:

DataTest DCW 1,2,3 ;分配一片连续的半字存储单元并初始化。

3、 DCD(或 DCDU)

语法格式:

标号 DCD(或 DCDU) 表达式

DCD(或 DCDU)伪指令用于分配一片连续的字存储单元并用伪指令中指定的表达式初始化。其中,表达 式可以为程序标号或数字表达式。DCD 也可用“&”代替。

用 DCD 分配的字存储单元是字对齐的,而用 DCDU 分配的字存储单元并不严格字对齐。

使用示例:

DataTest DCD 4,5,6 ;分配一片连续的字存储单元并初始化。

4、 DCFD(或 DCFDU)

语法格式:

标号 DCFD(或 DCFDU) 表达式

DCFD(或 DCFDU)伪指令用于为双精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式 初始化。每个双精度的浮点数占据两个字单元。

用 DCFD 分配的字存储单元是字对齐的,而用 DCFDU 分配的字存储单元并不严格字对齐。

使用示例:

FDataTest DCFD 2E115,-5E7 ;分配一片连续的字存储单元并初始化为指定的双精度数。

5、 DCFS(或 DCFSU)

语法格式:

标号 DCFS(或 DCFSU) 表达式

DCFS(或 DCFSU)伪指令用于为单精度的浮点数分配一片连续的字存储单元并用伪指令中指定的表达式 初始化。每个单精度的浮点数占据一个字单元。

用 DCFS 分配的字存储单元是字对齐的,而用 DCFSU 分配的字存储单元并不严格字对齐。

使用示例:

FDataTest DCFS 2E5,-5E-7 ;分配一片连续的字存储单元并初始化为指定的单精度数。

6、 DCQ(或 DCQU)

语法格式:

标号 DCQ(或 DCQU) 表达式

DCQ(或 DCQU)伪指令用于分配一片以 8 个字节为单位的连续存储区域并用伪指令中指定的表达式初始 化。

用 DCQ 分配的存储单元是字对齐的,而用 DCQU 分配的存储单元并不严格字对齐。

使用示例:

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

7、 SPACE

语法格式:

标号 SPACE 表达式

SPACE 伪指令用于分配一片连续的存储区域并初始化为 0。其中,表达式为要分配的字节数。SPACE 也可 用“%”代替。

使用示例:

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

8、 MAP

语法格式:

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

MAP 伪指令用于定义一个结构化的内存表的首地址。MAP 也可用“^”代替。

表达式可以为程序中的标号或数学表达式,基址寄存器为可选项,当基址寄存器选项不存在时,表达式的 值即为内存表的首地址,当该选项存在时,内存表的首地址为表达式的值与基址寄存器的和。

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

使用示例:

MAP 0x100,R0 ;定义结构化内存表首地址的值为 0x100+R0。

9、 FILED

语法格式:

标号 FIELD 表达式

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

4.1.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.1.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 伪指令通知编译器,其后的指令序列为 16 位的 Thumb 指令。

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

若在汇编源程序中同时包含 ARM 指令和 Thumb 指令时, 可用 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 ;程序结束

4、 ENTRY

语法格式:

ENTRY

ENTRY 伪指令用于指定汇编程序的入口点。在一个完整的汇编程序中至少要有一个 ENTRY(也可以有多 个,当有多个 ENTRY 时,程序的真正入口点由链接器指定),但在一个源文件里最多只能有一个 ENTRY (可以没有)。

使用示例:

AREA Init,CODE,READONLY

ENTRY ;指定应用程序的入口点

……

5、 END

语法格式:

END

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

使用示例:

AREA Init,CODE,READONLY

……

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,但 Main 在其他源文件中定义

……

END

9、 EXTERN

语法格式:

EXTERN 标号{[WEAK]}

EXTERN 伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,如果当 前源文件实际并未引用该标号,该标号就不会被加入到当前源文件的符号表中。

标号在程序中区分大小写,[WEAK]选项表示当所有的源文件都没有定义这样一个标号时,编译器也不给 出错误信息,在多数情况下将该标号置为 0,若该标号为 B 或 BL 指令引用,则将 B 或 BL 指令置为 NOP 操作。

使用示例:

AREA Init,CODE,READONLY

EXTERN Main ;通知编译器当前文件要引用标号 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

GE T 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 之间。

4.2 汇编语言的语句格式

ARM(Thumb)汇编语言的语句格式为:

{标号} {指令或伪指令} {;注释}

在汇编语言程序设计中,每一条指令的助记符可以全部用大写、或全部用小写,但不用许在一条指令中大、 小写混用。

同时,如果一条语句太长,可将该长语句分为若干行来书写,在行的末尾用“\”表示下一行与本行为同一条 语句。

4.2.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!”

4.2.2 汇编语言程序中的表达式和运算符

在汇编语言程序设计中,也经常使用各种表达式,表达式一般由变量、常量、运算符和括号构成。常用的 表达式有数字表达式、逻辑表达式和字符串表达式,其运算次序遵循如下的优先级:

— 优先级相同的双目运算符的运算顺序为从左到右。

— 相邻的单目运算符的运算顺序为从右到左,且单目运算符的优先级高于其他运算符。

— 括号运算符的优先级最高。

1、 数字表达式及运算符

数字表达式一般由数字常量、数字变量、数字运算符和括号构成。与数字表达式相关的运算符如下:

— “+”、“-”、“×”、“/” 及“MOD”算术运算符

以上的算术运算符分别代表加、减、乘、除和取余数运算。例如,以 X 和 Y 表示两个数字表达式,则:

X+Y 表示 X 与 Y 的和。

X-Y 表示 X 与 Y 的差。

X×Y 表示 X 与 Y 的乘积。

X/Y 表示 X 除以 Y 的商。

X:MOD:Y 表示 X 除以 Y 的余数。

— “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 按位作逻辑或的操作。

:NOT:Y 表示将 Y 按位作逻辑非的操作。

X:EOR:Y 表示将 X 和 Y 按位作逻辑异或的操作。

2、 逻辑表达式及运算符

逻辑表达式一般由逻辑量、逻辑运算符和括号构成,其表达式的运算结果为真或假。与逻辑表达式相关的 运算符如下:

— “=”、“>”、“<”、“>=”、“<= ”、“/=”、“ <>” 运算符

以 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。

— “LAND”、“LOR”、“LNOT”及“LEOR”运算符

以 X 和 Y 表示两个逻辑表达式,以上的逻辑运算符代表的运算如下:

X:LAND:Y 表示将 X 和 Y 作逻辑与的操作。

X:LOR:Y 表示将 X 和 Y 作逻辑或的操作。

:LNOT:Y 表示将 Y 作逻辑非的操作。

X:LEOR:Y 表示将 X 和 Y 作逻辑异或的操作。

3、 字符串表达式及运算符

字符串表达式一般由字符串常量、字符串变量、运算符和括号构成。编译器所支持的字符串最大长度为 512 字节。常用的与字符串表达式相关的运算符如下:

— LEN 运算符

LEN 运算符返回字符串的长度(字符数),以 X 表示字符串表达式,其语法格式如下:

:LEN:X

— CHR 运算符

CHR 运算符将 0~255 之间的整数转换为一个字符,以 M 表示某一个整数,其语法格式如下:

:CHR:M

— STR 运算符

STR 运算符将将一个数字表达式或逻辑表达式转换为一个字符串。对于数字表达式,STR 运算符将其转换 为一个以十六进制组成的字符串;对于逻辑表达式,STR 运算符将其转换为字符串 T 或 F,其语法格式如 下:

:STR:X

其中,X 为一个数字表达式或逻辑表达式。

— LEFT 运算符

LEFT 运算符返回某个字符串左端的一个子串,其语法格式如下:

X:LEFT:Y

其中:X 为源字符串,Y 为一个整数,表示要返回的字符个数。

— RIGHT 运算符

与 LEFT 运算符相对应,RIGHT 运算符返回某个字符串右端的一个子串,其语法格式如下:

X:RIGHT:Y

其中:X 为源字符串,Y 为一个整数,表示要返回的字符个数。

— CC 运算符

CC 运算符用于将两个字符串连接成一个字符串,其语法格式如下:

X:CC:Y

其中:X 为源字符串 1,Y 为源字符串 2,CC 运算符将 Y 连接到 X 的后面。

4、 与寄存器和程序计数器(PC)相关的表达式及运算符

常用的与寄存器和程序计数器(PC)相关的表达式及运算符如下:

— BASE 运算符

BASE 运算符返回基于寄存器的表达式中寄存器的编号,其语法格式如下:

:BASE:X

其中,X 为与寄存器相关的表达式。

— INDEX 运算符

INDEX 运算符返回基于寄存器的表达式中相对于其基址寄存器的偏移量,其语法格式如下:

:INDEX:X

其中,X 为与寄存器相关的表达式。

5、 其他常用运算符

— ?运算符

?运算符返回某代码行所生成的可执行代码的长度,例如:

?X

返回定义符号 X 的代码行所生成的可执行代码的字节数。

— DEF 运算符

DEF 运算符判断是否定义某个符号,例如:

:DEF:X

如果符号 X 已经定义,则结果为真,否则为假。

4.3 汇编语言的程序结构 汇编语言的程序结构 4.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 伪指令,指示代码段的结束。

4.3.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

4.3.3 汇编语言程序示例

以下是一个基于 S3C4510B 的串行通讯程序,关于 S3C4510B 的串行通讯的工作原理,可以参考第六章的 相关内容,在此仅向读者说明一个完整汇编语言程序的基本结构:

;********************************************************************************

; Institute of Automation,Chinese Academy of Sciences

;Description: This example shows the UART communication!

;Author: JuGuang,Lee

;Date:

;********************************************************************************

UARTLCON0 EQU 0x3FFD000

UARTCONT0 EQU 0x3FFD004

UARTSTAT0 EQU 0x3FFD008

UTXBUF0 EQU 0x3FFD00C

UARTBRD0 EQU 0x3FFD014

AREA Init,CODE,READONLY

ENTRY

;**************************************************

;LED Display

;**************************************************

LDR R1,=0x3FF5000

LDR R0,=&ff

STR R0,[R1]

LDR R1,=0x3FF5008

LDR R0,=&ff

STR R0,[R1]

;*************************************************

;UART0 line control register

;*************************************************

LDR R1,=UARTLCON0

LDR R0,=0x03

STR R0,[R1]

;**************************************************

;UART0 control regiser

;**************************************************

LDR R1,=UARTCONT0

LDR R0,=0x9

STR R0,[R1]

;**************************************************

;UART0 baud rate divisor regiser

;Baudrate=19200,对应于 50MHz 的系统工作频率

;***************************************************

LDR R1,=UARTBRD0

LDR R0,=0x500

STR R0,[R1]

;***************************************************

;Print the messages!

;***************************************************

LOOP

LDR R0,=Line1

BL PrintLine

LDR R0,=Line2

BL PrintLine

LDR R0,=Line3

BL PrintLine

LDR R0,=Line4

BL PrintLine

LDR R1,=0x7FFFFF

LOOP1

SUBS R1,R1,#1

BNE LOOP1

B LOOP

;***************************************************

;Print line

;***************************************************

PrintLine

MOV R4,LR

MOV R5,R0

Line

LDRB R1,[R5],#1

AND R0,R1,#&FF

TST R0,#&FF

MOVEQ PC,R4

BL PutByte

B Line

PutByte

LDR R3,=UARTSTAT0

LDR R2,[R3]

TST R2,#&40

BEQ PutByte

LDR R3,=UTXBUF0

STR R0,[R3]

MOV PC,LR

Line1 DCB &A,&D,"******************************************************************",0

Line2 DCB &A,&D,"Chinese Academy of Sciences,Institute of Automation,Complex System Lab.",0

Line3 DCB &A,&D," ARM Development Board Based on Samsung ARM S3C4510B.",0

Line4 DCB &A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D,&A,&D, &A,&D,&A,&D,0

END

4.3.4 汇编语言与 C/C++的混合编程 的混合编程

在应用系统的程序设计中,若所有的编程任务均用汇编语言来完成,其工作量是可想而知的,同时,不利 于系统升级或应用软件移植,事实上,ARM 体系结构支持 C/C+以及与汇编语言的混合编程,在一个完整 的程序设计的中,除了初始化部分用汇编语言完成以外,其主要的编程任务一般都用 C/C++ 完成。

汇编语言与 C/C++的混合编程通常有以下几种方式:

- 在 C/C++代码中嵌入汇编指令。

- 在汇编程序和 C/C++的程序之间进行变量的互访。

- 汇编程序、C/C++程序间的相互调用。

在以上的几种混合编程技术中,必须遵守一定的调用规则,如物理寄存器的使用、参数的传递等,这对于 初学者来说,无疑显得过于烦琐。在实际的编程应用中,使用较多的方式是:程序的初始化部分用汇编语 言完成,然后用 C/C++完成主要的编程任务,程序在执行时首先完成初始化过程,然后跳转到 C/C++ 程序代码中,汇编程序和 C/C++程序之间一般没有参数的传递,也没有频繁的相互调用,因此,整个程 序的结构显得相对简单,容易理解。以下是一个这种结构程序的基本示例,该程序基于第五、六章所描述 的硬件平台:

;*************************************************************************

; Institute of Automation, Chinese Academy of Sciences

;File Name: Init.s

;Description:

;Author: JuGuang,Lee

;Date:

;************************************************************************

IMPORT Main ;通知编译器该标号为一个外部标号

AREA Init,CODE,READONLY ;定义一个代码段

ENTRY ;定义程序的入口点

LDR R0,=0x3FF0000 ;初始化系统配置寄存器,具体内容可参考第五、六章

LDR R1,=0xE7FFFF80

STR R1,[R0]

LDR SP,=0x3FE1000 ;初始化用户堆栈,具体内容可参考第五、六章

BL Main ;跳转到 Main()函数处的 C/C++代码执行

END ;标识汇编程序的结束

以上的程序段完成一些简单的初始化,然后跳转到 Main()函数所标识的 C/C++代码处执行主要的任务, 此处的 Main 仅为一个标号,也可使用其他名称,与 C 语言程序中的 main()函数没有关系。

/*******************************************************************************

* Institute of Automation, Chinese Academy of Sciences

* File Name: main.c

* Description: P0,P1 LED flash.

* Author: JuGuang,Lee

* Date:

******************************************************************************/

void Main(void)

{

int i;

*((volatile unsigned long *) 0x3ff5000) = 0x0000000f;

while(1)

{

*((volatile unsigned long *) 0x3ff5008) = 0x00000001;

for(i=0; i<0x7fFFF; i++);

*((volatile unsigned long *) 0x3ff5008) = 0x00000002;

for(i=0; i<0x7FFFF; i++);

}

} 4.4 本章小节

本章介绍了 ARM 程序设计的一些基本概念,以及在汇编语言程序设计中常见的伪指令、汇编语言的基本 语句格式等,汇编语言程序的基本结构等,同时简单介绍了 C/C++和汇编语言的混合编程等问题,这些 问题均为程序设计中的基本问题,希望读者掌握,注意本章最后的两个示例均与后面章节介绍的基于 S3C4510B 的硬件平台有关系,读者可以参考第五、六章的相关内容。

应用系统设计与调试 调试(1) 第 5 章 应用系统设计与调试

本章主要介绍基于 S3C4510B 的硬件系统的详细设计步骤、实现细节、硬件系统的调试方法等,通过对本 章的阅读,可以使绝大多数的读者具有根据自身的需求、设计特定应用系统的能力。

尽管本章所描述的内容为基于 S3C4510B 的应用系统设计,但由于 ARM 体系结构的一致性、以及外围电 路的通用性,本章的所有内容对设计其他基于 ARM 内核芯片的应用系统,也具有很大的参考价值。

本章的主要内容包括:

- 嵌入式系统设计的基本方法。

- S3C4510B 概述。

- S3C4510B 的基本工作原理

- 基于 S3C4510B 的硬件系统设计详述

- 硬件系统的调试方法 5.1 系统设计概述

根据用户需求,设计出特定的嵌入式应用系统,是每一个嵌入式系统设计工程师应该达到的目标。嵌入式 应用系统的设计包含硬件系统的设计和软件系统设计两个部分,并且这两部分的设计是互相关联、密不可 分的,嵌入式应用系统的设计经常需要在硬件和软件的设计之间进行权衡与折中。因此,这就要求嵌入式 系统设计工程师具有较深厚的硬件和软件基础,并具有熟练应用的能力。这也是嵌入式应用系统设计与其 他的纯粹的软件设计或硬件设计最大的区别。

本章以北京微芯力科技有限公司(www.winsilicon.com)设计生产的 ARM Linux 评估开发板为原型,详细 分析系统的软、硬件设计步骤、实现细节以及调试技巧等。ARM Linux 评估开发板的设计以学习与应用兼 顾为出发点,在保证用户完成 ARM 技术的学习开发的同时,考虑了系统的扩展、电路板的面积、散热、 电磁兼容性以及安装等问题,因此,该板也可作为嵌入式系统主板,直接应用在一些实际系统中。

图 5.1.1 是 ARM Linux 评估开发板的结构框图,各部分基本功能描述如下:

- 串行接口电路用于 S3C4510B 系统与其他应用系统的短距离双向串行通讯;

- 复位电路可完成系统上电复位和在系统工作时用户按键复位;

- 电源电路为 5V 到 3.3V 的 DC-DC 转换器,给 S3C4510B 及其他需要 3.3V 电源的外围电路供电;

- 10MHz 有源晶振为系统提供工作时钟,通过片内 PLL 电路倍频为 50MHz 作为微处理器的工作时钟;

- FLASH 存储器可存放已调试好的用户应用程序、嵌入式操作系统或其他在系统掉电后需要保存的用户 数据等;

- SDRAM 存储器作为系统运行时的主要区域,系统及用户数据、堆栈均位于 SDRAM 存储器中;

- 10M/100M 以太网接口为系统提供以太网接入的物理通道,通过该接口,系统可以 10M 或 100Mbps 的 速率接入以太网;

- JTAG 接口可对芯片内部的所有部件进行访问,通过该接口可对系统进行调试、编程等;

- IIC 存储器可存储少量需要长期保存的用户数据;

- 系统总线扩展引出了数据总线、地址总线和必须的控制总线,便于用户根据自身的特定需求,扩展外围

电路。

5.2 S3C4510B 概述

5.2.1 S3C4510B 及片内外围简介

在进行系统设计之前,有必要对 ARM Linux 评估开发板上的 ARM 芯片 S3C4510B 及其工作原理进行比较 详细的介绍,读者只有对该微处理器的工作原理有了较详细的了解,才能进行特定应用系统的设计。

Samsung 公司的 S3C4510B 是基于以太网应用系统的高性价比 16/32 位 RISC 微控制器,内含一个由 ARM 公司设计的 16/32 位 ARM7TDMI RISC 处理器核,ARM7TDMI 为低功耗、高性能的 16/32 核,最适合用于 对价格及功耗敏感的应用场合。

除了 ARM7TDMI 核以外,S3C4510B 比较重要的片内外围功能模块包括:

— 2 个带缓冲描述符(Buffer Descriptor)的 HDLC 通道

— 2 个 UART 通道

— 2 个 GDMA 通道

— 2 个 32 位定时器

— 18 个可编程的 I/O 口。

片内的逻辑控制电路包括:

— 中断控制器

— DRAM/SDRAM 控制器

— ROM/SRAM 和 FLASH 控制器

— 系统管理器

— 一个内部 32 位系统总线仲裁器

— 一个外部存储器控制器。

S3C4510B 结构框图如图 5.2.1 所示。

S3C4510B 的特性描述如下:

体系结构

— 用于嵌入式以太网应用的集成系统

— 全 16/32 的 RISC 架构

— 支持大、小端模式。内部架构为大端模式,外部存储器可为大、小端模式

— 内含效率高、功能强的 ARM7TDMI 处理器核

— 高性价比、基于 JTAG 接口的调试方案

— 边界扫描接口

系统管理器

— 支持 ROM/SRAM、FLASH、DRAM 和外部 I/O 以 8/16/32 位的方式操作

— 带总线请求/应答引脚的外部总线控制器

— 支持 EDO/常规或 SDRAM 存储器

— 可编程的访问周期(可设定 0~7 个等待周期)

— 4 字的写缓冲

— 高性价比的从存储器到外围的 DMA 接口

一体化的指令/数据 Cache

— 一体化的 8K Cache

— 支持 LRC(近期最少使用)替换算法

— Cache 可配置为内部 SRAM

IIC 接口

— 仅支持主控模式

— 串行时钟由波特率发生器生成

Ethernet 控制器

— 带猝发模式的 DMA 引擎

— DMA 发送/接收缓冲区(256 字节发送,256 字节接收)

— MAC 发送/接收 FIFO 缓冲区(80 字节发送,16 字节接收)

— 数据对准逻辑

— 支持端模式变换

— 100M/10Mbps 的工作速率

— 与 IEEE802.3 标准完全兼容

— 提供 MII 和 7 线制 10Mbps 接口

— 站管理信号生成

— 片内 CAM(可达 21 个目的地址)

— 带暂停特性的全双工模式

— 支持长/短包模式

— 包拆装 PDA 生成

HDLC (High-Level Data Link Control) 高层数据链路协议

— HDLC 协议特征:标志检测与同步;零插入与删除;空闲检测和发送;FCS 生成和检测(16 位);终止 检测与发送

— 地址搜索模式(可扩展到四字节)

— 可选择 CRC 模式或非 CRC 模式

— 用于时钟恢复的数字 PLL 模块

— 波特率生成器

— 发送和接收支持 NRZ/NRZI/FM/曼切斯特数据格式

— 回环与自动回波模式

— 8 字的发送和接收 FIFO

— 可选的 1 字或 4 字数据传送方式

— 数据对准逻辑

— 可编程中断

— Modem 接口

— 高达 10Mbps 的工作速率

— 基于 8 位位组的 HDLC 帧长度

— 每个 HDLC 有 2 通道 DMA 缓冲描述符用于发送和接收

DMA 控制器

— 用于存储器到存储器、存储器到 UATR、UATR 到存储器数据传送的 2 通道通用 DMA 控制器,不受 CPU 干预

— 可由程序或外部 DMA 请求启动

— 可增减源地址或目的地址,无论 8 位、16 位或 32 位数据传输

— 4 种数据猝发模式

UART

— 2 个可工作于 DMA 方式或中断方式的 UART 模块

— 支持 5、6、7、8 位的串行数据发送和接收

— 波特率可编程

— 1 位或 2 位停止位

— 奇/偶校验

— 间隔信号的生成与检测

— 奇偶校验、覆盖和帧错误检测

— ×16 时钟模式

— 支持红外发送和接收

定时器

— 2 个可编程 32 位定时器

— 间隔模式或触发模式工作

可编程 I/O 口

— 18 个可编程 I/O 口

— 可分别配置为输入模式、输出模式或特殊功能模式

中断控制器

— 21 个中断源,包括 4 个外部中断源

— 正常中断或快速中断模式(IRQ、FIQ)

— 基于优先级的中断处理

PLL

— 外部时钟可由片内 PLL 倍频以提高系统时钟

— 输入频率范围:10~40MHz

— 输出频率可以是输入时钟的 5 倍

工作电压

— 3.3V,偏差不超过 5%

工作温度

— 0oC~70oC

工作频率

— 最高为 50MHz

封装形式

— 208 脚 QFP 封装 5.2.2 S3C4510B 的引脚分布及信号描述

各引脚信号描述如下:

表 5-2-1 S3C4510B 的引脚信号描述

信 号 XCLK

引脚号 80

类 型 I





S3C4510B 的系统时钟源。如果 CLKSEN 为低电平,通过 PLL 倍频的输出时钟作为 S3C4510B 的内部系统时钟。如 果 CLKSEN 为高电平,XCLK 直接作为 S3C4510B 的内部系 统时钟。

MCLKO/SDCLK

77

O

系统时钟输出。SDCLK 为

SDRAM 提供时钟信号 CLKSEL 83 I 时钟选择。如果 CLKSEL 为低 电平,PLL 输出时钟作为 S3C4510B 的内部系统时钟。如 果 CLKSEL 为高电平,XCLK 直接作为 S3C4510B 的内部系 统时钟。 nRESET 82 I 复位信号。nRESET 为 S3C4510B 的复位信号,要使系 统可靠复位, nRESET 必须至少 保持 64 个主时钟周期的低电 平。 CLKOEN 76 I 时钟输出允许/禁止。高电平允 许系统时钟信号输出,低电平 禁止。 TMODE 63 I 测试模式选择。低电平为正常 工作模式,高电平为芯片测试 模式。 FILTER 55 AI 如果使用 PLL,应在该引脚和 数字地之间接 820pF 的陶瓷电 容。 TCK 58 I JTAG 测试时钟。 JTAG 测试时钟 信号用于切换状态信息和检测 数据的输入输出。该引脚在片 内下拉。 TMS 59 I JTAG 测试模式选择。该信号控 制 S3C4510B 的 JTAG 测试操 作。该引脚在片内上拉。 TDI 60 I JTAG 测试数据输入。在 JTAG 测试操作的过程中,该信号将 指令和数据串行送入 S3C4510B。 该引脚在片内上拉。 TDO 61 O JTAG 测试数据输出。在 JTAG 测试操作的过程中,该信号将 指令和数据串行送出 S3C4510B。 nTRST 62 I JTAG 复位信号,低电平复位。 异步复位 JTAG 逻辑。 该引脚在 片内上拉。 ADDR[21:0]/ ADDR[10]/AP 117-110 129-120 135-132 O 地址总线。22 位的地址总线可 寻址每一个 ROM/ SRAM 组、 FLASH 存储器组、 DRAM 组和 外部 I/O 组的 4M 字 (64M 字节)

的地址范围。

XDATA[31:0]

141-136 154-144 166-159 175-169

I/O

外部数据总线(双向、32 位)。 S3C4510B 支持外部 8 位,16 位,32 位的数据宽度。

nRAS[3:0]/ nSDCS[3:0]

94,91, 90,89

O

DRAM 行地址锁存信号。 S3C4510B 支持最多 4 个 DRAM 组,每个 nRAS 输出控制一组。 nSDCS[3:0]用作 SDRAM 的片 选信号。

nCAS[3:0] nCAS[0] /nSDRAS nCAS[1] /nSDCAS nCAS[2]/CKE

98,97, 96,95

O

DRAM 列地址锁存信号。无论访 问哪一个 DRAM 组, 个 nCAS 4 输出信号均表示字节选择。 nSDRAS 作为 SDRAM 的行地 址锁存信号,nSDCAS 作为 SDRAM 的列地址锁存信号, CKE 作为 SDRAM 的时钟使能 信号。

nDWE

99

O

DRAM 写使能信号。该引脚为 DRAM 组提供写操作信号。 (nWBE[3:0]用于为 ROM/SRAM/FLASH 存储器组 提供写操作信号。)

nECS[3:0]

70,69, 68,67

O

外部 I/O 片选信号。可以有 4 个外部 I/O 组映射到存储空间, 每一个外部 I/O 组的地址范围 最大为 16KB。nECS 提供每一 个外部 I/O 组的片选信号。

nEWAIT

71

I

外部等待信号。该信号用于在 访问外部 I/O 设备时,由外设插 入等待周期。

nRCS[5:0]

88-84,75

O

ROM/SRAM/FLASH 片选信号。 S3C4510B 可访问多达 6 个的外 部 ROM/SRAM/FLASH 组。

B0SIZE[1:0]

74,73

I

ROM/SRAM/FLASH 存储器组 0 的 数据总线宽度设定。 ROM/SRAM/FLASH 存储器组 0 常用于程序的启动。‘01’= 字节 (8 位) ‘10’ ; =半字 (16 位);‘11’=字(32 位); ‘00’=保留

nOE

72

O

输出使能。当对存储器进行访 问的时候,该信号控制存储器 的输出使能。

nWBE[3:0]/ DQM[3:0]

107, 102-100

O

写字节使能。当对存储器进行 写操作时,该信号控制存储器 (DRAM 除外)的写使能。对 于 DRAM 存储器组,由 nCAS[3:0]和 nDWE 控制写操 作。DQM 用于 SDRAM 数据输 入/输出的屏蔽信号。

ExtMREQ

108

I

外部总线控制器请求信号。外 部总线控制器通过该引脚请求 控制外部总线,当该信号有效 时,S3C4510B 将外部总线置为 高阻状态,以便外部总线控制 器取得对外部总线的控制。当 ExtMACK 信号为的电平时, S3C4510B 重新取得对外部总 线的控制权。

ExtMACK MDC

109 50

O O

外部总线应答信号。 管理数据时钟。该引脚产生 MDIO 数据输入输出时所需的 时钟信号。

MDIO

48

I/O

管理数据输入/输出。当执行一 个读数据的命令时,该引脚输 入由物理层产生的数据,当执 行一个写数据的命令时,由该 引脚输出数据到物理层 (PHY)。

LITTLE

49

I

小端模式选择引脚。当该引脚 为高电平时,S3C4510B 工作在 小端模式,当该引脚为低电平 时,工作在大端模式。该引脚 在片内已下拉, 因此, S3C4510B 缺省工作在大端模式。

COL/COL_10M TX_CLK/ TXCLK_10M

38 46

I I

冲突检测/10M 冲突检测。该引 脚显示是否检测到冲突。 发送时钟/10M 发送时钟。 S3C4510B 在 TX_CLK 的上升 沿驱动 TXD[3:0]和 TX_EN, 当 工作在 MII 模式时,PHY 在 TX_CLK 的上升沿采样 TXD[3:0]和 TX_EN。在发送数 据时,TXCLK_10M 由 10M 的

PHY 产生。

TXD[3:0] LOOP_10M TXD_10M

44,43, 40,39

O

发送数据/10M 发送数据/10M 回 环测试。TXD[3:0]为发送数据 引脚, TXD_10M 为 10M 的 PHY 的发送数据引脚,LOOP_10M 由控制寄存器的回环测试位驱 动。

TX_EN/ TXEN_10M TX_ERR/ PCOMP_10M CRS/CRS_10M RX_CLK/ RXCLK_10M

47 45 28 37

O O I I

发送使能/10M 发送使能。 发送错误/10M 包压缩使能。 载波侦听/10M 载波侦听。 接收时钟/10M 接收时钟。 RX_CLK 为连续的时钟信号, 当其频率为 25MHz 时,数据传 输速率为 100M,当其频率为 2.5MHz 时,数据传输速率为 10M。在接收数据时, RXCLK_10M 由 10M 的 PHY 产 生。

RXD[3:0] RXD_10M RX_DV/ LINK10M RX_ERR TXDA RXDA nDTRA

35,34, I 33,30 29 36 9 7 6 I I O I O

接收数据/10M 接收数据。 接收数据有效/10M 连接状态。 接收错误。 HDLC Ch-A 发送数据。 HDLC Ch-A 接收数据。 HDLC Ch-A 终端准备就绪。 nDTRA 引脚指示数据终端设备 准备发送或接收。

nRTSA nCTSA nDCDA nSYNCA RXCA TXCA TXDB RXDB nDTRB

8 10 13 15 14 16 20 18 17

O I I O I I/O O I O

HDLC Ch-A 传送请蟆?span lang="EN-US"> HDLC Ch-A 传送清除。 HDLC Ch-A 数据载波检测。 HDLC Ch-A 同步检测。 HDLC Ch-A 接收时钟。 HDLC Ch-A 发送时钟。 HDLC Ch-B 发送数据。 HDLC Ch-B 接收数据。 HDLC Ch-B 终端准备就绪。

nRTSB nCTSB nDCDB nSYNCB RXCB TXCB UCLK

19 23 24 26 25 27 64

O I I O I I/O I

HDLC Ch-B 传送请求。 HDLC Ch-B 传送清除。 HDLC Ch-B 数据载波检测。 HDLC Ch-B 同步检测。 HDLC Ch-B 接收时钟。 HDLC Ch-B 发送时钟。 外部 UART 时钟输入。可由外部 输入时钟作为 UART 时钟,通 常由系统时钟提供 UART 时钟 输入。

UARXD0 UATXD0 nUADTR0

202 204 203

I O I

UART0 数据接收。 UART0 数据发送。 UART0 数据终端准备就绪。 该输 入信号通知 S3C4510B,外设 (或其他主机)已准备好发送 或接收数据。

nUADSR0

205

O

UART0 数据设备准备就绪。 该输 出信号通知外设(或其他主 机),UART0 已准备好发送或 接收数据。

UARXD1 UATXD1 nUADTR1 nUADSR1 P[7:0] XINTREQ [3:0] P[11:8] NXDREQ[1:0]/P[13:12]

206 4 3 5 185-179, 176 191-189, 186 193,192

I O I O I/O I/O

UART1 数据接收。 UART1 数据发送。 UART1 数据终端准备就绪。 参见 nUADTR0。 UART1 数据设备准备就绪。 参见 nUADSR0。 通用 I/O 口。 外部中断请求信号,或作为通 用 I/O 口。

I/O

外部 DAM 请求信号,或作为通 用 I/O 口。

nXDACK[1:0]/P[15:14] TOUT0/P[16] TOUT1/P[17] SCL SDA VDDP

195,194 196 199 200 201 1,21,

I/O I/O I/O I/O I/O Power

外部 DAM 应答信号,或作为通 用 I/O 口。 定时器 0 溢出, 或作为通用 I/O 口。 定时器 1 溢出, 或作为通用 I/O 口。 I C

相关文章:
嵌入式系统原理及应用试卷1
嵌入式系统原理应用试卷1_计算机软件及应用_IT/计算机_专业资料。1.嵌入式微处理器一般可分为嵌入式微控制器 、嵌入式数字信号处理器 、嵌入式微处理 器和嵌入...
嵌入式系统原理与应用技术
嵌入式系统原理与应用技术_工学_高等教育_教育专区。嵌入式系统 考题 考点 复习1.嵌入式系统的定义:一般都认为嵌入式系统是以应用为中心,以计算 机技术为基础,并...
嵌入式系统原理及应用
嵌入式系统原理应用_电脑基础知识_IT/计算机_专业资料。塔里木大学课程论文 2017 届结课论文 《嵌入式系统原理应用》结课论文 学生姓名 学号 艾力江 5021212126...
嵌入式系统原理及应用试卷2
嵌入式系统原理应用试卷2_计算机软件及应用_IT/计算机_专业资料。一、 填空 1.嵌入式系统的组织架构是 用软件 所组成的。 (每空 1 分,共 20 分) 嵌入式...
嵌入式系统原理与应用(报告)
嵌入式系统原理与应用(报告)_信息与通信_工程科技_专业资料。天津电子信息职业技术学院 《嵌入式系统原理与应用》课程报告 论文名称:SEP3203 的中断响应机制 课程...
嵌入式系统原理及应用
嵌入式系统原理应用_工学_高等教育_教育专区。封面 嵌入式系统原理应用题目: I2C 控制步进电机 学生姓名: 陈阳阳 学专号:1361142215 业: 自动化 指导教师: ...
嵌入式系统原理及应用
嵌入式系统原理应用_计算机硬件及网络_IT/计算机_专业资料。ARM嵌入式实验 嵌入式系统原理应用实验一【实验项目】 GPIO 实验——LED 及按键项目 1. 循环“...
嵌入式系统原理与应用
嵌入式系统原理应用... 20页 免费如要投诉违规内容,请到百度文库投诉中心;如要提出功能问题或意见建议,请点击此处进行反馈。 ...
嵌入式系统原理与应用习题解析
嵌入式系统原理与应用习题解析_工学_高等教育_教育专区。1.8 练习题 1.选择题 (1)A P14 说明:嵌入式系统的发展趋势表现在以下几方面: 1.产品种类不断丰富,...
《嵌入式系统原理与应用》课程教学大纲
嵌入式系统原理与应用》课程教学大纲一、课程目的 本课程的目的是系统地学习有关嵌入式系统的基本原理、 设计方法以及嵌入式系统的最新发展, 初步掌握嵌入式系统...
更多相关标签:
嵌入式技术原理与应用 | 嵌入式系统 | 嵌入式系统原理及应用 | 嵌入式系统原理与设计 | 嵌入式系统原理与实践 | 嵌入式系统原理与开发 | 嵌入式系统原理与实验 | 微机原理与嵌入式系统 |