3、中断管理:如何让电机响应更快更准
电机控制这活儿,说白了就是跟时间赛跑。你想想看,一个电机在高速旋转时,转子位置每毫秒都在变。如果你不能及时响应,轻则抖动,重则直接失步。我做了这么多年电机控制,最深的体会就是:中断管理做得好,电机控制就成功了一半。
3.1 中断优先级:别让重要的事排队
RT-Thread默认的中断优先级配置,其实挺适合通用场景的。但电机控制不一样,它有自己的脾气。
我个人习惯把电机控制相关的中断分成三个梯队:
| 优先级 | 中断类型 | 典型用途 |
|---|---|---|
| 最高(0-1) | PWM周期中断、霍尔传感器捕获 | 电流环、速度环核心计算 |
| 中等(2-3) | 编码器Z信号、过流保护 | 位置校准、故障保护 |
| 较低(4-5) | 通信接收、按键输入 | 参数调整、状态显示 |
我在项目中遇到过一个问题:某次调试无刷电机,电机在低速时一切正常,但一加速就开始抖动。查了半天,发现是UART接收中断和PWM中断抢优先级。UART中断处理时间太长,把PWM周期中断给堵住了。电流环更新不及时,电机自然就抖了。
3.2 中断嵌套:能省则省
很多人喜欢开中断嵌套,觉得这样响应更快。其实不然。中断嵌套多了,上下文切换的开销反而更大。
我的做法是:关键中断关嵌套,非关键中断随便。
比如PWM周期中断,我一般会关掉其他中断的嵌套。这个中断执行时间很短,通常就几十微秒。你想想看,如果在这个中断里又被别的中断打断,那电流环的计算时间就不可控了。
/* 在PWM中断服务函数中 */
void PWM_IRQHandler(void)
{
/* 进入中断时,硬件自动关中断 */
/* 这里执行电流采样和FOC计算 */
current_loop_calc();
/* 退出中断时,硬件自动开中断 */
/* 不需要手动操作 */
}
嗯,这里要注意:RT-Thread在中断中会做一些调度工作。如果你在中断里调用了rt_sem_release之类的函数,系统会在中断退出时检查是否需要切换任务。这个机制挺好的,但如果你在嵌套中断里用,可能会出问题。
3.3 中断延迟:看不见的杀手
中断延迟,说白了就是从中断触发到CPU开始执行中断服务函数的时间。这个时间如果太大,电机控制精度就会下降。
影响中断延迟的因素主要有三个:
- 中断屏蔽时间:其他中断处理过程中关中断的时间
- 中断优先级仲裁:高优先级中断抢占的时间
- 硬件响应时间:CPU本身的中断响应延迟
我曾经在一个项目里,电机控制周期是100微秒。按理说够用了,但实际测下来,中断延迟有时候会到30微秒。这意味着什么?电流环的有效计算时间只剩下70微秒,稍微复杂点的算法就跑不完。
解决方法是:
- 精简中断服务函数:只做必要的事,其他事交给线程
- 使用零延迟中断:RT-Thread支持中断服务函数直接运行,不经过线程调度
- 避免在中断中调用耗时函数:比如打印、动态内存分配这些
/* 推荐的中断服务函数写法 */
void TIM_IRQHandler(void)
{
/* 清除中断标志 */
TIM_ClearFlag();
/* 读取电流值 - 必须立即做 */
current_adc = ADC_GetValue();
/* 设置一个标志,让线程去处理 */
motor_control_flag = 1;
/* 千万不要在这里打印或延时 */
/* rt_kprintf("current: %d\n", current_adc); // 错误! */
}
3.4 中断与线程的配合:分工明确
很多新手会把所有控制逻辑都塞到中断里,觉得这样响应快。其实这是误区。中断里代码越多,中断延迟越大,反而影响响应速度。
我常用的模式是:
- 中断层:负责时间敏感的操作,比如电流采样、PWM更新、故障保护
- 线程层:负责计算密集型操作,比如速度环、位置环、滤波算法
举个例子,FOC控制中:
/* 中断中 - 电流环 */
void PWM_IRQHandler(void)
{
/* 采样电流 */
ia = ADC_GetValue(ADC_CH_A);
ib = ADC_GetValue(ADC_CH_B);
/* Clark变换 */
i_alpha = ia;
i_beta = (ia + 2*ib) / sqrt3;
/* Park变换 */
id = i_alpha * cos(theta) + i_beta * sin(theta);
iq = -i_alpha * sin(theta) + i_beta * cos(theta);
/* PI调节 */
vd = PI_Calc(id_ref, id);
vq = PI_Calc(iq_ref, iq);
/* 反Park变换 */
v_alpha = vd * cos(theta) - vq * sin(theta);
v_beta = vd * sin(theta) + vq * cos(theta);
/* 更新PWM */
PWM_Update(v_alpha, v_beta);
/* 通知线程计算速度 */
rt_sem_release(&speed_sem);
}
/* 线程中 - 速度环 */
void speed_loop_thread(void *parameter)
{
while(1)
{
rt_sem_take(&speed_sem, RT_WAITING_FOREVER);
/* 计算速度 */
speed = encoder_get_speed();
/* 速度PI调节 */
iq_ref = PI_Calc(speed_ref, speed);
/* 这里可以加滤波、限幅等处理 */
iq_ref = limit_value(iq_ref, -MAX_CURRENT, MAX_CURRENT);
}
}
你看,这样分工后,中断的执行时间非常短,大概就十几微秒。而速度环的计算虽然复杂,但放在线程里,不影响中断响应。
3.5 实测调优:用数据说话
理论说再多,不如实际测一测。我每次调完中断,都会用逻辑分析仪抓一下中断响应时间。
具体做法是:
- 在中断服务函数入口和出口各拉一个GPIO
- 用逻辑分析仪测量这个GPIO的脉冲宽度
- 这个宽度就是中断服务函数的执行时间
我曾经在一个项目中,通过这种方式发现PWM中断的执行时间从最初的45微秒优化到了12微秒。怎么做到的?就是把那些不必要的计算移到了线程里。
还有一个容易被忽略的点:中断优先级分组。RT-Thread默认使用4位优先级分组,也就是16级优先级。但有些MCU支持更多级。如果你用的MCU支持,可以试试把电机中断放在更高的优先级组里。
好了,关于中断管理,我就说这么多。记住一句话:中断是电机控制的命脉,管好中断,电机才能听话。下一章我们聊聊任务调度,看看怎么让多个控制任务和谐共处。