ARM Cortex-M3嵌入式开发实例详解
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第1章 Cortex-M3体系结构

ARM公司是全球领先的半导体知识产权(IP)提供商,32位嵌入式微处理器的行业领先提供商,已推出各种各样基于通用架构的处理器,这些处理器具有高性能和行业领先的特点,而且其系统成本也有所降低。ARM公司拥有至少900芯片生产厂家、工具和软件的合作伙伴,并已推出一系列(20多种)处理器,可以解决每个应用挑战。迄今为止,ARM公司已生产出超过250亿个处理器,每天的销量超过1600万个,是真正意义上的嵌入式系统的基础。

本章主要介绍目前ARM公司的Cortex-M3处理器,包括发展历史、处理器结构、各个功能特征。本章将结合LPC17XX对其功能特征进行介绍。

1.1 Cortex-M3简介

ARM公司的Cortex-M3是一种基于ARMV7构架的最新ARM嵌入式内核,采用哈佛结构,具有低成本、低功耗特点。与ARM7TDMI相比,Cortex-M3具有各种优势。本节将介绍ARM的历史、Cortex-M3的特点及与ARM7TDMI之间的性能对比。

1.1.1 ARM的历史

ARM这个词既代表了一个公司,也代表了微处理器,还代表了一项技术。1991年,ARM公司成立于英国剑桥,主要出售芯片技术的授权(目前已经授权给多家公司)。利用这种授权关系,ARM公司很快成为许多全球性RISC标准的缔造者。

1.ARM体系结构的版本历史

ARM体系结构是构建每个ARM处理器的基础。ARM体系结构支持跨越多个性能点的实现,并已在许多细分市场中成为主导的体系结构。ARM体系结构支持非常广泛的性能,因而可以利用最新的微体系结构技术获得极小的ARM处理器实现和极有效的高级设计实现。ARM体系结构能够解决实现规模小、性能差和功耗低的问题。

ARM体系结构通常描述为精简指令集计算机(RISC)体系结构,它包含以下典型的RISC体系结构特征。

(1)大的、统一的寄存器文件。

(2)简单的寻址模式。

(3)统一和固定长度的指令域,3地址指令格式,简化了指令的译码。采用3地址指令格式、较多寄存器和对称的指令格式便于生成优化代码。

(4)单周期操作。ARM指令系统中的指令只需要执行简单的和基本的操作,因此其执行过程在一个机器周期内完成。

(5)固定的32位长度指令。指令长度固定为32位,使得指令译码结构简单,效率提高。

(6)采用指令流水线技术。

ARM处理器对基本RISC体系结构进行功能扩展,实现了高性能、较小代码大小、较低功耗和较小硅面积的良好平衡。

ARM体系结构自诞生至今,已经发生了很大的演变,至今已定义了8种不同的版本,用版本号V1~V8表示。

(1)V1版架构只在原型机ARM1中出现过,其基本指令包括基本的数据处理指令(无乘法),字节、半字和字的Load/Store指令,转移指令,子程序调用及链接指令,软件中断指令,其寻址空间为64MB。

(2)V2版架构对V1版架构进行了扩展,如ARM2与ARM3(V2a版)架构,增加的指令包括乘法和乘加指令、支持协处理器操作指令、快速中断模式指令、SWP/SWPB的最基本的存储器与寄存器交换指令,其寻址空间为64MB。

(3)V3版架构对ARM体系结构做了较大的改动,把寻址空间增至32位(4GB),增加了当前程序状态寄存器CPSR和程序状态保存寄存器SPSR,以便于进行异常处理;增加了中止和未定义2种处理器模式。ARM6就采用了该版架构。该版架构的指令集的变化包括增加了MRS/MSR指令(以访问新增的CPSR/SPSR寄存器)及从异常处理返回的指令。

(4)V4版架构是目前最广泛应用的ARM体系结构,它对V3版架构进行了进一步扩充,有的还引进了16位的Thumb指令集,使得ARM的使用变得更加灵活。ARM7、ARM9和StrongARM都采用了该版结构。其指令集中增加的功能包括:增加了符号化和非符号化半字及符号化字节的存/取指令,增加了16位Thumb指令集,完善了软件中断SWI指令的功能,处理器系统模式引进特权方式时使用用户寄存器操作,把一些未使用的指令空间捕捉为未定义指令。

(5)V5版架构在V4版架构基础上增加了一些新的指令。ARM10和XScale都采用该版架构。这些新增指令有带有链接和交换的转移BLX指令、计数前导零计数CLZ指令、BRK中断指令、信号处理指令(V5TE版)、协处理器的更多可选择的指令。

(6)V6版架构是2001年发布的。其基本特点包括100%与以前的体系兼容;SIMD媒体扩展,使媒体处理速度快了1.75倍;改进了内存管理,使系统性能提高30%;改进了混合端(Endian)与不对齐数据支持,使得小端系统支持大端数据(如TCP/IP)。例如,许多RTOS是小端的,为实时系统缩短了中断响应时间,将最坏情况下的35周期减小为11个周期。

(7)V7版架构是2005年发布的。它使用了能够带来更高性能、功耗效率和代码密度的Thumb-2技术。它首次采用了强大的信号处理扩展集,对H.264和MP3等媒体编解码提供了加速功能。Cortex-M3处理器采用的就是V7版架构。

(8)V8版架构开始支持64位体系结构,它包括以下部分。

① 64位通用寄存器、SP(堆栈指针)和PC(程序计数器)。

② 64位数据处理和扩展的虚拟寻址。

③ 两种主要执行状态。

a.AArch64-64位执行状态,包括该状态的异常模型、内存模型、程序员模型和指令集支持。

b.AArch32-32位执行状态,包括该状态的异常模型、内存模型、程序员模型和指令集支持。

④ 支持三个主要指令集。

a.A32(或ARM):32位固定长度指令集,通过不同体系结构变体增强。部分32位体系结构执行环境现在称为AArch32。

b.T32(Thumb):以16位固定长度指令集的形式引入,随后在引入Thumb-2技术时增强为16位和32位混合长度指令集。

c.A64:提供与ARM和Thumb指令集有类似功能的32位固定长度指令集。它随ARMv8-A一起引入,是一种AArch64指令集。

2.处理器内核的历史

ARM公司开发了多种处理器内核,目前广泛使用的有ARM7系列、ARM9系列、ARM9E系列、ARM11系列、SecurCore系列及Cortex系列。不同处理器内核使用不同的体系结构版本。

如表1-1所示是ARM内核与ARM体系结构版本的对应关系。

表1-1 ARM内核与ARM体系结构版本的对应关系

下面对几种使用广泛的ARM内核做简单介绍。

1)ARM7系列

ARM7系列具有三级流水、空间统一的指令与数据Cache、平均功耗为0.6mW/MHz、时钟速度为66MHz、每条指令平均执行1.9个时钟周期等特性。其中ARM710、ARM720和ARM740为内带Cache的ARM内核。ARM7指令集与Thumb指令集扩展组合在一起,可以减少内存容量和系统成本。同时,它还利用嵌入式ICE调试技术来简化系统设计,并用一个DSP增强扩展来改进性能。ARM7体系结构是小型、快速、低能耗、集成式的RISC内核结构。该产品的典型用途是数字蜂窝电话和硬盘驱动器等。目前主流的ARM7内核是ARM7TDMI、ARM7TDMI-S、ARM7EJ-S、ARM720T。现在市场上用得最多的ARM7系列有思智浦公司的LPC2000系列微控制器、Samsung公司的S3C44BOX与S3C4510处理器、Atmel公司的AT91FR40162系列处理器、Cirrus公司的EP73xx系列等。该系列包括ARM7TDMI、ARM7TDMI-S、带有高速缓存处理器宏单元的ARM720T和扩充了Jazelle的ARM7EJ-S等。这些处理器提供Thumb 16位压缩指令集和EmbededICE软件调试方式,适用于更大规模的SoC设计中。

