深入理解用于中断控制的 NVIC 寄存器
NVIC 中有多个用于中断控制的寄存器(异常类型 16~255),这些寄存器位于系统控制空间(SCS)地址区域。下表是这些寄存器的概览:
除了软件触发寄存器(STIR)外,所有这些寄存器都只能在特权级访问。STIR 默认只能在特权等级访问,不过可以配置为非特权等级访问。
复位后的默认设置如下:
-
所有中断被禁止(使能位=0)
-
所有中断的优先级为 0 (最高可编程优先级)
-
所有中断的挂起状态清零
中断使能寄存器
中断使能寄存器可由两个地址进行配置。要设置使能位,需要写入 NVIC->ISER[n] 寄存器地址。要清除使能位,需要写入 NVIC->ICER[n] 寄存器地址。这样,使能或禁止一个中断时就不会影响其他的中断使能状态,ISER/ICER 寄存器都是 32 位宽,每个位代表一个中断输入。
由于 Cortex-M3 或 Cortex-M4 处理器可能存在 32 个以上的外部中断,因此 ISER 和 ICER 寄存器也不止一个。如 NVIC->ISER[0] 和 NVIC->ISER[1] 等,如下所示:
只有存在的中断其对应的使能位才会被实现,因此,若只有 32 个中断输入,则寄存器只会有 ISER 和 ICER。尽管 CMSIS-Core 头文件将 ISER 和 ICER 定义成了字(32位),这些寄存器可以按照字、半字或字节的方式访问。其中由于前 16 个异常类型为系统异常,外部中断 #0 的异常编号为 16,具体如下表所示:
CMSIS-Core 提供了下面用于访问中断使能寄存器的函数:
void NVIC_EnableIRQ (IRQn_Type IRQn); // 使能一个中断
void NVIC_DisableIRQ (IRQn_Type IRQn); // 禁用一个中断
中断挂起状态的设置与清除
若中断产生但没有立即执行(例如当前正在执行另一个更高优先级的中断处理),它就会被挂起。
中断挂起状态可以通过中断挂起设置寄存器(NVIC->ISPR[n])和中断挂起清除寄存器(NVIC->ICPR[n])进行访问。与使能寄存器类似,若存在 32 个以上的外部中断输入,则挂起状态控制寄存器可能不止一个。
挂起状态寄存器的数值可由软件修改,因此可以通过 NVIC->ICPR[n] 取消一个当前被挂起的异常,或通过 NVIC->ISPR[n] 来触发软件中断,如下表所示:
CMSIS-Core 提供了下面用于访问中断挂起寄存器的函数:
void NVIC_SetPendingIRQ(IRQn_Type IRQn); // 设置中断的挂起状态
void NVIC_ClearPendingIRQ(IRQn_Type IRQn); // 清除中断的挂起状态
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn); // 获取中断的挂起状态
活跃状态
每个外部中断都会有一个活跃状态位,当处理器开始执行中断处理时,该位会被置 1,而在执行中断返回时被清零。不过,在中断服务程序(ISR)执行期间,更高优先级的中断可能会产生且抢占。在此期间,尽管处理器在执行另一个中断处理,之前的中断仍被定义为活跃的。虽然 IPSR 表示正在执行的异常服务,它无法告诉你当有嵌套异常时某个异常是否为活跃的。中断活跃状态寄存器为 32 位宽,不过还能通过半字或字节访问。若外部中断的数量超过 32,则活跃状态寄存器会不止一个。外部中断的活跃状态寄存器为只读的,如下表所示:
CMSIS-Core 提供了下面用于访问中断活跃状态寄存器的函数:
uint32_t NVIC_GetActive(IRQn_Type IRQn); // 获取中断的活跃状态
优先级
每个中断都有对应的优先级寄存器,其最大宽度为 8 位,最小为 3 位。具体可以见下文:
深入理解 Cortex-M 处理器中异常与中断的优先级
正如上文中所描述的,每个寄存器可以根据优先级分组设置被进一步划分为分组优先级和子优先级。优先级寄存器可以通过字节、半字或字访问。优先级寄存器的数量取决于芯片中实际存在的外部中断数,如下表所示:
CMSIS-Core 提供了下面用于访问中断优先级寄存器的函数:
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority); // 设置中断或异常优先级
uint32_t NVIC_GetPriority(IRQn_Type IRQn); // 读取中断或异常优先级
若要确定 NVIC 中可用的优先级数量,可以使用微控制器供应商提供的 CMSIS-Core 头文件中的 _NVIC_PRIO_BITS 伪指令。另外,还可以将 0xFF 写入其中一个中断优先级寄存器,并将其读回后查看多少位为 1。若设备实现了 8 个优先级(3位),读回值则应该为 0xE0(二进制位 1110 0000)。
软件触发中断寄存器
除了 NVIC->ISPR[n] 寄存器外,还可以通过软件触发中断寄存器(NVIC->STIR)利用软件来触发中断,如下表所示:
例如,可以用以下 C 代码来触发 中断#3:
NVIC->STIR = 3;
其功能与下面利用 NVIC->ISPR[n] 的 CMSIS-Core 函数调用相同:
NVIC_SetPendingIRQ(Timer0_IRQn); // 此处假设 Timer0_IRQn 等于 3
// Timer0_IRQn 为定义在设备相关头文件中的枚举
与只能在特权等级访问的 NVIC->ISPR[n] 不同,要让非特权程序代码触发一次软件中断,可以设置配置寄存器(地址 0xE000ED14)中的第一位(USERSETMPEND)。默认情况下 USERSETMPEND 为清零状态,这就意味着只有特权代码才能使用 NVIC->STIR。
与 NVIC->ISPR[n] 类似,NVIC->STIR 无法触发 NMI 以及 SysTick 等系统异常。系统控制块(SCB)中的其他寄存器可用于系统异常的管理。
中断控制类型寄存器
最后,NVIC 在 0xE000E004 地址处还有一个中断控制器类型寄存器,它是一个只读寄存器,给出了 NVIC 支持的中断输入数量,单位为 32,如下表所示:
利用 CMSIS 的设备驱动库,可以使用 SCnSCB->ICTR 来访问这个只读寄存器(SCnSCB 表示不在 SCB 中的系统控制寄存器)。
不过中断控制器类型寄存器只能给出可用中断的大致数量,而在 PRIMASK 置位的情况下(禁止产生中断),通过下面的方法能得到可用中断的确切数量:写入中断使能或挂起寄存器等中断控制寄存器,读回后查看中断使能或挂起寄存器中的实际有效位数。