学习笔记-CortexM3-中断与异常

此笔记是CortexM3内核学习笔记中,中断与异常的部分。
中断与异常是CortexM3内核中最核心的处理机制,篇幅较长,内容复杂,股单独分为一篇

中断与异常:两者当概念非常接近,主要区别在于

  • 中断:来自CM核外部的240个中断信号,相对CM3是异步的
  • 异常:来自CM核内部的16个信号,相对CM3是同步的。

下面两者都记录了当前正服务的异常,给出了它的编号。

  • 在 NVIC 的中断控制及状态寄存器中的VECTACTIVE 位段
  • 特殊功能寄存器 IPSR。

“悬起” (pending):一个发生的异常不能被即刻响应

  • 少数 fault 异常是不允许被悬起的。
  • 原因:
    • 可能是系统当前正在执行一个更高优先级异常的服务例程,
    • 或者因相关掩蔽位的设置导致该异常被除能。
  • 由 NVIC 的悬起状态寄存器记录:哪怕设备已经释放了请求信号,中断请求也不会错失。

基本特征

优先级

CM3 支持中断嵌套

  • 高优先级异常会抢占(preempt)低优先级异常。

有固定的优先级的异常:

  • 复位, NMI 以及硬 fault
  • 并且它们的优先级号是负数,从而高于所有其它异常。

根据设计,

  • 原则上, CM3 支持 3 个固定的高优先级和多达 256 级的可编程优先级,并且支持 128 级抢 占

  • 实际上,绝大多数 CM3 芯片都会精简设计,以致实际上支持的优先级数会更少,如 8 级, 16 级, 32 级等。

  • CM3 允许最少要支持 8 级优先级

优先级以 MSB 对齐:便于移植

  • 比如,如果一个程序早先在支持 4 位优先级的器件上运行,在移植到只支持 3 位优先级的器件后,其功能不受影响。
  • 但若是对齐到 LSB,则会使 MSB 丢失,导致数值大于 7 的低优先级一下子升高了,甚至会发生“优先级反转”:
抢占优先级与子优先级

CM3 还把 256 级优先级按位分成高低两段,分别称为抢占优先级和子优先级,

  • 为了使抢占机能变得更可控,

**抢占优先级:**即主优先级。

  • 抢占优先级高可以抢占低的异常程序。
  • 若抢占优先级相同,无法抢占。
  • 所以,中断嵌套的层级有限。例如,若允许8级主优先级,则最多产生8级嵌套。

**子优先级:**若当前有多个挂起当中断的抢占优先级相同,则比较子优先级。

  • 子优先级只用于决定挂起中断当执行顺序。

两者组合就是CM3所说当256级中断。

优先级完全相同:先响应异常编号最小的那一个

优先级组别
  • NVIC有8个优先级组别,0~7
  • 不同组别意味着表达抢占优先级和子优先级位数。
    • 例如,组别 = 4,抢占= [7:5],子 = [4:0]
      组别 = 1,抢占= [7:2],子 = [1:0]
    • 即,优先级组N,表示从bitN开始划分**。N0为子,7N+1为抢占。**
  • 注意,对于没有使用的位,也允许用于划分组别。
    • 例如,只用[7:5]表示优先级,但组4是允许使用的。

通过NVIC中的AIRCR寄存器,可以配置优先级组别。

  • 应用程序中断及复位控制寄存器(AIRCR)(地址: 0xE000_ED00)
向量表

存放每个中断服务函数当入口地址。

  • 缺省情况下: CM3 认为该表位于零地址处, 且各向量占用 4 字节

地址 0 处应该存储引导代码,

  • 它通常映射到 Flash 或者是 ROM 器件,并且它们的值不得在运行时改变。

为了支持动态重分发中断, CM3 允许向量表重定位

  • 从其它地址处开始定位各异常向量。
  • 这些地址对应的区域可以是代码区 ,更多是在 RAM 区
    • 因为RAM 区可以修改向量的入口地址。

可以通过修改NVIC 中的**“向量表偏移量寄存器(VTOR) ”**( 0xE000_ED08 处),实现重定位向量表

向量表的起始地址是有要求的:

  • 必须先求出系统中共有多少个向量,再把这个数字向上“圆整”到 2 的整次幂,而起始地址必须对齐到后者的边界上。
  • 例如,如果一共有 32 个中断,则共有 32+16(系统异常) =48 个向量,向上圆整到 2 的整次幂后值为 64,因此向量表重定位的地址必须能被 64*4=256 整除,从而合法的起始地址可以是: 0x0, 0x100, 0x200 等。

