px
4_simple_app(PX4-Autopilot/src/exampes/px4_simple_app)
,
这个程序是用
c
语言调用
orb API
和
poll
机制订阅和发布通讯数据,但是这个例子并不是既有接收又有发送的完整例子。例子中订阅了
主题
vehicle_acceleration
,但是程序中没有发布
(publish)
这个主题数据的程序。反过来,这个程序
advertise/publish
主题
vehicle_
attitude
,但是没有
subscribe/copy
这个主题的程序。所以,这个例子是不完整的。
这个例子不好做,因为如果从自己建立主题开始可能其过程过于复杂了。
介绍建立主题本身就是一个过程,因为这里面有代码自动生成的软件。
传统的基于
client
和
server
通讯方法是
client
向
server
请求发送数据,
server
接收请求,然后把请求的数据发送给
client
。这个没有问题,但是,反过来做双向通讯,就需要两边都有
client
和
server
,这就增加了程序的复杂性了。确实,
orb
机制就是想解决任意两个程序模块之间的双向通讯。
有了
orb
机制,你并不需要自己做
client/server
。
orb
帮助你做了,当然也是用了
client/server
的办法,只是隐藏在了底层。
在
orb
机制中,被传输数据的数据结构在发送端和接收端都是已知的,或者说,数据结构在两边是共享的。比如:要传输的是一个整形数或者字符串,或者一个复杂的数据结构,数据可以是任意的,但结构是确定的。一个数据结构是一个
对
数据实体的描述
,它有自己的名字
。比如
:这里为了便于理解
,我们把例子中的
vehicle_acceleration
(数据本身有点复杂)
的主题名字改成
position
。对于
数据实体为
position
的数据
,我们用数据结构描述如下:
str
uc
t position
{ int x
,
int y
,
int z};
position
描述了一个飞行器在三维空间中的坐标。
一个程序
A
请求另一个叫
orb
的程序将
position
数据发送给
A
,请求发送数据的程序
A
只需要把这个数据结构的名字发送给程序
orb
,然后等待(
px4_
poll
)数据更新通知事件发生(
POLLIN
)。
orb
程序在接到
A
的请求后知道了请求发送的数据的名字是
position
。
orb
不是直接将
position
这个名字对应的数据发送给
A
,而是注册
position
这个名字和请求程序
A
的名字,这个注册记载了
A
程序想得到
position
数据。然而
,
真正产生
position
数据的程序不是
orb
这个程序,而是程序
C
。程序
C
定期从传感器获取当前的
position
数据并向数据交换存储区发送数据,当然在此之前,也要注册一下
postition
数据的名字以及发送程序
C
的名字,这个注册与
ORB
对
A
的请求注册是一个地方,不一样的是,
possition
名字后还有个发送程序的名字。这样,这个注册表除了
position
名字,还有请求者的名字(多个,可以想象成一个链表)和发送者的名字(多个,可以想象成一个链表)。
一旦
position
数据更新,由于
orb
可以从注册表中知道“
A
想拿到
position
数据”,所以,可以通知(触发
POLLIN
事件)程序
A
取走数据。另外
,
更新
position
的程序会在注册表中标识它更新了当前数据,所以,
orb
也可以让
A
程序知道当前的
position
数据是哪个
程序发送的,
可
供A选择。
orb_
advertise_multi
中的
instance
是输出参数
,
这个
instance
由
orb
程序产生并返回给应用程序
,应用程序可以用这个
instance
向
orb
程序说明我要哪个主题数据
。
想象一个两个程序都在更新
position
数据的例子
:
北斗模块
pub_
handle
= orb_advertise_multi(ORB_ID(
position
), &beidou_
position
, &instance_
beidou
, 1));
---
将北斗传感器数据存到
beidou_speed
变量中,然后
publish
(
ORB_
ID(
position
), pub_
handle
,
&
beidou_
position
);
负责发送北斗数据的程序执行上面语句
,则
instance_
beidou
就存储了标识北斗数据发送者的信息
,
gps
模块
pub_
handle
= orb_advertise_multi(ORB_ID(
position
), &gps_
position
, &instance_
gps
,
1));
---
将
gps
传感器数据存到
gps_
position
变量中,然后
publish
(
ORB_
ID(
position
), pub_
handle
,
&
beidou_
position
);
负责发送
GPS
数据的程序执行上述语句
,则
instance_
gps
就存储了标识
GPS
数据发送者的信息
;
A
程序如果想得到北斗数据
,则
s
ub_
fd
= orb_subscribe_multi(ORB_ID(
position
), instance_beidou);
copy(ORB_ID(
position
),
sub_fd,
&beidou_
position
);
想得到
GPS
数据
,则
s
ub
= orb_subscribe_multi(ORB_ID(
position
), instance_gps);
copy(ORB_ID(speed),
sub_fd,
&
gps
_
position
);
如果
position
这个数据只有一个发送源
,就不必区分了
;
所谓的
instance_
beidou
和
instance_gps
里面存的是什么
,其实不必关心
,就是一整数,是由
orb
程序生成的
。
每个主题都有对应的
instance
,如果只有一个数据发送源,
instance
就是
0
。在
程序语言中我们把
instance
叫实例
,第一次调用
orb_subscribe_
multi
给它编号为
1,
第二次调用
orb_subscribe_multi,
编号为
2
,同一个程序被调用了两次,每次执行的虽然是同一个程序,但是需要当两个不同程序来看待,这样就需要区分,通常用数字区分,这就是
instance
里面存储内容的意义。
从上面所述可以看出几点,首先
,
交换数据的存储区是两个模块
A
和
C
之间共享的
,当前放在存储区的
position
数据是谁要的,是谁发的,在注册表中都有标识
,
ORB
负责告诉你
。
position
数据长什么样
?因为在注册表中注册了
position
的名字,所以,数据结构两边都是知道的。
另外
,
A
不需要知道
C
的存在,因为可能存在多个
C
发送
position
数据
(
确实存在这样一种情况,虽然数据来源不一样,但是数据结构的名字是一样的,比如:为了获得可靠数据
,
安装了多种传感器,传感器数据是一样的,其数据结构当然是一样的,没有必要起不同的名字
)至于是
哪种传感器程序发送的
position
数据,
由
orb
程序在
C
程序以及其他程序在注册
position
数据时加以区分。
其次
,
C
程序不需要关心取
position
数据的是谁,因为可能存在多个程序在请求发送
position
数据,至于是程序
A
1
还是程序
A2
请求发送则由
orb
程序在
A
程序注册请求
position
的时候加以区分
。
最后可以知道
,
ORB
只是一个代理,并不真正产生
position
数据。
所有
PX
4
能传输的数据的数据结构在
PX
4-Autopilot/msg
中都定义好了,
PX4
通过代码自动生成了可以用在
C/C++
中的数据结构定义
,它们被保存在目录
PX4-Autopilot/build/uORB/topics
中。
px4_
poll
机制是这样的
,
poll
这个动作
是一个让你的线程停下来的操作,停下来的目的是
与orb
机制打交道的还有另外一个程序
C
,它将在适当的时候通知你的线程,你可以继续运行了,因为程序C已经改变了你程序中变量的值,你可以检查这个值变成什么样了,你自己做判断
。通常这个值叫
POLLIN
,表明你想要的数据准备好了
,可以把它取走了
。你可以把你的程序叫
client
,
把这个
C
线程叫
server
。
在这个例子中,这个
server
是一个PX4中运行的模块,在
: PX4-
Autopilot
/src/module/sensors/vehicle_acceration
目录中。事实上,在PX4中几乎所有module(模块)都可以看作是server。
综上所述
,
orb
机制确实在多个模块之间实现了多个
client
和多个
server
,在orb机制下,client和server两边都知道要交换的数据的数据结构。
题外话,这自然让我想到,DDS这种机制与uorb机制本质是一样的,由于两边的数据结构都知道,因此,写和读符合这些数据结构的数据的程序也可以自动生成了,uorb没有做到这一点,然而这正是DDS的核心技术之一。ROS2已经把DDS作为offboard计算机与PX4 uORB通讯的机制,实现了两对orb机制进行通讯,进一步,
ROS团队还准备把完整的DDS放进PX4,不知道何时能实现。如果真实现了,MAVSDK怎么办?mavlink团队和ROS团队竞争激烈,但是作为用户,我们只能有一种选择吧。
应用程序如果用到一个主题,
比如
px4
的官方用例
px4_simple_app.c
中,一般都需要
include
2
个文件:
#include <uORB/uORB.h>
#include <uORB/topics/vehicle_acceleration.h>
uORB.h
是在
platforms/common/uORB
目录中,是编译前就存在的,是
uORB
系统通用的一些机制的定义。vehicle_acceleration.h
是在
/build/uORB/topics
目录中。
vehicle_acceleration
这个主题对应的定义
是在编译过程中自动生成的。
在vehicle_acceleration.h文件中:
ORB_
DECLARE(vehicle_acceleration);
ORB_
DECLARE()
是一个宏,它在
uORB.h
中定义,这句话的意思是定义一个
vehicle_acceleration
的
orb
对象。我们看这个宏的定义:
//
#if defined(__cplusplus)
# define ORB_DECLARE(_name) extern "C" const struct orb_metadata __orb_##_name __EXPORT
#else
# define ORB_DECLARE(_name) extern const struct orb_metadata __orb_##_name __EXPORT
#endif
///
在代入vehicle_acceleration这个名字后展开得到:
extern const struct orb_metadata __orb_
vehicle_acceleration
__EXPORT
这就是
vehicle_acceleration
这个
orb
对象的名字,叫
__orb_vehicle_acceleration
。
接收
PX
4
的消息,应用程序需要订阅(
subscribe
)主题,订阅的目的是告诉
PX4
的发送方,我要你传输符合这个主题的数据,我等待接收。执行下面的
API
就可以订阅一个主题:
int sensor_sub_fd = orb_subscribe(ORB_ID(vehicle_acceleration));
orb_subscribe
是
PX4
的
uORB
系统提供的一个
API
,其他
API
都在
uORB.h
中定义了,所以,也需要
include <uORB.h>
,
API
在这里都做了声明,
详细的
API
定义可以参看:
uORBManager.hpp
。
在
subscribe
函数中,第一个参数是函数
ORB_
ID
(),这个函数可以
从主题名字获得主题
id
,在编译时
,已经为每个主题名字编了一个序号,返回的就是这个序号,见
build/px4_sitl_default/uORB/topics/uORBTopics.h
。
ORB_
ID()
也是一个宏,它在
uORB.h
中定义,我们来看
ORB_ID
这个宏定义:
//
#define ORB_ID(_name) &__orb_##_name
///
在代入vehicle_acceleration这个名字后展开得到:
&__orb_vehicle_acceleration
这样
int sensor_sub_fd = orb_subscribe(ORB_ID(vehicle_acceleration));
就变成了
:
int sensor_sub_fd = orb_subscribe(
&__orb_
vehicle_acceleration));
这样,orb_subscribe
函数传入的就是一个指向
__orb_vehicle_acceleration
对象的指针。
__orb_vehicle_acceleration
这个对象长什么样?
其实在
vehicle_acceleration.
cpp
中已经有定义
,
也是在编译时自动生成的
,存放在
build/px4_sitl_default/msg/topics_sources
中。我们来看这个文件中的内容:
#include <uORB/topics/vehicle_acceleration.h>
#include <uORB/topics/uORBTopics.hpp>
//
上述略去其他
.
h
文件
constexpr char __orb_vehicle_acceleration_fields[] = "uint64_t timestamp;uint64_t timestamp_sample;float[3] xyz;uint8_t[4] _padding0;";
ORB_DEFINE(vehicle_acceleration, struct vehicle_acceleration_s, 28, __orb_vehicle_acceleration_fields, static_cast<uint8_t>(ORB_ID::vehicle_acceleration));
其中
ORB_DEFINE
是一个宏,在
uORB.h
中定义,我们来看这个宏:
ORB_
DEFINE()
宏定义:
//
#define ORB_DEFINE(_name, _struct, _size_no_padding, _fields, _orb_id_enum) \
const struct orb_metadata __orb_##_name = {
#_name,
sizeof(_struct),
_size_no_padding,
_fields,
_orb_id_enum
}; struct hack
//
ORB_DEFINE(vehicle_acceleration, struct vehicle_acceleration_s, 28, __orb_vehicle_acceleration_fields, static_cast<uint8_t>(ORB_ID::vehicle_acceleration))
宏替换展开后
:
//
const struct orb_metadata __orb_
vehicle_acceleration
= {
“
vehicle_acceleration”
,
sizeof(
vehicle_acceleration_s
),
28
,
"uint64_t timestamp;uint64_t timestamp_sample;float[3] xyz;uint8_t[4] _padding0;",
150
}; struct hack
//
这里
vehicle_acceleration_s
在
vehicle_acceleration.h
已经定义:
struct vehicle_acceleration_s {
uint64_t timestamp;
uint64_t timestamp_sample;
float xyz[3];
uint8_t _padding0[4]; // required for logger
}
可以简单计算
,这个结构的尺寸是
28
个字节。
sizeof
(struct vehicle_acceleration_s)
算出来也是
28,150
是在
uORB
系统为
vehecle_acceleration
这种对象的一个编号,在
uORBTopics.h
中已经有定义。
orb_
metadata
类型也已经在
uORB.h
中定义如下
:
struct orb_metadata {
const char *o_name; /**< unique object name */
const uint16_t o_size; /**< object size */
const uint16_t o_size_no_padding;
/**< object size w/o padding at the end (for logger) */
const char *o_fields;
/**< semicolon separated list of fields (with type) */
uint8_t o_id; /**< ORB_ID enum */
};
这样就知道
,
__orb_vehicle_acceleration
是一个类型为
orb_metadata
的对象,在uORB机制中,这个对象叫object request broker,对象请求代理。
orb_subscribe
要的是指向这个对象的指针
,这个对象是个常量,这个常量是
orb_metadata
类的一个实例
(
instance
)
。有了这个对象请求代理,uORB机制就可以用它来读写vehicle_acceleration这个主题的数据,因为orb机制已经知道应用程序想要的数据的尺寸。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)