4、中断控制器寄存器详解

好,咱们今天来聊聊中断控制器的核心——那几个关键的寄存器。说实话,我早年做嵌入式的时候,觉得中断就是配个GPIO电平触发就完事了。直到有一次,一个中断没清干净,系统反复进中断,CPU占用率直接飙到100%。从那以后,我对这几个寄存器就格外上心。

中断控制器里,寄存器种类不少。但真正决定中断行为的,说白了就四类:状态寄存器使能寄存器优先级寄存器触发方式寄存器。咱们一个一个拆开讲。

4.1 中断状态寄存器

这个寄存器,我习惯叫它「中断的晴雨表」。它告诉你:哪个中断源正在请求服务

每个bit对应一个中断源。bit为1,表示该中断正在等待处理。你读它的时候,得到的是当前所有挂起的中断请求。嗯,这里有个坑——读状态寄存器不会清除中断。清除中断通常需要写另一个寄存器(比如中断清除寄存器),或者操作外设本身。

关键点:中断状态寄存器是只读的。你只能读它,不能写它。写它一般没效果,或者会触发未定义行为。

我在项目中遇到过一个问题:某个外设中断来了,状态寄存器对应bit置1。但ISR里忘了清中断,结果这个bit一直为1。CPU以为中断没处理完,反复进ISR。系统直接卡死。所以,进ISR第一件事,读状态寄存器确认中断源,第二件事,清中断。顺序别搞反。

4.2 中断使能寄存器

这个寄存器,说白了就是「开关」。你把它对应的bit写1,那个中断源就被激活了。写0,就屏蔽掉。

你想想看,系统里那么多外设,每个都可能产生中断。但有些中断你暂时不想处理,比如调试阶段。这时候,把使能寄存器对应bit清0,那个中断就进不来了。干净利落。

我个人习惯,在系统初始化阶段,先把所有中断使能位清0。然后一个一个按需打开。这样做的好处是:避免未初始化的外设产生意外中断。我曾经见过一个同事,初始化时没关中断,结果某个外设的默认状态直接触发了中断,ISR里又没处理,系统直接跑飞。

小技巧:使能寄存器通常支持读-改-写操作。不要直接写整个寄存器,用位操作(比如 |= 和 &=)来单独控制某个bit。这样可以避免影响其他中断的设置。

4.3 中断优先级寄存器

这个寄存器,决定了中断的「谁先谁后」。多个中断同时来的时候,优先级高的先处理。

优先级寄存器通常是一个数值。数值越小,优先级越高(或者反过来,看芯片手册)。我见过有些芯片支持256级优先级,有些只支持8级。嗯,别贪多,够用就行。

这里有个常见的误区:优先级不是越高越好。你把所有中断都设成最高优先级,那跟没设优先级有什么区别?正确的做法是:时间敏感的中断(比如定时器)设高优先级,非关键的中断(比如按键)设低优先级

我记得有一次做电机控制项目,电流采样中断优先级设得太低,结果被串口中断抢了。电机控制周期乱了,电机直接抖起来。后来我把电流采样中断优先级提到最高,问题解决。所以,关键任务的中断优先级,一定要仔细斟酌

中断类型 建议优先级 说明
定时器(高精度) 最高(0或1) 时间敏感,不能延迟
DMA传输完成 高(2或3) 数据搬运完成,需及时处理
外部中断(按键) 中(4或5) 可以容忍少量延迟
串口接收 低(6或7) 有缓冲区,不怕等

4.4 中断触发方式寄存器

这个寄存器,决定了中断怎么「跳出来」。是电平触发,还是边沿触发?是高电平有效,还是低电平有效?

触发方式寄存器里,每个中断源通常对应两个bit:一个选电平/边沿,一个选极性(高/低或上升/下降)。

  • 电平触发:只要电平保持有效状态,中断就一直请求。适合共享中断线的情况。
  • 边沿触发:只在电平变化的那一刻产生中断。适合按键、脉冲信号等。

我个人偏好边沿触发。为什么?因为电平触发有个麻烦:如果ISR处理慢了,外设的电平还没变,中断会再次触发。你想想看,ISR还没跑完,又来一个中断,嵌套处理搞不好就出问题。边沿触发就没这个烦恼,它只认跳变,不认电平。

注意:边沿触发有个缺点——容易丢中断。如果两次跳变间隔太短,硬件可能只捕获到一次。所以,对高速信号,建议用电平触发,配合硬件去抖。

我曾经在一个项目中,用边沿触发检测编码器的脉冲。编码器转速一高,中断就丢。后来换成电平触发,配合定时器采样,才把数据补全。嗯,触发方式的选择,要看信号特性

4.5 寄存器配置实战

光说不练假把式。咱们看一个实际的配置流程。假设芯片是ARM Cortex-M系列,中断控制器是NVIC。

// 1. 配置中断触发方式(外部中断,上升沿触发)
EXTI->RTSR |= (1 << 5);   // 第5号中断,上升沿触发
EXTI->FTSR &= ~(1 << 5); // 下降沿触发关掉

// 2. 使能中断
EXTI->IMR |= (1 << 5);   // 打开第5号中断的使能

// 3. 配置优先级(NVIC层面)
NVIC_SetPriority(EXTI5_IRQn, 2); // 优先级设为2(数值越小优先级越高)

// 4. 使能NVIC中的中断
NVIC_EnableIRQ(EXTI5_IRQn);

// 5. ISR中处理
void EXTI5_IRQHandler(void) {
    if (EXTI->PR & (1 << 5)) {  // 读状态寄存器,确认中断源
        // 处理中断...
        EXTI->PR = (1 << 5);    // 写状态寄存器,清除中断标志
    }
}

你看,流程很清晰:先配触发方式,再开使能,再设优先级,最后在ISR里读状态、清标志。顺序别乱,乱了容易出问题。

我的习惯:在ISR里,先读状态寄存器确认中断源,再处理业务逻辑,最后清中断标志。清中断一定要放在最后,否则可能出现「清早了,中断又来了,但业务还没处理完」的情况。

4.6 常见问题与避坑

  • 中断状态寄存器读出来全是0? 检查使能寄存器开了没。没开使能,中断根本进不来。
  • 中断触发了一次,但ISR进了两次? 大概率是电平触发,且ISR里没清中断。或者清中断的位置不对。
  • 优先级设了但没效果? 检查芯片是否支持抢占优先级和子优先级。有些芯片需要额外配置分组。
  • 中断触发方式改了,但行为没变? 记得改完后重新初始化外设。有些外设的触发方式寄存器需要外设复位后才生效。

我曾经遇到一个最诡异的问题:中断状态寄存器读出来,某个bit一直为1,但外设明明没产生中断。查了半天,发现是那个GPIO引脚被复用成了其他功能,电平状态一直有效。所以,配置中断前,先确认引脚功能是否正确

好了,中断控制器的这几个寄存器,咱们就聊到这儿。记住:状态寄存器是「眼睛」,使能寄存器是「开关」,优先级寄存器是「秩序」,触发方式寄存器是「性格」。把这四个管好了,中断系统就稳了。