向量表的起始处都必须包含以下向量:

  • 主堆栈指针(MSP)的初始值
  • 复位向量
  • NMI
  • 硬 fault 服务例程

中断输入及悬起

对于 NMI :除了以下情况之外,将会立即无条件执行其服务例程。

  • 当前已经在执行 NMI 服务例程;
  • CPU被调试器喊停(halted);
  • CPU 被一些严重的系统错误锁定(Lock up)。则新的 NMI 请求也将悬起

悬起:当中断输入脚被置为有效(asser) t 后,该中断就被悬起。

  • 即使中断源撤消中断请求,悬起的中断也会被记录下来。到了系统中它的优先级最高时得到响应。
  • 但是,若在中断得到响应之前,其悬起状态被清除了,则中断被取消 。
  • 中断开始执行时,悬起被硬件清除。
  • 在中断服务函数中,还可以直接悬起自己或他人的中断,但这种操作有风险。。

信号与悬起:

  • 若中断源持续发出中断信号:则悬起在硬件清除后,中断函数执行完成后,立刻再次置位。
  • 若在进入中断服务函数之前,发出多次中断信号:只视为一次中断。
  • 若在进入中断服务函数之后,中断信号有从无到有的跳变,则中断再次悬起。

总之,同时只能记录一个中断悬起状态。在悬起状态清除之后,可以再次记录。

中断触发方式

  • 外部中断输入:正常的触发方式,按照流程配置好中断,等待外部信号。
  • 设置NVIC的悬起寄存器中设置相关的位 :直接设置中断状态为悬起。
  • 使用NVIC的软件触发中断寄存器(STIR):会直接触发对应中断。

软件触发与SCV

  • 两者功能相似,都可以实现软件控制,进入中断。可以达到相同的效果。
  • 软件触发可以利用系统中空闲的中断。
  • 但软件触发会受到种种影响,例如写缓冲,并且配置方式较为复杂。
  • 推荐在需要软件触发中断的时候,使用SVC或PendSV
  • SVC中不能嵌套使用SVC,会引发fault

中断相关流程

中断配置流程

建立堆栈

  • 需要保证在程序运行过程中堆栈不会用穿。有以下方法决定堆栈大小。
  • 最保险的,计算当前可能的最大中断嵌套深度,假设每个中断都可以嵌套,为每一个中断保留8个32位空间。
  • 最方便的,堆栈和程序运行过程中用到的数据位于SRAM
    • 将SRAM的末尾作为SP指针,则剩余所有空间都可以作为堆栈使用,无需计算。
    • 相当于分配可能的最大堆栈空间。
    • 但只适用于存在单一堆栈的情况。
  • 最实用的,在调试的时候,实用一个较大内存的期间,让程序运行一段时间后,查看堆栈的最大用量,作为堆栈大小。

建立向量表

  • 若程序过程中,无需修改向量表,则可以将向量表放置到ROM中,不允许程序修改。
  • 若程序过程中,需要修改向量表,则需要将向量表放置到可读写存储器(如flash)中。
    • 修改向量表,有修改中断向量值,增减中断向量,以及重定位向量表。
  • 重定位向量表,需要修改向量表偏移寄存器,
    • 要注意,新的向量表中,需要包含系统异常的服务例程。

分配各中断的优先级

  • 测试可用的优先级位数:向一个优先级寄存器写0xff,然后读回,计算其中1的个数。
  • 注意位系统异常建立优先级,NMI和硬fault定死为-2和-1
    • 但是,如果需要让程序中的某些异常凌驾于NMI或硬Fault之上,可以调低两者的优先级。

使能中断

  • 在使能中断之前;
    • 保证之前的数据已经写入,执行“数据同步隔离(DSB)”指令 ,以防向量表存放的区域有写缓冲。
    • 清除已经悬起的中断。他们可能是噪声导致的。
  • 使能操作:对所有的SETENA 写1即可。
中断数量与优先级位数测试流程

测试中断数量:

  • 对每个SETENA位进行先写后读的测试,来获取支持的中断的精确数目
    • 往各SETENA中写1,不支持的中断将永远读回0,求出第1个0的位置即可
  • 亦可使用SETPEND等其它位来做此测试。

测试优先级位数:

  • 往某个优先级寄存器中写入0xFF,再读回来。
  • 则从MSB开始,有多少位是1就有多少位表达优先级。
  • 例如使用3个位,此时读回的是0xE0
中断响应流程