ARM7系列广泛应用于多媒体和嵌入式设备中,包括互联网设备、网络和调制解调器设备,以及移动电话、PDA等无线设备。

2)ARM9E系列

ARM9系列采用了ARMV4T(哈佛)体系结构。由于这种体系结构中的程序和数据存储器在两个分开的物理空间中,所以取指和执行能完全重叠。ARM9采用五级流水处理及分离的Cache结构,平均功耗为0.7mW/MHz。其时钟速度为120~200MHz,每条指令平均执行1.5个时钟周期。与ARM7系列相似,ARM9系列中的ARM920、ARM940和ARM9E处理器均为含有Cache的CPU核,其性能(速率)为132MIPS(120MHz时钟,3.3V供电)或220MIPS(200MHz时钟)。ARM9系列同时也配备了Thumb指令扩展、调试,以及Harvard总线。在生产工艺相同的情况下,其性能(速率)是ARM7TDMI处理器的两倍。它常用于无线设备、仪器仪表、联网设备、机顶盒设备、高端打印机及数码相机应用中。ARM9E内核在ARM9内核的基础上增加了紧密耦合存储器TCM及DSP部分。目前主流的ARM9内核是ARM920T、ARM922T、ARM940。相关的处理器芯片有Samsung公司的S3C2510、Cirrus公司的EP93xx系列等。主流的ARM9E内核是ARM926EJ-S、ARM946E-S、ARM966E-S等。

3)SecurCore系列

SecurCore系列提供了基于高性能的32位RISC技术的安全解决方案,该系列具有体积小、功耗低、代码密度大和性能高等特点。另外,最为特别的是,该系列提供了安全解决方案。它采用软内核技术,以提供最大限度的灵活性,以及防止外部对其进行扫描探测;提供面向智能卡的和低成本的存储保护单元MPU,可以灵活地集成用户自己的安全特性和其他的协处理器。该系列目前包括SC100、SC110、SC200、SC210共4种产品。

4)ARM11系列

ARM11系列可以在使用130nm工艺技术、芯片面积小至2.2mm2和功率低至0.24mW/MHz的前提下获得高达500MHz的速率。ARM11系列处理器以众多消费产品市场为目标,推出了许多新的技术,包括针对媒体处理的SIMD,用以提高安全性能的TrustZone技术,智能能源管理(IEM),以及需要非常高的、可升级的超过2600 Dhrystone 2.1 MIPS性能的系统多处理技术。主要的ARM11系列处理器有ARM1136JF-S、ARM1156T2F-S、ARM1176JZF-S、ARM11 MCORE等多种。

5)ARM Cortex系列

ARM Cortex系列基于ARMV7架构,又分为Cortex-M、Cortex-R和Cortex-A 3类。ARM Cortex系列的3款产品全都集成了Thumb®-2指令集,可满足各种不同的日益增长的市场需求。ARM Cortex系列的3款处理器瞄准的领域如下。

(1)ARM Cortex-A系列:针对复杂操作系统及用户应用设计的应用处理器。

(2)ARM Cortex-R系列:针对实时系统专用嵌入式处理器。

(3)ARM Cortex-M系列:针对微控制器和低成本应用、专门优化的深嵌入式处理器。

Cortex-M3是首款基于ARMV7-M架构的处理器,专门瞄准对功耗和成本敏感的嵌入式应用,是为使其实现高性能而设计的,它大大简化了可编程的复杂性,使得ARM体系结构成为各种应用方案(即使是最简单的方案)的上佳选择。

注意:NXP公司的LPC17XX系列属于Cortex-M3的核;LPC11XX系列属于Cortex-M0的ARM核。

Cortex系列处理器并没有开拓新的应用领域,从某种意义上讲,它是对原有应用领域的主流产品的一次大升级,提供了更好的性能。Cortex系列处理器对应于原有的历史上的各种处理核。ARM公司给出了各种产品的替代关系表,如表1-2所示。Cortex系列处理器主要是针对ARM11、ARM9、ARM7 3款经典处理器的升级。

表1-2 Cortex-M0/M3与ARM7/9/11的替代关系

1.1.2 Cortex-M3的特征

Cortex-M3是一个32位处理器内核。它基于哈佛构架,其指令和数据各使用一条总线,集成了分支预测、单周期乘法、硬件除法等特性。Cortex-M3增加了MPU,用于重要数据的保护及特权处理。它适用于高确定性的实时应用,适用于汽车车体系统、控制系统及无线网络和传感器等多个应用场合。

Cortex-M3的特点有以下几个。

(1)功耗低:Cortex-M3使用了最少的ARM内核,内核的核心部分(0.18um G)的门数仅为33000个,并支持扩展时钟门控和集成睡眠模式,使得Cortex-M3的功耗低,满足目前的白色家电和无线网络市场对低功耗的要求。

(2)位带操作:Cortex-M3支持两块位带存储区域,其中一块区域是SRAM,另一块区域是外设区。使用位带操作可简化外设控制流程。另外,通过使用布尔变量操作位带别名区可实现对位带区的单位操作,从而降低SRAM的使用率。

(3)采用Thumb®-2指令集,能够执行硬件除法、单周期乘法和位字段操作,从而获取最佳的性能和代码大小。

(4)低延迟中断处理机制:Cortex-M3处理器中集成的NVIC可实现硬件中断处理及低延迟,有利于减弱中断处理对处理器性能的影响。NVIC和处理器的紧密集成加快了中断服务程序的执行速度,并减少了进入中断所需的周期数。

(5)Cortex-M3 NVIC在设计时是可配置的,最多可提供240个具有单独优先级、动态重设优先级功能和集成系统时钟的系统中断。

(6)支持两种工作模式(线程模式和处理器模式)及两个等级(有特权和无特权)的代码访问,在不牺牲应用程序安全性的前提下执行复杂的开放式系统。

(7)丰富的连接功能和性能的组合使基于Cortex-M3的设备可以有效处理多个I/O通道和协议标准,如USB OTG(On-The-Go)。

(8)开发工具的多样化:串行线调试端口或串行线JTAG调试端口。

(9)ARM提供标准的CMSIS标准支持。

Cortex-M0/M3是针对ARM7TDMI-S用户群体设计的,ARM公司给出了将ARM7TDMI-S升级到Cortex-M0/3的益处,以及ARM7TDMI-S与Cortex-M0/3的性能对比,如表1-3所示。

表1-3 ARM7TDMI-S与Cortex-M0/3的对比关系表

如图1-1所示是Cortex-M3的内部功能接口框图。

图1-1 Cortex-M3的内部功能接口框图

NVIC是Cortex-M3处理器中一个完整的部分,它可以进行高度配置,为处理器提供出色的中断处理能力。在NVIC的标准执行中,它提供了1个非屏蔽中断(NMI)和32个通用物理中断,这些中断带有8级的抢占优先权。NVIC可以通过综合选择配置为1~240个物理中断中的任何一个,并带有多达256个优先级。

