2、中断系统精讲:PIE 中断控制器、中断向量表、中断优先级、中断嵌套与响应时间优化

各位同学,咱们今天聊聊中断系统。说实话,C2000 的中断系统是我最喜欢讲的部分之一。为什么?因为搞实时控制,中断就是你的命根子。电机转得快不快、电源响应及不及时,全看中断玩得溜不溜。

我刚开始用 C2000 那会儿,也踩过不少坑。有一次做伺服驱动,电机一启动就莫名其妙地抖动。查了三天,最后发现是中断优先级没配好,低优先级的中断把高优先级的给堵了。嗯,从那以后,我对中断系统就格外上心。

2.1 PIE 中断控制器——C2000 的中枢神经

先说说 PIE。PIE 的全称是外设中断扩展控制器。说白了,它就是 C2000 的中断管家。

C2000 的 CPU 只有 16 个中断线(INT1~INT14,加上两个不可屏蔽中断)。但外设那么多,ADC、ePWM、CAP、QEP……每个外设都可能产生中断。怎么办?PIE 就是来解决这个矛盾的。

PIE 把 96 个外设中断请求,分成了 12 组,每组 8 个。每组对应一个 CPU 中断线。比如:

  • INT1.1:ADC 中断
  • INT1.2:ePWM 中断
  • INT1.3:eCAP 中断
  • ……

你想想看,如果没有 PIE,CPU 得处理 96 个中断源,那得多累?PIE 相当于先帮你过滤一遍,只把最紧急的那个送到 CPU 面前。

核心要点:PIE 的每个组内,8 个中断是有优先级的。1 最高,8 最低。但组与组之间,优先级由 CPU 中断线决定。INT1 最高,INT14 最低。

2.2 中断向量表——中断的"通讯录"

中断向量表,我习惯叫它"中断通讯录"。CPU 收到中断信号后,得知道该跳到哪里去执行代码。这个地址信息,就存在中断向量表里。

C2000 的中断向量表默认在 PIE 模块里。每个中断源对应一个 32 位的向量地址。你需要在初始化时,把这些向量填好。

我个人习惯用 TI 提供的例程模板。但要注意一点:向量表必须放在 RAM 中,因为运行时要动态修改。放在 Flash 里?那你就等着跑飞吧。

看个代码示例:

// 中断向量表初始化
void InitPieVectTable(void)
{
    int16    i;
    Uint32   *Source = (void *)&PieVectTableInit;
    Uint32   *Dest   = (void *)&PieVectTable;

    // 复制默认向量表
    for(i = 0; i < 128; i++)
    {
        *Dest++ = *Source++;
    }

    // 注册自己的中断服务函数
    EALLOW;
    PieVectTable.ADCINT1 = &adc_isr;
    PieVectTable.EPWM1_INT = &epwm1_isr;
    EDIS;
}

小技巧:我建议你在写中断服务函数时,名字起得规范一点。比如 adc_isr、epwm1_isr。别用 isr1、isr2 这种。不然项目大了,你自己都找不到哪个是哪个。

2.3 中断优先级——谁先谁后,得有个规矩

中断优先级,说白了就是"谁更紧急谁先上"。C2000 的优先级分两层:

第一层:CPU 级别

INT1 优先级最高,INT14 最低。这是硬件决定的,你改不了。

第二层:PIE 组内级别

每组内,MUX 值小的优先级高。比如 INT1.1 比 INT1.2 高。

但这里有个坑:同一组内的中断,不能互相打断。什么意思?假设 INT1.1 正在执行,INT1.2 来了,它得等着。等 INT1.1 处理完了,INT1.2 才能上。

我曾经在一个项目中,把 ADC 中断和 ePWM 中断放在同一组。结果 ADC 采样频率一高,ePWM 的中断响应就慢了。电机波形开始抖动。后来我把它们分到不同组,问题就解决了。

