Cortex-M3学习笔记(持续更新)

缩写含义

缩略代号含义

ADK

AMBA设计套件

AHB AHB-AP AMBA     先进高性能总线AHB访问端口先进单片机总线架构
APB先进外设总线
ARM ARMARM架构参考手册
ASIC行业领域专用集成电路
ATB先进跟踪总线
BE8字节不变式大端模式
CPI每条指令的周期数
CPU中央处理单元
DAP调试访问端口
DSP数字信号处理器/数字信号处理
DWT数据观察点及跟踪
ETM嵌入式跟踪宏单元
FPB闪存地址重载及断点
FSRFault状态寄存器
HTMCoreSight AHB跟踪宏单元
ICE在线仿真器
IDE集成开发环境
IRQ中断请求(通常是指外部中断的请求)
ISA指令系统架构
ISR中断服务例程
ITM仪器化跟踪宏单元
JTAG JTAG-DP LR连结点测试行动组(一个关于测试和调试接口的标准)JTAG调试端口连接寄存器
LSB最低有效位
LSU加载/存储单元
MCU微控制器单元(俗称单片机)
MMU存储器管理单元
MPU存储器保护单元
MSB最高有效位
MSP主堆栈指针
NMI不可屏蔽中断
NVIC嵌套向量中断控制器
OS操作系统
PC程序计数器
PSP进程堆栈指针
PPB私有外设总线

 第一章 初识CM3

1.1 初识CM3

CM3的招牌功夫包括:

• 性能强劲:在相同的主频下能做处理更多的任务。(低CPI)

• 功耗低: 延长了电池的寿命——这简直就是便携式设备的命门(如无线网络应用)。

• 实时性好:采用了很前卫甚至革命性的设计理念,使它能极速地响应中断,而且响应中断所

需的周期数是确定的。

• 代码密度得到很大改善:一方面力挺大型应用程序,另一方面为低成本设计而省吃俭用。

• 使用更方便:现在从8位/16位处理器转到32位处理器之风刮得越来越猛,更简单的编程模型

和更透彻的调试系统,为与时俱进的人们大大减负。

• 低成本的整体解决方案: 让32位系统比和8位/16位的还便宜,低端的Cortex-M3单片机甚至还

卖不到1美元。

• 遍地开花的优秀开发工具:免费的,便宜的,全能的,要什么有什么。

1.1.1从 Cortex-M3 处理器内核到基于 Cortex-M3 的 MCU
 

 1.1.2 ARM 及 ARM 架构的背景

ARM在1990年成立,当初的名字是“Advanced RISC Machines Ltd.,”,当时它是三家公司的合资——它们分别是苹果电脑, Acorn电脑公司,以及VLSI技术(公司)。

除了设计处理器, ARM也设计系统级IP和软件IP。为了挺它们, ARM开发了许多配套的基础开发工具、硬件以及软件产品。使用这些工具, 合作伙伴可以更加舒心地开发他们自己的产品。

1.2 ARM 的各种架构版本:

         款式A(ARMv7-A):需要运行复杂应用程序的“应用处理器”(这里的“应用”尤指大型应用程序,像办公软件,导航软件,网页浏览器等。这些软件的使用习惯和开发模式都很像PC上的软件, 但是基本上没有实时要求)。 支持大型嵌入式操作系统,比如Symbian(诺基亚智能手机用), Linux, 以及微软的Windows CE和智能手机操作系统Windows Mobile。这些应用需要劲爆的处理性能, 并且需要硬件MMU实现的完整而强大的虚拟内存机制, 还基本上会配有Java支持, 有时还要求一个安全程序执行环境。典型的产品包括高端手机和手持仪器,电子钱包以及金融事务处理机。

         款式R(ARMv7-R):硬实时且高性能的处理器。标的是高端实时市场(通用处理器能否胜任实时系统的控制, 常遭受质疑,并且在这方面的争论从没停止过。从定义的角度讲, “实时”就是指系统必须在给定的死线(deadline,亦称作“最后期限” )内做出响应。在一个以ARM处理器为核心的系统中,决定能否达到“实时”这个目标的,有很多因素,包括是否使用“实时操作系统”,中断延迟,存储器延时,以及当时处理器是否在运行更高优先级的中断服务例程。
)。那些高级的玩意,像高档轿车的组件,大型发电机控制器, 机器手臂控制器等, 它们使用的处理器不但要很好很强大,还要极其可靠,对事件的反应也要极其敏捷。

        款式M(ARMv7-M):认准了旧世代单片机的应用而量身定制。在这些应用中,尤其是对于实时控制系统,低成本、低功耗、极速中断反应以及高处理效率,都是至关重要的。Cortex系列是v7架构的第一次亮相,其中Cortex-M3就是按款式M设计的。

1.2.1处理器命名法

处理器名字架构版本号存储器管理特性其它特性
ARM7TDMIv4T
ARM7TDMI-Sv4T
ARM7EJ-Sv5EDSP,Jazelle[译注3]
ARM920Tv4TMMU
ARM922Tv4TMMU
ARM926EJ-Sv5EMMUDSP,Jazelle
ARM946E-Sv5EMPUDSP
ARM966E-Sv5EDSP
ARM968E-Sv5EDMA,DSP
ARM966HSv5EMPU(可选)DSP
ARM1020Ev5EMMUDSP
ARM1022Ev5EMMUDSP
ARM1026EJ-Sv5EMMU 或 MPU[译注2]DSP, Jazelle
ARM1136J(F)-Sv6MMUDSP, Jazelle
ARM1176JZ(F)-Sv6MMU+TrustZoneDSP, Jazelle
ARM11 MPCorev6MMU+多处理器缓存支持DSP
ARM1156T2(F)-Sv6MPUDSP
Cortex-M3v7-MMPU(可选)NVIC
Cortex-R4v7-RMPUDSP
Cortex-R4Fv7-RMPUDSP+浮点运算
Cortex-A8v7-AMMU+TrustZoneDSP, Jazelle

 1.3 指令集的发展

        由于历史原因(从ARM7TDMI开始), ARM处理器一直支持两种形式上相对独立的指令集, 它们分别是:

  • 32位的ARM指令集:对应处理器状态: ARM状态
  • 16位的Thumb指令集:对应处理器状态: Thumb状态

        可见,这两种指令集也对应了两种处理器执行状态。在程序的执行过程中,处理器可以动态地在两种执行状态之中切换。 实际上, Thumb指令集在功能上是ARM指令集的一个子集,但它能带来更高的代码密度,给目标代码减肥。这对于要勒紧裤腰带的应用还是很经济的。

1.4 Thumb-2 指令集体系体系结构( ISA)

        Thumb-2强大,易用,轻佻,高效。 Thumb-2是16位Thumb指令集的一个超集,在Thumb-2中, 16位指令首次与32位指令并存,结果在Thumb状态下可以做的事情一下子丰富了许多,同样工作需要的指令周期数也明显下降。

     Thumb-2指令集与Thumb指令集的关系

        Cortex-M3拒绝了32位ARM指令集,把自己的处理能力以身相许般地全托给Thumb-2指令集。 这可能有些令人意外,但事实上这却见证了Cortex-M3的用情专一: 在内核水平上,就已经为适应单片机和小内存器件而抉择、取舍过了。但她没有嫁错郎, 因为Thumb-2完全胜任在这个领域挑大梁。不过, 这也意味着Cortex-M3作为新生代处理器,不是向后兼容的。因此,为ARM7写的ARM汇编语言程序不能直接移植到CM3上来。不过, CM3支持绝大多数传统的Thumb指令,因此用Thumb指令写的汇编程序就从善如流了。