MPU是Cortex-M3处理器中一个可选的部分,它通过保护用户应用程序中操作系统所使用的重要数据,分离处理任务(禁止访问各自的数据),禁止访问存储器区域,将存储器区域定义为只读,以及对有可能破坏系统的未知的存储器访问进行检测等手段来改善嵌入式系统的可靠性。

对Cortex-M3处理器系统的调试访问是通过调试访问端口(Debug Access Port)来实现的。该端口可以作为串行线调试端口(SW-DP)[构成一个两脚(时钟和数据)接口]或串行线JTAG调试端口(SWJ-DP)(使能JTAG或SW协议)使用。SWJ-DP在上电复位时默认为JTAG模式,并且可以通过外部调试硬件所提供的控制序列进行协议的切换。

总线矩阵用来将处理器和调试接口与外部总线相连。总线矩阵与下面的外部总线相连。

(1)I-Code总线:该总线用于从代码空间取指令和向量,是32位AHB-Lite总线。

(2)D-Code总线:该总线用于对代码空间进行数据加载/存储及调试访问,是32位AHB-Lite总线。

(3)系统总线:该总线用于对系统空间执行取指令和向量,数据加载/存储及调试访问,是32位AHB-Lite总线。

(4)PPB:该总线用于对PPB空间进行数据加载/存储及调试访问,是32位APB(v2.0)总线。

总线矩阵还对以下方面进行控制。

(1)非对齐访问:总线矩阵将非对齐的处理器访问转换为对齐访问。

(2)总线矩阵将位带别名访问转换为对位带区的访问:对位带加载进行位域提取;对位带存储进行原子读—修改—写;写缓冲。总线矩阵包含一个单入口写缓冲区,该缓冲区使得处理器内核不受到总线延迟的影响。

1.2 内核寄存器

Cortex-M3处理器拥有R0~R15的寄存器组,其中R13用做堆栈指针SP(SP有两个,但在同一时刻只能有一个可以看到),R14为寄存器,R15为程序计数寄存器。Cortex-M3有5个专用寄存器。

如表1-4所示是Cortex-M3的寄存器表,该表中给出了是否需要特权才能访问的情况。

表1-4 Cortex-M3的寄存器表

1.2.1 通用寄存器

R0~R12都是32位通用寄存器,用于数据操作,保存数据或地址值。R0~R7被称为低寄存器,其余的被称为高寄存器。16位Thumb指令只能访问R0~R7,而32位Thumb-2指令可以访问所有寄存器。

1.2.2 连接寄存器

连接寄存器在汇编代码中可以写成R14或LR。它用于存储函数调用(程序寄存器返回值)和异常的返回信息。当执行分支(branch)和链接(BL)指令或带有交换的分支和链接指令(BLX)时,LR用于接收来自PC的返回地址。复位时,LR的数值为0xFFFFFFFF。

startup_LPC17xx.s文件中定义了__user_initial_stackheap函数,该函数由MDK自动调用,不用用户调用。其格式如下:

            __user_initial_stackheap
            …
            BX     LR;返回

1.2.3 程序计数器

程序计数器(PC)指向当前的程序地址。如果修改它的值,就能改变程序的执行流。PC[0]总是0,这是因为指令的取值必须按半字对齐。复位时,处理器用复位向量的值加载PC,复位向量地址为0x00000004。

1.2.4 专用寄存器

Cortex-M3包含了一些专用寄存器,主要有3种类型,5(或者说7)个寄存器,这些寄存器只能通过MSR或MRS指令操作,无对应内存地址。

(1)程序状态寄存器:该寄存器又分为3个子状态寄存器,分别是应用程序状态寄存器(APSR)、中断号状态寄存器(IPSR)、执行状态寄存器(EPSR)。

(2)中断屏蔽寄存器,有PRIMASK、FAULTMASK、BASEPRI。

(3)控制寄存器(CONTROL)。

1.程序状态寄存器

程序状态寄存器(如表1-5所示)可以作为一个整体访问或通过APSR、IPSR、EPSR形式独立访问。程序状态寄存器作为一个整体访问时,寄存器的名字是“xPSR”。可以使用MRS指令读取程序状态寄存器的内容,使用MSR指令改变APSR的内容;EPSR和IPSR为只读寄存器。CMSIS中的core_cm3.c给出了__get_IPSR, __get_xPSR, __get_APSR 3个函数实现读取APSR、IPSR与xPSR,其中__get_APSR的代码如下,该函数通过MRS指令读取APSR。

表1-5 程序状态寄存器

            __ASM uint32_t __get_APSR(void)
            {
            mrs r0, apsr
            bx lr
            }

当需要读取EPSR及设置APSR寄存器时,可通过以下两条指令实现:

            MRS r0,EPSR
            MSR APSR,r0

2.中断屏蔽寄存器

中断屏蔽寄存器用于实现对中断的开放与关闭。中断屏蔽寄存器主要有PRIMASK、FAULTMASK、BASEPRI。

(1)PRIMASK:只有一位的寄存器。该寄存器被置1后,除了NMI和硬故障(hard fault)之外的所有异常将被关闭;写0,不影响。在特权模式下,可通过MSR、MRS、CPS指令对该寄存器进行访问。

(2)FAULTMASK:只有一位的寄存器。该寄存器被置1后,除NMI外的所有异常将被关闭。在特权模式下,可通过MSR、MRS、CPS指令对该寄存器进行访问。

(3)BASEPRI:定义了屏蔽异常的最低优先级。当BASEPRI设置为某数值时,所有不大于数值的异常将被屏蔽;写0,不影响。

对时序要求严格的应用程序,可以通过PRIMASK与BASEPRI寄存器临时屏蔽中断,以达到时序控制目的。FAULTNASK主要用于在操作系统中关闭任务崩溃的各种异常。

CMSIS提供了如下函数,用于读取或设置中断屏蔽寄存器:

            uint32_t __get_PRIMASK(void)
            uint32_t  __get_BASEPRI(void)
            uint32_t  __get_FAULTMASK(void)
            void __set_BASEPRI(uint32_t basePri)
            void __set_FAULTMASK(uint32_t faultMask)
            void __set_PRIMASK(uint32_t priMask)

3.控制寄存器

控制寄存器(CONTROL)主要用于定义特权级别,以及选择使用哪个堆栈指针。

如表1-6所示是控制寄存器的位含义。

表1-6 控制寄存器的位含义

在处理模式下,始终使用主堆栈指针MSP。只有在异常进入和返回情况下,才会更新控制寄存器。

CONTROL[0]:仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径就是触发一个(软)中断(SVC指令),再由服务例程改写该位。

CMSIS中提供了对该寄存器进行访问及操作的函数:

            __get_CONTROL();// 读取当前CONTROL寄存器中的内容
            __set_CONTROL(x);// 设置CONTROL寄存器中的内容

1.3 操作模式和特权级别

Cortex-M3有两种操作模式,分别是线程模式和处理器模式,它们与ARM7TDM的操作模式有很大的区别。这两个模式的访问方式为特权和非特权方式,可以在不牺牲应用程序安全的前提下实现对复杂的开放式系统的执行。

线程模式:用于执行应用软件。处理器在退出复位时进入线程模式。该模式是常用的工作模式。它同时支持享有特权的代码和没有特权的代码。

处理器模式(handler mode):用于处理异常。处理器在完成异常处理后退回线程模式。该模式中的所有代码都享有特权。

软件执行的特权级别分别为非特权等级(或者称为用户等级)和特权等级。

