1. 课程导入

PX4 是当前开源无人机飞控领域应用最广的软件栈之一,它采用模块化架构,把姿态估计、位置控制、导航、通信、日志等能力拆分为大量可独立运行的功能模块。对于希望把算法、传感器或业务逻辑真正落到飞控上的开发者来说,仅会调用现成功能远远不够,必须掌握自定义模块与 uORB 消息总线这两项核心能力。本课程围绕“如何从零构建一个可编译、可运行、可调试、可复用的 PX4 自定义模块”展开,帮助你把想法变成飞控中稳定运行的功能单元。

一、学习目标:从“会用”到“能改、能扩、能调”

本课程不是简单的 API 罗列,而是以工程化视角组织内容。完成学习后,你应当能够独立完成以下任务:

  • 理解 PX4 的模块化启动机制,掌握 px4_task_spawn_cmd、模块入口函数与 ModuleBase 模板的用法;
  • 掌握 uORB 的发布/订阅模型,能够自定义消息类型并完成 msg/ 目录下的消息定义与生成;
  • 能够编写一个完整的自定义模块,实现数据订阅、周期处理、结果发布与参数绑定;
  • 掌握编译、烧录、启动脚本配置、日志查看与常见故障排查方法;
  • 具备把自定义模块接入实际飞行控制链路的能力,例如读取传感器、输出控制量或扩展通信接口。
核心判断标准:一个合格的 PX4 自定义模块,应当做到“可编译、可启动、可观测、可配置、可复用”。这五点也是本课程贯穿始终的验收标准。

二、核心概念:模块、uORB 与参数系统

PX4 的运行时结构可以概括为“模块 + 消息 + 参数”三件套。模块是执行单元,uORB 是模块之间的数据通道,参数系统则是模块行为的可配置入口。三者关系如下:

  • 模块(Module):一个独立编译单元,通常继承 ModuleBase 或 ModuleParams,拥有 task_spawn、instantiate、run、custom_command 等关键接口。
  • uORB:PX4 的轻量级发布/订阅消息总线。发布者通过 orb_advertise 或 orb_advertise_multi 注册主题,订阅者通过 orb_subscribe 获取数据,使用 orb_copy 读取最新消息。
  • 参数系统:通过 PARAM_DEFINE_FLOAT、PARAM_DEFINE_INT32 等宏定义参数,模块可在运行时读取和更新,实现无需重新编译即可调整行为。

理解这三者的协作方式,是后续所有实战的基础。例如一个避障模块,通常会订阅距离传感器主题,读取参数中的安全阈值,经过算法处理后发布一条新的控制或状态主题,供其他模块使用。

三、整体框架:从源码到飞控的完整链路

PX4 自定义模块的开发链路可以拆分为“消息定义 → 模块实现 → 编译构建 → 启动配置 → 运行调试”五个阶段。下图展示了这一整体框架与关键对象之间的关系:

