3. 中断与轮询模式选择:中断上下文开销分析、轮询频率与CPU负载的平衡

做嵌入式驱动开发,尤其是飞控这种实时性要求极高的系统,中断和轮询的选择,几乎是每天都要面对的问题。我见过不少新手,一上来就全用中断,觉得这样响应快;也有人图省事,全用轮询,结果CPU被吃干榨净。这两种极端,在PX4里都走不远。

说白了,中断和轮询没有绝对的好坏。关键看你的外设是什么、数据量多大、实时要求多高。今天我就结合自己的踩坑经历,跟你聊聊怎么选。

3.1 中断上下文开销:别小看那几微秒

很多人觉得中断就是“来了就处理,处理完就走”,开销很小。其实不然。中断的开销,比你想象的要大得多。

中断的开销主要来自三部分:

  • 硬件开销:CPU要保存当前上下文(寄存器、状态字),跳转到中断向量表,再跳转到ISR。这部分是固定的,一般几微秒到十几微秒。
  • 软件开销:ISR里要关中断、开中断、操作外设寄存器、可能还要唤醒一个任务。这部分取决于你的代码质量。
  • 调度开销:如果ISR里触发了任务切换(比如用hrt_call_after或者信号量),那调度器还要跑一遍,这又是几十微秒。

关键点:在STM32F4这类Cortex-M4核上,一次中断的完整开销(从触发到ISR退出)通常在3~15微秒之间。如果中断频率是1kHz,那光中断开销就占了CPU的1%~1.5%。看起来不多?但如果你的中断频率是10kHz呢?那就是10%~15%的CPU被白白吃掉了。

我在项目中遇到过一个问题:一个IMU传感器用了中断模式,频率是8kHz。结果发现CPU负载飙到了40%以上。后来一查,ISR里做了不少浮点运算,还调用了px4_scheduler的接口。改成轮询后,CPU负载直接降到了12%。

我的建议:中断里只做最必要的事。比如只读一个寄存器、设置一个标志位,然后把真正的数据处理扔给一个高优先级任务。千万别在ISR里做浮点运算、打印日志、或者调用复杂API。

3.2 轮询频率与CPU负载的平衡

轮询看起来简单——就是在一个循环里不断读状态。但轮询频率怎么定?这是个学问。

轮询频率太高:CPU被白白占用。比如一个气压计,更新速率只有100Hz,你非要用1kHz去轮询,那90%的轮询都是无效的,白白浪费CPU。

轮询频率太低:数据丢失。比如一个光流传感器,数据更新速率是400Hz,你只用100Hz去轮询,那每4个数据里就有3个被漏掉。

我个人的经验是:轮询频率设置为外设数据更新速率的1.5~2倍。这样既能保证不丢数据,又不会浪费太多CPU。

外设类型 典型数据更新速率 推荐轮询频率 CPU负载估算(@100MHz)
气压计(MS5611) 100 Hz 150~200 Hz 0.5%~1%
IMU(MPU9250) 1 kHz 1.5~2 kHz 2%~5%
光流传感器 400 Hz 600~800 Hz 3%~6%
GPS(UART) 10 Hz 20~30 Hz <0.1%

注意:轮询不是简单的“读一下寄存器”。如果轮询里还做了数据滤波、校验、格式转换,那CPU负载会成倍增加。我曾经见过一个同事,在轮询里做了卡尔曼滤波,结果CPU负载直接爆了。嗯,后来他改成了在任务里做滤波,轮询只负责读原始数据。

3.3 中断 vs 轮询:什么时候选哪个?

这个问题,我一般用下面这个决策树来判断。你可以直接拿来用。

中断 vs 轮询 决策树 外设数据特性分析 数据速率高(>1kHz) 数据速率低(<1kHz) 实时性要求极高? CPU负载敏感? ✅ 中断模式 ✅ 轮询模式 数据突发性强? 数据均匀稳定? ✅ 中断模式 ✅ 轮询模式 核心原则:中断保实时,轮询省CPU 高频率+高实时 → 中断 | 低频率+稳定 → 轮询

你想想看,如果外设数据速率很高(比如IMU的1kHz以上),而且对实时性要求极严(比如姿态控制),那中断是唯一选择。但如果数据速率不高,或者CPU负载已经很高了,轮询反而更合适。

3.4 PX4中的实际应用:混合模式

在PX4里,其实很少纯用中断或纯用轮询。更多是混合模式。比如:

  • SPI总线上的传感器:用中断通知数据就绪,然后在任务里通过轮询读取数据。这样既保证了响应速度,又避免了ISR里做复杂操作。
  • UART接收:用中断接收每个字节,但数据解析放在任务里。这样不会丢字节,也不会阻塞其他任务。
  • I2C传感器:我一般用轮询。因为I2C本身速度慢(400kHz),中断的开销占比太大,不划算。

实战经验:在PX4的drivers/imu目录下,你可以看到很多传感器驱动都用了hrt_call_every来实现定时轮询。这个接口本质上是一个高精度定时器中断,但它的开销比硬件中断小得多。我个人习惯:能用hrt_call_every解决的,尽量不用硬件中断。

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 中断里调用printf:我曾经在调试时在ISR里加了printf,结果系统直接卡死。因为printf是阻塞的,而且可能触发其他中断。记住:ISR里永远不要做IO操作。
  • 轮询频率设得太高:有一次我把一个100Hz的传感器轮询频率设成了10kHz,结果CPU负载飙到80%,飞控直接失控。后来用示波器一看,大部分轮询都是无效的。
  • 中断优先级设置不当:PX4里有些中断(比如systick)优先级很高。如果你把外设中断优先级设得比systick还高,那系统调度会出问题。我建议:外设中断优先级不要高于systick。

小技巧:在调试阶段,可以用perf_counter来测量中断和轮询的实际开销。PX4里提供了perf_beginperf_end接口,可以精确测量代码段的执行时间。我每次调驱动都会用这个工具,非常实用。

好了,关于中断和轮询的选择,今天就聊这么多。记住一句话:没有最好的模式,只有最合适的模式。根据你的外设特性和系统负载,灵活选择,才是正道。


公众号:蓝海资料掘金营,微信deep3321