IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> PX4-3-uORB -> 正文阅读

[嵌入式]PX4-3-uORB

uORB(Micro Object Request ?Broker,微对象请求代理器)是PX4中非常重要且关键的一个模块,用于各个模块之间的数据交互。实际上uORB是一套跨「进程」 的IPC通讯模块。在PX4中, 所有的功能被独立以进程模块为单位进行实现并工作。而进程间的数据交互就尤为重要,必须要实时高效。

uORB多个进程通过共享内存的方式,进行数据交互和共享。进程通过命名的「总线」交换的消息称之为「topic」(topic),在PX4中,一个topic仅包含一种消息类型。每个进程可以「订阅」或者「发布」topic,可以存在多个发布者,或者一个进程可以订阅多个topic。

由于PX4代码更新很快,uORB模块在各个版本中可能有所不同,如最新发布的1.12版本就有比较大的变化,将uORB模块由modules中移动到了plorforms/common中,uORB的定位有所改变,上升到了系统基础组件,不过这里还是以1.11.3版本这个去年发布的稳定版本为例讲解。

这一篇的内容比较长,需要花一些时间阅读,建议可以先大致阅读,如果对uORB的机制感兴趣可以收藏一下,后面结合实际代码阅读。

接口

uORB的外部接口提供了C和C++两种接口,应用程序可以使用C编写也可以使用C++

C接口

C接口定义在 src/modules/uORB/uORB.h

// 向消息网络公告一个发布者,它初始化这个topic公告,在/obj目录下创建一个topic节点(如果需要)并发布初始数据
orb_advert_t orb_advertise(const struct orb_metadata *meta, const void *data, unsigned int queue_size = 1)
// 向消息网络公告一个发布者,它初始化这个topic公告,在/obj目录下创建一个topic节点(如果需要)并发布初始数据,multi可用于创建同一topic的多个独立实例(每个实例都有自己的缓冲区)。这对于发布同一topic的多个发布者非常有用。订阅人然后订阅所有实例并选择要使用的源。 
orb_advert_t orb_advertise_multi(const struct orb_metadata *meta, const void *data, int *instance, ORB_PRIO priority,unsigned int queue_size = 1);
// 取消
int orb_unadvertise(orb_advert_t handle);
// 向topic发布一个新数据
int ?orb_publish(const struct orb_metadata *meta, orb_advert_t handle, const void *data);
// 订阅一个topic
int ?orb_subscribe(const struct orb_metadata *meta);
// 一个topic存在多个发布者时,订阅指定实例
int ?orb_subscribe_multi(const struct orb_metadata *meta, unsigned instance);
// 取消订阅一个topic
int ?orb_unsubscribe(int handle);
// 检测一个topic是否有数据更新
int ?orb_check(int handle, bool *updated);
// 检查一个topic是否存在并且有数据发布
int ?orb_exists(const struct orb_metadata *meta, int instance);
// 返回topic的优先级
int ?orb_priority(int handle, enum ORB_PRIO *priority);
// 设置订阅topic更新的最小间隔,可用于防止订阅者过快的更新数据
int ?orb_set_interval(int handle, unsigned interval);
// 获取订阅topic更新的最小间隔
int orb_get_interval(int handle, unsigned *interval);

C++接口

C++的接口就简单很多,只需要new 对应的类就可以了

// 定义消息发布者
uORB::Publication<xxx_topic_s>      xxx_topic_pub{ORB_ID(xxx_topic)};
// 定义消息订阅者
uORB::Subscription      xxx_topic_sub{ORB_ID(xxx_topic)};
// 消息发布
xxx_topic_pub..publish(data);
// 消息订阅
if (xxx_topic_sub.updated()) {
    xxx_topic_sub.copy(&data);
}

添加新的消息

添加消息非常简单,在/msg目录下创建一个新的msg文件,然后在msg/CMakeLists.txt文件中添加新增加的文件即可

内部机制

自动生成代码

PX4使用了大量自动代码生成方法,减少用户的重复性代码编辑,比如Mavlink、uavcan、param的消息创建以及部分gazebo模型文件的生成。

uORB的代码生成脚本位于 msg/tools,使用python进行代码生成。

生成的文件在 build/xxx/msg/tmp目录下可以找到,包括一个头文件目录和一个cpp文件目录,阅读创建的文件内容可以大概了解主要创建了什么内容。以airspeed文件为例,airspeed.h文件中