非特权:对MSR和MRS指令的有限访问权限,且不能执行CPS指令;不能访问系统定时器、NVIC、系统控制模块;可能限制对存储器或外设的访问。

特权:软件可使用所有指令,并可访问全部资源。

如图1-2所示是操作模式与特权等级之间的切换过程。

图1-2 操作模式与特权等级之间的切换过程

特权级下的代码可以通过置位CONTROL[0]来进入用户级。不管是由于什么原因产生了什么异常,处理器都将用特权级来运行其服务例程,异常返回后,处理器将回到产生异常时所处的级别。用户级下的代码不能再通过试图修改CONTROL[0]来回到特权等级。它必须通过SVC指令实现一个异常处理器模式,由这个异常处理器模式来修改CONTROL[0],然后才能在返回线程模式后拿到特权等级。

1.4 存储器映射

ARM7内核没有定义存储器映射,各芯片厂商自己定义了存储器映射。Cortex-M3内核规定了存储器映射,从而使得各厂商生产的基于Cortex-M3内核的微控制器芯片具有相同的存储器映射。Cortex-M3留给了各厂商存储器空间,厂商可在此基础上进行扩展使用。本节将介绍Cortex-M3预定义的存储器映射,并说明LPC17XX在遵照Cortex-M3的预定义基础上使用的预留空间情况。

1.4.1 地址空间

Cortex-M3采用了预定义的内存映射方式。Cortex-M3的中断控制器及调试组件可通过简单的内存存储指令进行访问,从而使得软件设计适合用C语言编程实现。预定义的内存方式使得Cortex-M3更优化与高度集成。

如图1-3所示是Cortex-M3预定义的存储器映射,其空间是4G(0x00000000~0xFFFFFFFF)。

图1-3 Cortex-M3预定义的存储器映射

程序可以在代码区(0x00000000~0x1FFFFFFF)、内部SRAM区及外部RAM区中执行。由于指令总线与数据总线是分开的,所以最理想的是把程序放到代码区,从而使取指和数据访问各自使用自己的总线。

片上SRAM区的大小是512MB,用于让芯片制造商连接片上的SRAM,这个区通过系统总线来访问。有一个1MB的区间被称为“位带区”。该位带区还有一个对应的32MB的“位带别名(alias)区”。位带区对应的是最低的1MB地址范围,而位带别名区里的每个字对应的是位带区的一个比特。位带操作只适用于数据访问,不适用于取指。通过位带的功能,可以把多个布尔型数据打包在单一的字中,但是依然可以从位带别名区中像访问普通内存一样地使用它们。

片上外设区(0x40000000~0x5FFFFFFF)供片上寄存器使用。这个区中也有一个32MB的位带别名区,以便于快捷地访问外设寄存器,其用法与内部SRAM区中的位带相同。例如,可以方便地访问各种控制位和状态位。要注意的是,外设区内不允许执行指令。

0x60000000~0x9FFFFFFF区域与0xA0000000~0xDFFFFFFF区域分别用于连接外部RAM和外部设备,它们之中没有位带。两者的区别在于外部RAM区允许执行指令,而外部设备区则不允许执行指令。

0xE0000000~0xE00FFFFF是Cortex-M3独立拥有的区域,该区域不提供给芯片制造厂家使用。如图1-4所示是Cortex-M3的私有外设总线的内存映射图。私有外设总线有以下两条。

图1-4 Cortex-M3的私有外设总线的内存映射图

(1)AHB私有外设总线:只用于CM3内部的AHB外设,它们是NVIC,FPB,DWT和ITM。

(2)APB私有外设总线:既用于CM3内部的APB设备,也用于外部设备(指的是芯片生产厂家的APB外设)。CM3允许芯片生产厂家增添一些片上APB外设到APB私有总线上,它们通过APB接口来访问。

下面来看一下LPC17XX是如何使用Cortex-M3规定的内存映射的。如表1-7所示是LPC17XX的内存映射。LPC17XX在Cortex-M3的预定义的代码区中有64KB的SRAM,该部分用于执行代码与保存数据。

表1-7 LPC17XX的内存映射

如图1-5所示是LPC1768的存储器映射情况。LPC177X/LPC178X与LPC1768除了AHB外设(即DMA、USB、以太网及新增加的LCD)的存储空间不一样外,其余的APB占用空间几乎一样。Cortex-M3的内核空间均符合Cortex-M3的存储空间分配要求。

图1-5 LPC176X的存储器映射情况

技巧:编译程序时,输出map文件可以查看内存分配情况,以及程序代码、数据占用空间情况。

1.4.2 位带操作

1.位带概念

位带(bit-band)操作更像C语言中的union与struct相结合后的位操作。为了更好地理解它,下面先举一个例子:

            struct_Bitband_Alias_Bits  {
                Uint32 Alias0:1;
            …
                Uint32 Alias31:1;
            };
            @@@
            union BitBand_Operator {
                Uint32  BitBand;
                struct  _Bitband_Alias_Bits Bitband_Alias_Bits;
            };
        BitBand_Operator myBitBand;

由上述程序可知,通过myBitband. Bitband_Alias_Bits. Alias0的访问是通过对myBitBand. BitBand的第0位操作实现的。

Cortex-M3的位带操作类似于上面的C语言操作方式,但区别在于BitBand与Bitband_Alias_Bits是两块独立的内存地址,而且位带操作中的Alias0代表一个32位的数据,但只有最低0位是有效数据。

Cortex-M3支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读/写。在Cortex-M3中,有两个区中实现了位带:一个是SRAM区的最低1MB范围(0x20000000-0x200FFFFF),对应的位带别名区是0x22000000~0x23FFFFFF;第二个则是片内外设区的最低1MB范围(0x40000000~0x400FFFFF),对应的别名区是0x42000000~0x43FFFFFF。

映射公式显示如何将别名区中的字与bit-band区中的对应位或目标位关联。映射公式举例:

        bit_word_offset=(byte_offset×32)+(bit_number×4)
        bit_word_addr=bit_band_base+bit_word_offset

这里:

        Bit_word_offset为bit-band存储区中的目标位的位置;
        Bit_word_addr为别名存储区中映射为目标位的字的地址;
        Bit_band_base是别名区的开始地址;
        Bit_offset为bit-band区中包含目标位的字节的编号;
        Bit_number为目标位的位置(0~7)。

如图1-6所示为SRAM位带区与对应的别名区之间的关系图。图1-6中箭头所指映射的转换关系如下:

图1-6 SRAM位带区与对应的别名区之间的关系图

        地址0x23FFFFE0的别名字映射为0x200FFFFC的位带字节的位0:
        0x23FFFFE0=0x22000000+(0xFFFFF*32)+0*4
        地址 0x23FFFFEC的别名字映射为 0x200FFFFC的位带字节的位 7:
        0x23FFFFEC=0x22000000+(0xFFFFF*32)+7*4
        地址 0x22000000 的别名字映射为 0x20000000 的位带字节的位 0:
        0x22000000=0x22000000+(0*32)+0*4
        地址 0x220001C的别名字映射为 0x20000000 的位带字节的位 0:
        0x2200001C=0x22000000+(0*32)+7*4

2.位带操作方法

向别名区中的一个字执行写操作会更新位带区中的单个位。但并不是别名区的所有位都有效,别名区的一个字的第0位是有效的,会影响对应位带区的数值,即向别名区中的一个字写入值的位[0]决定了写入位带区中目标位的值:写入一个位[0]设为1的值,则会向位带位写入一个1;写入一个位[0]设为0的值,则会向位带位写入一个0。