入栈: 依次把xPSR, PC, LR, R12以及R3-R0由硬件自动压入适当的堆栈中:

  • 当前在使用哪个堆栈,就压入哪里。在中断服务程序中,一定使用主堆栈。
  • 顺序**:PC -> xPSR -> R0-R3 -> R12 -> LR**
    • 先保存PC与xPSR,可以更早启动指令的预取,因为这需要修改PC;
    • 连续压入 R0-R3 、 R12,方便读取。
    • 只使用R0-R3 、 R12,是为了适配ARM的C函数调用标准。
      • 《C/C++ Procedure Call Standard for the ARM Architecture》, AAPCS,Ref5

**取向量:**从向量表中找出对应的服务程序入口地址

  • 入栈使用数据总线,取向量后,在服务程序的入口预取值,使用指令总线。两者可以同时进行。

更新寄存器:在前两步完成之后,执行服务函数之前。

  • SP:在入栈后会把堆栈指针(PSP或MSP)更新到新的位置。使用MSP
  • PSR: 更新IPSR位段为新响应的异常编号。
  • PC:在取向量完成后, PC将指向服务例程的入口地址,
  • LR: 在出入ISR的时候, LR的值将被称为**“EXC_RETURN”,**
    • 其最低4位则有另外的含义 ,其余全部为0.

EXC_RETURN位段详解

位段含义
[31:4]EXC_RETURN的标识:必须全为1
30=返回后进入Handler模式 1=返回后进入线程模式
20=从主堆栈中做出栈操作,返回后使用MSP, 1=从进程堆栈中做出栈操作,返回后使用PSP
1保留,必须为0
00=返回ARM状态。 1=返回Thumb状态。 在CM3中必须为1
异常返回流程

触发中断返回:利用LR中的EXC_RETURN。

  • 将EXC_RETURN写入PC中即可触发,有多种指令可以实现。

出栈:恢复先前压入栈中的寄存器

  • 内部的出栈顺序与入栈时的相对应,堆栈指针的值也改回先前的值。

更新NVIC寄存器:硬件清楚中断活动位。

  • 对于外部中断,倘若中断输入再次被置为有效,悬起位也将再次置位,新一次的中断响应序列也可随之再次开始。

在上述流程中,可能会触发如下fault。

入栈或出栈期间

  • 总线fault:“入栈错误” (stacking error)

    • 本次入栈操作将被强行中止,并且把总线异常悬起或者在允许时立即响应。
    • 若被屏蔽,则上访为硬fault
  • 存储管理fault:入栈操作引起MPU访问违例

    • 服务例程必须能立即执行,否则无条件硬fault。
  • 用法fault :出入栈是自动完成的,因此不可能产生

取向量期间

  • 总线fault :直接硬fault

无效返回

  • **用法Fault:**LR中的EXC_RETURN不是合法的值

Fault异常

  • 总线 faults
  • 存储器管理 faults(MemMangefault)
  • 用法 faults
  • 硬 fault

对于上述fault,如果是:

  • 被同级或高优先级异常的服务例程引发,即不可抢占的例程。
  • fault被除能时,触发了fault信号

则他们会上访成为硬 fault,最终执行的是硬 fault 的服务例程。

总线 Faults

产生原因:

  • 当 AHB 接口上正在传送数据时,如果回复了一个错误信号(error response),
    • 取指,通常被称作“预取流产”(prefetch abort)
    • 数据读/写,通常被称作“数据流产”(data abort)
  • 执行如下动作时,如果地址有误:
    • 中断处理起始阶段的堆栈 PUSH 动作。此时若发生总线 fault,则称为“入栈错误”
    • 中断处理收尾阶段的堆栈 POP 动作。此时若发生总线 fault,则称为**“出栈错误”**
    • 在处理器启动中断服务序列(sequence)后读取向量时。这是一种极度罕见的特殊情况,
      被归类为硬 fault。

AHB 回复的错误信号 ,可能的原因:

  • 企图访问无效的存储器 region。常见于访问的地址没有相对应的存储器。
  • 设备还没有作好传送数据的准备。比如,在尚未初始化 SDRAM 控制器的时候试图访问 SDRAM。
  • 在企图启动一次数据传送时,传送的尺寸不能为目标设备所支持。例如,某设备只接受字型数据,却试图送给它字节型数据。
  • 设备不能接受数据传送。例如,某些设备只有在特权级下才允许访问,可当前却是用户级。
存储器管理 faults

多与 MPU 有关, 常常是某次访问触犯了 MPU 设置的保护规范

  • 访问了所有 MPU regions 覆盖范围之外的地址
  • 访问空地址
  • 往只读 region 写数据
  • 用户级下访问了只允许特权级访问的地址
用法 faults

