第2章 中断控制器配置:NVIC寄存器详解、中断使能与屏蔽、中断挂起与清除

中断控制器,说白了就是CPU的“秘书”。它帮你决定:哪个中断先处理?哪个中断现在不能打扰?哪个中断正在排队?在CMS32单片机里,这个秘书叫NVIC——嵌套向量中断控制器。

我刚开始用CMS32时,总觉得NVIC配置就是填几个寄存器,没什么技术含量。直到有一次,一个外设中断死活不响应,查了两天才发现是挂起位没清干净。嗯,从那以后我再也不敢小看NVIC了。

2.1 NVIC核心寄存器一览

CMS32的NVIC支持最多32个中断源,每个中断都有独立的控制位。你不需要记住所有寄存器地址,但必须理解这四类寄存器的作用:

寄存器组 功能 位宽 操作特点
ISER 中断使能设置 32位 写1使能,读回当前状态
ICER 中断清除/屏蔽 32位 写1屏蔽,读回使能状态
ISPR 中断挂起设置 32位 写1强制挂起,用于软件触发
ICPR 中断挂起清除 32位 写1清除挂起状态

关键点:这四个寄存器都是“写1有效,写0无效”。读回的值表示当前状态,不是写入的值。这个设计是为了避免读-改-写带来的竞态问题。

2.2 中断使能与屏蔽——别搞反了

使能和屏蔽,听起来像一回事,但在NVIC里完全是两码事。

  • 中断使能(ISER):允许这个中断源向CPU发请求。默认所有中断都是禁能的。
  • 中断屏蔽(ICER):临时关闭某个中断,但不改变它的使能状态。说白了就是“先闭嘴,但没开除你”。

我个人习惯这样用:系统初始化时统一配置ISER,把需要用到的中断都打开。运行时如果需要临时关掉某个中断,用ICER屏蔽。等条件满足了,再重新使能。

小技巧:如果你只是想在临界区临时关中断,别动ICER。直接用__disable_irq()全局关中断,退出时用__enable_irq()恢复。这样更安全,也不会丢中断标志。

代码示例:使能与屏蔽

/* 使能UART1中断(假设中断号17) */
NVIC->ISER[0] = (1UL << 17);

/* 屏蔽UART1中断(不清除使能位) */
NVIC->ICER[0] = (1UL << 17);

/* 重新使能UART1中断 */
NVIC->ISER[0] = (1UL << 17);

你看,使能和屏蔽用的是不同的寄存器。ICER只是把中断请求挡在门外,ISER里的使能位还在。这就是“屏蔽不改变使能状态”的含义。

2.3 中断挂起与清除——最容易踩的坑

挂起(Pending)是NVIC里一个非常巧妙的设计。当中断条件满足但CPU正在处理更高优先级的中断时,NVIC会把该中断标记为“挂起”。等CPU空闲了,再根据优先级决定是否响应。

为什么会这样?因为中断信号可能是脉冲式的。如果CPU没来得及响应,脉冲就消失了,那这个中断就丢了。挂起机制相当于一个“记忆位”,帮你把中断请求存下来。

挂起寄存器(ISPR)

写1可以软件触发一个中断。我在调试时经常用这招:先写ISPR模拟中断发生,看中断服务程序能不能正确执行。不用等外设信号,效率高很多。

清除挂起寄存器(ICPR)

写1清除挂起状态。注意:必须在中断服务程序退出前清除,否则会反复进入中断。

我曾经踩过的坑:有一次做ADC采集,中断服务程序里忘了清ICPR。结果每次ADC转换完成,中断标志确实清了,但NVIC的挂起位还在。CPU刚退出中断,又立刻进来,系统直接卡死。查了整整一个下午才发现是ICPR没写。

代码示例:挂起与清除

/* 软件触发中断17(调试用) */
NVIC->ISPR[0] = (1UL << 17);

/* 在中断服务程序中清除挂起 */
void UART1_IRQHandler(void)
{
    /* 先处理中断业务 */
    uint8_t data = UART1->DR;
    
    /* 清除NVIC挂起位 —— 这一步不能省 */
    NVIC->ICPR[0] = (1UL << 17);
    
    /* 清除外设中断标志 */
    UART1->ICR |= (1UL << 0);
}

2.4 中断优先级配置

CMS32支持4级可编程优先级(0~3),0最高,3最低。复位后默认都是0,如果不配置,所有中断优先级一样高。

优先级配置寄存器是IPR(Interrupt Priority Register),每个中断占用2位。配置方法很简单:

/* 设置中断17的优先级为2 */
NVIC->IPR[17] = 2;  /* 注意:IPR数组索引就是中断号 */

我的建议:优先级别全设成0。至少留一个低优先级中断做“兜底”。否则高优先级中断嵌套太多,栈空间容易爆。我一般把定时器中断设成0,串口设成1,ADC设成2,按键设成3。

2.5 NVIC配置流程图

下面这张图总结了NVIC配置的完整流程。你可以把它贴在工位旁边,每次配中断时对照着走一遍。

NVIC配置流程图 NVIC中断配置流程 开始配置中断 步骤1:配置IPR优先级 步骤2:ISER使能中断 运行时需屏蔽? 是 ICER屏蔽中断 否 进入中断服务程序 ICPR清除挂起位 注意:ICPR清除必须在中断退出前完成,否则会反复触发中断 ISER/ICER/ISPR/ICPR均为“写1有效,写0无效”

2.6 实战避坑清单

最后,我把自己这些年踩过的坑整理成了一份清单。你配置NVIC时,逐条核对一遍,能省不少调试时间。

  1. 中断号别搞错——CMS32的中断号从0开始,不是1。查数据手册时看清楚。
  2. ISER和ICER不是互斥的——使能了中断不代表不能屏蔽,屏蔽了也不代表使能位被清。
  3. ICPR一定要清——中断服务程序里,先清ICPR再清外设标志。顺序反了可能导致中断再触发。
  4. 优先级别全设0——至少留一个低优先级中断,防止嵌套过深导致栈溢出。
  5. 调试时善用ISPR——软件触发中断比等外设信号快得多,排查问题首选。

总结一句话:NVIC配置不难,但细节决定成败。ISER管“能不能进来”,ICER管“暂时别进来”,ISPR管“假装进来了”,ICPR管“已经处理完了”。把这四个寄存器搞明白,中断系统就稳了。