注意:别名字的位[31:1]对位带位无影响,写入0x01与写入0xFF效果相同,写入0x00与写入0x0E效果相同。

读取别名区中的一个字,如读取数据为

(1)0x00000000,表示位带区中的目标位被设为0;

(2)0x00000001,表示位带区中的目标位被设为1。

位带区的访问可通过字节、半字、字的方式进行访问。

通过C语言实现对Cortex-M3的位带操作一般使用宏定义方式进行,并通过“1位带概念”中的位带与别名区映射公式进行。下列代码给出的定义中采用了右移代替乘法运算。

        /*SRAM的位带区与别名区对应地址*/
        #define BITBAND_SRAM_REF   0x20000000
        #define BITBAND_SRAM_BASE  0x22000000
        // 将位带地址转换到别名区,其中a代表位带区地址,b代表要转换的位带字节的位数
        #define BITBAND_SRAM(a,b) ((BITBAND_SRAM_BASE + ((a-BITBAND_SRAM_REF)<<5) +
(b<<2)))
        /* 外设位带地址宏定义 */
        #define BITBAND_PERI_REF   0x40000000
        #define BITBAND_PERI_BASE  0x42000000
        // 将位带地址转换到别名区,其中a代表位带区地址,b代表要转换的位带字节的位数
        #define BITBAND_PERI(a,b)((BITBAND_PERI_BASE+((a-BITBAND_PERI_REF)<<5)+(b<<2)))

当使用位带功能时,要访问的变量必须用volatile来定义。因为C编译器并不知道同一个比特可以有两个地址,所以需要通过volatile使编译器每次都如实地把新数值写入存储器中,而不再出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回——这会导致按不同的方式访问同一个位得到不一致的结果。

小知识:对于volatile声明的变量,编译器在访问该变量的代码时不再进行优化,从而可以提供对特殊地址的稳定访问。

下列程序演示了通过位带方式操作SPI控制寄存器的过程。

        /* SRAM的位带区与别名区对应地址*/
        #define BITBAND_SRAM_REF   0x20000000
        #define BITBAND_SRAM_BASE  0x22000000
        // 将位带地址转换到别名区,其中a代表位带区地址,b代表要转换的位带字节的位数
        #define BITBAND_SRAM(a,b) ((BITBAND_SRAM_BASE + ((a-BITBAND_SRAM_REF)<<5) +
    (b<<2)))
        /* 外设位带地址宏定义 */
        #define BITBAND_PERI_REF   0x40000000
        #define BITBAND_PERI_BASE  0x42000000
        // 将位带地址转换到别名区,其中a代表位带区地址,b代表要转换的位带字节的位数
        #define BITBAND_PERI(a,b) ((BITBAND_PERI_BASE + ((a-BITBAND_PERI_REF)<<5) + (b<<2)))
        /* 位带区操作函数 */
        #define BITBAND_SRAM_ClearBit(a,b) (*(volatile uint32_t *) (BITBAND_SRAM(a,b)) = 0)
        #define BITBAND_SRAM_SetBit(a,b) (*(volatile uint32_t*)(BITBAND_SRAM(a,b))=1)
        #define BITBAND_SRAM_GetBit(a,b) (*(volatile uint32_t *) (BITBAND_SRAM(a,b)))
    @@@
        #define BITBAND_PERI_ClearBit(a,b) (*(volatile uint32_t *) (BITBAND_PERI(a,b)) = 0)
        #define BITBAND_PERI_SetBit(a,b)  (*(volatile uint32_t*)(BITBAND_PERI(a,b))=1)
        #define BITBAND_PERI_GetBit(a,b)  (*(volatile uint32_t*)(BITBAND_PERI(a,b)))
        #define VAR_ADDRESS      LPC_GPIO2_BASE//0x2007C000
        #define VAR_BIT           3//bit 3
    @@@
        #define PERI_ADDRESS  0x40020000//SPI控制寄存器地址(S0SPCR)
        #define PERI_BIT       5//bit 5-Master mode select(主模式选择)
        uint32_t temp;
        uint32_t temp1;
    @@@
        int main(void) {
    @@@
          //SRAM
              temp = (*(volatile uint32_t *)(VAR_ADDRESS));
          temp1 = BITBAND_SRAM_GetBit(VAR_ADDRESS,VAR_BIT);
          if(temp1==(temp&1<<VAR_BIT))
          {
          ;//可在此设置断点,验证结果的正确性
          }
    @@@
          //设置第3位为1
          BITBAND_SRAM_SetBit(VAR_ADDRESS,VAR_BIT);
          temp=(*(volatile uint32_t*)(VAR_ADDRESS));  //temp的第3位为1,即temp=0x8
          temp1= BITBAND_SRAM_GetBit(VAR_ADDRESS,VAR_BIT); //temp1 =1
          //外设区代码演示
          LPC_SPI->SPCR=(1<<5);  //直接设置0x40020000
          temp1 = (*(volatile uint32_t *)(PERI_ADDRESS));//读取0x40020000
          temp=BITBAND_PERI_GetBit(PERI_ADDRESS,PERI_BIT);  //读取对应别名区,即temp=1
          BITBAND_PERI_ClearBit(PERI_ADDRESS,PERI_BIT);
          temp=BITBAND_PERI_GetBit(PERI_ADDRESS,PERI_BIT);  //temp=0
          temp1=(*(volatile uint32_t*)(PERI_ADDRESS));
          BITBAND_PERI_SetBit(PERI_ADDRESS,PERI_BIT);
          temp=BITBAND_PERI_GetBit(PERI_ADDRESS,PERI_BIT);  //temp=1
          temp1=(*(volatile uint32_t*)(PERI_ADDRESS));   //temp1=0x20,即temp结果右移5位
          while(1);
          return 0;
        }

3.位带操作的用途

对应于LPC17XX的外设,所有外设(除了以太网、USB、DMA、GPIO以外)都位于位带操作区域。APB0与APB1对应的外设内存都属于位带区域。

注意:由于GPIO不属于位带区,所以如果经常需要设置引脚状态时,不能通过位带方式进行操作。

1.4.3 端模式

Cortex-M3支持32位字、16位半字、8位字节数据类型。Cortex-M3处理器将存储器看做一个从0开始增长的线性集合。存储器可理解成:

(1)地址0~3保存了第一个要保存的字。

(2)地址4~7保存了第二个要保存的字。

Cortex-M3支持大端模式和小端模式(但对应于具体芯片时,可能不遵守这个约定)。可以通过读取AIRCR.ENDIANNESS的位判断当前的端模式。端模式的配置通过Cortex-M3提供的BIGEND引脚进行。

注意:LPC17XX仅支持小端模式,不支持大端模式。它没有提供配置引脚。如果程序中出现大端模式需求,需要自己手工解决这个问题(即大端模式需求)。

鉴于LPC17XX仅支持小端模式,本节仅给出小端模式的概念。

小端模式是指处理器将一个字的最低(有效)位位字节存储在序号最小的字节中,将最高(有效)位位字节存储在序号最大的字节中。图1-7给出了小端模式下,在地址A到地址A+3之间如何保存一个字数据及将数据存储在寄存器中的情况。

图1-7 小端模式举例

1.5 总线接口

Cortex-M3内部有若干条总线接口,使得Cortex-M3能同时取指和访问内存。本节将介绍各总线,以及LPC17XX的总线对应的外设情况。