触发原因:

  • 执行了协处理器指令。
    • Cortex-M3 本身并不支持协处理器,但是通过 fault 异常机制,可以建立一套“软件模拟”的机制,方便移植。
  • 执行了未定义的指令。
    • 可用于软件模拟未定义指令的功能。
  • 尝试进入 ARM 状态。
    • CM3 不支持 ARM 状态
    • 可以利用此机制来测试某处理器是否支持 ARM 状态。
  • 无效的中断返回(LR 中包含了无效/错误的值)
  • 使用多重加载/存储指令时,地址没有对齐。
  • 还可以让 CM3 在遇到除数为零的时候,以及遇到未对齐访问的时候也产生用法 fault。
    • 在 NVIC 中有两个控制位与这两种情况对应。
硬 fault

硬 fault 是上文讨论的总线 fault、存储器管理 fault 以及用法 fault 上访的结果。

  • 若硬fault是上访产生的,则还需要去检查其他fault的状态寄存器。
fault相关寄存器
  • 都位于NVIC中,

  • 每种fault都对应一个状态寄存器

  • 总线fault与存储器fault还分别有一个地址寄存器, 存放引发此 fault 时访问的地址

SVC 和 PendSV

SVC(系统服务调用)

用于产生系统函数的调用请求。

  • 操作系统通常不让用户程序直接访问硬件,而是提供系统服务函数
  • 用户程序可以产生一个SVC 异常,然后SVC 异常服务例程再调用相关的操作系统函数。
  • SVC 异常是必须在执行 SVC 指令后立即得到响应。
    • 若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬 fault

SVC 异常通过执行”SVC”指令来产生。

SVC 0x3 ; 调用 3 号系统服务
  • CM3的SVC指令与ARM的SWI指令完全相同。

使用注意:由于同优先级异常不能抢占自身,会导致如下情况。

  • 不能在 SVC 服务例程中嵌套使用 SVC 指令 ,会产生用法 fault
  • 在 NMI服务例程中不得使用 SVC,否则将触发硬 fault。
PendSV(可悬起系统调用)

PendSV可以软件触发

  • 与SVC不同,它是可以像普通的中断一样被悬起的

OS 可以利用它**“缓期执行”**一个异常

  • 直到其它重要的任务完成后才执行动作。

典型使用场合 :上下文切换 (在不同任务之间切换)

  • 例如,有两个任务,A和B,需要通过systick来实现轮转执行
    • 即两个任务都在主程序中执行,但是每经过一次systick,则切换到另一个任务。
    • 并且,如果任务切换成功,则上一个任务留存的内容不会在这一个任务中执行。
  • 若在任务A执行中断处理时,来了一个systick,会直接抢占当前中断,并把线程状态切换到任务B。至于A执行到一半的中断,只能等下一次切换回来再继续执行。
    • 若A的中断相关内容对实时性有任何要求,这种情况都会导致错误。
  • 所以,可以采用PendSV来实现任务切换
    • 在systick中,做好切换准备后,调用PendSV,在其中实现切换。
    • 将PendSV的优先级设为最低。
    • 这样就可以保证,如果任务A在切换之前,还有任何残留的中断悬起没有处理,都会在这些中断处理完成之后,才执行任务的切换。

在这里插入图片描述

问题:对于这里提到的上下文切换。如果使用一个定时器,设定最低优先级,是不是也可以实现与PendSV相同效果??

**PendSV可以用于:**通过高优先级中断引发的一连串操作,对于操作的实时性有一定要求。但是部分操作又比较耗时。

  • 若全部使用一个高优先级中断实现,则会导致其他中断被延缓执行。
  • 所以,使用PendSV,将部分比较耗时的操作交给低优先级中断执行。其他中断可以随时打断它。但是线程又不至于进入主循环。
SVC服务例程编写
; 汇编封皮,用于提出堆栈帧的起始位置,并放到R0中,然后跳转至实际的SVC服务例程中
__asm void svc_handler_wrapper(void)
{
IMPORT svc_handler
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B svc_handler
}
;不必写下BX LR来返回,而是由svc_handler来做决定
  • 将堆栈帧的起始位置存入R0,作为SVC服务函数的传入参数使用。
    • 相当于传入一个指针参数。
