中断与异常
嵌套向量中断控制器,简称为NVIC,其支持为数众多的系统异常和外部中断。
Cortex-M3支持256个中断。
中断/异常类型表如下
编号为 1-15 的称为系统异常(注意:没有编号为 0 的异常),大于等于 16 的称为(外部)中断。
除了个别异常的优先级被定死外,其它中断/异常的优先级都是可编程的
外部中断表如下
优先级
-
在 CM3 中,优先级对于异常来说很关键的,它会决定一个异常是否能被掩蔽,以及在未掩蔽的情况下何时可以响应。优先级的数值越小,则优先级越高。
-
CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。
-
有3个系统异常的优先级是固定的,并且是负数,分别为:复位,NMI 以及硬 fault
-
所有其它异常的先级则都是可编程的((但不能被编程为负数)
CM3 支持 3 个固定的高优先级(复位,NMI 以及硬 fault)和多达 256 级的可编程优先级,并且支持 128 级抢占式优先级
256级对应一个字节的大小,8位二进制,也就是每一个可编程的中断/异常有一个至少一个字节大小的寄存器来配置其优先级。并且是以 MSB 对齐的。
CM3 允许的最少使用位数为 3 个位,亦即至少要支持 8 级优先级
如使用3个位来定义优先级,则只有最高3位有效。
优先级 | 优先级设置寄存器的值 |
---|---|
0 | 0000 0000 |
1 | 0010 0000 |
2 | 0100 0000 |
3 | 0110 0000 |
… | … |
7 | 1110 0000 |
优先级分组
为了使抢占机能变得更可控,CM3 还把 256 级优先级按位分成高低两段,分别称为抢占优先级和子优先级
通过应用程序中断及复位控制寄存器(AIRCR)地址:0xE000_ED00
来设置抢占式优先级与子优先级占用的位数。
PRIGROUP的取值
子优先级至少是 1 个位。因此抢占优先级最多是 7 个位,造成了最多只有 128 级抢占的现象。
如使用最高3位来表达优先级,优先级分组选择5,则抢占式优先级用2个位表达,剩下的一个位则是子优先级。
使用最高3位来表达优先级,优先级分组选择1,则抢占式优先级占用3位,子优先级无效
如果两个中断的抢占式优先级和子优先级都相同,则先响应异常编号最小的那一个,例如,当 IRQ #3 的优先级与 IRQ #5 的优先级相等时,IRQ #3 会比 IRQ #5 先得到响应。
向量表
当发生中断或者异常时,需要定位其服务例程的入口地址(中断复位函数的入口地址)这些入口地址存储在所谓的“向量表”中。
也就是说向量表存储的是一个个函数的地址。默认情况下,,CM3 认为该表位于零地址处,且各向量占用 4 字节。
CM3支持动态重分发中断,CM3 允许向量表重定位——从其它地址处开始定位各异常向量。
为了实现这个功能,NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址 0xE000_ED08 处),通过修改它的值就能重定位向量表。
中断的触发与悬起(挂起)
当中断发生时候,该中断就被悬起。即使后来中断源撤消了中断请求,
已经被标记成悬起的中断也被记录下来。到了系统中它的优先级最高的时候,就会得到响应
悬起被置位:告诉CPU,有一个中断触发了,当该中断优先级最高时候,CPU将会根据向量表找到中断服务函数的入口地址,从而处理中断,当某中断的服务例程开始执行时,就称此中断进入了“活跃”状态,并且其悬起位会被硬件自动清除。
但是如果在某个中断得到响应之前,其悬起状态被清除了,则中断被取消。
如果中断源咬住请求信号不放,该中断就会在其上次服务例程返回后再次被置为悬起状态
Fault 类异常
CM3 中的 Faults 可分为以下几类:
- 总线 faults
- 存储器管理 faults
- 用法 faults
每个faults都有其对应的faults状态寄存器,用来指示出错的原因与类型。
更多细节请看CM3权威指南。
NVIC
嵌套向量中断控制器(NVIC)
NVIC 的访问地址是 0xE000_E000。所有 NVIC 的中断控制/状态寄存器都只能在特权级下访问,仅软件触发中断寄存器可以在用户级下访问以产生软件中断。
每个外部中断都有在 NVIC 的下列寄存器
- 使能与除能(失能)寄存器
- 悬起与“解悬”寄存器
- 优先级寄存器
- 活动状态寄存器(中断是否正在被CPU响应)
使能与除能(失能)寄存器
- SETENA(使能)/CLRENA(失能) 可以按字/半字/字节的方式来访问
- 32 位寄存器,写1使能/失能,写0无效
- CM3 中可以有 240 对使能位/除能位(SETENA 位/CLRENA 位),每个中断拥有一对。这 240 个对子分布在 8 对 32 位寄存器中(最后一对没有用完)
悬起与解悬寄存器
如果中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。
中断的悬起状态可以通过“中断设置悬起寄存器(SETPEND)”和“中断悬起清除寄存器(CLRPEND)”来读取,还可以写它们来手工悬起中断。
- 其用法与前面介绍的使能/除能寄存器完全相同
中断优先级寄存器
- 每个外部中断都有一个对应的优先级寄存器,每个寄存器占用 8 位。
- 优先级寄存器都可以按字节访问,当然也可以按半字/字来访问
- 根据优先级组的设置,优先级可以被分为高低两个位段,分别是抢占优先级和亚优先级,
- 通过应用程序中断及复位控制寄存器(AIRCR)
地址:0xE000_ED00
来设置抢占式优先级与子优先级占用的位数。
系统异常的优先级设置
活动状态
每个外部中断都有一个活动状态位。在处理器执行了其 ISR(中断服务函数) 的第一条指令后,它的活动位就被置 1,并且直到 ISR 返回时才硬件清零。
哪怕中断被抢占,其活动状态也依然为 1。
- 按字/半字/字节访问,但是只读的
软件中断
软件触发中断请求
STM32的中断
以STM32F103C8T6为例
- 68个可屏蔽中断通道(不包含16个Cortex™-M3的中断线);
- 16个可编程的优先等级(使用了4位来表示中断优先级);
下面介绍HAL库的常用中断相关函数
其他的函数请查看stm32f1xx_hal_cortex.c
文件
中断优先级分组
/**
* @brief Sets the priority grouping field (preemption priority and subpriority)
* using the required unlock sequence.
* @param PriorityGroup: The priority grouping bits length.
* This parameter can be one of the following values:
* @arg NVIC_PRIORITYGROUP_0: 0 bits for preemption priority
* 4 bits for subpriority
* @arg NVIC_PRIORITYGROUP_1: 1 bits for preemption priority
* 3 bits for subpriority
* @arg NVIC_PRIORITYGROUP_2: 2 bits for preemption priority
* 2 bits for subpriority
* @arg NVIC_PRIORITYGROUP_3: 3 bits for preemption priority
* 1 bits for subpriority
* @arg NVIC_PRIORITYGROUP_4: 4 bits for preemption priority
* 0 bits for subpriority
* @note When the NVIC_PriorityGroup_0 is selected, IRQ preemption is no more possible.
* The pending IRQ priority will be managed only by the subpriority.
* @retval None
*/
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */
NVIC_SetPriorityGrouping(PriorityGroup);
}
设置中断的抢占式优先级与子优先级
/**
* @brief Sets the priority of an interrupt.
* @param IRQn: External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xx.h))
* @param PreemptPriority: The preemption priority for the IRQn channel.
* This parameter can be a value between 0 and 15
* A lower priority value indicates a higher priority
* @param SubPriority: the subpriority level for the IRQ channel.
* This parameter can be a value between 0 and 15
* A lower priority value indicates a higher priority.
* @retval None
*/
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
{
uint32_t prioritygroup = 0x00U;
/* Check the parameters */
assert_param(IS_NVIC_SUB_PRIORITY(SubPriority));
assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority));
prioritygroup = NVIC_GetPriorityGrouping();
NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));
}
使能中断
/**
* @brief Enables a device specific interrupt in the NVIC interrupt controller.
* @note To configure interrupts priority correctly, the NVIC_PriorityGroupConfig()
* function should be called before.
* @param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))
* @retval None
*/
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
/* Enable interrupt */
NVIC_EnableIRQ(IRQn);
}
失能中断
/**
* @brief Disables a device specific interrupt in the NVIC interrupt controller.
* @param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))
* @retval None
*/
void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
/* Disable interrupt */
NVIC_DisableIRQ(IRQn);
}
清除中断的挂起位
/**
* @brief Clears the pending bit of an external interrupt.
* @param IRQn External interrupt number.
* This parameter can be an enumerator of IRQn_Type enumeration
* (For the complete STM32 Devices IRQ Channels list, please refer to the appropriate CMSIS device file (stm32f10xxx.h))
* @retval None
*/
void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
/* Check the parameters */
assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
/* Clear pending interrupt */
NVIC_ClearPendingIRQ(IRQn);
}