1.5.1 3级流水线

原有的ARM7TDMI的指令执行速度为0.9MIPS/MHz。目前采用ARMv6构建的Cortex-M0已经可以达到这个执行速度,而采用哈佛总线结构的Cortex-M3的指令执行速度可达到1.25 DMIPS/MHz。Cortex-M3处理器使用一个3级流水线。流水线的3级分别是取指、译码和执行,如图1-8所示。

图1-8 Cortex-M3的3级流水线

由于使用了指令流水线,所以读PC时返回的值是当前指令的地址+4。例如:

    0x1000:   MOV   R0,   PC   ;  读到R0=0x1004,而不是0x1000

这个偏移量总是4,不管是执行16位指令还是32位指令,这就保证了在Thumb和Thumb2之间的一致性。

1.5.2 总线矩阵

总线矩阵用来将处理器和调试接口与外部总线相连。总线矩阵与下面的外部总线相连。

(1)I-Code总线:该总线用于从代码空间取指令和向量,是32位AHB-Lite总线。

(2)D-Code总线:该总线用于对代码空间进行数据加载/存储及调试访问,是32位AHB-Lite总线。

(3)系统总线:该总线用于对系统空间执行取指令和向量,数据加载/存储及调试访问,是32位AHB-Lite总线。

(4)PPB:该总线用于对PPB空间进行数据加载/存储及调试访问,是32位APB(v2.0)总线。

注意:对于多层AHB矩阵,只有当多个主机试图同时访问同一矩阵的从机端口时,才会做主机之间的仲裁。在默认情况下,Cortex-M3的D-Code总线有最高优先级,然后是I-Code总线。所有其他主机的优先级较低。

如图1-9所示是Cortex-M3总线接口示例,该图结合LPC17XX,说明了系统总线与各个设备之间的连接关系。在LPC17XX中,Cortex-M3内核总线通过多层AHB总线矩阵连接各个外设设备。图1-9中给出的外设是LPC17XX的部分外设。

图1-9 Cortex-M3总线接口示例

1.I-Code总线

I-Code总线是一条基于AHB-Lite总线协议的32位总线,负责在0x00000000~0x1FFFFFFF之间的取指操作。取指以字的长度执行,即使是对于16位指令也如此。因此,CPU内核可以一次取出两条16位Thumb指令。

2.D-Code总线

D-Code总线也是一条基于AHB-Lite总线协议的32位总线,负责在0x00000000~0x1FFFFFFF之间的数据访问操作。尽管Cortex-M3支持非对齐访问,但该总线不允许这样操作,这是因为处理器的总线接口会把非对齐的数据传送都转换成对齐的数据传送。因此,连接到D-Code总线上的任何设备都只需要支持AHB-Lite的对齐访问,而不需要支持非对齐访问。

ARM公司推荐D-Code总线的优先级高于I-Code总线的优先级。

3.系统总线

系统总线也是一条基于AHB-Lite总线协议的32位总线,负责在0x20000000~0xDFFFFFFF和0xE0100000~0xFFFFFFFF之间的所有数据传送,取指和数据访问都包括在内。和D-Code总线一样,系统总线的所有数据传送都是对齐的。

4.私有外设总线

私有外设总线是一条基于APB总线协议的32位总线。此总线负责0xE0040000~0xE00FFFFF之间的私有外设访问。但是由于此APB存储空间的一部分已经被TPIU、ETM及ROM表用掉了,所以只留下0xE0042000~E00FF000这个区间用于配接附加的(私有)外设。

1.6 存储器保护单元(MPU)

1.6.1 MPU概述

存储器保护单元(MPU)是Cortex-M3选配的功能,用于实现对存储器的保护,从而使软件更加健壮和可靠。MPU将存储器映射划分为多个区,并定义了每个区的位置、大小、访问权限及存储器属性。可定义8个单独的存储区及1个背景区。存储区可重叠,此时存储器访问受最大的区的属性影响(7的优先级最高,0的优先级最低)。每个区域的大小可以是32B~4GB。

背景区具有与默认存储映射相同的存储器访问属性,但只能被特权软件访问。

如果程序访问被MPU禁止的存储区,则处理器会产生一个存储器管理异常。因此,在嵌入式操作系统中,内核可根据执行的任务动态更新MPU区设置,从而实现存储器保护。

例如,FreeRTOS实现了具有MPU功能的FreeRTOS-MPU,用于根据任务情况实现对存储区设置只读属性、将其他区设置成不可执行及利用xTaskCreateRestricted()生成一些访问受限的任务。

1.6.2 MPU的寄存器

MPU的寄存器如表1-8所示。

表1-8 MPU的寄存器

TYPE寄存器指示是否存在MPU,以及如果存在,它支持多少个存储区。

CTRL寄存器用于使能MPU;使能默认存储器映射的背景区;在硬故障、不可屏蔽中断(NMI)和FAULTMASK升级的处理程序中,使能MPU的使用。

RNR寄存器用于选择哪个存储区被RBAR和RASR寄存器引用。

RBAR寄存器定义了RNR所选择MPU区的基址,并可以更新RNR的值。

RASR寄存器定义了区的大小和RNR所指定的MPU区的存储器属性,并使能该区,以及其子区的寄存器汇总。

1.6.3 MPU的使用

CMSIS给出了MPU寄存器的C语言访问方式,主要有MPU->TYPE、MPU->RNR、MPU->RBAR、MPU->RASR、MPU->CTRL,与表1-8中名字相同的寄存器一一对应。MPU的设置流程如图1-10所示。

图1-10 MPU的设置流程

1.7 中断和异常

Cortex-M3采用ARMV7-M体系结构中的异常(中断)模型,取消了原来在ARM7(如LPC23XX,LPC24XX)系列中的FIQ,但采用了中断优先级,引入了嵌套中断模式,从而实现了嵌套中断,即一个高优先级的中断能够覆盖或抢占低优先级的中断。Cortex-M3支持11个系统异常,最多240个外部中断。外设产生的中断信号,除了SysTick外,全部连接到NVIC的中断输入信号线上。

注意:LPC17XX的外部中断是35个。实际上芯片制造商并不会全部使用240个外部中断。

所有的异常处理都是在处理模式下实现的。当出现中断时,处理器的状态自动保存到堆栈中,并在中断服务程序(ISR)结束时自动从堆栈中恢复。取出向量和保存状态是同时进行的,从而提高了进入中断的效率。Cortex-M3还支持末尾连锁(Tail-chaining),使得处理器无须保存和恢复状态便可执行连续的中断。

注意:末尾连锁(Tail-chaining)指的是当处理器响应某中断时,又发生了其他优先级较低的中断,则低优先级的中断先被挂起。在当前中断执行返回后,不再执行出栈和入栈操作,直接处理挂起的中断,就好像后一个中断与前一个中断的尾连接起来了,前后只执行了一次入栈/出栈操作,从而使得两个ISR之间的间隔大大缩短。

Cortex-M3的中断处理方式与原有的ARM处理中断方式相比,具有不易丢失中断的特点。如果一个发生的异常不能被即刻响应,就称它被“挂起”(pending)。不过,少数fault异常是不允许被挂起的。一个异常被挂起的原因,可能是系统当前正在执行一个更高优先级异常的服务例程,或者因相关掩蔽位的设置导致该异常被禁能。对于每个异常源,在被挂起的情况下,都会有一个对应的“挂起状态寄存器”保存其异常请求。等到该异常能够响应时,执行其服务例程,这与传统的ARM是完全不同的。在以前,是由产生中断的设备保持住请求信号的;在Cortex-M3中,则由NVIC的挂起状态寄存器来保持住请求信号的。于是,哪怕设备在后来已经释放了请求信号,曾经的中断请求也不会错失。