1.5 Cortex-M3 应用场景

 高性能+高代码密度+小硅片面积, 3璧合一,使得CM3大面积地成为理想的处理平台:低成本单片机: CM3与生俱来就适合做单片机,甚至简单到用于做玩具和小电器的单片机,都能使用CM3作为内核。这里本是8位机和16位机统治最牢固的腹地, 但是CM3更便宜,更高性能,更易使用,所以值得开发者们转到这个新生的ARM32位系统中来,哪怕花点时间重新学习

  • 汽车电子: CM3也是汽车电子的好俅。 CM3同时拥有非常高的性能和极低的中断延迟,打入实时领域的大门。 CM3处理器能支持多达240个外部中断, 内建了嵌套向量中断控制器,还可以选择配上一个存储器保护单元(MPU)。所有这些,使它用于高集成度低成本的汽车应用最合适不过了。
  • 数据通信: CM3的低成本+高效率,再加上Thumb-2的强大位操作指令s,使CM3非常理想地适合于很多数据通信应用,尤其是无线数传和Ad-Hoc网络, 如ZigBee和蓝牙等。
  • 工业控制:在工控场合,关键的要素在于简洁、快速响应以及可靠。再一次地, CM3处理器的中断处理能力,低中断延迟, 强化的故障处理能力(fault-handing,以后fault就不再译成中文了——译注) ,足以让它能昂首挺胸地踏入这片热土。
  • 消费类产品:以往,在许多消费产品中,都必须使用一块甚至好几块高性能的微处理器。你别看CM3只是个小处理器,它的高性能和MPU机制可是足以让复杂的软件跑起来的,同时提供健壮的存储器保护。

目前在市场上已经有了好多基于Cortex-M3内核的处理器产品,最便宜的还不到1美元,让ARM终于比很多8位机还便宜了。

第二章 CM3简介

2.1 简介

        Cortex-M3 是一个 32 位处理器内核。内部的数据路径是 32 位的,寄存器是 32 位的,存储器接口也是 32 位的。 CM3 采用了哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线,从而提升了性能。为实现这个特性, CM3 内部含有好几条总线接口,每条都为自己的应用场合优化过,并且它们可以并行工作。但是另一方面,指令总线和数据总线共享同一个存储器空间(一个统一的存储器系统)。换句话说,不是因为有两条总线,可寻址空间就变成 8GB 了。

        比较复杂的应用可能需要更多的存储系统功能,为此 CM3 提供一个可选的 MPU,而且在需要的情况下也可以使用外部的 cache。另外在 CM3 中, Both 小端模式和大端模式都是支持的。

        CM3 内部还附赠了好多调试组件,用于在硬件水平上支持调试操作,如指令断点,数据观察点等。另外,为支持更高级的调试,还有其它可选组件,包括指令跟踪和多种类型的调试接口。

 

2.2 寄存器组
 

Cortex-M3 处理器拥有 R0-R15 的寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。

2.2.1 R0-R12:通用寄存器

R0-R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访问 R0-R7,而 32 位 Thumb-2 指令可以访问所有寄存器。

2.2.2 Banked R13: 两个堆栈指针

Cortex-M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。

  • 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)
  • 进程堆栈指针(PSP):由用户的应用程序代码使用。

堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。

        在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。 除了外部中断外, 当有指令执行了“非法操作”, 或者访问被禁的内存区间, 因各种错误产生的 fault, 以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外, 程序代码也可以主动请求进入异常状态的(常用于系统调用)。

2.2.3 R14:连接寄存器

        当呼叫一个子程序时,由 R14 存储返回地址
        ARM 为了减少访问内存的次数(访问内存的操作往往要 3 个以上指令周期,带 MMU 和cache 的就更加不确定了),把返回地址直接存储在寄存器中。这样足以使很多只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于 1 级,则需要把前一级的 R14 值压到堆栈里。在 ARM上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。在 RISC 处理器中,为了强调访内操作越过了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。

2.2.4 R15:程序计数寄存器

        指向当前的程序地址。如果修改它的值,就能改变程序的执行流。

2.2.5 特殊功能寄存器

        Cortex-M3 还在内核水平上搭载了若干特殊功能寄存器,包括程序状态字寄存器组(PSRs)

中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)控制寄存器(CONTROL)

 

                         Cortex-M3 中的特殊功能寄存器集合

寄存器功能
xPSR记录 ALU 标志(0 标志,进位标志,负数标志,溢出标志),执行状态,以及当前正服务的中断号
PRIMASK除能所有的中断——当然了,不可屏蔽中断(NMI)才不甩它呢。
FAULTMASK除能所有的 fault——NMI 依然不受影响,而且被除能的 faults 会“上访”,见后续章节的叙述。
BASEPRI除能所有优先级不高于某个具体数值的中断。
CONTROL定义特权状态(见后续章节对特权的叙述),并且决定使用哪一个堆栈指针

2.3 操作模式和特权极别

        Cortex-M3 处理器支持两种处理器的操作模式,还支持两级特权操作。两种操作模式分别为: 处理者模式(handler mode,以后不再把 handler 中译——译注)和线程模式(thread mode)。引入两个模式的本意,是用于区别普通应用程序的代码和异常服务例程的代码——包括中断服务例程的代码。Cortex-M3 的另一个侧面则是特权的分级——特权级和用户级。这可以提供一种存储器访问。引入两个模式的本意,是用于区别普通应用程序的代码和异常服务例程的代码——包括中断服务例程的代码。

        Cortex-M3 的另一个侧面则是特权的分级——特权级和用户级。这可以提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。处理器支持两种特权级,这也是一个基本的安全模型。


                                                 Cortex-M3 下的操作模式和特权级别

        在 CM3 运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。在特权级下,程序可以访问所有范围的存储器(如果有 MPU,还要在 MPU 规定的禁地之外),并且可以执行所有指令。

        在特权级下的程序可以为所欲为,但也可能会把自己给玩进去——切换到用户级。一旦进入用户级,再想回来就得走“法律程序”了——用户级的程序不能简简单单地试图改写 CONTROL 寄存器就回到特权级,它必须先“申诉”:执行一条系统调用指令(SVC)。这会触发 SVC 异常,然后由异常服务例程(通常是操作系统的一部分)接管,如果批准了进入,则异常服务例程修改 CONTROL 寄存器,才能在用户级的线程模式下重新进入特权级。

        事实上,从用户级到特权级的唯一途径就是异常:如果在程序执行过程中触发了一个异常,处理器总是先切换入特权级, 并且在异常服务例程执行完毕退出时,返回先前的状态(也可以手工指定返回的状态——译注)。

                                                         合法的操作模式转换图

        通过引入特权级和用户级,就能够在硬件水平上限制某些不受信任的或者还没有调试好的程序,不让它们随便地配置涉及要害的寄存器,因而系统的可靠性得到了提高。进一步地,如果配了 MPU,它还可以作为特权机制的补充——保护关键的存储区域不被破坏,这些区域通常是操作系统的区域。

        举例来说,操作系统的内核通常都在特权级下执行,所有没有被MPU禁掉的存储器都可以访问。在操作系统开启了一个用户程序后, 通常都会让它在用户级下执行,从而使系统不会因某个程序的崩溃或恶意破坏而受损。

