注:这是看过好多文章总结出来的,转载了较多人的博客,希望有知道原出处的人把地址留下,我贴上来。在此谢谢各位前辈的总结。(我会在后续笔记中贴出在我自己的程序中对于uorb的使用)
进程与应用程序(传感器应用程序发送传感器数据到姿态过滤应用程序)之间的通讯是pixhawk软件架构的重要组成部分,进程(即所谓的节点)通过命名的总线交换消息称之为“主题”,在pixhawk中,一个主题仅包含一种消息类型,例如:vehicle_attitude主题传输包含姿态结构(翻滚,俯仰和偏航估算)。节点可以在总线,主题上发布一跳消息或者订阅总线,主题。通讯双方之间并不知道在与谁通讯,可以存在多个发布或一条消息有多个订阅者。这种设计模式可以防止锁定的问题。
pixhawk的发布订阅机制是通过“微对象请求代理”(uORB)来实现的。
快速入门:
在深入细节之前,以下是一对简单,完整的发布/订阅模型。发布者发布一条名为“random——integer”的主题并用随机整数更新该主题。订阅者检查并打印这些更新。
- topic.h
- /* declare the topic */
- ORB_DECLARE(random_integer);/* define the data structure that will be published where subscribers can see it */
- struct random_integer_data {int r;};
- publisher.c
- #include <topic.h>/* create topic metadata */
- ORB_DEFINE(random_integer);/* file handle that will be used for publishing */
- static int topic_handle;
- int init(){/* generate the initial data for first publication */
- struct random_integer_data rd = { .r = random(), };/* advertise the topic and make the initial publication */
- topic_handle = orb_advertise(ORB_ID(random_integer), &rd);
- }
- int update_topic(){/* generate a new random number for publication */
- struct random_integer_data rd = { .r = random(), };/* publish the new data structure */
- orb_publish(ORB_ID(random_integer), topic_handle, &rd);
- }
- subscriber.c
- #include <topic.h>/* file handle that will be used for subscribing */
- static int topic_handle;
- int init(){/* subscribe to the topic */
- topic_handle = orb_subscribe(ORB_ID(random_integer));
- }
- void check_topic(){
- bool updated;struct random_integer_data rd;/* check to see whether the topic has updated since the last time we read it */
- orb_check(topic_handle, &updated);
- if (updated) {/* make a local copy of the updated data structure */
- orb_copy(ORB_ID(random_integer), topic_handle, &rd);
- printf("Random integer is now %d\n", rd.r);
- }
- }
topic.h
/* declare the topic */
ORB_DECLARE(random_integer);/* define the data structure that will be published where subscribers can see it */
struct random_integer_data {int r;};
publisher.c
#include <topic.h>/* create topic metadata */
ORB_DEFINE(random_integer);/* file handle that will be used for publishing */
static int topic_handle;
int init(){/* generate the initial data for first publication */
struct random_integer_data rd = { .r = random(), };/* advertise the topic and make the initial publication */
topic_handle = orb_advertise(ORB_ID(random_integer), &rd);
}
int update_topic(){/* generate a new random number for publication */
struct random_integer_data rd = { .r = random(), };/* publish the new data structure */
orb_publish(ORB_ID(random_integer), topic_handle, &rd);
}
subscriber.c
#include <topic.h>/* file handle that will be used for subscribing */
static int topic_handle;
int init(){/* subscribe to the topic */
topic_handle = orb_subscribe(ORB_ID(random_integer));
}
void check_topic(){
bool updated;struct random_integer_data rd;/* check to see whether the topic has updated since the last time we read it */
orb_check(topic_handle, &updated);
if (updated) {/* make a local copy of the updated data structure */
orb_copy(ORB_ID(random_integer), topic_handle, &rd);
printf("Random integer is now %d\n", rd.r);
}
}
发布:
发布分为三个独立但又相关的行为;确定主题,公告主题和发布主题更新。
确定主题:
pixhawk系统为提供部件之前的通用接口定义了许多标准主题,如果发布者想使用标准主题和相关的数据结构不需要做额外的工作。
自定义主题
要定义一个自定义主题,发布者需要提供给订阅者一个头文件(参考上面的topic.h),在这个头文件中必须有:
1.用主题的名称做作为参数调用ORB_DECLARE()宏来定义一个实例
2.定义一个结构体,用来描述将要用来发布的数据结构
主题的名称应该要具有描述性,pixhawk的管理使用下划线来分割主题名称为独立的部分并且首选更通用的术语表示元件的名称。
例如:raw sensor data发布在sensors_raw主题
除了头文件,发布者必须要具有使用ORB_DEFINE()宏在源码中定义一个实例,当固件被构建时,他将被编译并且链接到固件。
可选主题:
如果一个主题通过一个软件组件来发布,那么它属于可选主题,并且可能不会存在于发布后的固件,这种情况下,头文件也可以改用ORB_DECLARE_OPTIONAL()宏来替代,以这种方式声明主题,发布者不需要专门来处理什么。但在下面讨论的也有额外要处理的情况,当处理可选主题时订阅者必须要注意。
公告主题:
在数据被发布到一个主题前,它必须被公告,发布者可以使用下面的API来公告一个新的主题。
- extern int orb_advertise(const struct orb_metadata *meta, const void *data);
extern int orb_advertise(const struct orb_metadata *meta, const void *data);
公告也可以发布初始化数据到主题,meta参数是传递给API的一个指针,指向由ORB_DEFINE()宏定义好的数据,通常使用ORB_ID()宏来根据主题名称获取该指针。请注意,虽然主题更新可以从中断处理函数发布,公告主题必须在常规的线程上下文中执行。
多个发布:
只有一个发布者可以具有发布一次一个主题,但是该主题手柄可以被关闭,因为是文件描述符,可以通过close()函数关闭。
发布更新:
一旦公告了一个主题,公告主题后返回的句柄可使用下面的API来发布主题更新。
- extern int orb_publish(const struct orb_metadata *meta, int handle, const void *data);
extern int orb_publish(const struct orb_metadata *meta, int handle, const void *data);
U
ORB不换冲多个更新,当用户检查一个主题,他们将只能看到最新的更新。
订阅者:
订阅主题的要求如下:
1.调用ORB_DEFINE()或ORB_DEFINE_OPTIONAL()宏(在订阅者的头文件中包含他们)
2.发布到主题的数据结构定义(通常与发布者使用同一头文件)
如果满足上面的条件后,订阅者可以使用下面的api来订阅一个主题:
- extern int orb_subscribe(const struct orb_metadata *meta);
extern int orb_subscribe(const struct orb_metadata *meta);
如果可选主题不存在于固件之中,订阅到可选的主题将会失败,但其他主题即便发布者没有进行公告也会订阅成功,这样可大大降低系统对启动顺序的安排。
这里没有专门来限制一个任务的最大订阅数。
要取消订阅一个主题,可以用下面的API:
- extern int orb_unsubscribe(int handle);
extern int orb_unsubscribe(int handle);
拷贝数据到主题:
订阅者不能引用ORB中存储的数据或其他订阅共享的数据,而是在订阅者请求时从ORB拷贝数据到订阅者的临时缓冲区。副本拷贝的方式可以避免锁定ORB的问题,并保持两者之间(发布者,订阅者)的API接口简单。它也允许订阅者在必要的时候直接修改拷贝副本的数据供自己使用。
当订阅者想要把主题中的最新数据拷贝一份全新的副本,可以使用:
- extern int orb_copy(const struct orb_metadata *meta, int handle, void *buffer);
extern int orb_copy(const struct orb_metadata *meta, int handle, void *buffer);
拷贝是以原子操作进行的,所以可以保证获取到发布者最新的数据。
检查更新:
订阅者可以使用下面的API来检查一个主题在发布者最后更新后,有没有人调用过orb_copy来接收,处理:
- extern int orb_check(int handle, bool *updated);
extern int orb_check(int handle, bool *updated);
如果主题在被公告前就有人订阅,那么这个API将返回“not-updated”直到主题被公告。
发布时间戳:
订阅者可以使用下面的API来检查一个主题最后发布的时间。
- extern int orb_stat(int handle, uint64_t *time);
extern int orb_stat(int handle, uint64_t *time);
需要注意的是,要小心的使用这个调用,因为不能保证再调用返回后不久主题就不会被发布(调用返回后不久,主题可能马上又被发布,导致最后更新时间错误)
uORB的管理罗辑是通过创建线程后台运行方式实现。
uORB深入探索
uORB是pixhawk系统中非常重要的一个模块,它肩负了整个系统的数据传输任务,所有的传感器数据,GPS,ppm信号等都要从芯片获取后通过uORB进行传输到各个模块进行计算处理。
1.uORB的架构简述:
uORB是一套跨进程的IPC通讯模块。在pixhawk中,所有的功能被独立以进程模块为单位进行实现并工作。而进城间的数据交互尤为重要,必须要能够符号实时,有序的特点。
pixhawk使用nuttx实时ARM系统,而uORB对于nuttx而言,它仅仅是一个普通的文件设备对象,这个设备支持open,close,read,write,ioctl以及poll机制。通过这些接口的实现,uORB提供了一套“点对多”的跨进程广播通讯机制。“点”指的是通讯消息的“源”,“多”指的是一个源可以有多个用户来接受,处理。而源和用户的关系在于,源不需要去考虑用户是否课余i收到某条被广播的消息或什么时候收到这条消息。它只是需要单纯的把要广播的数据推送到uORB的消息总线上,对于用户而言,源推送了多少次的小心也不重要,重要的是取回最新的这条消息。
2.uORB的实现位于固件源码的src/modules/uORB/uORB.cpp文件,它通过重载CDev基类来组织一个uORB的设备实例。并且完成Read/Write等功能的重载。uORB的入口点是uorb_main函数,在这里它检查uORB的启动参数来完成对应的功能,uORB支持start/test/status这3条启动参数,在pixhawk的rcS启动脚本中,使用start参数来进行初始化,其他2个参数分别用来进行uORB功能的自检和列出uORB的当前状态。
在rcS中使用start参数启动后,uORB会创建并初始化它的设备实例,其中的实现大部分都在CDev基类完成。这个过程类似于Linux设备驱动中的Probe函数,通过init调用完成设备的创建,节点注册以及派遣例程的设置等。
源码解读:(最新版本的uORB)
uORB文件夹说明
1.uORB文件夹结构
2.文件/目录说明
objects_common.cpp:通用接口标准主题定义集合,如添加新主题就在这里定义。
uORBMap.hpp:对象请求节点链表管理(驱动节点)
uORBSet.hpp:对象请求节点链表管理(非驱动节点)
Publication.cpp/ Publication.hpp:在不同的发布中遍历使用
Subscription.cpp/ Subscription.hpp:在不同的发布中遍历使用
uORB.cpp:uORB的实现
uORB.h:uORB的头文件
uORBCommon.hpp:uORB公共部分变量定义实现
uORBCommunicator.hpp:远程订阅的接口实现,实现了对不同的通信通道管理,如添加、移除订阅者,可以基于TCP/IP或者fastRPC;传递给通信链路的实现,以提供在信道上接收信息的回调。
uORBDevices_nuttx.cpp:节点操作,close,open,read,write等
uORbMain.cpp:uORB入口
uORBManager.hpp:uORB功能函数实现的头文件
uORBManager_nuttx.cpp:uORB功能函数的实现(Nuttx)
uORBManager_posix.cpp:uORB功能函数的实现(Posix)
uORBTest_UnitTest.cpp:uORB测试
uORBTest_UnitTest.hpp:uORB测试头文件,包括主题定义和声明等