1.7.1 异常类型

Cortex-M3自带的异常是16个,其中异常号为0与7~10的异常保留,以供将来使用。异常主要有以下几种。

1.复位

复位的异常号为0,优先级为-3(最高优先级)。

上电或热复位时,复位启动。异常模型将复位当做一种特殊的异常形式。当复位使能时,处理器可能在一条指令的任何位置停止操作。当复位禁能时,由向量表中的复位表项提供重新开始执行的地址。在线程模式下,重新执行是特权执行。

2.NMI

不可屏蔽中断(NMI)可由一个外设发出信号或由软件触发。这是除复位外的最高优先级的异常。它被永久性使能,具有固定的优先级-2。NMI不能因任何其他异常的激活而被屏蔽或阻止,不能被除复位外的其他任何异常抢占。

3.硬故障

硬故障是一种异常,其发生原因是异常在处理期间出错,或异常无法被任何其他异常机制管理。硬故障具有固定优先级-1,这意味着它的优先级高于任何具有可配置优先级的异常。

4.存储器管理故障

存储器管理故障是一种由与存储器保护相关的故障引发的异常。对于指令和数据存储事务,MPU或固定的存储器保护约束条件决定此故障。此故障用于终止对从不执行(XN)存储区的指令的访问,即使MPU被禁能也是如此。

5.总线故障

总线故障是一种异常,由一个指令或数据存储器事务的存储器的相关故障引发。这可能源于从一个存储器系统总线上检测出的错误。

6.使用故障

使用故障是与指令执行相关的故障导致的一种异常。使用故障包括:

(1)未定义的指令;

(2)非法的非对齐访问;

(3)指令执行时的无效状态;

(4)异常返回时的错误。

当内核被配置为报告使用故障时,以下情况可以导致一个使用故障:

(1)字和半字存储器访问时的非对齐地址;

(2)除以零。

7.SVCall

SVCall是指执行系统服务调用指令(SVC)引发的异常。一次超级用户调用是由SVC指令触发的异常。在OS环境中,应用程序可使用SVC指令来访问OS内核函数和设备驱动程序。

8.PendSV

PendSV是一个对系统级服务的中断驱动请求。在OS环境中,当无其他有效的异常时,使用PendSV进行上下文切换。

9.SysTick

SysTick是当系统定时器达到零时产生的异常。软件也可以产生一个SysTick。在OS环境中,处理器可将此异常用做系统节拍。

10.中断(IRQ)

中断(IRQ)是由一个外设发出信号,或由一个软件请求产生的异常。所有中断都与指令执行异步。在系统中,外设使用中断与处理器进行通信。

注意:中断(IRQ)在LPC17XX中是指一些外设的中断,如看门狗、UART的中断。在LPC176X总共有35个中断(LPC177X/LPC178X中为41个)。

1.7.2 异常优先级

Cortex-M3中的异常优先级可决定一个异常是否能被掩蔽,以及在未掩蔽的情况下何时可以响应。优先级的数值越小,优先级越高。Cortex-M3支持中断嵌套,使得高优先级异常会抢占低优先级异常。有3个系统异常:复位,NMI及硬fault,它们有固定的优先级,并且它们的优先级号是负数,高于所有其他异常。所有其他异常的优先级都是可编程的。

异常优先级的设置分为两步:

(1)设置组优先级有效位(LPC17XX通过设置系统控制寄存器AIRCR[10:8]实现);

(2)设置组优先级及子优先级(LPC17XX通过设置各自的IPRx或系统控制中的SHPx寄存器实现)。

注意:组优先级在有的书中也叫做抢占优先级或主优先级。有的文献中使用group priority,有的文献中使用preempt priority,虽然使用的词语不一样,但它们的含义是一样的,均指具有抢占功能。

如表1-9所示是优先级分组。其中分组位置指的是AIRCR[10:8]中的数值。IPRx寄存器分成抢占优先级与子优先级两部分。

表1-9 优先级分组

如果有多个挂起异常共用相同的组优先级,则需使用次优先级区来决定同组中异常的优先级,这就是同组内的次优先级。组优先级和次优先级的结合就是通常所说的优先级。如果两个挂起异常具有相同的优先级,则挂起异常的编号越低,优先级越高。这与优先级机制是一致的。

抢占优先级(组优先级)决定了抢占行为:当系统正在响应某异常L时,如果来了抢占优先级更高的异常H,则H可以抢占L。只有组优先级才能决定中断异常的抢占。当处理器正在执行一个中断异常处理程序时,另一个与正在处理中的中断具有相同组优先级的中断不会抢占处理程序。

子优先级处理组内优先级的情况:当抢占优先级相同的异常有不止一个挂起时,就最先响应子优先级最高的异常。这种优先级分组做出了如下规定:子优先级至少是1个位。因此,抢占优先级最多是7个位,128级。

从表1-9中可以看出,Cortex-M3允许从比特7处分组,此时所有的位都表达子优先级,没有任何位表达抢占优先级,因此所有优先级可编程的异常之间就不会发生抢占——相当于在它们之中禁用了Cortex-M3的中断嵌套机制。复位、NMI和硬fault属于例外情况,它们无论何时出现,都立即抢占所有中断(异常)。

CMSIS提供设置优先级函数:

        void NVIC_SetPriorityGrouping(uint32_t PriorityGroup);   //如何进行优先级分组
        void NVIC_SetPriority(IRQn_Type IRQn,uint32_t priority);//设置中断源IRQn的优先级情况(包
                                                      //含组优先级与子优先级)
    NVIC_EncodePriority(uint32_tPriorityGroup,uint32_tPreemptPriority,uint32_t SubPriority);;//将组优
                                      //先级与子优先级组合起来,用于给NVIC_SetPriority函数赋值

LPC17XX中存在EINT0、EINT1、EINT2、EINT3四个外部中断,在使用时分别给予了不同优先级。在这里设定EINT0>EINT1的优先级。可以根据需要采用两种方式:一种是设定不同组,这样EINT0可以抢占EINT1的优先级,并可能存在优先级嵌套方式;另外一种设定同一组,但EINT0的子优先级高于EINT1的优先级。

程序代码如下所示。该程序使用宏定义方式来区分两种方式,并根据需要设定了INT_MODE的数值,以及采用了NVIC_EncodePriority简化组优先级和子优先级的组合计算问题。

        #define P_Group  4     //组属性定义
        #if(INT_MODE==0)    //相同组,不同子优先级
        NVIC_SetPriorityGrouping(P_Group);//设定组属性;子优先级为3
        NVIC_SetPriority(EINT0_IRQn,NVIC_EncodePriority(P_Group,0,2));  //组优先级:0;子优先
                                                              //级:2
        NVIC_SetPriority(EINT3_IRQn,NVIC_EncodePriority(P_Group,0,1));  //组优先级:0;子优先
                                                              //级:1
        #else//不同组,抢占模式
        NVIC_SetPriorityGrouping(P_Group);  //sets group priorities:8-subpriorities:3
        NVIC_SetPriority(EINT0_IRQn,NVIC_EncodePriority(P_Group,0,0));   //组优先级:0;子优先
                                                              //级:0
        NVIC_SetPriority(EINT3_IRQn,NVIC_EncodePriority(P_Group,1,0));  //组优先级:1;子优先
                                                              //级:0
        #endif

