此笔记是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为抢占。**
- 例如,组别 = 4,抢占= [7:5],子 = [4:0]
- 注意,对于没有使用的位,也允许用于划分组别。
- 例如,只用[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 |
3 | 0=返回后进入Handler模式 1=返回后进入线程模式 |
2 | 0=从主堆栈中做出栈操作,返回后使用MSP, 1=从进程堆栈中做出栈操作,返回后使用PSP |
1 | 保留,必须为0 |
0 | 0=返回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 ,则取消执行,返回后重新开始。
异常信号表
系统异常:
编号 | 类型 | 优先级 | 简介 |
---|---|---|---|
0 | N/A | N/A | 没有异常在运行 |
1 | 复位 | -3(最高) | 复位 |
2 | NMI | -2 | 不可屏蔽中断(来自外部 NMI 输入脚) |
3 | 硬(hard)fault | -1 | 所有被除能的 fault,都将“上访” (escalation)成硬 fault。只要 FAULTMASK 没有置位,硬 fault 服务例程就被强制执行。 Fault 被除能的原因包括被禁用,或者被 PRIMASK/BASEPRI 被掩蔽。 若 FAULTMASK 也置位,则硬 fault 也被除能,此时彻底“关中” |
4 | MemManage fault | 可编程 | 存储器管理 fault, MPU 访问违例以及访问非法位置均可引发。 企图在“非执行区”取指也会引发此 fault |
5 | 总线 fault | 可编程 | 从总线系统收到了错误响应,原因可以是预取流产( Abort)或 数据流产,企图访问协处理器也会引发此 fault |
6 | 用法(usage) Fault | 可编程 | 由于程序错误导致的异常。通常是使用了一条无效指令,或者是非法的状态转换,例如尝试切换到 ARM 状态 |
7-10 | 保留 | N/A | N/A |
11 | SVCall | 可编程 | 执行系统服务调用指令( SVC)引发的异常 |
12 | 调试监视器 | 可编程 | 调试监视器( 断点,数据观察点,或者是外部调试请求) |
13 | 保留 | N/A | N/A |
14 | PendSV | 可编程 | 为系统设备而设的“可悬挂请求”( pendable request) |
15 | SysTick | 可编程 | 系统滴答定时器(也就是周期性溢出的时基定时器——译注) |
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:16 | VECTKEY | RW | - | 访问钥匙:任何对该寄存器的写操作,都必 须同时把 0x05FA 写入此段,否则写操作被 忽略。若读取此半字,则 0xFA05 |
15 | ENDIANESS | R | - | 指示端设置。 1=大端(BE8), 0=小端。此 值是在复位时确定的,不能更改。 |
10:8 | PRIGROUP | R/W | 0 | 优先级分组 |
2 | SYSRESETREQ | W | - | 请求芯片控制逻辑产生一次复位 |
1 | VECTCLRACTIVE | W | - | 清零所有异常的活动状态信息。通常只在调 试时用,或者在 OS 从错误中恢复时用。 |
0 | VECTRESET | W | - | 复位 CM3 处理器内核(调试逻辑除外),但 是此复位不影响芯片上在内核以外的电路 |
向量表偏移量寄存器(VTOR)
( 0xE000_ED08 处)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
7-28 | TBLOFF | RW | 0 | 向量表的起始地址 |
29 | TBLBASE | R | - | 向量表是在 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 的:
- 使能控制
- 悬起状态和大多数系统异常的活动状态
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
18 | USGFAULTENA | R/W | 0 | 用法 fault 服务例程使能位 |
17 | BUSFAULTENA | R/W | 0 | 总线 fault 服务例程使能位 |
16 | MEMFAULTENA | R/W | 0 | 存储器管理 fault 服务例程使能位 |
15 | SVCALLPENDED | R/W | 0 | SVC 悬起中。本来已经要 SVC 服务例程,但 是却被更高优先级异常取代 |
14 | BUSFAULTPENDED | R/W | 0 | 总线 fault 悬起中,细节同上。 |
13 | MEMFAULTPENDED | R/W | 0 | 存储器管理 fault 悬起中,细节同上 |
12 | USGFAULTPENDED | R/W | 0 | 用法 fault 悬起中,细节同上 |
11 | SYSTICKACT | R/W | 0 | SysTick 异常活动中 |
10 | PENDSVACT | R/W | 0 | PendSV 异常活动中 |
9 | - | - | - | - |
8 | MONITORACT | R/W | 0 | Monitor 异常活动中 |
7 | SVCALLACT | R/W | 0 | SVC 异常活动中 |
6:4 | - | - | - | - |
3 | USGFAULTACT | R/W | 0 | 用法 fault 异常活动中 |
2 | - | - | - | - |
1 | BUSFAULTACT | R/W | 0 | 总线 fault 异常活动中 |
0 | MEMFAULTACT | R/W | 0 | 存储器管理 fault 异常活动中 |
- 非常不建议修改这些寄存器,
- 设置或者清零这些位,会改变处理器中对异常活动的记录,却不会对应地修复堆栈中的数据(不会为了此改动而特意执行一次自动入栈或自动出栈操作),于是埋下了破坏堆栈内容而引起程序跑飞的隐患;
- 另外,其它一些重要的数据结构也得不到清除,后患无穷。
fault状态与地址寄存器
总线 fault 状态寄存器”(BFSR) :地址: 0xE000_ED29
- 通过它,可以确定产生 fault 的场合 :是在数据访问时,在取指时,还是在中断的堆栈操作时
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
7 | BFARVALID | - | 0 | =1 时表示 BFAR 有效 |
6:5 | - | - | - | - |
4 | STKERR | R/Wc | 0 | 入栈时发生错误 |
3 | UNSTKERR | R/Wc | 0 | 出栈时发生错误 |
2 | IMPRECISERR | R/Wc | 0 | 不精确的数据访问违例(violation) |
1 | PRECISERR | R/Wc | 0 | 精确的数据访问违例 |
0 | IBUSERR | R/Wc | 0 | 取指时的访问违例 |
“总线 fault 地址寄存器(BFAR)” :
- 对于精确的总线fault,还可以找到肇事指令的地址。
- 精确的总线fault:被最后一个完成的指令触发,例如读存储器。
- 不精确的总线fault:导致fault的指令早已完成。例如缓冲区写入,写入的内容有误,但实际内容写入时,写入指令早已完成。
“存储器管理 fault 状态寄存器(MFSR)”:地址: 0xE000_ED28
-
指出导致 MemManage fault 的原因。
位段 名称 类型 复位值 描述 7 MMARVALID - 0 =1 时表示 MMAR 有效 6:5 - - - - 4 MSTKERR R/Wc 0 入栈时发生错误 3 MUNSTKERR R/Wc 0 出栈时发生错误 2 - - - - 1 DACCVIOL R/Wc 0 数据访问违例 0 IACCVIOL R/Wc 0 取指访问违例
“存储器管理地址寄存器(MMAR)”:
- 如果是因为数据访问违例(DACCVIOL 位)或取指访问违例(IACCVIOL 位)触发,
- 且还MMARVALID位被置位,
“用法 fault 状态寄存器(UFSR)” 地址: 0xE000_ED2A
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
9 | DIVBYZERO | R/Wc | 0 | 表示除法运算时除数为零(只有在 DIV_0_TRP 置位时才会发生) |
8 | UNALIGNED | R/Wc | 0 | 未对齐访问导致的 fault |
7:4 | - | - | - | - |
3 | NOCP | R/Wc | 0 | 试图执行协处理器相关指令 |
2 | INVPC | R/Wc | 0 | 在异常返回时试图非法地加载 EXC_RETURN 到 PC。包括非法的指令,非法的上下文以及 非法的 EXC_RETURN 值。 The return PC 指 向的指令试图设置 PC 的值(要理解此位的含 义,还需学习后面的讨论中断级异常的章节) |
1 | INVSTATE | R/Wc | 0 | 试图切入 ARM 状态 |
0 | UNDEFINSTR | R/Wc | 0 | 执行的指令其编码是未定义的——解码不能 |
硬 fault 状态寄存器(HFSR) ,指出产生硬 fault 的原因
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
31 | DEBUGEVT | R/Wc | 0 | 硬 fault 因调试事件而产生 |
30 | FORCED | R/Wc | 0 | 硬 fault 是被上访的。上访者可以是总线 fault、存储器管理 fault 或是用法 fault |
29:2 | - | - | - | - |
1 | VECTBL | R/Wc | 0 | 硬 fault 是在取向量时发生的 |
0 | - | - | - | - |
中断控制及状态寄存器ICSR
(地址: 0xE000_ED04)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
31 | NMIPENDSET | R/W | 0 | 写 1 以悬起 NMI。因为 NMI 的优先级最高且从不 掩蔽,在置位此位后将立即进入 NMI 服务例程。 |
28 | PENDSVSET | R/W | 0 | 写 1 以悬起 PendSV。读取它则返回 PendSV 的 状态 |
27 | PENDSVCLR | W | 0 | 写 1 以清除 PendSV 悬起状态 |
26 | PENDSTSET | R/W | 0 | 写 1 以悬起 SysTick。读取它则返回 PendSV 的 状态 |
25 | PENDSTCLR | W | 0 | 写 1 以清除 SysTick 悬起状态 |
23 | ISRPREEMPT | R | 0 | =1 时,则表示一个悬起的中断将在下一步时进 入活动状态(用于单步执行时的调试目的) |
22 | ISRPENDING | R | 0 | 1=当前正有外部中断被悬起(不包括 NMI) |
21:12 | VECTPENDING | R | 0 | 悬起的 ISR 的编号。如果不止一个中断悬起, 则它的值是这次中断中,优先级最高的那一个。 |
11 | RETTOBASE | R | 0 | 如果异常返回后将回到基级(base level), 并 且没有其它异常悬起时,此位为 1。若是在线程 模式下,在某个服务例程中,有不止一级的异常 处于活动状态,或者在异常没有活动时执行了异 常服务例程(此时执行返回指令将产生 fault。 此乃高危行为,大虾也需慎用),则此位为 0 |
9:0 | VECTACTIVE | R | 0 | 当前活动的ISR编号,该位段指出当前运行中的 ISR是哪个中断的(提供异常序号),包括NMI和 硬fault。 如果多个异常共享一个服务例程,该例程可 根据本位段的值来判定是哪一个异常的响应导致它的执 行。把本位段的值减去16,就得到了外中断的编号,并 可以用此编号来操作外中断相关的使能/除能等寄存器。 |
中断控制器类型寄存器ICTR
(地址: 0xE000_E004)
- 可以获取当前的芯片支持的中断数目,
- 但只能得到32的倍数的结果,比较粗糙。
位 段 | 名称 | 类 型 | 复 位 值 | 描述 |
---|---|---|---|---|
4:0 | INTLINESUM | R | - | 中断输入的数量,以 32 为粒度,如 0=1 至 32 1=33 至 64 2=65 至 96 … |
软件触发中断寄存器STIR
(地址: 0xE000_EF00)
位 段 | 名称 | 类 型 | 复 位 值 | 描述 |
---|---|---|---|---|
8:0 | INTID | W | - | 影响编号为 INTID 的外部中断,其悬起位 被置位。例如,写入 8,则悬起 IRQ #8 |
SysTick 定时器
24位倒计数定时器。
SysTick定时器被捆绑在NVIC中,用于产生SysTick异常(异常号: 15)。
- 当处理器在调试期间被喊停(halt)时,则SysTick定时器亦将暂停运作。
- 一般定时器则不会。
时钟源:
- 自由运行时钟” FCLK:系统时钟停止时 FCLK 也继续运行
- 外部的参考时钟:
- 外部时钟通过 FCLK 来采样,其周期必须至少是FCLK 的两倍(采样定理)
寄存器
SysTick控制及状态寄存器(地址: 0xE000_E010)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
16 | COUNTFLAG | R | 0 | 如果在上次读取本寄存器后, SysTick 已经计到 了 0,则该位为 1。如果读取该位,该位将自动清 零 |
2 | CLKSOURCE | R/W | 0 | 0=外部时钟源(STCLK) 1=内核时钟(FCLK) |
1 | TICKINT | R/W | 0 | 1=SysTick倒数计数到0时产生SysTick异常请 求 0=数到 0 时无动作 |
0 | ENABLE | R/W | 0 | SysTick 定时器的使能位 |
SysTick重装载数值寄存器(地址: 0xE000_E014)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | RELOAD | R/W | 0 | 当倒数计数至零时,将被重装载的值 |
SysTick当前数值寄存器(地址: 0xE000_E018)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
23:0 | CURRENT | R/Wc | 0 | 读取时返回当前倒计数的值,写它则使之清零, 同时还会清除在SysTick控制及状态寄存器中的 COUNTFLAG 标志 |
SysTick校准数值寄存器(地址: 0xE000_E01C)
位段 | 名称 | 类型 | 复位值 | 描述 |
---|---|---|---|---|
31 | NOREF | R | - | 1=没有外部参考时钟(STCLK 不可用) 0=外部参考时钟可用 |
30 | SKEW | R | - | 1=校准值不是准确的 10ms 0=校准值是准确的 10ms |
23:0 | TENMS | R/W | 0 | 在 10ms 的间隔中倒计数的格数。芯片设计者应 该通过 Cortex-M3 的输入信号提供该数值。若该 值读回零,则表示无法使用校准功能 |
存储器保护单元(MPU)
MPU在执行其功能时,以“region”为单位。
- region是一段连续的地址,只是它们的位置和范围都要满足限制(对齐方式,最小容量等)。
- 允许把每个region进一步划分成更小的“子region”
- regions可以相互交迭。如果某块内存落在多个region中,则访问属性和权限将由编号最大的region来决定。