ARM Cortex-M3基础
性能简介
高性能
- 许多指令都是单周期的——包括乘法相关指令。并且从整体性能上, Cortex-M3 比得过绝大多数其它的架构。
- 指令总线和数据总线被分开,取值和访内可以并行不悖
- Thumb-2 的到来告别了状态切换的旧世代,再也不需要花时间来切换于 32 位 ARM 状态和16 位 Thumb 状态之间了。这简化了软件开发和代码维护,使产品面市更快。
- Thumb-2 指令集为编程带来了更多的灵活性。许多数据操作现在能用更短的代码搞定,这意味着 Cortex-M3 的代码密度更高,也就对存储器的需求更少。
- 取指都按 32 位处理。同一周期最多可以取出两条指令,留下了更多的带宽给数据传输。
- Cortex-M3 的设计允许单片机高频运行(现代半导体制造技术能保证 100MHz 以上的速度)。即使在相同的速度下运行, CM3 的每指令周期数(CPI)也更低,于是同样的 MHz 下可以做更多的工作;另一方面,也使同一个应用在 CM3 上需要更低的主频。
先进的中断处理功能
- 内建的嵌套向量中断控制器支持多达 240 条外部中断输入。向量化的中断功能剧烈地缩短了中断延迟,因为不再需要软件去判断中断源。中断的嵌套也是在硬件水平上实现的,不需要软件代码来实现。
- Cortex-M3 在进入异常服务例程时,自动压栈了 R0-R3, R12, LR, PSR 和 PC,并且在返回时自动弹出它们,这多清爽!既加速了中断的响应,也再不需要汇编语言代码了。
- NVIC 支持对每一路中断设置不同的优先级,使得中断管理极富弹性。最粗线条的实现也至少要支持 8 级优先级,而且还能动态地被修改。
- 优化中断响应还有两招,它们分别是“咬尾中断机制”和“晚到中断机制”。
- 有些需要较多周期才能执行完的指令,是可以被中断-继续的——就好比它们是一串指令一样。这些指令包括加载多个寄存器(LDM),存储多个寄存器(STM),多个寄存器参与的PUSH,以及多个寄存器参与的 POP。
- 除非系统被彻底地锁定, NMI(不可屏蔽中断)会在收到请求的第一时间予以响应。对于很多安全-关键(safety-critical)的应用, NMI 都是必不可少的。
低功耗
- Cortex-M3 需要的逻辑门数少,所以先天就适合低功耗要求的应用(功率低于 0.19mW/MHz)。
- 在内核水平上支持节能模式(SLEEPING 和 SLEEPDEEP 位)。通过使用“等待中断指令(WFI)”和“等待事件指令(WFE)”,内核可以进入睡眠模式,并且以不同的方式唤醒。另外,模块的时钟是尽可能地分开供应的,所以在睡眠时可以把 CM3 的大多数“官能团”给停掉。
- CM3 的设计是全静态的、同步的、可综合的。任何低功耗的或是标准的半导体工艺均可放心饮用。
系统特性
- 系统支持“位寻址带”操作(8051 位寻址机制的“威力大幅加强版”),字节不变的大端模式,并且支持非对齐的数据访问。
- 拥有先进的 fault 处理机制,支持多种类型的异常和 faults,使故障诊断更容易。
- 通过引入 banked 堆栈指针机制,把系统程序使用的堆栈和用户程序使用的堆栈划清界线。如果再配上可选的 MPU,处理器就能彻底满足对软件健壮性和可靠性有严格要求的应用。
调试支持
- 在支持传统的 JTAG 基础上,还支持更新更好的串行线调试接口。
- 基于 CoreSight 调试解决方案,使得处理器哪怕是在运行时,也能访问处理器状态和存储器内容。
- 内建了对多达 6 个断点和 4 个数据观察点的支持。
- 可以选配一个 ETM,用于指令跟踪。数据的跟踪可以使用 DWT
- 在调试方面还加入了以下的新特性,包括 fault 状态寄存器,新的 fault 异常,以及闪存修补 (patch)操作,使得调试大幅简化。
- 可选 ITM 模块,测试代码可以通过它输出调试信息,而且“拎包即可入住”般地方便使用。
基于Cortex-M3的芯片设计
Cortex-M3处理器内核是芯片的中央处理单元,完整的MCU还需要很多其他组件,例如存储,外设,IO等。芯片设计商得到CM3核的授权后,就会把CM3用到自己的芯片中,做一些定制化的设计,所以不同的厂商有不同的配置,想了解具体型号的处理器需要查阅厂家提供的文档,比如stm32,nxp,ti,Freescale。基于ARM低成本和高效的处理器设计方案, 得到授权的厂商生产了多种多样的的处理器、 单片机以及片上系统(SoC)。这种商业模式就是所谓的“知识产权授权”。
ARM发展历史
ARMv7架构的闪亮登场。在这个版本中,内核架构首次从单一款式变成3种款式。
- ARM-A:设计用于高性能的“开放应用平台”
- ARM-R:用于高端的嵌入式系统,尤其是那些带有实时要求的
- ARM-M:用于深度嵌入的,单片机风格的系统中
处理器名字 | 架构版本号 | 存储器管理特性 | 其它特性 |
---|---|---|---|
ARM7TDMI | v4T | ||
ARM7TDMI-S | v4T | ||
ARM7EJ-S | v5E | DSP,Jazelle | |
ARM920T | v4T | MMU | |
ARM922T | v4T | MMU | |
ARM926EJ-S | v5E | MMU | DSP,Jazelle |
ARM946E-S | v5E | MPU | DSP |
ARM966E-S | v5E | DSP | |
ARM968E-S | v5E | DMA,DSP | |
ARM966HS | v5E | MPU(可选) | DSP |
ARM1020E | v5E | MMU | DSP |
ARM1022E | v5E | MMU | DSP |
ARM1026EJ-S | v5E | MMU 或 MPU | DSP, Jazelle |
ARM1136J(F)-S | v6 | MMU | DSP, Jazelle |
ARM1176JZ(F)-S | v6 | MMU+TrustZone | DSP, Jazelle |
ARM11 MPCore | v6 | MMU+多处理器缓存支持 | DSP |
ARM1156T2(F)-S | v6 | MPU | DSP |
Cortex-M3 | v7-M | MPU(可选) | NVIC |
Cortex-R4 | v7-R | MPU | DSP |
Cortex-R4F | v7-R | MPU | DSP+浮点运算 |
Cortex-A8 | v7-A | MMU+TrustZone | DSP, Jazelle |
- DSP(Digital Signal Processor):数字信号处理是一种专用的处理器,用于执行数字信号处理任务,如音频处理、图像处理、无线通信等。
- MMU(内存管理单元):用于管理程序对内存的访问,主要负责将逻辑地址(由 CPU 生成)转换为物理地址(内存中实际存储的地址),并提供一些额外的功能,例如内存保护、虚拟内存和缓存控制等。
- Jazelle:是ARM处理器的硬件Java加速器
- DMA(Direct Memory Access):直接内存访问是一种技术,用于实现数据在外设和内存之间的直接传输,而无需CPU的干预。
- MPU:可以把MPU认为是MMU的功能子集,它只支持分区保护,不支持具有“定位决定性”的虚拟内存机制
- TrustZone:是 ARM 公司提供的一种硬件支持的安全解决方案,旨在提高处理器级别的系统安全性。TrustZone 技术通过将处理器划分为两个独立的安全域(Secure World 和 Normal World)来实现安全隔离。Secure World 是一个受保护的执行环境,用于运行安全敏感的代码和数据,而 Normal World 则是普通的执行环境,用于运行一般的应用程序和操作系统。
M3架构图
Cortex-M3 是一个32位处理器内核。内部的数据路径是32位的,寄存器是32位的,存储器接口也是32位的。 CM3采用了哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线,从而提升了性能。Both 小端模式和大端模式都是支持的。
寄存器组
Cortex-M3 处理器拥有 R0-R15 的寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。复位后,寄存器默认值不确定。
R0-R12:通用寄存器
R0-R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访问 R0-R7,而 32 位 Thumb-2 指令可以访问所有寄存器。
Banked R13: 两个堆栈指针
Cortex-M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。
- 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程)
- 进程堆栈指针(PSP):由用户的应用程序代码使用。
堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。
在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。
R14:连接寄存器
当呼叫一个子程序时,由 R14 存储返回地址
不像大多数其它处理器,ARM 为了减少访问内存的次数(访问内存的操作往往要 3 个以上指令周期,带 MMU 和cache 的就更加不确定了),把返回地址直接存储在寄存器中。这样足以使很多只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于 1 级,则需要把前一级的 R14 值压到堆栈里。在 ARM上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。在 RISC 处理器中,为了强调访内操作越过了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。
R15:程序计数寄存器
指向当前的程序地址。如果修改它的值,就能改变程序的执行流。
特殊功能寄存器
Cortex-M3 还在内核水平上搭载了若干特殊功能寄存器,包括
- 程序状态字寄存器组(PSRs)
- 中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)
- 控制寄存器(CONTROL)
寄存器 | 功能 |
---|---|
xPSR | 记录 ALU 标志(0 标志,进位标志,负数标志,溢出标志),执行状态,以及当前正服务的中断号 |
PRIMASK | 除能所有的中断——当然了,不可屏蔽中断(NMI)才不甩它呢。 |
FAULTMASK | 除能所有的 fault——NMI 依然不受影响,而且被除能的 faults 会“上访”。 |
BASEPRI | 除能所有优先级不高于某个具体数值的中断。 |
CONTROL | 定义特权状态,并且决定使用哪一个堆栈指针 |
操作模式和特权级别
特权级和用户级。这可以提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。处理器支持两种特权级,这也是一个基本的安全模型。在 CM3 运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。在特权级下,程序可以访问所有范围的存储器并且可以执行所有指令。
在特权级下的程序可以为所欲为,但也可能会把自己给玩进去——切换到用户级。一旦进入用户级,再想回来就得走“法律程序”了——用户级的程序不能简简单单地试图改写 CONTROL 寄存器就回到特权级,它必须先“申诉”:执行一条系统调用指令(SVC)。这会触发 SVC 异常,然后由异常服务例程(通常是操作系统的一部分)接管,如果批准了进入,则异常服务例程修改 CONTROL 寄存器,才能在用户级的线程模式下重新进入特权级。
事实上,从用户级到特权级的唯一途径就是异常:如果在程序执行过程中触发了一个异常,处理器总是先切换入特权级,并且在异常服务例程执行完毕退出时,返回先前的状态。
嵌套向量中断控制器
Cortex-M3 在内核水平上搭载了一颗中断控制器——嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller)。它与内核有很深的“亲密接触”——与内核是紧耦合的。 NVIC 提供如下的功能:
- 可嵌套中断支持
- 向量中断支持
- 动态优先级调整支持
- 中断延迟大大缩短
- 中断可屏蔽
可嵌套中断支持
可嵌套中断支持的作用范围很广,覆盖了所有的外部中断和绝大多数系统异常。外在表现是,这些异常都可以被赋予不同的优先级。当前优先级被存储在 xPSR 的专用字段中。当一个异常发生时,硬件会自动比较该异常的优先级是否比当前的异常优先级更高。如果发现来了更高优先级的异常,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常——即立即抢占。
向量中断支持
当开始响应一个中断后,CM3 会自动定位一张向量表,并且根据中断号从表中找出 ISR 的入口地址,然后跳转过去执行。不需要像以前的 ARM 那样,由软件来分辨到底是哪个中断发生了,也无需半导体厂商提供私有的中断控制器来完成这种工作。这么一来,中断延迟时间大为缩短。
动态优先级调整支持
软件可以在运行时期更改中断的优先级。如果在某ISR中修改了自己所对应中断的优先级,而且这个中断又有新的实例处于悬起中(pending),也不会自己打断自己,从而没有重入(reentry)。所谓的重入,就是指某段子程序还没有执行完,就因为中断或者是多任务操作系统的调度原因,导致该子程序在一个新的寄存器上下文中被执行(请不要把重入与递归混淆,它们有本质的区别)。这种情况常常会闹出乱子,因此有“可重入性”的研究。
中断延迟大大缩短
Cortex-M3 为了缩短中断延迟,引入了好几个新特性。包括自动的现场保护和恢复,以及其它的措施,用于缩短中断嵌套时的 ISR 间延迟。详情请见后面关于“咬尾中断”和“晚到中断”的讲述。
咬尾中断:当处理器在响应某异常时,如果又发生其它异常,但它们优先级不够高,则被阻塞——这个我们已经知道。那么在当前的异常执行返回后,系统处理悬起的异常时,倘若还是先POP,然后又把POP出来的内容PUSH回去,这不成了砸锅炼铁再铸锅,白白浪费CPU时间吗,可知还有多少紧急的事件悬而未决呀!正因此,CM3不会傻乎乎地POP这些寄存器,而是继续使用上一个异常已经PUSH好的成果,消灭了这种铺张浪费。这么一来,看上去好像后一个异常把前一个的尾巴咬掉了,前前后后只执行了一次入栈/出栈操作。
晚到中断:CM3的中断处理还有另一个机制,它强调了优先级的作用,这就是“晚到的异常处理”。当CM3对某异常的响应序列还处在早期:入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,则本次入栈就成了为高优先级中断所做的了——入栈后,将执行高优先级异常的服务例程。
中断可屏蔽
既可以屏蔽优先级低于某个阈值的中断/异常(设置BASEPRI寄存器),也可以全体封杀(设置PRIMASK和FAULTMASK寄存器)。这是为了让时间关键(time-critical)的任务能在死线(deadline,或曰最后期限)到来前完成,而不被干扰。
存储器映射
总体来说,Cortex-M3 支持 4GB 存储空间:
从图中可见,不像其它的 ARM 架构,它们的存储器映射由半导体厂家说了算,Cortex-M3 预先定义好了“粗线条的”存储器映射。通过把片上外设的寄存器映射到外设区,就可以简单地以访问内存的方式来访问这些外设的寄存器,从而控制外设的工作。结果,片上外设可以使用 C 语言来操作。这种预定义的映射关系,也使得对访问速度可以做高度的优化,而且对于片上系统的设计而言更易集成。
Cortex-M3 的内部拥有一个总线基础设施,专用于优化对这种存储器结构的使用。在此之上,CM3 甚至还允许这些区域之间“越权使用”。比如说,数据存储器也可以被放到代码区,而且代码也能够在外部 RAM 区中执行(但是会变慢不少)。
处于最高地址的系统级存储区,是 CM3 用于藏“私房钱”的——包括中断控制器、MPU 以及各种调试组件。所有这些设备均使用固定的地址。通过把基础设施的地址定死,就至少在内核水平上,为应用程序的移植扫清了障碍。
总线接口
Cortex-M3 内部有若干个总线接口,以使 CM3 能同时取址和访内(访问内存),它们是:
- 指令存储区总线(两条)
- 系统总线
- 私有外设总线
有两条代码存储区总线负责对代码存储区的访问,分别是I-Code 总线和 D-Code总线。前者用于取指,后者用于查表等操作,它们按最佳执行速度进行优化。
系统总线用于访问内存和外设,覆盖的区域包括 SRAM,片上外设,片外 RAM,片外扩展设备,以及系统级存储区的部分空间。
私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。
存储器保护单元(MPU)
Cortex-M3 有一个可选的存储器保护单元。配上它之后,就可以对特权级访问和用户级访问分别施加不同的访问限制。当检测到犯规(violated)时,MPU 就会产生一个 fault 异常,可以由fault 异常的服务例程来分析该错误,并且在可能时改正它。
MPU 有很多玩法。最常见的就是由操作系统使用 MPU,以使特权级代码的数据,包括操作系统本身的数据不被其它用户程序弄坏。MPU 在保护内存时是按区(region)管理的。它可以把某些内存 region 设置成只读,从而避免了那里的内容意外 被更改;还可以在多任务系统中把不同任务之间的数据区隔离。一句话,它会使嵌入式系统变得更加健壮,更加可靠。
中断和异常
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 个预定义的异常类型,如表所示。
编号 | 类型 | 优先级 | 简介 |
---|---|---|---|
0 | NA | NA | 没有异常在运行 |
1 | 复位 | -3(最高) | 复位 |
2 | NMI | -2 | 不可屏蔽中断(来自外部 NMI 输入脚) |
3 | 硬(hard) fault | -1 | 所有被除能的 fault,都将“上访”成硬 fault |
4 | MemManage fault | 可编程 | 存储器管理 fault,MPU 访问犯规以及访问非法位置 |
5 | 总线 fault | 可编程 | 总线错误(预取流产(Abort)或数据流产) |
6 | 用法(usage) Fault | 可编程 | 由于程序错误导致的异常 |
7-10 | 保留 | NA | NA |
11 | SVCall | 可编程 | 系统服务调用 |
12 | 调试监视器 | 可编程 | 调试监视器(断点,数据观察点,或者是外部调试请求 |
13 | 保留 | NA | NA |
14 | PendSV | 可编程 | 为系统设备而设的“可悬挂请求”(pendable request) |
15 | SysTick | 可编程 | 系统滴答定时器(也就是周期性溢出的时基定时器——译注) |
16 | IRQ #0 | 可编程 | 外中断#0 |
17 | IRQ #1 | 可编程 | 外中断#1 |
… | … | … | … |
255 | IRQ #239 | 可编程 | 外中断#239 |
虽然 CM3 是支持 240 个外中断的,但具体使用了多少个是由芯片生产商决定。CM3 还有一个NMI(不可屏蔽中断)输入脚。当它被置为有效(assert)时,NMI 服务例程会无条件地执行。
调试支持
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。所有这些调试组件都可以由 DAP 总线接口来控制, CM3 内核提供 DAP 接口。此外,运行中的程序也能控制它们。所有的跟踪信息都能通过 TPIU 来访问到。