2.4 内建的嵌套向量中断控制器

        Cortex-M3 在内核水平上搭载了一颗中断控制器——嵌套向量中断控制器 NVIC(NestedVectored Interrupt Controller)。它与内核有很深的“亲密接触” ——与内核是紧耦合的。NVIC 提供如下的功能:

  • 可嵌套中断支持
  • 向量中断支持
  • 动态优先级调整支持
  • 中断延迟大大缩短
  • 中断可屏蔽

2.4.1 可嵌套中断支持

        可嵌套中断支持的作用范围很广,覆盖了所有的外部中断和绝大多数系统异常。外在表现是,这些异常都可以被赋予不同的优先级。当前优先级被存储在 xPSR 的专用字段中。当一个异常发生时,硬件会自动比较该异常的优先级是否比当前的异常优先级更高。如果发现来了更高优先级的异常,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常——即立即抢占。

2.4.2 向量中断支持

        当开始响应一个中断后, CM3 会自动定位一张向量表,并且根据中断号从表中找出 ISR 的入口地址,然后跳转过去执行。不需要像以前的 ARM 那样, 由软件来分辨到底是哪个中断发生了,也无需半导体厂商提供私有的中断控制器来完成这种工作。这么一来,中断延迟时间大为缩短。

2.4.3 动态优先级调整支持

        软件可以在运行时期更改中断的优先级。如果在某ISR中修改了自己所对应中断的优先级,而且这个中断又有新的实例处于悬起中(pending),也不会自己打断自己,从而没有重入风险。(reentry)。(所谓的重入,就是指某段子程序还没有执行完,就因为中断或者是多任务操作系统的调度原因,导致该子程序在一个新的寄存器上下文中被执行(请不要把重入与递归混淆,它们有本质的区别)。这种情况常常会闹出乱子,因此有“可重入性”的研究。)、


2.4.4 中断延迟大大缩短

        Cortex-M3 为了缩短中断延迟,引入了好几个新特性。包括自动的现场保护和恢复,以及其它的措施,用于缩短中断嵌套时的 ISR 间延迟。详情请见后面关于“咬尾中断”和“晚到中断”的讲述。

2.4.5 中断可屏蔽

        既可以屏蔽优先级低于某个阈值的中断/异常(“异常”,均指除了外部中断之外的异常,而使用“中断”来表示所有外部中断——也就是对于处理器来说是异步的中断)(设置BASEPRI寄存器),也可以全体封杀(设置PRIMASK和FAULTMASK寄存器)。这是为了让时间关键( time-critical)的任务能在死线(deadline,或曰最后期限)到来前完成,而不被干扰。

2.5 存储器映射


总体来说, Cortex-M3 支持 4GB 存储空间,如图所示地被划分成若干区域。

        从图中可见,不像其它的 ARM 架构,它们的存储器映射由半导体厂家说了算, Cortex-M3 预先定义好了“粗线条的”存储器映射。通过把片上外设的寄存器映射到外设区,就可以简单地以访问内存的方式来访问这些外设的寄存器,从而控制外设的工作。结果,片上外设可以使用 C 语言来操作。这种预定义的映射关系,也使得对访问速度可以做高度的优化,而且对于片上系统的设计而言更易集成(还有一个重要的,不用每学一种不同的单片机就要熟悉一种新的存储器映射——译注)。

        Cortex-M3 的内部拥有一个总线基础设施,专用于优化对这种存储器结构的使用。在此之上, CM3 甚至还允许这些区域之间“越权使用”。比如说,数据存储器也可以被放到代码区,而且代码也能够在外部 RAM 区中执行(但是会变慢不少——译注 )。

        处于最高地址的系统级存储区,是 CM3 用于藏“私房钱”的——包括中断控制器、 MPU 以及各种调试组件。所有这些设备均使用固定的地址(本书第 5 章讨论存储器系统)。通过把基础设施的地址定死,就至少在内核水平上,为应用程序的移植扫清了障碍。

2.6 总线接口

Cortex-M3 内部有若干个总线接口,以使 CM3 能同时取址和访内(访问内存),它们是:

  • 指令存储区总线(两条)
  • 系统总线
  • 私有外设总线

        有两条代码存储区总线负责对代码存储区的访问,分别是 I-Code 总线D-Code 总线。前者用于取指,后者用于查表等操作,它们按最佳执行速度进行优化。

        系统总线用于访问内存和外设,覆盖的区域包括 SRAM,片上外设,片外 RAM,片外扩展设备,以及系统级存储区的部分空间。

        私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。

 2.7 存储器保护单元( MPU)

        Cortex-M3 有一个可选的存储器保护单元。配上它之后,就可以对特权级访问和用户级访问分别施加不同的访问限制。当检测到犯规(violated)时, MPU 就会产生一个 fault 异常,可以由fault 异常的服务例程来分析该错误,并且在可能时改正它。

        MPU 有很多玩法。最常见的就是由操作系统使用 MPU,以使特权级代码的数据,包括操作系统本身的数据不被其它用户程序弄坏。 MPU 在保护内存时是按区管理的(“区”的原文是 region,以后不再中译此名词——译注)。它可以把某些内存 region 设置成只读,从而避免了那里的内容意外被更改;还可以在多任务系统中把不同任务之间的数据区隔离。一句话,它会使嵌入式系统变得更加健壮,更加可靠(很多行业标准,尤其是航空的,就规定了必须使用 MPU 来行使保护职能——译注)。

 2.8 指令集

        Cortex-M3 只使用 Thumb-2 指令集。这是个了不起的突破,因为它允许 32 位指令和 16 位指令水乳交融,代码密度与处理性能两手抓,两手都硬。而且虽然它很强大,却依然易于使用。

        在过去,做 ARM 开发必须处理好两个状态。这两个状态是井水不犯河水的,它们是: 32 位的ARM 状态和 16 位的 Thumb 状态。当处理器在 ARM 状态下时,所有的指令均是 32 位的(哪怕只是个”NOP”指令),此时性能相当高。而在 Thumb 状态下,所有的指令均是 16 位的,代码密度提高了一倍。不过, thumb 状态下的指令功能只是 ARM 下的一个子集,结果可能需要更多条的指令去完成相同的工作,导致处理性能下降。

        为了取长补短,很多应用程序都混合使用 ARM 和 Thumb 代码段。然而,这种混合使用是有额外开销(overhead)的,时间上的和空间上的都有,主要发生在状态切换之时。另一方面, ARM 代码和 Thumb 代码需要以不同的方式编译,这也增加了软件开发管理的复杂度。

                              在诸如 ARM7 处理器上的状态切换模式图

        伴随着 Thumb-2 指令集的横空出世,终于可以在单一的操作模式下搞定所有处理了,再也没有来回切换的事来烦你了。 事实上, Cortex-M3 内核干脆都不支持 ARM 指令,中断也在 Thumb 态下处理(以前的 ARM 总是在 ARM 状态下处理所有的中断和异常)。这可不是小便宜,它使 CM3 在好几个方面都比传统的 ARM 处理器更先进:

  • 消灭了状态切换的额外开销,节省了 both 执行时间和指令空间。
  • 不再需要把源代码文件分成按 ARM 编译的和按 Thumb 编译的,软件开发的管理大大减负。
  • 无需再反复地求证和测试:究竟该在何时何地切换到何种状态下,我的程序才最有效率。开发软件容易多了。

        不少有趣和强大的指令为 Cortex-M3 注入了新鲜的青春血液,下面给出几个例子:

  • UBFX, BFI, BFC: 位段提取,位段插入,位段清零。支持 C 位段,也简化了外设寄存器操作。
  • CLZ, RBIT: 计算前导零指令和位反转指令。二者组合使用能实现一些特技。
  • UDIV, SDIV: 无符号除法和带符号除法指令。
  • SEV, WFE, WFI: 发送事件,等待事件以及等待中断指令。用于实现多处理器之间的任务同步,还可以进入不同的休眠模式。
  • MSR,MRS: 通向禁地——访问特殊功能寄存器。

        因为 CM3 专情于最新的 Thumb-2,旧的应用程序需要移植和重建。对于大多数 C 源程序,只需简单地重新编译就能重建,汇编代码则可能需要大面积地修改和重写,才能使用 CM3 的新功能,并且融入 CM3 新引入的统一汇编器框架(unified assembler framework)中。

       请注意: CM3 并不支持所有的 Thumb-2 指令, ARMv7-M 的规格书只要求实现 Thumb-2 的一个子集。举例来说,协处理器指令就被裁掉了(可以使用外部的数据处理引擎来替代)。 CM3 也没有实现 SIMD 指令集。旧世代的一些 Thumb 指令不再需要,因此也被排除。不支持指令还包括 v6 中引入的 SETEND 指令。如欲查出一个完整的指令列表,可以去看附录 A。