1.7.3 异常响应过程

异常响应的基本原则就是只要中断挂起寄存器为0,而且该中断没有被屏蔽,就可进入中断挂起状态。如果在进入handler模式之前,挂起寄存器被清除掉,则进入中断服务程序。

如果挂起寄存器被清0(不管是软件清除,还是通过进入中断服务程序方式清除),就可响应新的异常触发信号。如果挂起寄存器已经为1,对于外部新的中断请求,也仅响应一次。

异常响应的状态主要有以下6种。

(1)当中断输入脚生效后,该中断就被挂起。即使后来中断源取消了中断请求,已经被标记成挂起的中断也被记录下来。当在系统中它的优先级最高时,它就会得到响应,如图1-11所示。

图1-11 中断响应

(2)如果在某个中断得到响应之前,其挂起状态被清除了(例如,当PRIMASK或FAULTMASK置位时,软件清除了挂起状态标志),则中断被取消,如图1-12所示。

图1-12 挂起状态被提前清除掉

(3)当某中断的服务例程开始执行时,就称此中断进入了“活跃”状态,并且其挂起位会被硬件自动清除。在一个中断活跃后,直到其服务例程执行完毕,并且返回后,才能对该中断的新请求予以响应。当然,新请求的响应也是由硬件自动清零并挂起标志位的。中断服务例程也可以在执行过程中把自己对应的中断重新挂起(使用时要注意避免进入“死循环”),如图1-13所示。

图1-13 中断请求被清除

(4)如果中断源上一直存在请求信号,该中断就会在其上次服务例程返回后再次被置为挂起状态,在这一点上,CM3和传统的ARM7TDMI是相同的。这种情况发生在电平触发源类型的中断过程中,如图1-14所示。

图1-14 请求一直有效

(5)如果某个中断在得到响应之前,其请求信号以若干脉冲形式呈现,则被视为只有一次中断请求,多出的请求脉冲全部丢失,这种情况是由于中断请求过快造成的。

假如设计程序时,将外部中断EINT0的优先级设定得比较低,虽然触发多次中断,但因为其他高优先级的中断一直占用CPU,所以最后将仅表现为响应一次中断。因此,对中断的优先级及中断响应时长都需要严格规划好,否则会出现各种意想不到的问题,如图1-15所示。

图1-15 中断请求多次触发

(6)如果在服务例程执行过程中,中断请求释放了,但是在服务例程返回前又重新被置为有效,则CM3会记住此动作,重新挂起该中断。例如,使用SysTick时,如果中断处理程序比较长(运行时间需要1ms),但时钟间隔设置比较短(设置成了500μs),这种情况发生后,程序将一直占用处理器,除非具有更高优先级的中断产生,如图1-16所示。

图1-16 中断挂起状态消失后重新触发

从最后两种形式可以看出,中断的挂起状态影响着中断的响应次数。因为中断挂起状态只能是0和1,无其他状态,所以只要该状态寄存器被清除,就可以响应下一次中断了。该寄存器如果已经为1,则将无法响应新的状态。

1.8 指令系统

Cortex-M3不再支持ARM指令,而是支持ARMV6Thumb指令,同时引入了Thumb-2指令。Thumb-2指令是一种新型混合指令集,融合了16位和32位指令,用于实现密度和性能的最佳平衡。在不对性能进行折中的情况下,它节省了许多高集成度系统级设计的总体存储成本。

Cortex-M3支持的指令可归纳为6大类:数据传送、数据处理、分支指令、系统指令、饱和指令和杂项指令。

数据传送指令可:

(1)在两个寄存器之间传送数据;

(2)在寄存器与存储器之间传送数据;

(3)在寄存器与特殊功能寄存器之间传送数据;

(4)将一个立即数加载到寄存器中。

数据处理指令主要是一些四则运算指令与移位操作指令,包含16位与32位操作指令。

分支指令又分为无条件跳转及有条件跳转,如B、BL、CBZ指令。

系统指令主要是一些直接针对Cortex-M3寄存器的操作指令,如CPSIE和CPSID。

饱和指令主要是SSAT和USAT,用于对寄存器进行饱和操作。

由于目前Cortex-M3进行软件开发时,更多地关注于C语言开发,所以CMSIS也提供了各种对应指令的C语言版本函数及操作寄存器的C语言函数。仅仅在Cortex-M3的启动文件涉及部分汇编代码。

下面将对启动文件(Startup.s)中涉及的跳转指令和加载指令进行解释说明。

启动文件中使用的跳转指令:

    B   Label   ;跳转到Label处对应的地址
    BX   reg    ;跳转到由寄存器reg给出的地址
    BLX   reg    ;跳转到由寄存器reg给出的地址,并根据REG的LSB切换处理器状态,还要
    ;把转移前的下条指令地址保存到LR

BL和BLX指令可将下一条指令的地址复制到链接寄存器(LR)R14中。使用BL与BLX时要小心,因为它们还带有改变状态的功能。寄存器reg的LSB必须是1,以确保处理器不会试图进入ARM状态。如果忘记置位LSB,将会出现UsageFault异常。

加载指令LDR用于把存储器中的内容加载到寄存器中。Startup.s使用了LDR伪指令。如果汇编器发现要产生的立即数是一个程序地址,它会自动地把LSB置位,例如:

          LDR   r0,   =address1   ;R0=0x40001
          …
      address1
      0x4000:  MOV  R0,  R1

在这个例子中,由于汇编器会认出address1是一个程序地址,所以自动置位LSB,即R0的复制不是0x4000,而是0x4001。另外,如果汇编器发现要加载的是数据地址,则不会自动置位LSB,例如:

        LDR   R0,   =address1   ; 会把0x4000 原封不动地加载到R0
        …
      address1
      0x4000:   DCD   0x0    ;0x4000 处记录的是一个数据

至此,我们可以看一下Startup.s里面的使用情况:

      IMPORT  SystemInit
      IMPORT  __main
      LDR    R0,=SystemInit;自动将R0的LSB置位
      BLX    R0;切换到Thumb状态,并加载下一条指令
      LDR    R0,=__main
      BX     R0

由于C语言不能直接访问Cortex-M3指令,所以CMSIS提供了通过C语言访问指令及直接访问寄存器的方式,这些方式通过内联汇编的方式实现。

表1-10中给出了CMSIS指令关系。

表1-10 CMSIS指令关系

1.9 小结

本章介绍了Cortex-M3的基本框图、寄存器、存储器的使用情况及异常的基础理论,对比了Cortex-M3与ARM7TDMI,并结合LPC17XX,给出了一些Cortex-M3具体应用于某款芯片时的一些特点和用法。

本章中的中断知识是以后章节中经常使用的内容。Cortex-M3对中断的处理机制与ARM7TDMI有非常大的区别。本章中结合CMSIS给出了对中断进行设置的方法。

本章对原有ARM7TDMI经常涉及的指令系统只做了简单介绍。这主要是因为目前Cortex-M3的产品设计是C语言的天下(当然对于ARM公司而言,可能仍是汇编语言的天下,但对于普通用户而言,就是C语言的天下了),在Cortex-M3的软件设计中几乎可以不用了解汇编。ARM公司为Cortex-M3软件设计提供的便捷方法会在第3章的CMSIS一节中介绍到。

第2章将介绍进行LPC17XX硬件设计的基础理论内容。