4、中断管理基础:中断向量表、中断优先级、中断嵌套、中断延迟的组成

好,咱们今天聊聊中断管理。这玩意儿在ADAS系统里,可以说是命根子一样的存在。你想想看,一个紧急刹车信号来了,要是中断响应慢了半拍,那后果……嗯,我不说你也懂。

我个人习惯把中断管理拆成四个核心模块来讲:向量表、优先级、嵌套、还有延迟。咱们一个一个来。

4.1 中断向量表:系统的“紧急联络簿”

中断向量表是什么?说白了,就是一张表。这张表里存着每个中断号对应的处理函数地址。CPU一收到中断信号,就跑到这张表里查一下:“哦,这个中断号是第42号,对应的处理函数在地址0x0000A8C0,跳过去执行。”

我在项目中遇到过一个问题:有个同事把向量表放到了RAM里,结果上电时没初始化好,一进中断就飞了。查了两天才找到原因。所以我的建议是——向量表最好放在ROM里,或者至少确保上电后第一时间完成重映射

关键点:在ARM Cortex-R系列(ADAS常用)中,向量表基地址可以通过VTOR寄存器设置。默认在0x00000000,但你可以挪到别处。

// 以ARM Cortex-R5为例,重映射向量表
#define VECTOR_TABLE_ADDR  0x80000000U

void vector_table_init(void)
{
    // 1. 将向量表复制到目标地址
    memcpy((void*)VECTOR_TABLE_ADDR, (void*)__Vectors, __Vectors_Size);
    
    // 2. 设置VTOR寄存器
    __set_VTOR(VECTOR_TABLE_ADDR);
    
    // 3. 确保内存屏障
    __DSB();
    __ISB();
}

小技巧:调试阶段可以把向量表放在RAM里,方便动态修改。但量产时一定要放ROM,防止意外篡改。

4.2 中断优先级:谁更紧急谁先上

ADAS系统里中断多得很:CAN报文来了、雷达数据到了、摄像头帧同步信号来了、看门狗要喂了……这么多中断同时来怎么办?靠优先级。

每个中断都有一个优先级数值。数值越小,优先级越高(这是ARM的惯例,有些MCU反过来)。高优先级的中断可以打断低优先级的——这就是中断嵌套的基础。

中断源 优先级(0最高) 典型用途
NMI(不可屏蔽中断) 0 硬件故障、看门狗超时
刹车信号中断 1 紧急制动响应
摄像头帧同步 2 图像采集时序
CAN接收中断 3 报文接收
定时器中断 4 周期性任务调度

我曾经在一个项目里犯过傻:把所有中断优先级都设成一样。结果多个中断同时触发时,系统就卡住了。后来才意识到,优先级分组也很重要。ARM Cortex-M/R支持优先级分组,把优先级分成抢占优先级和子优先级两部分。

注意:中断优先级不是越多越好。一般8级(3位)或16级(4位)就够用了。优先级太多,反而容易搞混,而且会增加硬件判断的延迟。

4.3 中断嵌套:高优先级打断低优先级

中断嵌套,说白了就是“中断里套中断”。高优先级的中断来了,CPU正在处理低优先级的中断,那就先挂起低优先级的,去处理高优先级的。处理完再回来继续。

但这里有个坑——嵌套深度不能太深。我见过一个项目,嵌套了5层,结果栈空间爆了,系统直接复位。你想想看,每嵌套一层,就要压栈一次(保存现场),栈空间消耗很快。

我的经验是:嵌套深度控制在3层以内。如果超过3层,就要考虑是不是中断设计有问题了。比如,是不是有些中断处理时间太长?是不是可以把一些工作放到任务里做?

核心原则:中断服务函数(ISR)要短、平、快。只做最紧急的事,比如读数据、置标志位。复杂的处理放到任务里做。

// 好的ISR设计:只做必要的事
void CAN_IRQHandler(void)
{
    // 1. 读取接收到的数据
    can_msg_t msg = CAN->RX_DATA;
    
    // 2. 置标志位,通知任务处理
    g_can_msg_ready = 1;
    g_can_msg = msg;
    
    // 3. 清除中断标志
    CAN->ICR |= CAN_ICR_RXIF;
    
    // 绝不在这里做复杂运算!
}

4.4 中断延迟的组成:硬件延迟 + 软件延迟

中断延迟,就是从中断信号产生,到CPU开始执行ISR第一条指令的时间。这个时间很关键,尤其是在ADAS里。比如,刹车信号延迟1ms,可能就多跑了1米。

中断延迟由两部分组成:硬件延迟软件延迟

4.4.1 硬件延迟

硬件延迟是芯片本身决定的,你改不了太多。主要包括:

  • 中断信号传递时间:从外设到中断控制器,再到CPU核心。一般几十ns。
  • 中断优先级判断时间:多个中断同时来,硬件要比较优先级。这个时间跟优先级数量有关,一般几十到几百ns。
  • 当前指令完成时间:CPU必须等当前指令执行完才能响应中断。最坏情况是遇到多周期指令(比如除法、浮点运算),可能几个us。
  • 压栈时间:保存当前上下文(寄存器、PC、PSR等)。ARM Cortex-R系列一般12个周期左右。

硬件延迟加起来,一般在几百ns到几个us之间。嗯,这个你控制不了,但心里要有数。

4.4.2 软件延迟

软件延迟才是我们能优化的重点。包括:

  • 中断屏蔽时间:你在代码里关了全局中断(比如调了__disable_irq()),这段时间里所有中断都进不来。这是最大的延迟来源。
  • 高优先级ISR执行时间:如果高优先级中断正在执行,低优先级的就得等着。
  • 中断向量表查找时间:从向量表里找到对应的ISR地址。如果向量表在慢速存储器里,这个时间会变长。

避坑指南:我曾经在一个项目里,为了做临界区保护,把全局中断关了200us。结果CAN报文全丢了。后来改用“关特定中断”的方式,只关自己关心的那个中断,延迟降到5us以内。

// 不好的做法:关全局中断
void critical_section_enter(void)
{
    __disable_irq();  // 所有中断都被屏蔽!
}

// 好的做法:只关特定中断
void critical_section_enter(void)
{
    // 只屏蔽CAN中断,其他中断不受影响
    NVIC_DisableIRQ(CAN_IRQn);
}

4.5 总结一下

中断管理这块,说白了就是四个字:快、准、稳、短

  • :中断响应要快,延迟要低。
  • :优先级分配要合理,不能乱。
  • :嵌套深度要控制,栈空间要够。
  • :ISR要短,别在里面做复杂事。

嗯,今天就聊到这儿。下一节咱们讲任务调度,到时候会用到今天讲的中断知识。你先把这些基础打牢了,后面就好办了。