2.9 中断和异常

        ARMv7-M 开创了一个全新的异常模型, CM3 采用了它。请你一定要划清界线:这种异常模型跟传统 ARM 处理器使用的完全是两码事。新的异常模型“使能”了非常高效的异常处理。它支持16-4-1=11 种系统异常(保留了 4+1 个档位),外加 240 个外部中断输入。在 CM3 中取消了 FIQ 的概念(v7 前的 ARM 都有这个 FIQ,快中断请求),这是因为有了更新更好的机制——中断优先级管理以及嵌套中断支持,它们被纳入 CM3 的中断管理逻辑中。因此,支持嵌套中断的系统就更容易实现 FIQ。

        CM3 的所有中断机制都由 NVIC 实现。除了支持 240 条中断之外, NVIC 还支持 16-4-1=11 个内部异常源,可以实现 fault 管理机制。结果, CM3 就有了 256 个预定义的异常类型,如表 2.2所示。

编号类型优先级简介
0N/AN/A没有异常在运行
1复位-3(最高)复位
2NMI-2不可屏蔽中断(来自外部 NMI 输入脚)
3硬(hard) fault-1所有被除能的 fault,都将“上访”成硬 fault
4MemManage fault可编程存储器管理 fault, MPU 访问犯规以及访问非法位置
5总线 fault可编程总线错误(预取流产( Abort)或数据流产)
6用法(usage)Fault可编程由于程序错误导致的异常
7-10保留N/AN/A
11SVCall可编程系统服务调用
12调试监视器可编程调试监视器(断点,数据观察点,或者是外部调试请求
13保留N/AN/A
14PendSV可编程为系统设备而设的“可悬挂请求”( pendable request)
15SysTick可编程系统滴答定时器(也就是周期性溢出的时基定时器——译注)
16IRQ #0可编程外中断#0
17IRQ #1可编程外中断#1
255IRQ #239可编程外中断#239

        虽然 CM3 是支持 240 个外中断的,但具体使用了多少个是由芯片生产商决定。 CM3 还有一个NMI(不可屏蔽中断)输入脚。当它被        置为有效(assert)时, NMI 服务例程会无条件地执行。

2.10 低功耗与高能效

        为了使我们的产品功耗更低,以及能源利用效率更高, Cortex-M3 在设计时加入了很多针对性的功能。

        首先,在节能模式上,它提供了睡眠模式和深度睡眠模式。芯片以及整个系统在设计时通过与内核的节能模式相呼应,就可以根据应用的要求,在空闲时降低功耗。第二,它精练的设计使得门数很低,并且在工作状态下电路的活动更少,所以 CM3 自己也是“身先士卒”地以身作则了。而且,由于 CM3 的程序代码密度高,程序容量也可以变得更少;同时,再加上它强大的性能减少了程序执行时间,使得系统能以最快的速度回到睡眠中,以削低对能源的用量。综上所述, Cortex-M3 的能效要高于大多的 8 位或 16 位单片机。

2.11 调试支持

        Cortex-M3 在内核水平上搭载了若干种调试相关的特性。最主要的就是程序执行控制,包括停机(halting)、单步执行(stepping)、指令断点、数据观察点、寄存器和存储器访问、性能速写(profiling)以及各种跟踪机制。

        Cortex-M3 的调试系统基于 ARM 最新的 CoreSight 架构。不同于以往的 ARM 处理器,内核本身不再含有 JTAG 接口。取而代之的,是 CPU 提供称为“调试访问接口(DAP)”的总线接口。通过这个总线接口,可以访问芯片的寄存器,也可以访问系统存储器,甚至是在内核运行的时候访问!对此总线接口的使用,是由一个调试端口(DP)设备完成的。 DPs 不属于 CM3 内核,但它们是在芯片的内部实现的。目前可用的 DPs 包括 SWJ-DP(既支持传统的 JTAG 调试,也支持新的串行线调试协议),另一个 SW-DP 则去掉了对 JTAG 的支持。另外,也可以使用 ARM CoreSignt 产品家族的 JTAG-DP模块。这下就有 3 个 DPs 可以选了,芯片制造商可以从中选择一个,以提供具体的调试接口(通常都是选 SWJ-DP)。

        此外, CM3 还能挂载一个所谓的“嵌入式跟踪宏单元(ETM)”。 ETM 可以不断地发出跟踪信息,这些信息通过一个被称为“跟踪端口接口单元(TPIU)”的模块而送到内核的外部,再在芯片外面使用一个“跟踪信息分析仪”,就可以把 TIPU 输出的“已执行指令信息”捕捉到,并且送给调试主机——也就是 PC。

        在 Cortex-M3 中,调试动作能由一系列的事件触发,包括断点,数据观察点, fault 条件,或者是外部调试请求输入的信号。当调试事件发生时, Cortex-M3 可能会停机,也可能进入调试监视器异常 handler。具体如何反应,则根据与调试相关寄存器的配置。

        与调试相关的还有其它的绝活。现在要介绍的是“仪器化跟踪宏单元(ITM)”,它也有自己的办法把数据送往调试器。通过把数据写到 ITM 的寄存器中,调试器能够通过跟踪接口来收集这些数据,并且显示或者处理它。此法不但容易使用,而且比 JTAG 的输出速度更快。

        所有这些调试组件都可以由 DAP 总线接口来控制, CM3 内核提供 DAP 接口。此外,运行中的程序也能控制它们。所有的跟踪信息都能通过 TPIU 来访问到。

2.12 Cortex-M3 的品性简评

2.12.1 高性能

许多指令都是单周期的——包括乘法相关指令。并且从整体性能上, Cortex-M3 比得过绝大多数其它的架构。

  • 指令总线和数据总线被分开,取值和访内可以并行不悖
  • Thumb-2 的到来告别了状态切换的旧世代,再也不需要花时间来切换于 32 位 ARM 状态和16 位 Thumb 状态之间了。这简化了软件开发和代码维护,使产品面市更快。
  • Thumb-2 指令集为编程带来了更多的灵活性。许多数据操作现在能用更短的代码搞定,这意味着 Cortex-M3 的代码密度更高,也就对存储器的需求更少。
  • 取指都按 32 位处理。同一周期最多可以取出两条指令,留下了更多的带宽给数据传输。
  • Cortex-M3 的设计允许单片机高频运行(现代半导体制造技术能保证 100MHz 以上的速度)。
  • 即使在相同的速度下运行, CM3 的每指令周期数(CPI)也更低,于是同样的 MHz 下可以做
  • 更多的工作;另一方面,也使同一个应用在 CM3 上需要更低的主频。

2.12.2 先进的中断处理功能

  • 内建的嵌套向量中断控制器支持多达 240 条外部中断输入。向量化的中断功能剧烈地缩短了中断延迟,因为不再需要软件去判断中断源。中断的嵌套也是在硬件水平上实现的,不需要软件代码来实现。
  • Cortex-M3 在进入异常服务例程时,自动压栈了 R0-R3, R12, LR, PSR 和 PC,并且在返回时自动弹出它们,这多清爽!既加速了中断的响应,也再不需要汇编语言代码了(第8 章有详述)。
  • NVIC 支持对每一路中断设置不同的优先级,使得中断管理极富弹性。最粗线条的实现也至少要支持 8 级优先级,而且还能动态地被修改。
  • 优化中断响应还有两招,它们分别是“咬尾中断机制”和“晚到中断机制”。
  • 有些需要较多周期才能执行完的指令,是可以被中断-继续的——就好比它们是一串指令一样。这些指令包括加载多个寄存器(LDM),存储多个寄存器(STM),多个寄存器参与的PUSH,以及多个寄存器参与的 POP。
  • 除非系统被彻底地锁定, NMI(不可屏蔽中断)会在收到请求的第一时间予以响应。对很多安全-关键(safety-critical)的应用, NMI 都是必不可少的(如化学反应即将失控时的紧急停机)。

2.11.3 低功耗

  • Cortex-M3 需要的逻辑门数少, 所以先天就适合低功耗要求的应用(功率低于 0.19mW/MHz)
  • 在内核水平上支持节能模式(SLEEPING 和 SLEEPDEEP 位)。通过使用“等待中断指令(WFI)”和“等待事件指令(WFE)”,内核可以进入睡眠模式,并且以不同的方式唤醒。另外,模块的时钟是尽可能地分开供应的,所以在睡眠时可以把 CM3 的大多数“官能团”给停掉。
  • CM3 的设计是全静态的、同步的、可综合的。任何低功耗的或是标准的半导体工艺均可放心饮用。

2.11.4 系统特性

  • 系统支持“位寻址带”操作(8051 位寻址机制的“威力大幅加强版”), 字节不变的大端模式,并且支持非对齐的数据访问。
  • 拥有先进的 fault 处理机制,支持多种类型的异常和 faults,使故障诊断更容易。
  • 通过引入 banked 堆栈指针机制,把系统程序使用的堆栈和用户程序使用的堆栈划清界线。如果再配上可选的 MPU,处理器就能彻底满足对软件健壮性和可靠性有严格要求的应用。

 2.11.5 调试支持

  • 在支持传统的 JTAG 基础上,还支持更新更好的串行线调试接口。
  • 基于 CoreSight 调试解决方案,使得处理器哪怕是在运行时,也能访问处理器状态和存储器内容。
  • 内建了对多达 6 个断点和 4 个数据观察点的支持。
  • 可以选配一个 ETM,用于指令跟踪。数据的跟踪可以使用 DWT
  • 在调试方面还加入了以下的新特性,包括 fault 状态寄存器,新的 fault 异常,以及闪存修补 (patch)操作,使得调试大幅简化。
  • 可选 ITM 模块(软件应用程序驱动的跟踪源。它支持生成软件测量跟踪(SWIT),并且还提供粗略的时间戳功能。ITM模块的主要用途包括:支持printf风格调试,跟踪操作系统和应用程序事件,以及发出诊断系统信息。此外,ITM模块可以传输其他模块(如DWT模块)产生的Trace数据,同时还可以将软件实时测试的一些信息进行输出,且对系统实时性没有任何干扰。),测试代码可以通过它输出调试信息,而且“拎包即可入住”般地方便使用。

3.1 寄存器组

        CM3 拥有通用寄存器 R0-R15 以及一些特殊功能寄存器。 R0-R12 是最“通用目的”的,但是绝大多数的 16 位指令只能使用 R0-R7(低组寄存器),而 32 位的 Thumb-2 指令则可以访问所有通用寄存器。特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问。

3.1.1 通用目的寄存器 R0-R7     

        R0-R7 也被称为低组寄存器。所有指令都能访问它们。它们的字长全是 32 位,复位后的初始值是不可预料的。

3.1.2 通用目的寄存器 R8-R12

        R8-R12 也被称为高组寄存器。这是因为只有很少的 16 位 Thumb 指令能访问它们, 32 位的thumb-2 指令则不受限制。它们也是 32 位字长,且复位后的初始值是不可预料的。

 

3.1.3 特殊功能寄存器

                                 Cortex-M3 的寄存器组

 3.1.4 堆栈指针 R13


        R13 是堆栈指针。在 CM3 处理器内核中共有两个堆栈指针,于是也就支持两个堆栈。当引用 R13 (或写作 SP)时,引用到的是当前正在使用的那一个,另一个必须用特殊的指令来访问(MRS,MSR指令)。这两个堆栈指针分别是:

  • 主堆栈指针( MSP), 或写作 SP_main。这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
  • 进程堆栈指针( PSP), 或写作 SP_process。用于常规的应用程序代码(不处于异常服用例程中时)

如无特殊说明,“异常”与“中断”都是指当发生“事件”时,处理器改变正常执行流,去响应该事件的情况。只不过异常之于处理器是同步的,中断之于处理器是异步的。因此常混合使用二术语, ISR 和ESR 也混合使用,请读者不必工于辨析这两个术语的不同,在这里这不是重点

        要注意的是,并不是每个程序都要用齐两个堆栈指针才算圆满。简单的应用程序只使用 MSP 就够了。堆栈指针用于访问堆栈,并且 PUSH 指令和 POP 指令默认使用 SP。

堆栈的 PUSH 与 POP

        堆栈是一种存储器的使用模型。它由一块连续的内存和一个栈顶指针组成,用于实现“后进先出”的缓冲区。其最典型的应用,就是在数据处理前先保存寄存器的值,再在处理任务完成后从中恢复先前保护的这些值。

                                                             堆栈内存的基本概念

        在执行 PUSH 和 POP 操作时,那个通常被称为 SP 的地址寄存器,会由硬件自动调整它的值,以避免后续操作破坏先前的数据。本书的后续章节还要围绕着堆栈展开更详细的论述。

        在 Cortex-M3 中,有专门的指令负责堆栈操作——PUSH 和 POP。 它俩的汇编语言语法如下例所演示

PUSH {R0}      ; *(--R13)=R0。 R13 是 long*的指针
POP {R0}       ; R0= *R13++

        请注意后面 C 程序风格的注释,它诠释了所谓的“向下生长的满栈”(本章后面在讲到堆栈内存操作时还要展开论述), Cortex-M3 就是以这种方式使用堆栈的。因此,在 PUSH 新数据时,堆栈指针先减一个单元。通常在进入一个子程序后,第一件事就是把寄存器的值先 PUSH 入堆栈中,在子程序退出前再 POP 曾经 PUSH 的那些寄存器。另外, PUSH 和 POP 还能一次操作多个寄存器,如下所示:

subroutine_1
    PUSH {R0-R7, R12, R14} ; 保存寄存器列表
    …                      ; 执行处理
    POP {R0-R7, R12, R14}  ; 恢复寄存器列表
    BX R14                 ; 返回到主调函数

        在程序中为了突出重点,可以一直把 R13 写作 SP。在程序代码中, both MSP 和 PSP 都被称为R13/SP。不过,我们可以通过 MRS/MSR 指令来指名道姓地访问具体的堆栈指针。

        MSP,亦写作 SP_main,这是复位后缺省使用堆栈指针,服务于操作系统内核和异常服务例程;而 PSP,亦写作 SP_process,典型地用于普通的用户线程中。

        寄存器的 PUSH 和 POP 操作永远都是 4 字节对齐的——也就是说他们的地址必须是0x4,0x8,0xc,……。事实上, R13 的最低两位被硬线连接到 0,并且总是读出 0(Read As Zero)。

3.1.5 连接寄存器 R14

        R14 是连接寄存器(LR)。在一个汇编程序中,你可以把它写作 both LR 和 R14。 LR 用于在调用子程序时存储返回地址。例如,当你在使用 BL(分支并连接, Branch and Link)指令时,就自动填充 LR的值。

main ;主程序
…
BL function1 ; 使用“分支并连接”指令呼叫 function1
; PC= function1,并且 LR=main 的下一条指令地址
…
Function1
… ; function1 的代码
BX LR ; 函数返回(如果 function1 要使用 LR,必须在使用前 PUSH,
; 否则返回时程序就可能跑飞了——译注)

        尽管 PC 的 LSB 总是 0(因为代码至少是字对齐的), LR 的 LSB 却是可读可写的。这是历史遗留的产物。在以前,由位 0 来指示 ARM/Thumb 状态。因为其它有些 ARM 处理器支持 ARM 和 Thumb状态并存,为了方便汇编程序移植, CM3 需要允许 LSB 可读可写。

3.1.6 程序计数器 R15

        R15 是程序计数器,在汇编代码中一般我们都都叫它的外号“PC”。因为 CM3 内部使用了指令流水线,读 PC 时返回的值是当前指令的地址+4。比如说:0x1000: MOV R0, PC ; R0 = 0x1004
        如果向 PC 中写数据,就会引起一次程序的分支(但是不更新 LR 寄存器)。 CM3 中的指令至少是半字对齐的,所以 PC 的 LSB 总是读回 0。然而, 在分支时,无论是直接写 PC 的值还是使用分支指令, 都必须保证加载到 PC 的数值是奇数(即 LSB=1),用以表明这是在 Thumb 状态下执行。倘若写了 0,则视为企图转入 ARM 模式, CM3 将产生一个 fault 异常。

3.2 特殊功能寄存器组

Cortex-M3 中的特殊功能寄存器包括:

  • 程序状态寄存器组(PSRs 或曰 xPSR)
  • 中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及 BASEPRI)
  • 控制寄存器(CONTROL)

        它们只能被专用的 MSR/MRS 指令访问,而且它们也没有与之相关联的访问地址。

        MRS <gp_reg>, <special_reg> ;读特殊功能寄存器的值到通用寄存器

