中断概念解析:中断的定义、中断向量表、中断优先级、中断嵌套

各位同学,今天我们聊聊中断。说实话,中断是运动控制里最核心的机制之一。没有中断,实时控制就是一句空话。我见过不少新手,代码写得挺漂亮,但一上电机就各种抖、各种丢步,最后查出来都是中断配置出了问题。嗯,这节课我们就把中断的底裤扒干净。

1. 中断到底是什么?

中断,说白了就是CPU正在干一件事,突然被一个紧急事件打断,转去处理这个事件,处理完再回来接着干。你想想看,这像不像你正在写代码,领导突然喊你开会?开完会回来继续写。

在运动控制里,典型的中断事件有:

  • 编码器Z信号到来(找原点)
  • 定时器溢出(产生PWM周期)
  • ADC转换完成(读取电流值)
  • 外部急停信号触发

我个人习惯把中断分成三类:

类型 触发方式 运动控制典型用途
外部中断 GPIO电平变化 限位开关、急停、编码器索引
定时器中断 计数器溢出/匹配 PWM生成、速度环周期控制
DMA中断 数据传输完成 ADC采样数据搬运、脉冲发送

核心要点:中断不是万能的,但没有中断的运动控制是万万不能的。实时性要求越高的场合,中断的依赖就越重。

2. 中断向量表——CPU的紧急通讯录

中断向量表是什么?我打个比方:你手机里存了各个部门的紧急联系电话。火灾打119,急救打120,报警打110。CPU也一样,每个中断源都有一个固定的入口地址,这个地址就存在中断向量表里。

以STM32为例,中断向量表默认放在Flash起始地址0x00000000。当某个中断触发时,CPU直接去查表,找到对应的中断服务函数地址,跳过去执行。

// STM32中断向量表示例(简化)
__attribute__((section(".isr_vector")))
void (* const g_pfnVectors[])(void) = {
    &__initial_sp,          // 0x0000: 栈顶指针
    Reset_Handler,          // 0x0004: 复位中断
    NMI_Handler,            // 0x0008: 不可屏蔽中断
    HardFault_Handler,      // 0x000C: 硬件错误中断
    // ... 省略中间
    TIM1_UP_IRQHandler,     // 定时器1更新中断
    TIM2_IRQHandler,        // 定时器2中断
    // ... 其他外设中断
};

我的经验:曾经有一次调试伺服驱动器,电机转起来就死机。查了两天,最后发现是中断向量表地址配错了——我把APP的中断向量表偏移设成了0x8000,但实际代码烧在了0x8010。差这16个字节,CPU一进中断就飞到火星去了。所以,向量表偏移一定要和链接脚本里的Flash分区对齐。

3. 中断优先级——谁更紧急谁先来

中断优先级,说白了就是给中断排个队。运动控制里,优先级排错了,后果很严重。

举个例子:你正在执行速度环的PID计算(定时器中断),突然来了个串口接收中断。如果串口优先级更高,那速度环就被打断了。速度环的周期是固定的,比如100μs一次,被打断一次可能就导致电流输出延迟,电机就开始抖。

我一般遵循这个原则:

  1. 时间敏感度越高,优先级越高——比如电流环中断(10-50μs周期)优先级最高
  2. 数据丢失风险大的,优先级次之——比如编码器计数溢出中断
  3. 非实时通信类,优先级最低——比如串口、CAN接收
中断源 典型周期 我建议的优先级 原因
电流环定时器 10-50μs 最高(0) 直接影响电机力矩输出
速度环定时器 100-500μs 次高(1) 依赖电流环结果
编码器Z信号 事件触发 高(2) 丢失会导致原点定位错误
串口接收 不定 低(3) 可以用DMA+轮询替代

避坑指南:我曾经在一个四轴联动项目里,把两个轴的电流环中断设成了相同优先级。结果两个中断同时触发时,CPU不知道先处理谁,导致其中一个轴的电流环周期抖动超过50%。电机直接啸叫,吓得我赶紧按了急停。记住:同优先级的中断,硬件上是有固定仲裁顺序的,但千万别依赖这个,老老实实分清楚优先级。

4. 中断嵌套——中断里还有中断

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

你想想看:低优先级中断正在执行,高优先级来了,CPU保存现场、跳转、执行高优先级、恢复现场、继续执行低优先级。这一套下来,光是压栈出栈就要几十个CPU周期。如果嵌套层数多了,栈空间可能溢出。

我一般这样控制嵌套:

  • 电流环中断里绝不开启其他中断——它自己就是最高优先级,不需要被嵌套
  • 速度环中断里只允许电流环打断——其他中断一律挂起
  • 通信中断里允许所有中断打断——反正通信本身就不要求实时
// 中断嵌套控制示例(伪代码)
void TIM1_IRQHandler(void)  // 电流环,最高优先级
{
    // 默认已经关闭了所有低优先级中断
    read_current_sensor();
    compute_pid_current();
    update_pwm_duty();
    // 退出时自动恢复
}

void TIM2_IRQHandler(void)  // 速度环,次高优先级
{
    // 只允许电流环中断打断
    __set_BASEPRI(1 << (8 - 1));  // 屏蔽优先级低于1的中断
    read_encoder_speed();
    compute_pid_speed();
    set_current_reference();
    __set_BASEPRI(0);  // 恢复所有中断
}

关键原则:中断嵌套不是越多越好。每嵌套一层,实时性就损失一分。我个人的经验是:运动控制里,嵌套深度不要超过2层。超过这个数,系统的确定性就很难保证了。

5. 知识体系总览

下面这张图,把中断的核心概念串起来了。你可以把它当成一张地图,以后遇到中断相关的问题,先看这张图定位问题在哪一层。

中断概念知识体系 中断 (Interrupt) 中断定义 CPU暂停当前任务 中断向量表 中断服务函数入口地址表 中断优先级 决定中断响应顺序 中断嵌套 高优先级打断低优先级 核心原则:实时性 > 完整性 > 便利性 运动控制中,宁可丢数据,不可丢实时

嗯,中断这块内容就讲到这里。记住:中断是运动控制的灵魂,配置好了,系统稳如老狗;配不好,电机跳舞给你看。下次遇到中断相关的问题,先检查向量表对不对,再看优先级合不合理,最后看嵌套有没有失控。这三板斧下去,大部分问题都能解决。