// 使用C写成的SVC服务例程,接受一个指针参数(pwdSF):堆栈栈的起始地址。
// pwdSF[0] = R0 , pwdSF[1] = R1
// pwdSF[2] = R2 , pwdSF[3] = R3
// pwdSF[4] = R12, pwdSF[5] = LR
// pwdSF[6] = 返回地址(入栈的PC)
// pwdSF[7] = xPSR
unsigned long svc_handler(unsigned int* pwdSF)
{
unsigned int svc_number;
unsigned int svc_r0;
unsigned int svc_r1;
unsigned int svc_r2;
unsigned int svc_r3;
int retVal; //用于存储返回值
svc_number = ((char *) pwdSF[6])[-2]; // 没想到吧, C的数组能用得这么绝!
svc_r0 = ((unsigned long) pwdSF[0]);
svc_r1 = ((unsigned long) pwdSF[1]);
svc_r2 = ((unsigned long) pwdSF[2]);
svc_r3 = ((unsigned long) pwdSF[3]);
printf (“SVC number = %xn”, svc_number);
printf (“SVC parameter 0 = %x\n”, svc_r0);
printf (“SVC parameter 1 = %x\n”, svc_r1);
printf (“SVC parameter 2 = %x\n”, svc_r2);
printf (“SVC parameter 3 = %x\n”, svc_r3);
//做一些工作,并且把返回值存储到retVal中
pwdSF[0]=retVal;
return 0;
}
  • 传入参数pwdSF,实际上是进入中断服务历程之前的堆栈指针,其中存放来当时的寄存器值。
    • 传出返回值也依赖pwdSF实现。
  • 返回值:函数返回的其实不是0 。SVC服务例程时按照异常返回处理,而非C函数
    • C函数的返回值通过R0寄存器实现
    • 异常返回时会自动出栈,重建R0,所以明面上的返回值是无效的。

特殊中断类型

嵌套的中断

只需为每个中断适当地建立优先级 ,CM3内核会自行根据优先级设置处理抢占与嵌套行为。

需要注意:主堆栈容量需要充足,最好能够承载可能的最大嵌套深度

  • 每一级嵌套都需要知道8个32位的堆栈空间。中断服务函数本身的运行还需要一定的中断。
  • 如果堆栈溢出,则数据会跑到不受保护的位置,如果被篡改,可能导致程序死机。
咬尾中断

允许连续执行多个中断,减少出入栈的过程

  • 若在某次异常的响应完成之前,就有其他优先级不够的异常被阻塞,正在等待。
  • 则在当前异常完成之后,直接执行下一个异常响应的程序。

但是,咬尾中断机制会导致中断的执行时间不稳定,可能在某些时候变短。

如果需要一个稳定的中断执行时间,可以在进入中断之前,关闭中断。(disable_irq)执行完成后再开启。

或者给与中断一个足够高的优先级。

晚到中断

高优先级中断,在低优先级中断响应早期到来

  • 即开始执行中断服务例程的指令之前,但已经开始处理入栈。

直接将本次入栈作为高优先级中断的准备,执行他的中断服务例程。

中断延迟

定义:从检测到某中断请求,到执行了其服务例程的第一条指令的时间。

在CM3中,若存储器系统够快,且总线系统允许入栈与取指同时进行,同时该中断可以立即响应,则中断延迟是雷打不动的12周期(满足硬实时所要求的确定性)。

  • 当处理咬尾中断时,耗时可以短至6周期

对于中断响应时正在执行的指令:

  • 执行周期较多:取消执行,等待返回后重新开始。
  • 在总线接口上还有未完成的数据传送:等待此传送完成。
  • 对于LDM/STM,它们其实是一串LDR/STR的速度优化版。CM3支持LDM/STM指令的中止和继续
    • 指令的中止和继续需要用到xPSR中的几个位。
    • IF-THEN(IT)指令的执行也需要几个位,并且和LDM/STM 重合。
    • 所以,若在在IF-THEN中使用了LDM/STM ,则取消执行,返回后重新开始。

异常信号表

系统异常:

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

5.内核设备

NVIC

  • 立即抢占:
    • 当前优先级被存储在 xPSR 的专用字段中。
    • 当一个异常发生时,硬件会自动比较该异常的优先级是否比当前的异常优先级更高。
    • 如果发现来了更高优先级的异常,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常
  • 中断向量:
    • 当开始响应一个中断后, CM3 会自动定位一张向量表,
    • 并且根据中断号从表中找出 ISR 的入口地址,然后跳转过去执行。
  • 向量表:通过 NVIC 中的一个重定位寄存器来指出向量表的地址。
    • 复位后,该寄存器的值为 0。
    • 因此**,在地址 0 处必须包含一张向量表,用于初始时的异常分配。**
    • 向量表是一个 32 位整数数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。
  • 动态优先级调整 :
    • 可以在运行时期更改中断的优先级。
    • 如果在某ISR中修改了自己所对应中断的优先级,而且这个中断又有新的实例处于悬起中(pending),也不会自己打断自己,从而没有重入(reentry) 的风险

