4. uORB基础概念

uORB(micro Object Request Broker)是 PX4 飞控软件栈中最重要的进程间通信中间件。它并不像传统 ROS 那样依赖网络协议栈,而是运行在同一个操作系统内的轻量级发布/订阅总线。理解 uORB 的五个核心概念——发布/订阅模型、主题、消息、句柄、队列机制——是编写自定义模块、读取传感器数据、输出控制指令的前提。本节将围绕这五个概念,结合通信原理与代码示例,建立完整的认知框架。

一、发布/订阅模型:解耦生产与消费

uORB 采用典型的发布/订阅(Publish/Subscribe)模型。任何模块都可以作为发布者(Publisher)向某个主题写入数据,也可以作为订阅者(Subscriber)从某个主题读取数据。发布者与订阅者之间不需要知道对方的存在,也不需要建立点对点连接,它们只通过“主题”这一逻辑通道进行交互。

这种模型带来三个直接好处:第一,模块之间高度解耦,新增一个订阅者不会影响发布者的代码;第二,一对多广播天然支持,例如 sensor_combined 主题可以被姿态估计、日志记录、地面站遥测等多个模块同时订阅;第三,数据流方向清晰,便于调试与性能分析。在 PX4 中,几乎所有模块间通信都通过 uORB 完成,包括传感器驱动、EKF2、位置控制器、commander 等。

发布者只负责“写”,订阅者只负责“读”,双方通过主题名和消息类型约定契约。发布者不关心谁在读,订阅者不关心谁在写。

二、主题、消息与句柄:三个关键对象

主题(Topic)是 uORB 中的逻辑通道名称,例如 vehicle_attitude、vehicle_command、sensor_gps。主题名在系统中唯一,是发布者和订阅者匹配的依据。每个主题在编译期由 .msg 文件定义,生成对应的 C/C++ 结构体。

消息(Message)是主题承载的数据结构。一个消息由若干字段组成,字段类型可以是基本类型(uint64、float、bool 等)、数组或嵌套消息。例如 vehicle_attitude.msg 中包含四元数、角速度、时间戳等字段。消息定义决定了主题的数据布局,也决定了订阅者能读取到的内容。

句柄(Handle)是模块与主题之间的“连接凭证”。发布者通过 orb_advertise() 获得发布句柄,订阅者通过 orb_subscribe() 获得订阅句柄。句柄本质上是一个整数索引,指向内核中该主题的元数据与数据缓冲区。没有句柄,模块就无法访问主题数据。

uORB 发布/订阅通信结构 发布者模块 sensor_driver orb_advertise() 主题 Topic sensor_combined 消息结构体 + 数据缓冲区 队列深度 / 订阅者计数 订阅者 A ekf2 订阅者 B logger 订阅者 C mavlink publish subscribe 发布句柄 orb_advert_t 订阅句柄 orb_subscriber_t (int) 队列机制 Queue FIFO 缓冲 / 最新值覆盖

三、队列机制与通信原理

uORB 的通信原理可以概括为“共享内存 + 通知机制”。每个主题在内核中维护一块数据缓冲区,发布者写入数据后,订阅者通过 orb_copy() 从缓冲区读取。为了处理发布与订阅速率不一致的问题,uORB 提供了两种队列策略:

  • 最新值模式(Latest Value):缓冲区只保留最新一条消息,订阅者每次读取到的都是当前最新值。适合姿态、位置等状态类主题。
  • FIFO 队列模式(Queue):通过 orb_advertise_queue() 或 orb_subscribe_multi() 指定队列深度,发布者连续写入的多条消息按顺序排队,订阅者按先进先出顺序读取。适合传感器原始数据、日志记录等需要保留历史序列的场景。

订阅者通常使用 orb_check() 或 px4_poll() 等待新数据。前者非阻塞地检查是否有更新,后者阻塞等待直到超时或有新数据到达。发布者则通过 orb_publish() 将消息写入主题。整个过程中,uORB 通过原子操作和信号量保证多线程环境下的数据一致性。

// 发布者示例
orb_advert_t pub = orb_advertise(ORB_ID(sensor_combined), &data);
orb_publish(ORB_ID(sensor_combined), pub, &new_data);

// 订阅者示例
int sub = orb_subscribe(ORB_ID(sensor_combined));
struct sensor_combined_s msg;
orb_copy(ORB_ID(sensor_combined), sub, &msg);

// 阻塞等待新数据
px4_pollfd_struct_t fds[1];
fds[0].fd = sub;
fds[0].events = POLLIN;
px4_poll(fds, 1, 1000);  // 超时 1000ms

四、典型场景:从传感器到姿态估计

以 sensor_combined 主题为例,传感器驱动模块以固定频率发布融合后的 IMU 数据,EKF2 模块订阅该主题进行姿态解算,logger 模块订阅同一主题写入日志,mavlink 模块订阅后转发给地面站。三个订阅者互不干扰,发布者只需发布一次。如果新增一个自定义模块需要监控 IMU 数据,只需调用 orb_subscribe(ORB_ID(sensor_combined)) 即可,无需修改任何已有代码。

在自定义模块中,常见的操作流程是:先订阅所需主题,然后在主循环中轮询或阻塞等待,读取数据后执行控制逻辑,最后发布自己的输出主题。例如一个自定义的“避障控制”模块,订阅 distance_sensor,发布 vehicle_command 或自定义的 obstacle_avoidance 主题。

五、常见误区与复盘

误区 现象 正确做法
忘记检查句柄有效性 orb_advertise 返回 ERROR,后续 publish 失败 判断句柄是否有效,必要时重试或报错退出
混淆最新值与队列模式 高频数据丢失,日志不连续 需要历史序列时使用队列模式并设置合适深度
订阅后不检查更新 重复读取旧数据,控制逻辑滞后 使用 orb_check 或 px4_poll 等待新数据
在中断中直接发布 系统不稳定或死锁 中断中只做标记,由工作队列或主循环发布
主题名拼写错误 订阅失败,句柄为负 使用 ORB_ID 宏,编译期检查主题名
uORB 句柄是模块与主题之间的唯一连接。句柄无效时,所有读写操作都会失败。调试时可用 uorb top 查看主题发布频率与订阅者数量,用 listener sensor_combined 查看具体消息内容。

掌握 uORB 的发布/订阅模型、主题与消息定义、句柄管理以及队列机制,是后续编写自定义模块、实现模块间数据流的基础。在实际开发中,建议先通过 uorb top 和 listener 命令观察现有主题的发布频率与数据结构,再动手编写自己的发布者或订阅者代码,这样可以显著降低调试成本。