异常与中断
异常VS中断
中断(Interrupts):
- 来源**:中断信号来自 Cortex-M3 内核的外部。它们可以由各种片上外设(如定时器、串口等)或外部设备(如按钮、传感器等)产生。**
- 性质**:**中断是“异步”的,因为它们的发生不依赖于 Cortex-M3 内核的当前操作,它们可以在任何时候发生,无论内核正在做什么。
- 数量**:Cortex-M3 支持多达 240 个外部中断,这些中断被认为是意外的、不可预测的事件。**
异常(Exceptions):
- 来源**:异常是由 Cortex-M3 内核自身的活动产生的。它们通常是在执行指令或访问存储器时发生的。**
- 性质**:****异常是“同步”的,因为它们的发生与 Cortex-M3 内核的当前操作直接相关。**例如,执行一条非法指令会立即触发一个异常。
- 类型**:常见的异常包括硬错误(Hard Fault)、内存管理错误(MemManage Fault)、总线错误(Bus Fault)、用法错误(Usage Fault)等。**
核心的区别是:异常是内核某些活动产生的错误,不依赖于外部,是具有同步性质;而中断需要依赖外部产生某个事情,例如定时器溢出、串口收到数据等,它是不确定的,和你代码执行顺序无关(异步的)。
异常
Cortex-M支持大量异常,包括 16-4-1=11 个系统异常(M0要少一点,具体可以看启动文件)。
这里的 16-4-1=11 个是什么意思?
16 个异常向量 4 个是保留的,不用于实际异常处理 1 个用于外部中断的向量
本来是5个保留的,这里留一个作为异常记录到系统异常里面
异常汇总表
编号 | 类型 | 优先级 | 简介 |
---|---|---|---|
0 | N/A | N/A | 没有异常在运行 |
1 | 复位 | -3(最高) | 复位 |
2 | NMI | -2 | 不可屏蔽中断(来自外部 NMI 输入脚) |
3 | 硬(hard) fault | -1 | 所有被除能的 fault, 都将“上访”成硬 fault。除能的原因包括当前被禁用,或者被 PRIMASK 或 BASPRI 掩蔽。 |
4 | MemManagefault | 可编程 | 存储器管理 fault, MPU 访问犯规以及访问非法位置均可引发。企图在“非执行区”取指也会引发此 fault |
5 | 总线 fault | 可编程 | 从总线系统收到了错误响应,原因可以是预取流产( Abort)或数据流产,或者企图访问协处理器 |
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 | 可编程 | 系统滴答定时器(也就是周期性溢出的时基定时器) |
有一定数量的系统异常是用于 fault 处理的,它们可以由多种错误条件引发。 NVIC 还提供了一些 fault 状态寄存器,以便于 fault 服务例程找出导致异常的具体原因。
启动汇编文件截图--来自STM32F103RCT6
额外还有最多 240 个外部中断——简称 IRQ,具体使用了这 240 个中断源中的多少个,则由芯片制造商决定;
不可屏蔽中断
NMI 究竟被拿去做什么?
NMI是不可屏蔽中断,顾名思义,无法屏蔽它;NMI 可以在任何时间被激活,甚至是在处理器刚刚复位之后。** **
具体还要视处理器的设计而定
-
** 硬件故障检测**
- 电源故障:在电源即将失效时触发 NMI,使系统有机会执行紧急保存操作,例如保存关键数据到非易失性存储器。
- 看门狗计时器:如果系统进入死循环或长时间无响应,看门狗计时器会触发 NMI,以重启系统或执行故障处理。
-
安全相关事件
- 安全攻击检测:当检测到安全攻击或系统被非法篡改时,触发 NMI 以执行安全措施或重启系统。
- 内存保护错误:在内存保护单元(MPU)检测到非法访问时,触发 NMI 以处理违规行为。
-
紧急系统维护
- 故障诊断:在某些高可靠性系统中,NMI 被用于触发故障诊断和系统健康检查
- 关键任务保护:在实时系统中,NMI 可以用于保证关键任务的执行,即使其他任务出现问题。
-
调试和开发
- 调试中断:在开发阶段,NMI 可用于调试目的,例如在检测到某些异常条件时触发 NMI,以便开发人员可以检查系统状态。
举例:
- 电源管理:在一些嵌入式系统中,电源管理模块可以检测到电源电压即将跌落到不可接受的水平。在这种情况下,电源管理模块会触发 NMI,使处理器有机会快速执行数据保存操作,防止数据丢失。
- 看门狗计时器:许多微控制器内置看门狗计时器,用于检测系统是否正常工作。如果看门狗计时器在预设时间内没有被重置,它会触发 NMI,导致系统重启或进入安全模式,以恢复正常操作。
- 安全攻击检测:在安全关键系统中,例如金融终端或安全通信设备,NMI 可用于响应未授权的内存访问或系统篡改,触发紧急处理程序,以保护系统免受攻击。
**存在意义: NMI 的不可屏蔽特性使其非常适合用于处理紧急情况、硬件故障和安全相关事件。由于其高优先级和不可屏蔽性,NMI 能够在最关键的时刻确保系统能够及时响应并执行必要的操作,保护系统的稳定性和数据完整性。 **
硬错误
硬错误如何产生的?
硬错误的产生有如下途径:
- 当某些类型的错误(例如内存管理错误MemManage fault、总线错误Bus fault、用法错误usage Fault)被禁用或无法处理时**,这些错误会被提升(escalation)为硬错误(Hard Fault)。这样做的目的是保证了即使特定错误被禁用,系统仍然能够对其进行处理。**
- **只要 FAULTMASK 寄存器没有被设置,硬错误(Hard Fault)的中断服务程序(ISR)就会被执行。这意味着,**即使其他错误被屏蔽,硬错误仍然会被处理。
如何强制关闭硬错误?
- PRIMASK 寄存器设置时,所有可屏蔽中断被屏蔽,若把它设置为1,则屏蔽除了NMI以外的所有中断和异常。
- BASEPRI 寄存器设置时,低于该优先级的中断被屏蔽,若设置为小于硬错误优先级的中断,硬错误也不会响应
。
中断
除了 SysTick的之外,全都连接到 NVIC 的中断输入信号线,这样做的具体意义
- 连接到 NVIC 的中断输入信号线:除了 SysTick 以外,其他所有系统异常和外部中断都通过 NVIC 进行管理。NVIC 为这些中断和异常提供向量表、优先级管理和嵌套支持。
- **SysTick 的独立性:**SysTick 定时器中断独立于 NVIC 管理。这意味着 SysTick 定时器有自己的控制机制,不需要通过 NVIC 的中断输入信号线进行中断管理。
启动汇编文件截图--来自STM32F103RCT6
向量表
** CM3 内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR 的入口地址, CM3 使用了“向量表查表机制”。这里使用一张向量表。**
向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常**,**该下标元素的值则是该 ESR 的入口地址。
向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 处必须包含一张向量表,用于初始时的异常分配。
异常类型 | 表 项 地 址偏移量 | 异常向量 |
---|---|---|
0 | 0x00 | MSP的初始值 |
1 | 0x04 | 复位 |
2 | 0x08 | NMI |
3 | 0x0C | 硬fault |
4 | 0x10 | MemManage fault |
5 | 0x14 | 总线fault |
6 | 0x18 | 用法fault |
7-10 | 0x1c-0x28 | 保留 |
11 | 0x2c | SVC |
12 | 0x30 | 调试监视器 |
13 | 0x34 | 保留 |
14 | 0x38 | PendSV |
15 | 0x3c | SysTick |
16 | 0x40 | IRQ #0 |
17 | 0x44 | IRQ #1 |
18-255 | 0x48-0x3FF | IRQ #2 - #239 |
举个例子,如果发生了异常 11(SVC),则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入,默认情况下需要自己去实现里面的函数。
- 定义了中断向量表,里面的位置SVC在地11位
- 默认使用WEAK定义了处理函数,即默认使用这个函数进行处理(在外部没有定义的情况下),这个函数里面是个死循环(B.)。
- 在HAL库下面额外定义处理函数,可以自己往里面添加内容
要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值(它是stm32启动过程中执行的第一句话,详细见STM32启动代码)。
优先级
- 优先级的数值越小,则优先级越高。
- CM内核具有中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。
- 三个系统异常:复位, NMI 以及硬 fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。
- 除3描述的三个系统异常外,所有其它异常的优先级则都是可编程的,但不能被编程为负数。
- 不管使用多少位来表达优先级,都是以 MSB(数据高位存在高地址) 对齐的。
MSB对齐:在MSB对齐中,****数据的最高有效位(MSB)位于数据结构或字节的最高地址处。这意味着在存储器或通信中,最重要的位被放置在数据结构的开始或字节的最高位,便于处理器或接收方直接访问。
LSB对齐:相反地,****LSB对齐意味着数据的最低有效位(LSB)位于数据结构或字节的最高地址处。这种对齐方式并不常见,但在某些特定的应用场景中可能会出现。
为什么需要使用MSB对齐?
假设使用了4位作为中断优先级,优先级为8:
使用MSB的情况--那么数据就为1000, 移植到只有3位的情况下,变成了100,虽然降低了精度,但是重要位置得以保留。
** 使用LSB的情况--如果程序在原始的4位优先级器件上采用LSB对齐方式存储优先级,最低有效位(LSB)位于存储单元的最高位置。在移植到只支持3位优先级的器件时,由于只会保存最低的3位,会导致MSB丢失。**
110页
中断输入及悬起行为
NVIC
Cortex-M在内核水平上搭载了一颗中断控制器——嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller)。
NVIC重要特征
NVIC功能如下:
- 可嵌套中断支持
- 向量中断支持
- 动态优先级调整支持
- 中断延迟大大缩短
- 中断可屏蔽
可嵌套中断支持
中断都可以被赋予不同的优先级。当前优先级被存储在 xPSR 的专用字段中。当一个异常发生时,硬件会自动比较该异常的优先级是否比当前的异常优先级更高。如果发现来了更高优先级的异常,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常——即立即抢占。
向量中断支持
当开始响应一个中断后, CM3 会自动定位一张向量表,并且根据中断号从表中找出 ISR 的入口地址,然后跳转过去执行。
ARM是由软件来分辨到底是哪个中断发生了,也无需半导体厂商提供私有的中断控制器来完成这种工作。这么一来,中断延迟时间大为缩短。
动态优先级调整支持
软件可以在运行时期更改中断的优先级。如果在某ISR中修改了自己所对应中断的优先级,而且这个中断又有新的实例处于悬起中(pending),也不会自己打断自己,从而没有重入(reentry)风险。
举例说明: 假设系统中有一个处理外部信号的中断,中断优先级为3(数字越小优先级越高)。在执行这个中断的ISR时,软件将该中断的优先级改为5。此时,如果有另一个相同的中断触发,由于其优先级降低,不会立即打断当前正在执行的ISR,而是等待当前ISR执行完毕。这就避免了同一个中断服务程序的重入问题。
中断重入带来的风险
- 数据不一致:ISR通常会访问和修改共享资源,如果ISR被重入,可能会导致数据不一致。例如,一个ISR在修改全局变量时,被另一个相同的ISR打断,这可能导致数据未能正确更新。
- 栈溢出:每次中断都会使用一定的栈空间,如果ISR频繁重入,可能会导致栈溢出,进而引发系统崩溃。
- 复杂性增加:重入增加了程序设计和调试的复杂性。程序员需要考虑更多的同步和互斥问题,确保ISR在任何情况下都能正确执行。
重入风险举例
- 初次中断:中断A开始执行,读取传感器数据并准备存储到全局数组。
- 重入中断:在第一次中断的ISR还未完成时,第二次中断A触发并打断当前的ISR,重新开始读取和存储数据。
- 数据覆盖:由于两个ISR实例同时操作全局数组,可能导致数据覆盖,最终存储的数据不正确。
- 数据不一致:系统中的全局变量可能处于不一致状态,影响后续处理。
解决方案
- 禁用中断嵌套:在ISR开始时禁用同类中断,结束时重新启用,以防止重入。
- 调整中断优先级:如之前所述,在ISR中修改中断优先级,降低同类中断的优先级,使其无法打断当前ISR。
- 使用互斥机制:在访问共享资源时使用互斥锁或信号量,确保资源访问的原子性。
目的**:**利用动态优先级调整支持这一特征可以很好的避免中断重入问题。
中断延迟大大缩短
Cortex-M3 为了缩短中断延迟,引入了好几个新特性。包括自动的现场保护和恢复,以及其它的措施,用于缩短中断嵌套时的 ISR 间延迟。详情请见后面关于“咬尾中断”和“晚到中断”的讲述。
中断可屏蔽
既可以屏蔽优先级低于某个阈值的中断/异常(设置BASEPRI寄存器),也可以全体封杀(设置PRIMASK和FAULTMASK寄存器)。这是为了让时间关键( time-critical)的任务能在死线(deadline,或曰最后期限)到来前完成,而不被干扰。
-
可以设置屏蔽优先级低于某个阈值的中断/异常
- BASEPRI寄存器:这个寄存器用于设置一个阈值,低于该阈值的中断或异常将被屏蔽(即不被处理)。
- 作用:通过设置BASEPRI寄存器,可以屏蔽那些不太重要的中断和异常,从而确保高优先级的任务能够顺利执行,不被低优先级的中断打扰。
-
可以全部屏蔽
- PRIMASK寄存器:这个寄存器用于屏蔽所有可屏蔽的异常,但不包括不可屏蔽的中断(如NMI)和硬错误。
-
**用途: PRIMASK 通常用于临界区的保护,在临界区期间防止中断干扰。 **
-
FAULTMASK寄存器**:这个寄存器用于屏蔽所有异常,除了不可屏蔽的中断(如NMI)。**
-
** 用途:FAULTMASK 的设置通常用于处理非常严重的错误情景,确保系统不受任何中断或异常的干扰。 **
-
作用:通过设置PRIMASK或FAULTMASK寄存器,可以实现对所有中断和异常的屏蔽,确保当前任务不受任何干扰,适用于需要绝对优先权的关键任务。
-
确保时间关键任务的完成
- 时间关键任务(time-critical tasks):这些任务对时间要求非常严格,必须在特定的时间段内完成,超过这个时间段可能会导致系统故障或性能下降(如电机控制)。
- 死线(deadline):这是任务必须完成的最后时间点。如果在死线前任务未完成,可能会导致系统错误或未达到预期性能。
-
中断可屏蔽存在的意义:确保时间关键任务的完成,尤其是对时间非常敏感的应用。
举例说明
- 假设一个嵌入式系统正在执行一个需要在10毫秒内完成的关键任务。这时,系统可能会发生多个中断,如果这些中断都被处理,可能导致关键任务无法在10毫秒内完成。
- 使用BASEPRI寄存器:将BASEPRI寄存器设置为一个阈值,使得低优先级的中断被屏蔽。这样,只有高优先级的重要中断会被处理,减少对关键任务的干扰。
- 使用PRIMASK寄存器:在关键任务开始前设置PRIMASK寄存器,屏蔽所有可屏蔽的中断,确保关键任务不被打断。
- 使用FAULTMASK寄存器:在极端情况下,使用FAULTMASK寄存器,屏蔽所有中断和异常,确保关键任务绝对不会被打断。
通过这些方法,可以确保关键任务能够在规定时间内完成,满足系统的实时性要求。