NVIC 还包含了 MPU、 SysTick 定时器以及调试控制相关的寄存器。

访问地址: 0xE000_E000

  • 所有 NVIC 的中断控制/状态寄存器都只能在特权级下访问。
  • 例外——软件触发中断寄存器可以在用户级下访问。
  • 所有的中断控制/状态寄存器均可按字/半字/字节的方式访问
使能与除能、悬起与解悬

使用**240 对使能位/除能位(**SETENA 位/CLRENA 位) 来控制中断的使能和除能。

  • 即8对32位寄存器。
  • 一位对应一个可屏蔽中断。
  • 通过写1实现控制,写0无效。
    • 这样可以使得在设置的时候,无需考虑一个寄存器中无关位的状态,只要写0 就好,不会造成影响。

使能\除能位按照顺序,与中断向量表中的16~255号向量对应

  • 例如,N号中断使能并且被触发,之后会执行中断向量表中第N项对应的中断服务函数。
  • 所以,使能\除能位与具体中断的对应关系,由中断向量表的顺序定义。

悬起与解悬 和 使能与除能一样,也是采用240对悬起\解悬位实现。

活动状态

每个外部中断都有一个活动状态位。

  • 在处理器执行了其 ISR 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。
  • 哪怕中断被抢占,其活动状态也依然为 1
  • 只读的

注意活动状态与悬起的不同。

寄存器

NVIC的相关寄存器有:

  • 使能与除能寄存器
  • 悬起与“解悬”寄存器
  • 优先级寄存器
  • 活动状态寄存器

另外,下列寄存器也对中断处理有重大影响

  • 异常掩蔽寄存器(PRIMASK, FAULTMASK 以及 BASEPRI)
  • 向量表偏移量寄存器
  • 软件触发中断寄存器
  • 优先级分组位段
优先级寄存器

每个外部中断都有一个对应的8位优先级寄存器,

  • CM3 允许在最“粗线条”的情况下,只使用最高 3 位
  • 4 个相临的优先级寄存器拼成一个 32 位寄存器

优先级分组设置见AIRCR寄存器。

应用程序中断及复位控制寄存器(AIRCR)

(地址: 0xE000_ED00)

位段名称类型复位值描述
31:16VECTKEYRW-访问钥匙:任何对该寄存器的写操作,都必 须同时把 0x05FA 写入此段,否则写操作被 忽略。若读取此半字,则 0xFA05
15ENDIANESSR-指示端设置。 1=大端(BE8), 0=小端。此 值是在复位时确定的,不能更改。
10:8PRIGROUPR/W0优先级分组
2SYSRESETREQW-请求芯片控制逻辑产生一次复位
1VECTCLRACTIVEW-清零所有异常的活动状态信息。通常只在调 试时用,或者在 OS 从错误中恢复时用。
0VECTRESETW-复位 CM3 处理器内核(调试逻辑除外),但 是此复位不影响芯片上在内核以外的电路
向量表偏移量寄存器(VTOR)

( 0xE000_ED08 处)

位段名称类型复位值描述
7-28TBLOFFRW0向量表的起始地址
29TBLBASER-向量表是在 Code 区(0),还是在 RAM 区(1)
异常屏蔽寄存器

PRIMASK 用于除能在 NMI 和硬 fault 之外的所有异常,

  • 把当前优先级改为 0 ,则没有一般中断可以抢占当前程序。

FAULTMASK除能在 NMI 之外的所有异常,包括硬 fault

  • 把当前优先级改为-1。这么一来,连硬fault都被掩蔽了。

BASEPRI掩蔽优先级低于某一阈值的中断

  • 如果往BASEPRI中写0,将停止掩蔽任何外部中断

BASEPRI_MAX :同样也是访问BASEPRI,但会使用一个条件写操作

  • 只允许新的优先级阈值比原来的那个在数值上更小,也就是说,只能一次次地扩大掩蔽范围,

操作示例

1. 关中断
MOV R0, #1
MSR PRIMASK, R0
2. 开中断
MOV R0, #0
MSR PRIMASK, R0
此外,还可以通过CPS指令快速完成上述功能:
CPSID i ;关中断
CPSIE i ;开中断

MOV R0, #0x60
MSR BASEPRI, R0;屏蔽优先级低于

MOV R0, #0
MSR BASEPRI, R0
fault配置寄存器

“系统Handler控制及状态寄存器(SHCSR)”(地址: 0xE000_ED24):包括对用法fault,总线fault、存储器管理fault 的:

  • 使能控制
  • 悬起状态和大多数系统异常的活动状态