PX4 自定义模块与 uORB 开发工程整体框架 ① 消息定义 msg/*.msg 生成 uORB 头文件 ② 模块实现 ModuleBase / ModuleParams run() + 订阅/发布 ③ 编译构建 CMakeLists.txt make px4_fmu-v6x ④ 启动配置 rcS / 启动脚本 模块名 start uORB 消息总线(发布 / 订阅 / 多实例) orb_advertise · orb_subscribe · orb_copy · orb_publish 参数系统 PARAM_DEFINE_FLOAT / INT32 运行调试与观测 listener · top · dmesg · ulog 发布/订阅 读取配置 观测数据 开发闭环:定义消息 → 实现模块 → 编译构建 → 配置启动 → 运行调试 → 迭代优化

从图中可以看到,uORB 总线处于中心位置,模块通过它交换数据,参数系统为模块提供运行时配置,调试工具则用于观测模块行为。整个开发过程是一个闭环:定义消息、实现模块、编译构建、配置启动、运行调试,再根据结果迭代优化。

四、实战路线图:按阶段推进的学习路径

为了让学习过程更有节奏,本课程按照“由浅入深、由静到动”的原则安排内容。下表给出了各阶段的核心任务与产出物:

阶段 核心任务 关键知识点 阶段产出
基础认知 理解 PX4 架构与模块生命周期 模块入口、启动流程、目录结构 能定位并阅读现有模块源码
消息入门 定义并生成自定义 uORB 消息 msg 文件语法、生成机制、多实例 可编译通过的自定义消息头文件
模块开发 编写订阅、处理、发布完整逻辑 ModuleBase、orb_subscribe、orb_publish 一个可运行的自定义模块
参数与配置 为模块增加参数与启动脚本 PARAM_DEFINE、rcS、模块命令 可通过参数调整行为的模块
调试与优化 观测运行状态并排查问题 listener、top、dmesg、ulog 稳定的模块与调试记录

五、典型场景与常见误区

假设我们要开发一个“低电量返航触发模块”。它需要订阅电池状态主题,读取参数中的返航阈值,当电量低于阈值时发布一条自定义状态消息,通知导航模块执行返航。这个场景几乎覆盖了本课程的全部核心技能:消息定义、模块实现、参数配置、发布订阅和调试观测。

建议在动手写代码前,先用 listener battery_status 和 listener vehicle_status 观察现有主题的数据字段与更新频率,这能显著减少消息定义阶段的返工。

初学者最容易陷入的误区包括:

  • 只写模块不定义消息:直接复用现有主题,导致语义不清或字段不够用;
  • 忽略启动脚本:模块编译成功却从未被启动,误以为代码有问题;
  • 订阅后不检查更新:在循环中盲目 orb_copy,造成阻塞或数据重复;
  • 参数未注册:在代码中直接写死阈值,失去运行时调整能力;
  • 不做日志与观测:出现问题后只能靠猜,排查效率极低。
调试阶段务必先确认模块是否真正运行:使用 module status 或 top 查看任务是否存在,再用 listener 确认消息是否按预期发布。顺序错了,很容易把“没启动”误判为“逻辑错误”。

六、一个最小可运行模块的骨架

下面给出一个自定义模块的最小骨架,后续章节会在此基础上逐步扩展。它展示了模块入口、任务创建和主循环的基本结构:

#include <px4_platform_common/module.h>
#include <px4_platform_common/px4_config.h>
#include <uORB/uORB.h>

class MyModule : public ModuleBase<MyModule>
{
public:
    static Descriptor desc;
    static int task_spawn(int argc, char *argv[]);
    static int custom_command(int argc, char *argv[]);
    static int print_usage(const char *reason = nullptr);

    void run() override;

private:
    uORB::Subscription _battery_sub{ORB_ID(battery_status)};
    uORB::Publication<my_status_s> _status_pub{ORB_ID(my_status)};
};

int MyModule::task_spawn(int argc, char *argv[])
{
    MyModule *instance = new MyModule();
    if (!instance) { return -1; }
    _object.store(instance);
    _task_id = px4_task_spawn_cmd("my_module",
                                  SCHED_DEFAULT,
                                  SCHED_PRIORITY_DEFAULT,
                                  2048,
                                  (px4_main_t)&ModuleBase::run_trampoline,
                                  nullptr);
    return PX4_OK;
}

这段代码虽然简短,但已经包含了模块类定义、订阅对象、发布对象、任务创建等关键要素。理解它的每一行,是进入 PX4 自定义模块开发的第一步。

本课程后续将围绕这条主线逐步展开:先建立对 PX4 模块化架构的整体认识,再深入 uORB 消息机制,然后完成自定义消息与模块的编写,最后通过参数、启动脚本和调试工具把模块真正接入飞控系统。只要按照路线图稳步推进,你就能从“阅读源码”走向“独立开发”,把自定义功能稳定地运行在 PX4 飞控之上。