Cortex-M3 处理器内核是芯片的中央处理单元,完整的 MCU 还需要很多其他组件,例如存储,外设,IO 等。芯片设计商得到 CM3 核的授权后,就会把 CM3 用到自己的芯片中,做一些定制化的设计,所以不同的厂商有不同的配置,想了解具体型号的处理器需要查阅厂家提供的文档,比如 stm32,nxp,ti,Freescale。基于 ARM 低成本和高效的处理器设计方案, 得到授权的厂商生产了多种多样的的处理器、 单片机以及片上系统(SoC)。这种商业模式就是所谓的“知识产权授权”。
ARMv7 架构的闪亮登场。在这个版本中,内核架构首次从单一款式变成 3 种款式。
处理器名字 | 架构版本号 | 存储器管理特性 | 其它特性 |
---|---|---|---|
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 |
Cortex-M3 是一个 32 位处理器内核。内部的数据路径是 32 位的,寄存器是 32 位的,存储器接口也是 32 位的。 CM3 采用了哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线,从而提升了性能。Both 小端模式和大端模式都是支持的。
Cortex-M3 处理器拥有 R0-R15 的寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。复位后,寄存器默认值不确定。
R0-R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访问 R0-R7,而 32 位 Thumb-2 指令可以访问所有寄存器。
Cortex-M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。
堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。
在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。
当呼叫一个子程序时,由 R14 存储返回地址
不像大多数其它处理器,ARM 为了减少访问内存的次数(访问内存的操作往往要 3 个以上指令周期,带 MMU 和 cache 的就更加不确定了),把返回地址直接存储在寄存器中。这样足以使很多只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于 1 级,则需要把前一级的 R14 值压到堆栈里。在 ARM 上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。在 RISC 处理器中,为了强调访内操作越过了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。
指向当前的程序地址。如果修改它的值,就能改变程序的执行流。
Cortex-M3 还在内核水平上搭载了若干特殊功能寄存器,包括
寄存器 | 功能 |
---|---|
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,片外扩展设备,以及系统级存储区的部分空间。
私有外设总线负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。
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 来访问到。