位段名称类型复位值描述
18USGFAULTENAR/W0用法 fault 服务例程使能位
17BUSFAULTENAR/W0总线 fault 服务例程使能位
16MEMFAULTENAR/W0存储器管理 fault 服务例程使能位
15SVCALLPENDEDR/W0SVC 悬起中。本来已经要 SVC 服务例程,但 是却被更高优先级异常取代
14BUSFAULTPENDEDR/W0总线 fault 悬起中,细节同上。
13MEMFAULTPENDEDR/W0存储器管理 fault 悬起中,细节同上
12USGFAULTPENDEDR/W0用法 fault 悬起中,细节同上
11SYSTICKACTR/W0SysTick 异常活动中
10PENDSVACTR/W0PendSV 异常活动中
9----
8MONITORACTR/W0Monitor 异常活动中
7SVCALLACTR/W0SVC 异常活动中
6:4----
3USGFAULTACTR/W0用法 fault 异常活动中
2----
1BUSFAULTACTR/W0总线 fault 异常活动中
0MEMFAULTACTR/W0存储器管理 fault 异常活动中
  • 非常不建议修改这些寄存器,
    • 设置或者清零这些位,会改变处理器中对异常活动的记录,却不会对应地修复堆栈中的数据(不会为了此改动而特意执行一次自动入栈或自动出栈操作),于是埋下了破坏堆栈内容而引起程序跑飞的隐患;
    • 另外,其它一些重要的数据结构也得不到清除,后患无穷。
fault状态与地址寄存器

总线 fault 状态寄存器”(BFSR) :地址: 0xE000_ED29

  • 通过它,可以确定产生 fault 的场合 :是在数据访问时,在取指时,还是在中断的堆栈操作时
位段名称类型复位值描述
7BFARVALID-0=1 时表示 BFAR 有效
6:5----
4STKERRR/Wc0入栈时发生错误
3UNSTKERRR/Wc0出栈时发生错误
2IMPRECISERRR/Wc0不精确的数据访问违例(violation)
1PRECISERRR/Wc0精确的数据访问违例
0IBUSERRR/Wc0取指时的访问违例

“总线 fault 地址寄存器(BFAR)” :

  • 对于精确的总线fault,还可以找到肇事指令的地址。
  • 精确的总线fault:被最后一个完成的指令触发,例如读存储器。
  • 不精确的总线fault:导致fault的指令早已完成。例如缓冲区写入,写入的内容有误,但实际内容写入时,写入指令早已完成。

“存储器管理 fault 状态寄存器(MFSR)”:地址: 0xE000_ED28

  • 指出导致 MemManage fault 的原因。

    位段名称类型复位值描述
    7MMARVALID-0=1 时表示 MMAR 有效
    6:5----
    4MSTKERRR/Wc0入栈时发生错误
    3MUNSTKERRR/Wc0出栈时发生错误
    2----
    1DACCVIOLR/Wc0数据访问违例
    0IACCVIOLR/Wc0取指访问违例

“存储器管理地址寄存器(MMAR)”

  • 如果是因为数据访问违例(DACCVIOL 位)或取指访问违例(IACCVIOL 位)触发,
  • 且还MMARVALID位被置位,

用法 fault 状态寄存器(UFSR)” 地址: 0xE000_ED2A

位段名称类型复位值描述
9DIVBYZEROR/Wc0表示除法运算时除数为零(只有在 DIV_0_TRP 置位时才会发生)
8UNALIGNEDR/Wc0未对齐访问导致的 fault
7:4----
3NOCPR/Wc0试图执行协处理器相关指令
2INVPCR/Wc0在异常返回时试图非法地加载 EXC_RETURN 到 PC。包括非法的指令,非法的上下文以及 非法的 EXC_RETURN 值。 The return PC 指 向的指令试图设置 PC 的值(要理解此位的含 义,还需学习后面的讨论中断级异常的章节)
1INVSTATER/Wc0试图切入 ARM 状态
0UNDEFINSTRR/Wc0执行的指令其编码是未定义的——解码不能

硬 fault 状态寄存器(HFSR) ,指出产生硬 fault 的原因

位段名称类型复位值描述
31DEBUGEVTR/Wc0硬 fault 因调试事件而产生
30FORCEDR/Wc0硬 fault 是被上访的。上访者可以是总线 fault、存储器管理 fault 或是用法 fault
29:2----
1VECTBLR/Wc0硬 fault 是在取向量时发生的
0----

中断控制及状态寄存器ICSR

(地址: 0xE000_ED04)