//定义了消息的结构体
#ifdef __cplusplus
struct __EXPORT airspeed_s {
#else
struct airspeed_s {
#endif
    uint64_t timestamp;
    float indicated_airspeed_m_s;
    float true_airspeed_m_s;
    float air_temperature_celsius;
    float confidence;
#ifdef __cplusplus
#endif
};
// 申明消息的uORB元数据,用于uORB通过ORB_ID(_name)定位消息
ORB_DECLARE(airspeed);

airspeed.cpp文件中

constexpr char __orb_airspeed_fields[] = "uint64_t timestamp;float indicated_airspeed_m_s;float true_airspeed_m_s;float air_temperature_celsius;float confidence;";
// 定义消息的uORB元数据,对应头文件中的ORB_DECLARE(airspeed);
ORB_DEFINE(airspeed, struct airspeed_s, 24, __orb_airspeed_fields, static_cast<uint8_t>(ORB_ID::airspeed));
?

消息公告机制

消息是如何发布到uORB的呢,我们来看看消息advertise都做了哪些事情

// 消息公告的c接口
orb_advert_t orb_advertise(const struct orb_metadata *meta, const void *data)
// ->
// 调用uORB::Manager类的消息公告函数
uORB::Manager::get_instance()->orb_advertise(meta, data);
// ->
// 多消息实例公告函数的复用接口
orb_advert_t uORB::Manager::orb_advertise_multi(const struct orb_metadata *meta, const void *data, int *instance,ORB_PRIO priority, unsigned int queue_size)
// -> 
 ? ?// open消息节点
 ? ?int fd = node_open(meta, true, instance, priority);
?
 ? ? ? ?// ->
 ? ? ? ?// 调用消息节点管理类uORB::DeviceMaster的消息公告接口
 ? ? ? ?ret = _device_master->advertise(meta, advertiser, instance, priority);
?
 ? ? ? ? ? ?// ->
 ? ? ? ? ? ?// 创建消息节点,这个节点类定义了消息的主要读写方法
 ?          uORB::DeviceNode *node = new uORB::DeviceNode(meta, group_tries, devpath, priority);
?
 ? ?// 设置消息buffer的大小
 ? ?int result = px4_ioctl(fd, ORBIOCSETQUEUESIZE, (unsigned long)queue_size);
?
 ? ?// 发布消息以初始化消息对象
 ? ?result = orb_publish(meta, advertiser, data);
?
 ? ? ? ?// ->
 ? ? ? ?// 调用消息节点类的消息发布接口
 ? ? ? ?uORB::DeviceNode::publish(meta, handle, data);
?
 ? ? ? ? ? ?// ->
 ? ? ? ? ? ?// 调用消息写入接口
 ? ? ? ? ? ?ret = devnode->write(nullptr, (const char *)data, meta->o_size);
?
 ? ? ? ? ? ? ? ?// ->
 ? ? ? ? ? ? ? ?// 申请消息对象内存
 ?              if (nullptr == _data) {
 ?                  _data = new uint8_t[_meta->o_size * _queue_size];
 ?              }
 ? ? ? ? ? ? ?

我们将消息公告接口的主要调用过程整理了一下,梳理了一下类的调用关系,消息公告主要完成:

  • open消息的字符设备对象 node_open

  • 实例化消息节点对象 new uORB::DeviceNode

  • 为消息申请内存 new uint8_t[_meta->o_size * _queue_size]

类的调用层级为

uORB::Manager 
 ?  -> uORB::DeviceMaster 
 ? ? ?  -> uORB::DeviceNode 
 ? ? ? ? ?  -> cdev::CDev

消息发布和订阅的机制

梳理了消息公告的过程,uORB的内部机制就有一个雏形了,其他的接口也都封装在这几个类中

消息的主要操作方法主要封装在uORB::DeviceNode类中,主要就是消息的读写接口

这里不过多展开,大家可以结合源码分析几个接口函数

消息回调函数的机制

消息订阅有两种机制,异步订阅和消息回调

  • 异步订阅

    // 订阅消息的任务在循环中判断是否消息有更新,如果有更新则处理消息
    // 这是一个非阻塞的判断
    if (xxx_topic_sub.updated()) {
        xxx_topic_sub.copy(&data);
    }
  • 消息回调

    可以注册回调函数,在消息发生更新时触发回调函数,进行任务数据更新

    参考src/modules/vtol_att_control/vtol_att_control_main.h中的例子

    // 定义一个消息订阅回调类
    uORB::SubscriptionCallbackWorkItem _actuator_inputs_fw{this, ORB_ID(actuator_controls_virtual_fw)};
    // 注册消息回调函数
    _actuator_inputs_fw.registerCallback()

我们看看这个消息回调类都做了哪些事情

类的调用层级为

SubscriptionCallbackWorkItem 
    -> SubscriptionCallback 
        -> SubscriptionInterval

在registerCallback函数中,任务类将回调函数注册到消息节点中,在消息发生更新时会调用这个callback方法

而调用的方法为

// src/modules/uORB/SubscriptionCallback.hpp 
// SubscriptionCallbackWorkItem
void call() override
{
	// schedule immediately if updated (queue depth or subscription interval)
	if ((_required_updates == 0)
	    || (_subscription.get_node()->published_message_count() >= (_subscription.get_last_generation() + _required_updates))) {
		if (updated()) {
			_work_item->ScheduleNow();
		}
	}
}

通过 _work_item->ScheduleNow() 方法进行任务的重调度

这里涉及 px4::WorkItem 类,PX4是如何通过ScheduleNow方法调度 VtolAttitudeControl::Run()这个run函数的呢

这个我们在下一个分享 PX4的任务调度 中分析。

往期分享:

PX4-2-系统架构浅析_AcmeUav的博客-CSDN博客

工欲善其事-3-整理技术笔记(gitbook)_AcmeUav的博客-CSDN博客

使用3D地面站(AcmeGCS)的编队飞行_AcmeUav的博客-CSDN博客

这个博客转载自我个人的微信公众号,这里会不定期更新一些我整理的无人机相关知识和个人设计,欢迎添加关注。

?

?

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-13 09:25:42  更:2021-09-13 09:27:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/30 1:45:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码