3.2.1 程序状态寄存器(PSRs 或曰 PSR)

程序状态寄存器在其内部又被分为三个子状态寄存器:

  • 应用程序 PSR(APSR)
  • 中断号 PSR(IPSR)
  • 执行 PSR(EPSR)

        通过 MRS/MSR 指令,这 3 个 PSRs 即可以单独访问,也可以组合访问(2 个组合, 3 个组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。

                                        Cortex-M3 中的程序状态寄存器(xPSR)

 

                                                合体后的程序状态寄存器(xPSR)

3.2.2 PRIMASK, FAULTMASK 和 BASEPRI

        这三个寄存器用于控制异常的使能和除能。

名字功能描述
PRIMASK这是个只有单一比特的寄存器。 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下 NMI 和硬 fault 可以响应。它的缺省值是 0,表示没有关中断。
FAULTMASK这是个只有 1 个位的寄存器。当它置 1 时,只有 NMI 才能响应,所有其它的异常,甚至是硬 fault,也通通闭嘴。它的缺省值也是 0,表示没有关异常。
BASEPRI这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。

                                                        Cortex-M3 的屏蔽寄存器组


        对于时间-关键任务而言,恰如其分地使用 PRIMASK 和 BASEPRI 来暂时关闭一些中断是非常重要的。而 FAULTMASK 则可以被 OS 用于暂时关闭 fault 处理机能,这种处理在某个任务崩溃时可能需要。因为在任务崩溃时,常常伴随着一大堆 faults。在系统料理“后事”时,通常不再需要响应这些 fault ——人死帐清。总之 FAULTMASK 就是专门留给 OS 用的。

        要访问 PRIMASK, FAULTMASK 以及 BASEPRI,同样要使用 MRS/MSR 指令,如:

MRS R0, BASEPRI ;读取 BASEPRI 到 R0 中
MRS R0, FAULTMASK ;似上
MRS R0, PRIMASK ;似上
MSR BASEPRI, R0 ;写入 R0 到 BASEPRI 中
MSR FAULTMASK, R0 ;似上
MSR PRIMASK, R0 ;似上

        只有在特权级下,才允许访问这 3 个寄存器。

为了快速地开关中断, CM3 还专门设置了一条 CPS 指令,有 4 种用法
CPSID I ;PRIMASK=1, ;关中断
CPSIE I ;PRIMASK=0, ;开中断
CPSID F ;FAULTMASK=1, ;关异常
CPSIE F ;FAULTMASK=0 ;开异常

3.2.3 控制寄存器(CONTROL)

        控制寄存器有两个用途,其一用于定义特权级别,其二用于选择当前使用哪个堆栈指针。由两个比特来行使这两个职能。

功能
CONTROL[1]

堆栈指针选择

0=选择主堆栈指针 MSP(复位后的缺省值)

1=选择进程堆栈指针 PSP

在线程或基础级(没有在响应异常——译注),可以使用 PSP。 在 handler 模式下,只允许使用 MSP,所以此时不得往该位写 1。

CONTROL[0]

0=特权级的线程模式

1=用户级的线程模式

Handler 模式永远都是特权级的。

CONTROL[1]

        在 Cortex-M3 的 handler 模式中, CONTROL[1]总是 0。在线程模式中则可以为 0 或 1。

        因此,仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改 LR 的位 2,也能实现模式切换。这是 LR 在异常返回时的特殊用法,颠覆了对 LR 的传统使用方式,将在第 5 章中展开论述。

CONTROL[0]

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

        CONTROL 寄存器也是通过 MRS 和 MSR 指令来操作的:

MRS	R0,	CONTROL
MSR	CONTROL,	R0

3.3 操作模式
 

Cortex-M3 支持 2 个模式和两个特权等级。

                                                                         操作模式和特权等级

        当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面, handler 模式总是特权级的。在复位后,处理器进入线程模式+特权级。

        在线程模式+用户级下,对系统控制空间(SCS)的访问将被阻止——该空间包含了配置寄存器组以及调试组件的寄存器组。除此之外,还禁止使用 MRS/MSR 访问刚才讲到的,除了 APSR 之外的特殊功能寄存器。如果以身试法,则对于访问特殊功能寄存器的,访问操作被忽略;而对于访问 SCS空间的,将 fault 伺候。

        在特权级下的代码可以通过置位 CONTROL[0]来进入用户级。而不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后,系统将回到产生异常时所处的级别。用户级下的代码不能再试图修改 CONTROL[0]来回到特权级。它必须通过一个异常 handler,由那个异常handler 来修改 CONTROL[0],才能在返回到线程模式后拿到特权级。

                                                    特权级和处理器模式转换图

        把代码按特权级和用户极分开对待,有利于使 CM3 的架构更加安全和健壮。例如,当某个用户程序代码出问题时,不会让它成为害群之马,因为用户级的代码是禁止写特殊功能寄存器和 NVIC中寄存器的。另外,如果还配有 MPU,保护力度就更大,甚至可以阻止用户代码访问不属于它的内存区域。

        为了避免系统堆栈因应用程序的错误使用而毁坏,我们可以给应用程序专门配一个堆栈,不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用 PSP,而异常服务例程则使用 MSP。这两个堆栈指针的切换是智能全自动的,就在异常服务的始末由 CM3 硬件处理。第 8 章将详细讨论此主题。

        如前所述,特权等级和堆栈指针的选择均由 CONTROL 负责。当 CONTROL[0]=0 时,在异常处理的始末,只发生了处理器模式的转换,如下图所示。

                                         中断前后的状态转换

但若 CONTROL[0]=1(线程模式+用户级),则在中断响应的始末, both 处理器模式和特权等极都要发生变化,如下图所示


                                 中断前后的状态转换+特权等级切换

        CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVC)”来触发“SVC 异常”,该异常的服务例程可以视具体情况而修改 CONTROL[0]。