位段名称类型复位值描述
31NMIPENDSETR/W0写 1 以悬起 NMI。因为 NMI 的优先级最高且从不 掩蔽,在置位此位后将立即进入 NMI 服务例程。
28PENDSVSETR/W0写 1 以悬起 PendSV。读取它则返回 PendSV 的 状态
27PENDSVCLRW0写 1 以清除 PendSV 悬起状态
26PENDSTSETR/W0写 1 以悬起 SysTick。读取它则返回 PendSV 的 状态
25PENDSTCLRW0写 1 以清除 SysTick 悬起状态
23ISRPREEMPTR0=1 时,则表示一个悬起的中断将在下一步时进 入活动状态(用于单步执行时的调试目的)
22ISRPENDINGR01=当前正有外部中断被悬起(不包括 NMI)
21:12VECTPENDINGR0悬起的 ISR 的编号。如果不止一个中断悬起, 则它的值是这次中断中,优先级最高的那一个。
11RETTOBASER0如果异常返回后将回到基级(base level), 并 且没有其它异常悬起时,此位为 1。若是在线程 模式下,在某个服务例程中,有不止一级的异常 处于活动状态,或者在异常没有活动时执行了异 常服务例程(此时执行返回指令将产生 fault。 此乃高危行为,大虾也需慎用),则此位为 0
9:0VECTACTIVER0当前活动的ISR编号,该位段指出当前运行中的 ISR是哪个中断的(提供异常序号),包括NMI和 硬fault。 如果多个异常共享一个服务例程,该例程可 根据本位段的值来判定是哪一个异常的响应导致它的执 行。把本位段的值减去16,就得到了外中断的编号,并 可以用此编号来操作外中断相关的使能/除能等寄存器。
中断控制器类型寄存器ICTR

(地址: 0xE000_E004)

  • 可以获取当前的芯片支持的中断数目,
  • 但只能得到32的倍数的结果,比较粗糙。
位 段名称类 型复 位 值描述
4:0INTLINESUMR-中断输入的数量,以 32 为粒度,如 0=1 至 32 1=33 至 64 2=65 至 96 …
软件触发中断寄存器STIR

(地址: 0xE000_EF00)

位 段名称类 型复 位 值描述
8:0INTIDW-影响编号为 INTID 的外部中断,其悬起位 被置位。例如,写入 8,则悬起 IRQ #8

SysTick 定时器

24位倒计数定时器。

SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号: 15)。

  • 当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。
    • 一般定时器则不会。

时钟源:

  • 自由运行时钟” FCLK:系统时钟停止时 FCLK 也继续运行
  • 外部的参考时钟
    • 外部时钟通过 FCLK 来采样,其周期必须至少是FCLK 的两倍(采样定理)
寄存器

SysTick控制及状态寄存器(地址: 0xE000_E010)

位段名称类型复位值描述
16COUNTFLAGR0如果在上次读取本寄存器后, SysTick 已经计到 了 0,则该位为 1。如果读取该位,该位将自动清 零
2CLKSOURCER/W00=外部时钟源(STCLK) 1=内核时钟(FCLK)
1TICKINTR/W01=SysTick倒数计数到0时产生SysTick异常请 求 0=数到 0 时无动作
0ENABLER/W0SysTick 定时器的使能位

SysTick重装载数值寄存器(地址: 0xE000_E014)

位段名称类型复位值描述
23:0RELOADR/W0当倒数计数至零时,将被重装载的值

SysTick当前数值寄存器(地址: 0xE000_E018)

位段名称类型复位值描述
23:0CURRENTR/Wc0读取时返回当前倒计数的值,写它则使之清零, 同时还会清除在SysTick控制及状态寄存器中的 COUNTFLAG 标志

SysTick校准数值寄存器(地址: 0xE000_E01C)

位段名称类型复位值描述
31NOREFR-1=没有外部参考时钟(STCLK 不可用) 0=外部参考时钟可用
30SKEWR-1=校准值不是准确的 10ms 0=校准值是准确的 10ms
23:0TENMSR/W0在 10ms 的间隔中倒计数的格数。芯片设计者应 该通过 Cortex-M3 的输入信号提供该数值。若该 值读回零,则表示无法使用校准功能

存储器保护单元(MPU)

MPU在执行其功能时,以“region”为单位。

  • region是一段连续的地址,只是它们的位置和范围都要满足限制(对齐方式,最小容量等)。
  • 允许把每个region进一步划分成更小的“子region”
  • regions可以相互交迭。如果某块内存落在多个region中,则访问属性和权限将由编号最大的region来决定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值