3. PX4中断注册与处理:使用px4_register_interrupt()注册中断,编写中断服务函数(ISR)

中断,说白了就是硬件在喊你:「嘿,CPU,有急事!」。在PX4这种实时飞控系统里,中断处理的好坏直接决定了你能不能抓住一个IMU数据,或者会不会错过一个关键的PWM边沿。我个人习惯把中断看作整个驱动的心脏跳动——它必须精准、快速、不拖泥带水。

这一节,我们就来聊聊怎么在PX4里注册中断,以及怎么写一个靠谱的ISR。嗯,这里要注意,PX4的中断框架和裸机开发不太一样,它封装了一层,用起来更安全,但也多了些规矩。

3.1 中断注册的核心:px4_register_interrupt()

在PX4里,你不需要直接操作芯片的NVIC寄存器。框架提供了一个统一的接口:px4_register_interrupt()。这个函数会把你的ISR挂到正确的硬件中断线上,同时帮你处理好上下文切换和临界区保护。

函数原型大概长这样:

int px4_register_interrupt(int irq_number,
                           px4_interrupt_t handler,
                           void *context,
                           int priority);

参数说明:

参数 含义 我踩过的坑
irq_number 硬件中断号,查芯片手册或board.h 不同芯片的IRQ编号可能不同,别想当然
handler 你的ISR函数指针 必须是快速响应函数,别在里面做复杂运算
context 传递给ISR的上下文参数,通常传NULL或设备结构体 我习惯传设备实例指针,方便在ISR里访问寄存器
priority 中断优先级,数值越小优先级越高 别把所有中断设成同一个优先级,会出问题的

重要提醒:px4_register_interrupt() 必须在系统启动阶段调用,最好放在驱动初始化函数里。如果在任务上下文里动态注册,可能会因为调度器状态不对而出问题。我曾经在某个项目中试过在任务里注册中断,结果系统直接挂掉——嗯,从那以后我再也不敢这么干了。

3.2 编写ISR:快进快出,别磨叽

ISR的全称是Interrupt Service Routine,中文叫中断服务函数。它的核心原则就四个字:快进快出。你想想看,CPU正在跑任务,突然被中断拉走了,如果你在ISR里磨蹭太久,其他中断就会排队等着,实时性就崩了。

一个标准的PX4 ISR模板长这样:

static int my_device_isr(int irq, void *context, void *arg)
{
    /* 1. 检查中断源,确认是不是自己的中断 */
    struct my_device_s *dev = (struct my_device_s *)arg;
    uint32_t status = dev->regs->SR;

    if (!(status & MY_DEVICE_INT_FLAG)) {
        return 0; /* 不是我的中断,直接返回 */
    }

    /* 2. 清除中断标志位 */
    dev->regs->SR = status & ~MY_DEVICE_INT_FLAG;

    /* 3. 读取数据,放到缓冲区 */
    uint8_t data = dev->regs->DR;
    circ_bbuf_push(&dev->rx_buffer, data);

    /* 4. 通知等待数据的任务(如果有) */
    px4_sem_post(&dev->rx_sem);

    return 0;
}

这里有几个关键点:

  • 第一步一定是检查中断源。 多个外设可能共享一根中断线,你不检查就处理,会误操作别人的数据。
  • 第二步是清中断标志。 不清标志的话,退出ISR后中断会再次触发,形成死循环。我记得有个同事忘了清标志,结果系统一直在中断里打转,看门狗都救不了。
  • 第三步只做最轻量的操作。 比如把数据塞进环形缓冲区,或者设置一个标志位。千万别在ISR里做malloc、printf、或者复杂的数学运算。
  • 第四步是唤醒任务。 如果数据需要被上层任务处理,用信号量或消息队列通知它。这样ISR可以快速返回,而数据处理在任务上下文里慢慢做。

我的小技巧:在ISR里尽量用px4_sem_post()而不是sem_post()。因为PX4的信号量实现考虑了中断上下文的安全性,不会引起优先级反转。这个细节我是在一次调试中发现的,当时用标准POSIX信号量,结果在高负载下任务一直拿不到信号量,排查了两天才找到原因。

3.3 中断与DMA的配合:谁该先跑?

很多外设同时支持中断和DMA。比如一个SPI设备,你可以用中断方式一个字节一个字节地读,也可以用DMA一次读一整个块。我个人建议:数据量小、频率高用中断;数据量大、频率低用DMA。

为什么会这样?因为中断每次进来都要保存和恢复上下文,如果数据量小但频率极高(比如IMU的SPI读取),中断开销会吃掉大量CPU时间。而DMA只需要在传输完成时触发一次中断,CPU负担小很多。

下面这张图展示了中断和DMA在PX4驱动中的典型配合流程:

PX4中断与DMA配合流程图 外设数据就绪 DMA使能? DMA传输数据 DMA完成中断 逐字节中断处理 每个字节触发ISR

从这张图可以看得很清楚:如果DMA使能了,数据搬运工作交给DMA控制器,CPU只在传输完成时被中断一次。如果没使能DMA,那就每个字节都触发一次ISR,CPU负担重很多。

警告:在ISR里千万不要调用px4_task_spawn()malloc()。这些函数可能引起调度或内存分配,而中断上下文里调度器是锁着的。我曾经在ISR里不小心调了一个带printf的调试函数,结果系统直接卡死,串口输出了一堆乱码——嗯,从那以后我写ISR都格外小心,只做最基础的操作。

3.4 实际项目中的中断调试技巧

调试中断是最头疼的事情之一。因为中断随时可能发生,你没法像调试普通任务那样设断点单步执行。我分享几个我常用的方法:

  • 用GPIO翻转来测量中断响应时间。 在ISR入口和出口各翻转一次GPIO,用示波器看脉冲宽度。如果脉冲太宽,说明ISR里干了太多活。
  • 用计数器统计中断频率。 在ISR里对一个全局变量做自增,然后在任务里定期打印这个值。如果频率远高于预期,可能是中断标志没清干净,产生了重复触发。
  • 用环形缓冲区记录中断历史。 在ISR里把中断发生的时间戳和状态寄存器值压入一个环形缓冲区,出问题后可以离线分析。这个技巧帮我解决过一个间歇性的SPI通信故障。

好了,关于PX4中断注册与处理的核心内容就这些。记住:中断是驱动的基础设施,写好了系统稳如泰山,写不好就是定时炸弹。下一节我们会深入DMA传输的配置与使用,到时候你会看到中断和DMA如何协同工作,把数据吞吐量拉到极致。


专注资料整理