中断概念解析:中断的定义、中断向量表、中断优先级、中断嵌套
各位同学,今天我们聊聊中断。说实话,中断是运动控制里最核心的机制之一。没有中断,实时控制就是一句空话。我见过不少新手,代码写得挺漂亮,但一上电机就各种抖、各种丢步,最后查出来都是中断配置出了问题。嗯,这节课我们就把中断的底裤扒干净。
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一次,被打断一次可能就导致电流输出延迟,电机就开始抖。
我一般遵循这个原则:
- 时间敏感度越高,优先级越高——比如电流环中断(10-50μs周期)优先级最高
- 数据丢失风险大的,优先级次之——比如编码器计数溢出中断
- 非实时通信类,优先级最低——比如串口、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. 知识体系总览
下面这张图,把中断的核心概念串起来了。你可以把它当成一张地图,以后遇到中断相关的问题,先看这张图定位问题在哪一层。
嗯,中断这块内容就讲到这里。记住:中断是运动控制的灵魂,配置好了,系统稳如老狗;配不好,电机跳舞给你看。下次遇到中断相关的问题,先检查向量表对不对,再看优先级合不合理,最后看嵌套有没有失控。这三板斧下去,大部分问题都能解决。