一天精通无人机第 19 讲 中级篇系列:uORB 原理与使用

file

飞控程序中内部消息传递采用的是异步消息机制uORB。它的设计理念很有趣,它可以实现不同模块中的数据快速通讯,并且以异步通讯为基本原则,也就是说在通讯过程中发送者只负责发送数据,而并不关心数据由谁接收,也不关心接收者是否能将所有的数据都接收到;而对于接收者来说并不关心数据是由谁发送的,也不关心在接收过程中是否将所有数据都接收到。关于uORB的设计原理我们会在后续章节中仔细讲述。这里只做一个简单的介绍,我们来举一个简单的例子说明一下uORB的设计原理:

有一个教室编号208,里面的黑板上可以写上一些文字内容,有一个同学名叫小强,他每隔1个小时就会来到208教室,先将黑板上原来的文字擦除,然后在黑板上写下一段新文字,之后离开208教室。而另外有一个同学叫小朋,他每隔3个小时就会来到208教室,将黑板上的文字抄写到自己的笔记本上,然后离开。我们可以用下列图例来说明一下这个过程:

file

我们可以看到,小强每次发布数据之后就会离开208教室,至于有没有人或是谁来读取他留下的文字,小强自己并不关心,也不再乎自己发布的数据是否有人收到了。而对于小朋来说,他每隔3小时来读取一次数据,至于这些数据是谁发布的他也不关心。他每隔3小时来读黑板上的文字时,其实小强已经在黑板上留言3次了,前两次的文字已经被小强擦除了,小朋看到的永远是小强留下最新的内容。

上面这个例子实际上就是uORB的实现原理:

发送者:小强每隔1小时发布一次数据orb_publish

接收者:小朋每隔3小时接收一次数据orb_copy

file

uORB在在数据发布与接收过程中并不保证发送者的所有数据都可以被接收者收到,而只保证接收者在想要接收时能收到最新的数据。而发送与接收的分离可以使飞程中各个模块相互独立,互不干扰。实际上一个uORB可以由多个发送者发布,也可以被多个接收者接收。实际上同一个uORB可以由多个发布者进行发布,而也可以由多个接收者接收,也就是说他们之间是多对多的关系。

下面我们来看一个具体的例子:

  1. 在msg文件夹中创建一个叫做extctl_sp.msg的文件,表示我们创建了一个新的uORB,它的名字叫作extctl_sp,其内容如下:
bool run_pos_control
bool run_alt_control
bool run_yaw_control
float32 sp_yaw
float32 sp_x
float32 sp_y
float32 sp_z
float32 vel_sp_x
float32 vel_sp_y
float32 vel_sp_z

这是作者在做“外部控制”模式下所用到的一个uORB,在本节中读者可以不用了解其中变量的具体含义,只知道我们需要在extctl_sp.msg中定义一些指定属性即可。不过需要注意其定义的语法与C/C++类似但有些不同,参考PX4中现有的msg文件内容即可。

  1. 在msg文件夹中的CMakeLists.txt中添加刚刚我们添加的extctl_sp.msg,表示对其做编译处理:

file

即程序在编译时会根据extctl_sp.msg生成extctl_sp.h和extctl_sp.cpp文件,也就是我们在程序中所用到的结构定义:

#pragma once

#include 
#ifndef __cplusplus
#endif
#ifdef __cplusplus
struct __EXPORT extctl_sp_s {
#else
struct extctl_sp_s {
#endif
    uint64_t timestamp; // required for logger
    float sp_yaw;
    float sp_x;
    float sp_y;
    float sp_z;
    float vel_sp_x;
    float vel_sp_y;
    float vel_sp_z;
    bool run_pos_control;
    bool run_alt_control;
    bool run_yaw_control;
    uint8_t _padding0[1]; // required for logger
#ifdef __cplusplus
#endif
};

/* register this as object request broker structure */
ORB_DECLARE(extctl_sp);
  1. 发布者发布uORB时需要做两步操作:

(1)公告/多重公告uORB;

(2)发布uORB。

我们来看下面的的发布者的例子:

//首先在头文件中包含此uORB
#include <uORB/topics/extctl_sp.h>
...
//uORB中msg生成后的结构体
struct extctl_sp_s _orb_sp = { 0 };
//公告主题
int _orb_sp_instance = -1;
//多重公告uORB
orb_advert_t _orb_sp_topic = orb_advertise_multi(ORB_ID(extctl_sp), &_orb_sp, &_orb_sp_instance, ORB_PRIO_DEFAULT);
...
//对_orb_sp结构体对象进行赋值
...
//发布uORB
int ret = orb_publish(ORB_ID(extctl_sp), _orb_sp_topic, &_orb_sp);
  1. 接收者接收uORB时需要做三步操作:

(1)订阅uORB;

(2)判断uORB数据是否有更新。

(3)复制uORB数据内容到本地内存。

我们来看下面的的者接收的例子

//首先在头文件中包含此uORB
#include <uORB/topics/extctl_sp.h>
...
//uORB中msg生成后的结构体
struct extctl_sp_s extctl_sp_s = { 0 };
//订阅uORB
int extctl_sp_sub = orb_subscribe(ORB_ID(extctl_sp));
...
bool updated = false;
//判断uORB是否有更新
orb_check(extctl_sp_sub, &updated);
if (updated)
{
    //复制uORB数据内容到本地内存
    orb_copy(ORB_ID(extctl_sp), extctl_sp_sub, &extctl_sp_s);
    //使用本地内存中结构体对象
    ...
}

这就是uORB的使用方法,注意,发布和订阅只需要执行一次即可,而发布、检查更新和接收可以执行多次。下面我们来看看uORB中比较关键的几个函数的定义:

//公告
orb_advert_t orb_advertise(const struct orb_metadata *meta, const void *data, unsigned int queue_size = 1);
//多重公告
orb_advert_t orb_advertise_multi(const struct orb_metadata *meta, const void *data, int *instance, int priority, unsigned int queue_size = 1);
//取消公告
int orb_unadvertise(orb_advert_t handle);
//发布
int orb_publish(const struct orb_metadata *meta, orb_advert_t handle, const void *data);
//订阅
int orb_subscribe(const struct orb_metadata *meta);
//多重订阅
int orb_subscribe_multi(const struct orb_metadata *meta, unsigned instance);
//取消订阅
int orb_unsubscribe(int handle);
//检查是否有数据更新
int orb_check(int handle, bool *updated);
//接收,复制数据到本地内存
int orb_copy(const struct orb_metadata *meta, int handle, void *buffer);
//检查uORB是否存在
int orb_exists(const struct orb_metadata *meta, int instance);
//取得优先级
int orb_priority(int handle, int32_t *priority);

关于这些函数的用法、参数及反回值不做过多的说明,这些函数的参数和反回值源代码中有详细的说明,还是比较容易理解和使用的。

下期预告:《中级篇:驱动开发》

file