3.4 异常与中断

        Cortex-M3 支持大量异常,包括 16-4-1=11 个系统异常,和最多 240 个外部中断——简称 IRQ。具体使用了这 240 个中断源中的多少个,则由芯片制造商决定。由外设产生的中断信号,除了 SysTick的之外,全都连接到 NVIC 的中断输入信号线。典型情况下,处理器一般支持 16 到 32 个中断,当然也有在此之外的。

        作为中断功能的强化, NVIC 还有一条 NMI 输入信号线。 NMI 究竟被拿去做什么,还要视处理器的设计而定。在多数情况下, NMI 会被连接到一个看门狗定时器,有时也会是电压监视功能块,以便在电压掉至危险级别后警告处理器。 NMI 可以在任何时间被激活,甚至是在处理器刚刚复位之后。

        下表列出了 Cortex-M3 可以支持的所有异常。有一定数量的系统异常是用于 fault 处理的,它们可以由多种错误条件引发。 NVIC 还提供了一些 fault 状态寄存器,以便于 fault 服务例程找出导致异常的具体原因。

Cortex-M3 中的异常类型:

编号类型优先级简介
0N/AN/A没有异常在运行
1复位-3(最高)复位
2NMI-2不可屏蔽中断(来自外部 NMI 输入脚)
3硬(hard) fault-1所有被除能的 fault, 都将“上访”成硬 fault。除能的原因包括当前被禁用,或者被 PRIMASK 或 BASPRI 掩蔽。
4MemManage fault可编程存储器管理 fault, MPU 访问犯规以及访问非法位置均可引发。企图在“非执行区”取指也会引发此 fault
5总线 fault可编程从总线系统收到了错误响应,原因可以是预取流产( Abort)或数据流产,或者企图访问协处理器
6用法(usage) Fault可编程由于程序错误导致的异常。通常是使用了一条无效指令,或者是非法的状态转换,例如尝试切换到 ARM 状态
7-10保留N/AN/A
11SVCall可编程执行系统服务调用指令( SVC)引发的异常
12调试监视器可编程调试监视器(断点,数据观察点,或者是外部调试请求
13保留N/AN/A
14PendSV可编程为系统设备而设的“可悬挂请求”( pendable request)
15SysTick可编程系统滴答定时器(也就是周期性溢出的时基定时器——译注)
16IRQ #0可编程外中断#0
17IRQ #1可编程外中断#1

255

IRQ #239

可编程

外中断#239

3.5 向量表

        当 CM3 内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR 的入口地址, CM3 使用了“向量表查表机制”。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 处必须包含一张向量表,用于初始时的异常分配。

 向量表结构:

异常类型

表项地址偏移量

异常向量
00x00MSP 的初始值
10x04复位
20x08NMI
30x0C硬 fault
40x10MemManage fault
50x14总线 fault
60x18用法 fault
7-100x1c-0x28保留
110x2cSVC
120x30调试监视器
130x34保留
140x38PendSV
150x3cSysTick
160x40IRQ #0
170x44IRQ #1
18-2550x48-0x3FFIRQ #2 - #239

        举个例子,如果发生了异常 11(SVC),则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值

3.6 栈内存操作

        在 Cortex-M3 中,除了可以使用 PUSH 和 POP 指令来处理堆栈外,内核还会在异常处理的始末自动地执行 PUSH 与 POP 操作。本节让我们来检视一下具体的动作,第 9 章则讨论异常处理时的自动栈操作。

3.6.1堆栈的基本操作

        笼统地讲,堆栈操作就是对内存的读写操作,但是访问地址由 SP 给出。寄存器的数据通过 PUSH操作存入堆栈,以后用 POP 操作从堆栈中取回。在 PUSH 与 POP 的操作中, SP 的值会按堆栈的使用法则自动调整,以保证后续的 PUSH 不会破坏先前 PUSH 进去的内容。

        堆栈的功能就是把寄存器的数据临时备份在内存中,以便将来能恢复之——在一个任务或一段子程序执行完毕后恢复。正常情况下, PUSH 与 POP 必须成对使用,而且参与的寄存器,不论是身份还是先后顺序都必须完全一致。当 PUSH/POP 指令执行时, SP 指针的值也根着自减/自增。

                                                 基本的堆栈操作:每次处理单个寄存器

                                                     堆栈操作的进一步探讨

        如果参与的寄存器比较多,这种 PUSH 和 POP 岂不是又臭又长? 放心, PUSH/POP 指令足够体贴,支持一次操作多个寄存器。像这样:

PUSH {R0-R2} ;压入 R0-R2
PUSH {R3-R5,R8, R12} ;压入 R3-R5,R8,以及 R12

在 POP 时,可以如下操作:

POP {R3-R5,R8, R12} ;弹出 R3-R5, R8,以及 R12
POP {R0-R2} ;弹出 R0-R2

        注意:在寄存器列表中,不管寄存器的序号是以什么顺序给出的,汇编器都将把它们升序排序。然后先 push 序号大的寄存器,所以也就先 pop 序号小的寄存器。(这是译者在实验中发现的)。如果不按升序写寄存器,也许有些汇编器会给出语法错误。

        PUSH/POP 对子还有这样一种特殊形式,形如:

PUSH {R0-R3, LR}
POP {R0-R3, PC}

        请注意: POP 的最后一个寄存器是 PC,并不是先前 PUSH 的 LR。这其实是一个返回的小技巧。与其按部就班地把先前 LR 的值弹回 LR,再复制给 PC 来返回;不如干脆绕过 LR,直接传给 PC!那不怕 LR 的值没有被恢复吗?不怕,因为 LR 在子程序调用中的唯一用处,就是在返回时提供返回地址。因此,在返回后,先前保存的返回地址就没有利用价值了,所以只要PC 得到了正确的值,不恢复也没关系。

        PUSH 指令等效于与使用 R13 作为地址指针的 STMDB 指令,而 POP 指令则等效于使用R13 作为地址指针的 LDMIA 指令——STMDB/LDMIA 还可以使用其它寄存器作为地址指针。至于这两个指令的细节,第 4 章讲到指令系统时再介绍。

 3.7 Cortex-M3 的堆栈实现

        Cortex-M3 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32 位数值。在下一次压栈时, SP 先自减 4,再存入新的数值。

                                             Cortex-M3 堆栈的 PUSH 实现方式

        POP 操作刚好相反:先从 SP 指针处读出上一次被压入的值,再把 SP 指针自增 4。

 

                                               Cortex-M3 堆栈的 POP 实现方式

        在进入 ESR 时, CM3 会自动把一些寄存器压栈,这里使用的是发生本异常的瞬间正在使用的 SP指针(MSP 或者是 PSP)。离开 ESR 后,只要 ESR 没有更改过 CONTROL[1],就依然使用发生本次异常的瞬间正在使用的 SP 指针来执行出栈操作。

3.7.1 再论 Cortex-M3 的双堆栈机制
 

                          CONTROL[1]=0 时的堆栈使用情况

        当 CONTROL[1]=1 时,线程模式将不再使用 MSP,而改用 PSP(handler 模式永远使用 MSP)。这样做的好处在哪里?原来,在使用 OS 的环境下,只要 OS 内核仅在 handler 模式下执行,用户应用程序仅在用户模式下执行,这种双堆栈机制派上了用场——防止用户程序的堆栈错误破坏 OS 使用的堆栈。

                          CONTROL[1]=1 时的堆栈切换情况

        在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制,示例代码如下:

MRS R0, MSP ; 读取主堆栈指针到 R0
MSR MSP, R0 ; 写 R0 的值到主堆栈中
MRS R0, PSP ; 读取进程堆栈指针到 R0
MSR PSP, R0 ; 写 R0 的值到进程堆栈中

        通过读取 PSP 的值, OS 就能够获取用户应用程序使用的堆栈,进一步地就知道了在发生异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈(使用 STMDB 和 LDMIA 的书写形式)。OS 还可以修改 PSP,用于实现多任务中的任务上下文切换。

 3.8 复位序列

在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:

  • 从地址 0x0000,0000 处取出 MSP 的初始值。
  • 从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量, LSB 必须是 1。 然后从这个值所对应的地址处取指。

                                                                        复位序列

       请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。 在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表(向量表在以后还可以被移至其它位置——译注)。 向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令。

                             初始 MSP 及 PC 初始化的一个范例

        因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加 1。举例 来说,如果你的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值就必须是 0x20008000。

        向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在 Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因,图 3.18 中使用0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。

        对于不同的开发工具,需要使用不同的格式来设置 MSP 初值和复位向量——有些则由开发工具自行计算并生成。如果想要获知细节,最快的办法就是参考开发工具提供的一个示例工程。本书的第 10 章和第 20 章介绍 ARM 提供的开发工具,第 19 章则介绍 GCC 工具链。

        终于“开荤”了,本章开始把 Cortex-M3 的指令系统展现出来,并且给出了一些简单却意味深长的例子。在本书的附录 A 中还有一个快速查阅参考。指令集的详细信息由《 ARMv7-M Architecture Application Level Reference Manual》 (Ref2)

 4.1 汇编语言基础

4.1.1 汇编语言:基本语法


汇编指令的最典型书写模式如下所示:

标号

        操作码        操作数 1,         操作数 2,         …         ;注释

        其中,标号是可选的,如果有,它必须顶格写。标号的作用是让汇编器来计算程序转移的地址。

        操作码是指令的助记符,它的前面必须有至少一个空白符, 通常使用一至二个“Tab”键来产生。操作码后面往往跟随若干个操作数,而第 1 个操作数,通常都给出本指令的执行结果存储处。不同指令需要不同数目的操作数,并且对操作数的语法要求也可以不同。举例来说,立即数必须以“#”开头,如

    MOV R0, #0x12 ; R0 ← 0x12
    MOV R1, #’A’ ; R1 ← 字母 A 的 ASCII 码

        注释均以”;”开头,它的有无不影响汇编器工作,只是给程序员看的,能让程序更易理解。还可以使用 EQU 指示字来定义常数,然后在代码中使用它们,例如:

NVIC_IRQ_SETEN0 EQU 0xE000E100 ; 注意: 常数定义必须顶格写
NVIC_IRQ0_ENABLE EQU 0x1
…
    LDR R0, =NVIC_IRQ_SETEN0      ;在这里的 LDR 是个伪指令,它会被汇编器转换成
                                  ;一条“相对 PC 的加载指令”
    MOV R1, #NVIC_IRQ0_ENABLE     ; 把立即数传送到 R1 中
    STR R1, [R0]                  ; *R0=R1,执行完此指令后 IRQ #0 被使能。

        如果汇编器不能识别某些特殊指令的助记符,你就要“手工汇编” ——查出该指令的确切二进制机器码,然后使用 DCI 编译器指示字。例如, BKPT 指令的机器码是 0xBE00,即可以按如下格式书写:

    DCI 0xBE00 ; 断点(BKPT),这是一个 16 位指令

        (在使用 DCI 时也必须在前面留出空白符——译注)

        类似地,你还可以使用 DCB 来定义一串字节常数,字节常数还允许以字符串的形式来表达;还可以使用 DCD 来定义一串 32 位整数。它们最常被用来在代码中书写表格。例如:

LDR R3, =MY_NUMBER ; R3= MY_NUMBER
LDR R4, [R3] ; R4= *R3
…
LDR R0, =HELLO_TEXT ; R0= HELLO_TEXT
BL PrintText ; 呼叫 PrintText 以显示字符串, R0 传递参数
…
MY_NUMBER
DCD 0x12345678
HELLO_TEXT
DCB ”Hello\n”,0

        请注意: 不同汇编器的指示字和语法都可以不同。上述示例代码都是按 ARM 汇编器的语法格式写的。如果使用其它汇编器,最好看一看它附带的示例代码。

4.1.2 汇编语言:后缀的使用
 

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值