LV16-13-Cortex-M3异常与NVIC
本文主要是Cortex-M3异常与NVIC介绍的一些的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
- 通用
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.st.com/content/st_com/zh.html | ST官方网站,在这里我们可以找到STM32的相关文档 | |
https://www.stmcu.com.cn/ | 意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档 | |
http://elm-chan.org/fsw/ff/00index_e.html | FatFs文件系统官网 | |
教程书籍 | 《ARM Cortex-M3权威指南》 | ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩 |
《ARM Cortex-M0权威指南》 | ||
《ARM Cortex-M3与Cortex-M4权威指南》 | ||
开发论坛 | http://47.111.11.73/forum.php | 开源电子网,正点原子的资料下载及问题讨论论坛 |
https://www.firebbs.cn/forum.php | 国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT | |
https://www.amobbs.com/index.php | 阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。 | |
http://download.100ask.net/index.html | 韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。 | |
博客参考 | http://www.openedv.com/ | 开源网-原子哥个人博客 |
http://blog.chinaaet.com/jihceng0622 | 博主是原Freescale现NXP的现场应用工程师 | |
cortex-m-resources | 这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接) |
- STM32
STM32 | STM32 HAL库开发实战指南——基于F103系列开发板 | 野火STM32开发教程在线文档 |
STM32库开发实战指南——基于野火霸道开发板 | 野火STM32开发教程在线文档 |
- SD卡
SD Association | 提供了SD存储卡和SDIO卡系统规范 |
点击查看相关文件下载
STM32F103xx英文数据手册 | STM32F103xC/D/E系列的英文数据手册 |
STM32F103xx中文数据手册 | STM32F103xC/D/E系列的中文数据手册 |
STM32F10xxx英文参考手册(RM0008) | STM32F10xxx系列的英文参考手册 |
STM32F10xxx中文参考手册(RM0008) | STM32F10xxx系列的中文参考手册 |
Arm Cortex-M3 处理器技术参考手册-英文版 | Cortex-M3技术参考手册-英文版 |
STM32F10xxx Cortex-M3编程手册-英文版(PM0056) | STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版 |
SD卡相关资料——最新版本 | 有关SD卡的一些资料可以从这里下载 |
SD卡相关资料——历史版本 | 有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议 |
SD 2.0 协议标准完整版 | 这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看 |
一、常见缩略语含义
缩写 | 说明 |
---|---|
IRQ | 中断请求(通常是指外部中断的请求) |
ISR | 中断服务例程 |
NMI | 不可屏蔽中断 |
NVIC | 嵌套向量中断控制器 |
PSP | 进程堆栈指针 |
二、异常
1. 什么是异常?
正常情况下,微处理器根据代码内容,按顺序执行指令。执行过程中,如果遇到其它紧急的事件需要处理,则先暂停当前任务,执行紧急事件,待紧急事件处理完后,再恢复到刚才暂停的地方继续执行。这个产生的紧急事件就叫做中断或异常 。
我们可以看一张更加详细的图片:
通常, 把CPU内部产生的紧急事件叫做异常,比如非法指令(除零) 、 地址访问越界等; 把来自CPU外部的片上外设产生的紧急事件叫做中断,比如GPIO引脚电平变化、 定时器溢出等。 异常和中断的效果基本一致,都是暂停当前任务, 优先执行紧急事件,因此一般将中断和异常统称为中断。
ARM公司设计了Cortex-M3内核,这个内核就包含了中断系统框架,对应资料“ [CortexM3权威指南.pdf](STM32开发相关资料/01ARM参考资料/ARM Cortex-M3权威指南(中文).pdf)”,后简称《CM3权威指南》。ST公司根据该内核, 因地制宜的设计了STM32系列产品, 对应资料“ STM32F10xx CortexM3编程手册.pdf”,后简称《CM3编程手册》。
2. 异常的类型
Cortex‐M3 在内核水平上搭载了一个异常响应系统, 支持为数众多的系统异常和外部中断。其中,编号为 1~15的对应系统异常,大于等于 16 的则全是外部中断。
除了个别异常的优先级被定死外, 其它异常的优先级都是可编程的(所有能打断正常执行流的事件都称为异常)。
因为芯片设计者可以修改 CM3 的硬件描述源代码,所以做成芯片后,支持的中断源数目常常不到 240 个,并且优先级的位数也由芯片厂商最终决定。
- 类型编号为 1-15 的系统异常如下表所示(注意: 没有编号为 0 的异常)
- 从 16 开始的外部中断类型如表 7.2 所示。
Reset( 复位) 、 NMI( Non Maskable Interrupt,不可屏蔽中断)、 HardFault( 硬件异常) 的优先级是固定的, 且优先级是负数,也就是最高的(优先级数字越小,优先级越高)。 剩下的异常或中断,都是可以通过修改NVIC的寄存器调整优先级( 但不能设置为负数),后边会提到 。
在 NVIC(后边会学到)的中断控制及状态寄存器中,有一个 VECTACTIVE 位段;另外,还有一个特殊功能寄存器 IPSR。在它们二者的里面,都记录了当前正服务异常的编号。需要注意:这里所讲的中断号,都是指 NVIC 所使用的中断号。另一方面,芯片一些管脚的名字也可能被取为类似”IRQ #”的名字,这两者不能混淆,它们没有必然联系。
常见的情况是,编号最靠前的几个中断源被指定到片上外设,接下来的中断源才给外部中断引脚使用,因此还是要参阅芯片的数据手册来弄清楚。
如果一个发生的异常不能被即刻响应,就称它被“悬起” (pending)。不过,少数 fault 异常是不允许被悬起的。一个异常被悬起的原因,可能是系统当前正在执行一个更高优先级异常的服务例程,或者因相关掩蔽位的设置导致该异常被除能。对于每个异常源,在被悬起的情况下,都会有一个对应的“悬起状态寄存器”保存其异常请求,直到该异常能够执行为止,这与传统的 ARM 是完全不同的。在以前,是由产生中断的设备保持住请求信号。现在NVIC 的悬起状态寄存器的出现解决了这个问题,即使后来设备已经释放了请求信号,曾经的中断请求也不会错失。
3. 优先级
从上一小节我们知道有256个中断,那么如此多的中断, 导致了一些新问题。 比如两个中断同时发生,应该先执行哪个中断任务?又比如一个中断发生了,又来了一个更紧急的中断,是该继续执行原来的中断,还是执行新的紧急中断? 于是就有了优先级的概念。
3.1 表示优先级的位数
在 CM3 中,优先级对于异常来说很关键的,它会影响一个异常是否能被响应,以及何时可以响应。优先级的数值越小,则优先级越高。 CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。
有 3 个系统异常:复位, NMI 以及硬 fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。所有其它异常的优先级则都是可编程的(但不能编程为负数)。
原则上, CM3 支持 3 个固定的高优先级和多达 256 级的可编程优先级,并且支持 128 级抢占(128 的来历请见下文)。但是,绝大多数 CM3 芯片都会精简设计,以致实际上支持的优先级数会更少,如 8 级, 16 级, 32 级等。它们在设计时会裁掉表达优先级的几个低端有效位,以达到减少优先级数的目的(可见,不管使用多少位,优先级号是以MSB 对齐的)。举例来说,如果只使用了 3 个位来表达优先级,则优先级配置寄存器的结构会如图所示:
在图中, [4:0]没有被实现,所以读它们总是返回零,写它们则忽略写入的值。因此,对于 3 个位的情况,我们能够使用的 8 个优先级为: 0x00(最高), 0x20, 0x40, 0x60, 0x80,0xA0, 0xC0 以及 0xE0。
如果使用更多的位来表达优先级,则能够使用的值也更多,同时需要的门也更多——带来更多的成本和功耗。CM3 允许的最少使用位数为 3 个位,也就是说至少要支持 8 级优先级。下图给出 3 个优先级位和 4 个优先级位的对比:
通过让优先级以 MSB 对齐,可以简化程序的跨器件移植。比如,如果一个程序早先在支持 4 位优先级的器件上运行,在移植到只支持 3 位优先级的器件后,其功能不受影响。但若是对齐到 LSB,则会使 MSB 丢失,导致数值大于 7 的低优先级一下子升高了,甚至会反转小于等于 7 的高优先级。如, 8 号优先级(1000 0000)因为损失了 MSB(高位的1丢掉了的话),现在反而变成 0 号了 。
那么当使用了 3 位、 5 位及 8 位来表达优先级时,各是什么情况呢?
3.2 为什么只剩128个抢占优先级?
明明支持 256 个优先级,为啥只有 128 个抢占级,剩下一半哪儿去了?原来,为了使抢占机能变得更可控, CM3 还把 256 级优先级按位分成高低两段,分别是抢占优先级和亚优先级。
NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器”:
它里面有一个位段名为“优先级组”。该位段的值对每一个优先级可配置的异常都有影响——把其优先级分为个位段: MSB 所在的位段(左边的)对应抢占优先级,而 LSB 所在的位段(右边的)对应亚优先级 :
抢占优先级决定了抢占行为:当系统正在响应某异常 L 时,如果来了抢占优先级更高的异常 H,则 H 可以抢占 L。亚优先级则处理“内务”:当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常。这种优先级分组规定:亚优先级至少是 1 个位。因此抢占优先级最多是 7 个位,造成了最多只有 128 级抢占的现象。
但是 CM3 允许从比特 7 处分组,此时所有的位都表达亚优先级,没有任何位表达抢占优先级,因而所有优先级可编程的异常之间就不会发生抢占——相当于在它们之中除能了CM3 的中断嵌套机制。当然还有凌架于法律之上的三位老大:复位, NMI 和硬 fault。它们无论何时出现,都立即无条件抢占所有优先级可编程的“平民异常”。
在计算抢占优先级和亚优先级的有效位数时,必须先求出下列值 :
- 芯片实际使用了多少位来表达优先级。
- 优先级组是如何划分的。
举个例子,如果只使用 3 个位来表达优先级([7:5]有效,剩下的[4:0]无效,全为0),那么此时的优先级组的值是 5(从比特 5 处分组),这样我们得到 4 级抢占优先级,且在每个抢占优先级的内部有 2 个亚优先级,如下图所示。
那么这种情况之下,其可用优先级的具体情况如下所示:
注意:虽然[4:0]未使用,却允许从它们中分组。例如,如果优先级组为 1 ,则所有可用的 8 个优先级(因为目前分析的是只有3个位表示优先级,也就是最高3位 [7:5] 才表示有效的优先级)都是抢占优先级,如下图(3 位优先级,从比特 1 处分组 ):
那么 3 位优先级,从比特 1 处分组,详细情况 如下图,即便是从位 1 处分组,理论上有4个亚优先级,32个抢占优先级,但是由于只有最高三位表示优先级,这就导致了只会有8个抢占优先级,0个亚优先级的情况:
如果优先级完全相同的多个异常同时悬起,则先响应异常编号最小的那一个。如 IRQ #3 会比 IRQ #5 先得到响应。
虽然优先级分组的功能很强大,但是粗心地更改会使它变得很暴力,尤其是在设计硬实时系统的时候,这常常会改变系统的响应特性,导致某些关键任务有可能得不到及时响应,凶多吉少的意外随时可能猛烈发作。其实在绝大多数情况下,优先级的分组都要预先经过计算论证,并且在开机初始化时一次性地设置好,以后就再也不动它了。只有在绝对需要且绝对有把握时,才小心地更改,并且要经过尽可能充分的测试。另外,优先级组所在的寄存器 AIRCR也基本上是“一次成型”,只是需要手工产生复位时才写里面相应的位。
4. 向量表
当发生了异常并且要响应它时, CM3 需要定位其处理例程的入口地址。这些入口地址存储在所谓的“(异常)向量表”中。缺省情况下, CM3 认为该表位于零地址处,且各向量占用 4 字节,因此每个表项占用 4 字节,如下表所示。
地址 | 异常编号 | 值(32 位整数) |
---|---|---|
0x0000_0000 | ‐ | MSP 的初始值 |
0x0000_0004 | 1 | 复位向量(PC 初始值) |
0x0000_0008 | 2 | NMI 服务例程的入口地址 |
0x0000_000C | 3 | 硬 fault 服务例程的入口地址 |
… | … | 其它异常服务例程的入口地址 |
因为地址 0 处应该存储引导代码,所以它通常是 Flash 或者是 ROM 器件,并且它们的值不得在运行时改变。然而,为了动态重分发中断, CM3 允许向量表重定位——从其它地址处开始定位各异常向量。这些地址对应的区域可以是代码区,但也可以是 RAM 区。
在 RAM区就可以修改向量的入口地址了。为了实现这个功能, NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址 0xE000_ED08 处),通过修改它的值就能定位向量表。
【注意】向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是 2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有 32 个中断,则共有 32+16(系统异常) = 48 个向量,向上增大到 2 的整次幂后值为 64,因此地址地址必须能被 64*4=256 整除,从而合法的起始地址可以是: 0x0, 0x100, 0x200 等。向量表偏移量寄存器的定义如下表所示。
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
29 | TBLBASE | RW | 0 | 向量表是在 Code 区(0),还是在 RAM 区(1) |
15 | ENDIANESS | R | ‐ | 向量表的起始地址 |
如果需要动态地更改向量表,则对于任何器件来说,向量表的起始处都必须包含以下向量:
(1)主堆栈指针(MSP)的初始值
(2)复位向量
(3)NMI(不可屏蔽中断)
(4)硬 fault 服务例程
后两者也是必需的,因为有可能在引导过程中发生这两种异常。我们可以在 SRAM 中开出一块用于存储向量表。然后在引导完成后,就可以启用内存中的向
量表,从而实现向量可动态调整的功能。
三、NVIC与中断控制
1. NVIC概览
Cortex-M3内核有一个专门管理中断的外设NVIC( Nested Vectored Interrupt Controller,嵌套向量中断控制器) , 通过优先级控制中断的嵌套和调度。NVIC是一个总的中断控制器, 无论是来在内核的异常还是外设的外部中断, 都由NVIC统一进行管理。 是 Cortex‐M3 不可分离的一部分, 它与 CM3 内核的逻辑紧密耦合, 有一部分甚至水乳交融在一起。
NVIC 与 CM3 内核同声相应,同气相求,相辅相成,里应外合,共同完成对中断的响应。 NVIC作为在内核里的外设,NVIC 的寄存器以存储器映射的方式来访问, 除了包含控制寄存器和中断处理的控制逻辑之外, NVIC 还包含了 MPU的控制寄存器、 SysTick 定时器以及调试控制。
NVIC 共支持 1 至 240 个外部中断输入(通常外部中断写作 IRQs)。 具体的数值由芯片厂商在设计芯片时决定。此外, NVIC 还支持一个“永垂不朽”的不可屏蔽中断(NMI)输入。NMI 的实际功能亦由芯片制造商决定。在某些情况下, NMI 无法由外部中断源控制。NVIC 的访问地址是 0xE000_E000。 所有NVIC 的中断控制/状态寄存器都只能在特权级下访问。 不过有一个例外——软件触发中断寄存器可以在用户级下访问以产生软件中断。
所有的中断控制/状态寄存器均可按字/半字/字节的方式访问。 此外, 有几个中断屏蔽寄存器也与中断控制密切相关,它们是“特殊功能寄存器”, 只能通过 MRS/MSR及 CPS 来访问。
2. 中断配置基础
每个外部中断都在 NVIC 的下列寄存器中“挂号”:
(1)使能与除能寄存器
(2)悬起与“解悬”寄存器
(3)优先级寄存器
(4)活动状态寄存器
另外,下列寄存器也对中断处理有重大影响
(1)异常掩蔽寄存器(PRIMASK, FAULTMASK 以及 BASEPRI)
(2)向量表偏移量寄存器
(3)软件触发中断寄存器
(4)优先级分组位段
3. 中断的使能与除能
中断的使能与除能分别使用各自的寄存器来控制——这与传统的, 使用单一比特的两个状态来表达使能与除能是不同的。 CM3 中可以有 240 对使能位/除能位,每个中断拥有一对。这 240 个对子分布在 8 对 32 位寄存器中(最后一对没有用完)。欲使能一个中断,需要写 1 到对应 SETENA 的位中;欲除能一个中断,需要写 1 到对应的 CLRENA 位中;如果往它们中写 0,不会有任何效果。通过这种方式,使能/除能中断时只需把“当事位”写成1,其它的位可以全部为零。再也不用像以前那样,害怕有些位被写入 0 而破坏其对应的中断设置(因为现在写 0 没有效果),从而实现每个中断都可以自顾地设置,而互不侵犯——只需单一的写指令,不再需要读‐改‐写。
如上所述, SETENA 位和 CLRENA 位可以有 240 对,对应的 32 位寄存器可以有 8 对,因此使用数字后缀来区分这些寄存器,如 SETENA0, SETENA1…SETENA7,如下表所示(SETENAs: xE000_E100 – 0xE000_E11C ; CLRENAs:0xE000E180 - 0xE000_E19C )。
名称 | 类型 | 地址 | 复位值 | 描述 |
---|---|---|---|---|
SETENA0 | R/W | 0xE000_E100 | 0 | 中断 0‐31 的使能寄存器,共 32 个使能位 位[n],中断#n 使能(异常号 16+n) |
SETENA1 | R/W | 0xE000_E104 | 0 | 中断 32‐63 的使能寄存器,共 32 个使能位 |
… | … | … | … | … |
SETENA7 | R/W | 0xE000_E11C | 0 | 中断 224‐239 的使能寄存器,共 16 个使能位 |
CLRENA0 | R/W | 0xE000_E180 | 0 | 中断 0‐31 的除能寄存器,共 32 个除能位 位[n],中断#n 除能(异常号 16+n) |
CLRENA1 | R/W | 0xE000_E184 | 0 | 中断 32‐63 的除能寄存器,共 32 个除能位 |
… | … | … | … | … |
CLRENA7 | R/W | 0xE000_E19C | 0 | 中断 224‐239 的除能寄存器,共 16 个除能位 |
但是在特定的芯片中,只有该芯片实现的中断,其对应的位才有意义。因此,如果使用的芯片支持 32 个中断,则只有 SETENA0/CLRENA0 才需要使用。SETENA/CLRENA 可以按字/半字/字节的方式来访问。又因为前 16 个异常已经分配给系统异常,故而中断 0 的异常号是 16。
4. 中断的悬起与解悬
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄存器(CLRPEND)”来读取,还可以写它们来手工悬起中断。
悬起寄存器和“解悬”寄存器也可以有 8 对,其用法和用量都与前面介绍的使能/除能寄存器完全相同, 如下表(SETPENDs:0xE000_E200 – 0xE000_E21C ; CLRPENDs:0xE000E280 - 0xE000_E29C ):
名称 | 类型 | 地址 | 复位值 | 描述 |
---|---|---|---|---|
SETPEND0 | R/W | 0xE000_E200 | 0 | 中断 0‐31 的悬起寄存器,共 32 个悬起位 位[n],中断#n 悬起(异常号 16+n) |
SETPEND1 | R/W | 0xE000_E204 | 0 | 中断 32‐63 的悬起寄存器,共 32 个悬起位 |
… | … | … | … | … |
SETPEND7 | R/W | 0xE000_E21C | 0 | 中断 224‐239 的悬起寄存器,共 16 个悬起位 |
CLRPEND0 | R/W | 0xE000_E280 | 0 | 中断 0‐31 的解悬寄存器,共 32 个解悬位 位[n],中断#n 解悬(异常号 16+n) |
CLRPEND1 | R/W | 0xE000_E284 | 0 | 中断 32‐63 的解悬寄存器,共 32 个解悬位 |
… | … | … | … | … |
CLRPEND7 | R/W | 0xE000_E29C | 0 | 中断 224‐239 的解悬寄存器,共 16 个解悬位 |
5. 优先级
5.1 优先级位数设置
通过应用中断和复位控制寄存器( Application Interrupt and Reset Control Register, AIRCR) 的Bits[10:8]( PRIGROUP)将优先级分组。分组决定每个可编程中断的PRI_n的Bits[7:0]的高低位分配,从而影响抢占优先和亚优先级的级数
PRIGROUP | 抢占优先级位 | 亚优先级位 | 抢占优先级级数 | 亚优先级级数 | |
---|---|---|---|---|---|
0 | [7:1] | [0] | 128 | 2 | |
1 | [7:2] | [1:0] | 64 | 4 | |
2 | [7:3] | [2:0] | 32 | 8 | |
3 | [7:4] | [3:0] | 16 | 16 | |
4 | [7:5] | [4:0] | 8 | 32 | |
5 | [7:6] | [5:0] | 4 | 64 | |
6 | [7] | [6:0] | 2 | 128 | |
7 | None | [7:0] | 1 | 256 |
假设将优先级分组( PRIGROUP)设置为2,此时每个中断可设置抢占优先级范围为032,亚优先级范围为08, 比如某中断的抢占优先级为2, 亚优先级为3。
5.2 优先级下的执行顺序
所有可编程的中断都需要指定抢占优先级和亚优先级, 抢占优先级决定是否可以产生中断嵌套,亚优先级决定中断响应顺序,若两种优先级一样则看中断在中断异常表中的位置,越靠前越先响应。抢占优先级高(值小) 的中断可以中断抢占优先级低(值大) 的中断处理函数,进而执行高优先级的中断处理函数,执行完毕后再继续执行被中断的低优先级的处理函数。
当两个中断的抢占优先级相同时,即这两个中断将没有嵌套关系,当一个中断到来后,若此时CPU正在处理另一个中断,则这个后到来的中断就要等到前一个中断处理函数处理完毕后才能被处理,当这两个中断同时到达,则中断控制器会根据它们的亚优先级决定先处理哪个。
如果两个中断的优先级都设置为一样了,那么谁先触发的就谁先执行;如果是同时触发的,那么就根据中断异常表的位置(靠前) 来决定谁先执行。
5.2 外部中断优先级控制
每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位,但是允许最少只使用最高 3 位。 4 个相临的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问。有意义的优先级寄存器数目由芯片厂商实现的中断数目决定,优先级配置相关寄存器如下表:
- 中断优先级寄存器阵列 (0xE000_E400 – 0xE000_E4EF)
名称 | 类型 | 地址 | 复位值 | 描述 |
---|---|---|---|---|
PRI_0 | R/W | 0xE000_E400 | 0(8 位) | 外中断#0 的优先级 |
PRI_1 | R/W | 0xE000_E401 | 0(8 位) | 外中断#1 的优先级 |
… | … | … | … | … |
PRI_239 | R/W | 0xE000_E4EF | 0(8 位) | 外中断#239 的优先级 |
- 系统异常优先级寄存器阵列 (0xE000_ED18 - 0xE000_ED23 )
地址 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
0xE000_ED18 | PRI_4 | 存储器管理 fault 的优先级 | ||
0xE000_ED19 | PRI_5 | 总线 fault 的优先级 | ||
0xE000_ED1A | PRI_6 | 用法 fault 的优先级 | ||
0xE000_ED1B | ‐ | ‐ | ‐ | ‐ |
0xE000_ED1C | ‐ | ‐ | ‐ | ‐ |
0xE000_ED1D | ‐ | ‐ | ‐ | ‐ |
0xE000_ED1E | ‐ | ‐ | ‐ | ‐ |
0xE000_ED1F | PRI_11 | SVC 优先级 | ||
0xE000_ED20 | PRI_12 | 调试监视器的优先级 | ||
0xE000_ED21 | ‐ | ‐ | ‐ | ‐ |
0xE000_ED22 | PRI_14 | PendSV 的优先级 | ||
0xE000_ED23 | PRI_15 | SysTick 的优先级 |
6. 活动状态
每个外部中断都有一个活动状态位。在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。由于支持嵌套,允许高优先级异常抢占某个ISR。然而,哪怕一个中断被抢占,其活动状态也依然为 1(请仔细琢磨前文讲到的“直到 ISR返回时才清零)。活动状态寄存器的定义,与前面讲的使能/除能和悬起/解悬寄存器相同,只是不再成对出现。它们也能按字/半字/字节访问,但他们是只读的,如下表。
- ACTIVE 寄存器族 (0xE000_E300_0xE000_E31C )
名称 | 类型 | 地址 | 复位值 | 描述 |
---|---|---|---|---|
ACTIVE0 | RO | 0xE000_E300 | 0 | 中断 0‐31 的活动状态寄存器,共 32 个状态位 位[n],中断#n 活动状态(异常号 16+n) |
ACTIVE1 | RO | 0xE000_E304 | 0 | 中断 32‐63 的活动状态寄存器,共 32 个状态位 |
… | … | … | … | … |
ACTIVE7 | RO | 0xE000_E31C | 0 | 中断 224‐239 的活动状态寄存器,共 16 个状态 位 |
7. 其他寄存器
后边还有一些其他的寄存器,这里就不写了,后边用到的话可以再查手册。
8. 中断建立全过程的演示
下面给出一个简单的例子,以演示如何建立一个外部中断 :
(1)当系统启动后,先设置优先级组寄存器。缺省情况下使用组0(7位抢占优先级, 1位亚优先级)。
(2)如果需要重定位向量表,先把硬fault和NMI服务例程的入口地址写到新表项所在的地址中。
(3)配置向量表偏移量寄存器,使之指向新的向量表(如果有重定位的话)。
(4)为该中断建立中断向量。因为向量表可能已经重定位了,保险起见需要先读取向量表偏移量寄存器的值,再根据该中断在表中的位置,计算出服务例程入口地址应写入的表项,再填写之。如果一直使用ROM中的向量表,则无需此步骤。
(5)为该中断设置优先级。
(6)使能该中断
另外,如果优先级组设置使得中断嵌套层次可以很深,则务必请确认主堆栈空间足够用。因为异常服务程序总是使用MSP,为安全起见,主堆栈的容量应是最大可能需求的量(嵌套最深时需要的量)。如果应用程序储存在ROM中,并且不需要改变异常服务程序,则我们可以把整个向量表编码到ROM的起始区域(从0地址开始的那段)。在这种情况下,向量表的偏移量将一直为0,并且中断向量一直在ROM中,因此上例可以大大简化,只需3步:
(1)建立优先级组
(2)为该中断指定优先级
(3)使能该中断
如果在I/O密集型系统中,软件需要控制大量的硬件设备,则可能必须要考虑如下因素:
该芯片支持的中断数
该芯片中表达优先级的位数
在CM3的NVIC中,有一个名为“中断控制器类型寄存器”,它提供了该芯片中支持的中断数目,粒度是32的整数倍,(如下表所示)。如果嫌它太粗枝大叶,也可以通过对每个SETENA位进行先写后读的测试,来获取支持的中断的精确数目(往各SETENA中写1,不支持的中断将永远读回0,求出第1个0的位置即可),亦可使用SETPEND等其它位来做此测试。这主要用于需要适应不同芯片的程序。如果已经确定使用固定的芯片,则无需多此一举。
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
4:0 | INTLINESUM | R | ‐ | 中断输入的数量,以 32 为粒度,如 0=1 至 32;1=33 至 64;2=65 至 96 … |
为了判定正在使用的芯片使用了多少位来表达优先级,也可使用类似的方法:往某个优先级寄存器中写入0xFF,再读回来。则从MSB开始,有多少位是1就有多少位表达优先级。最少要使用3个位,此时读回的是0xE0。
9. 软件中断
软件中断,包括手工产生的普通中断,能以多种方式产生。最简单的就是使用相应的SETPEND寄存器;而更专业更快捷的作法,则是通过使用软件触发中断寄存器STIR,如下表。
- 软件触发中断寄存器STIR(地址:0xE000_EF00)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
8:0 | INTID | W | ‐ | 影响编号为 INTID 的外部中断,其悬起位被置位。 例如,写入 8,则悬起 IRQ #8 |
注意:系统异常(NMI, faults, PendSV等),不能用此法悬起。而且缺省时就不允许用户程序改动NVIC寄存器的值。如果确实需要,必须先在NVIC的配置和控制寄存器(0xE000_ED14)中,把比特1(USERSETMPEND)置位,才能允许用户级下访问NVIC的STIR。