避坑指南:如果你有多个实时性要求高的中断,尽量把它们分到不同的 CPU 中断线上。比如 ADC 用 INT1,ePWM 用 INT2。这样它们可以互相嵌套,响应更快。

2.4 中断嵌套——高级玩法,但别玩脱了

中断嵌套,就是高优先级中断可以打断低优先级中断。听起来很美好,但用不好就是灾难。

C2000 默认情况下,中断是不嵌套的。也就是说,一个中断在跑的时候,其他中断都得等着。想用嵌套?你得手动开全局中断。

看代码:

interrupt void adc_isr(void)
{
    // 手动开启中断嵌套
    IER |= M_INT1;   // 允许 INT1 组的中断
    EINT;            // 开启全局中断

    // 处理 ADC 数据
    // ...

    // 退出前关中断
    DINT;
    IER &= ~M_INT1;

    // 清除中断标志
    PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
}

嗯,这里要注意:嵌套深度别太深。我见过有人嵌套了 4 层,结果栈空间爆了,程序直接跑飞。一般来说,嵌套 2 层就够用了。

另外,嵌套时一定要考虑临界区保护。如果你在低优先级中断里操作了一个全局变量,高优先级中断也操作它,那数据就可能乱掉。

我的建议:能用轮询解决的,就别用嵌套。嵌套虽然响应快,但调试起来真的很痛苦。我一般只在 ADC 采样和 PWM 触发这种强实时场景才用嵌套。

2.5 响应时间优化——把每一微秒都榨干

做实时控制,中断响应时间就是生命。我给大家分享几个实战经验。

第一,减少中断服务函数的代码量。

中断里只做最核心的事。比如 ADC 中断,只做数据读取和标志设置。复杂的滤波算法,放到主循环里做。

第二,合理使用 PIE 中断标志。

C2000 的 PIE 有 PIEACK 寄存器。你处理完中断后,必须手动清除 PIEACK,否则同组的中断进不来。

// 清除 PIE 组中断标志
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;  // 清除第 1 组

第三,把中断向量表放在 RAM 中。

从 RAM 读取向量比从 Flash 快得多。虽然 RAM 空间宝贵,但这点投资值得。

第四,优化 CPU 中断响应。

C2000 的 CPU 响应中断时,会自动保存一些寄存器。但如果你用 C 语言写中断,编译器会额外保存一些寄存器。我建议关键中断用汇编写,或者用 TI 的 intrinsic 函数。

实测数据:在我做的一个项目中,把中断服务函数从 C 改成汇编,响应时间从 1.2μs 降到了 0.6μs。整整快了一倍!当然,不是所有场景都需要这么极致,但如果你做高频开关电源,这 0.6μs 可能就是成败的关键。

第五,避免在中断中调用函数。

函数调用有压栈出栈的开销。如果中断里必须调用,用 inline 函数或者宏定义。

// 不推荐
interrupt void adc_isr(void)
{
    process_adc_data();  // 函数调用有开销
}

// 推荐
#define PROCESS_ADC_DATA()  { \
    adc_result = AdcResult.ADCRESULT0; \
    // 其他操作 \
}

interrupt void adc_isr(void)
{
    PROCESS_ADC_DATA();  // 宏展开,无调用开销
}

小结

好了,中断系统就讲这么多。总结一下:

  • PIE 是中断管家,帮你管理 96 个中断源
  • 向量表是通讯录,告诉 CPU 该去哪执行
  • 优先级分两层,组内组间都要考虑
  • 嵌套是双刃剑,用好了响应快,用不好出 bug
  • 响应时间优化,从代码、内存、编译器多个角度入手

下一章我们讲 ePWM 模块。那个才是 C2000 的精髓,也是做电机控制和电源管理的核心。到时候我会分享一些我在实际项目中调 PWM 波形的经验,保证干货满满。

各位同学,中断系统这块,有什么问题随时问我。咱们下节课见。