Vela sensor uORB 框架学习
1. 概述
uORB 是一个用于嵌入式系统的发布-订阅机制,是一个异步消息传递系统,主要用于不同模块、线程之间的通信。它的设计类似于ROS (Robot Operating System) 的话题机制,但更加轻量级,适合在资源受限的嵌入式系统中使用。
uORB 主要特点:
-
轻量级的发布-订阅消息传递系统
-
支持多个发布者/订阅者
-
支持多实例主题
-
基于文件系统的接口
-
支持数据队列
-
支持消息批处理和频率控制
2. 架构设计
uORB 系统主要由以下几个部分组成:
2.1 核心组件
主题 (Topics):
-
预定义的消息类型,用于数据交换
-
每个主题有唯一的名称和固定的数据结构
元数据 (Metadata):
-
存储主题的信息,包括名称、大小等
-
通过
orb_metadata
结构体定义
对象 (Objects):
-
主题的实例,可以有多个实例
-
通过
orb_object
结构体表示
状态 (State):
-
存储主题的运行状态
-
通过
orb_state
结构体表示
2.2 工作流程
广告 (Advertise):
-
发布者声明它将发布某个主题的消息
-
创建主题的设备节点
订阅 (Subscribe):
-
订阅者打开主题的设备节点来接收数据
发布 (Publish):
-
发布者向主题写入新数据
复制 (Copy):
-
订阅者从主题读取最新数据
2.3 文件系统结构
-
uORB 使用设备文件系统来实现主题的访问:
-
主题设备节点位于
/dev/uorb/
目录下 -
每个主题的设备节点命名为:
/dev/uorb/<topic_name><instance>
3. API 接口
3.1 主题定义
// 声明主题
ORB_DECLARE(topic_name);// 定义主题
ORB_DEFINE(topic_name, struct_type, format_string);// 获取主题元数据
#define ORB_ID(name) &g_orb_##name
3.1.1 主题定义详细说明
在 uORB 系统中,主题定义是通信的基础。每个主题都有特定的数据结构和唯一的名称。以下是主题定义的详细解释和示例:
主题定义步骤
-
声明主题 (ORB_DECLARE):
在头文件中声明主题,让其他模块可以使用
作用:告诉编译器该主题存在,但不提供实现
-
定义主题 (ORB_DEFINE):
在源文件中定义主题,提供实际实现
作用:创建全局主题元数据结构体
-
获取主题元数据 (ORB_ID):
获取主题的元数据引用
作用:在使用 API 函数时标识特定主题
示例:温度传感器主题
1. 头文件 (sensor/temp.h)
#ifndef _SENSOR_TEMP_H
#define _SENSOR_TEMP_H#include <uORB/uORB.h>/* 温度传感器数据结构 */
struct sensor_temp {orb_abstime timestamp; /* 时间戳 (微秒) */float temperature; /* 温度值 (摄氏度) */uint8_t precision; /* 精度 */
};/* 声明温度传感器主题 */
ORB_DECLARE(sensor_temp);#endif /* _SENSOR_TEMP_H */
2. 源文件 (sensor/temp.c)
#include <sensor/temp.h>/* 定义温度传感器主题 */
#ifdef CONFIG_DEBUG_UORB
ORB_DEFINE(sensor_temp, struct sensor_temp, "fffff");
#else
ORB_DEFINE(sensor_temp, struct sensor_temp, "");
#endif
3. 使用主题
#include <sensor/temp.h>/* 获取温度传感器主题元数据 */
orb_id_t temp_meta = ORB_ID(sensor_temp);
解释
-
ORB_DECLARE(sensor_temp):
作用:在头文件中声明主题,创建外部引用
展开为:extern const struct orb_metadata g_orb_sensor_temp
允许其他文件引用该主题而不需要了解其内部实现
-
ORB_DEFINE(sensor_temp, struct sensor_temp, "fffff"):
作用:在源文件中定义主题,创建实际的元数据结构体
参数:
sensor_temp
:主题名称
struct sensor_temp
:主题使用的数据结构
"fffff"
:格式字符串(调试模式下使用,用于格式化输出)
展开为:
-
ORB_ID(sensor_temp):
作用:获取主题元数据的引用,用于API调用
展开为:&g_orb_sensor_temp
在使用 orb_advertise()
, orb_subscribe()
等函数时必需
自定义主题示例
如果要创建自己的自定义主题,请按照以下步骤操作:
1. 创建头文件 (my_topic.h)
#ifndef _MY_TOPIC_H
#define _MY_TOPIC_H#include <uORB/uORB.h>/* 自定义数据结构 */
struct my_data {orb_abstime timestamp; /* 时间戳 (微秒) */int32_t value_a; /* 第一个值 */float value_b; /* 第二个值 */uint8_t flags; /* 标志位 */
};/* 声明主题 */
ORB_DECLARE(my_topic);#endif /* _MY_TOPIC_H */
2. 创建源文件 (my_topic.c)
#include "my_topic.h"/* 定义主题 */
#ifdef CONFIG_DEBUG_UORB
ORB_DEFINE(my_topic, struct my_data, "ifb");
#else
ORB_DEFINE(my_topic, struct my_data, "");
#endif
3. 在应用中使用
#include "my_topic.h"void publish_my_data(void) {struct my_data data;/* 设置数据 */data.timestamp = orb_absolute_time();data.value_a = 123;data.value_b = 45.67f;data.flags = 0x01;/* 广告并发布数据 */int fd = orb_advertise(ORB_ID(my_topic), &data);orb_close(fd);
}
通过这种方式,您可以为系统添加任意数量的自定义主题,实现各种模块之间的通信。
3.2 广告和发布
// 广告主题
int orb_advertise(const struct orb_metadata *meta, const void *data);// 广告多实例主题
int orb_advertise_multi(const struct orb_metadata *meta, const void *data, int *instance);// 广告带队列的主题
int orb_advertise_queue(const struct orb_metadata *meta,const void *data, unsigned int queue_size);// 发布数据
int orb_publish(const struct orb_metadata *meta, int fd, const void *data);// 自动发布(如果文件描述符不存在则先广告)
int orb_publish_auto(const struct orb_metadata *meta, int *fd, const void *data, int *instance);
3.2.1 广告和发布详细说明
在 uORB 系统中,发布者需要先"广告"一个主题,然后才能向该主题发布数据。"广告"操作会在系统中注册该主题,并创建相应的设备节点。以下是各个函数的详细解释和示例:
广告函数说明
-
orb_advertise
功能:广告一个单实例主题,并可选择性地发布初始数据
参数:
meta
:主题的元数据,通常使用 ORB_ID(topic_name) 获取
data
:要发布的初始数据,如果为 NULL 则不发布数据
返回值:成功返回文件描述符,失败返回负值
用途:用于只需要一个实例的主题,如系统状态信息
-
orb_advertise_multi
功能:广告一个多实例主题,允许同一主题有多个实例
参数:
meta
:主题的元数据
data
:要发布的初始数据
instance
:指向实例索引的指针,如果为NULL则使用首个可用的实例
返回值:成功返回文件描述符,失败返回负值
用途:当系统中有多个相同类型的设备时使用,如多个温度传感器
-
orb_advertise_queue
功能:广告一个带消息队列的主题
参数:
meta
:主题的元数据
data
:要发布的初始数据
queue_size
:队列中可存储的最大消息数
返回值:成功返回文件描述符,失败返回负值
用途:当希望保留历史消息时使用,如日志记录
发布函数说明
-
orb_publish
功能:向已广告的主题发布新数据
参数:
meta
:主题的元数据
fd
:广告返回的文件描述符
data
:要发布的数据
返回值:成功返回0,失败返回负值
用途:更新主题数据
-
orb_publish_auto
功能:自动执行广告和发布
参数:
meta
:主题的元数据
fd
:指向文件描述符的指针,如果为-1则先执行广告
data
:要发布的数据
instance
:主题的实例编号
返回值:成功返回0,失败返回负值
用途:简化代码,不需要分别调用广告和发布函数
示例:单实例主题
#include <uORB/uORB.h>
#include <sensor/temp.h>void example_advertise_publish(void) {// 创建数据结构struct sensor_temp temp_data = {0};int fd;// 设置数据temp_data.timestamp = orb_absolute_time();temp_data.temperature = 25.5f;// 广告主题fd = orb_advertise(ORB_ID(sensor_temp), &temp_data);if (fd < 0) {printf("广告主题失败\n");return;}// 更新数据for (int i = 0; i < 10; i++) {temp_data.timestamp = orb_absolute_time();temp_data.temperature += 0.1f;// 发布更新后的数据int ret = orb_publish(ORB_ID(sensor_temp), fd, &temp_data);if (ret != 0) {printf("发布数据失败: %d\n", ret);}sleep(1); // 延时1秒}// 关闭文件描述符orb_close(fd);
}
示例:多实例主题
#include <uORB/uORB.h>
#include <sensor/temp.h>void example_multi_instance(void) {struct sensor_temp temp_data1 = {0};struct sensor_temp temp_data2 = {0};int instance1 = 0;int instance2 = 1;int fd1, fd2;// 设置第一个传感器数据temp_data1.timestamp = orb_absolute_time();temp_data1.temperature = 20.0f;// 设置第二个传感器数据temp_data2.timestamp = orb_absolute_time();temp_data2.temperature = 30.0f;// 广告第一个传感器主题fd1 = orb_advertise_multi(ORB_ID(sensor_temp), &temp_data1, &instance1);if (fd1 < 0) {printf("广告第一个传感器失败\n");return;}// 广告第二个传感器主题fd2 = orb_advertise_multi(ORB_ID(sensor_temp), &temp_data2, &instance2);if (fd2 < 0) {printf("广告第二个传感器失败\n");orb_close(fd1);return;}// 更新数据for (int i = 0; i < 5; i++) {// 更新第一个传感器temp_data1.timestamp = orb_absolute_time();temp_data1.temperature += 0.5f;orb_publish(ORB_ID(sensor_temp), fd1, &temp_data1);// 更新第二个传感器temp_data2.timestamp = orb_absolute_time();temp_data2.temperature -= 0.3f;orb_publish(ORB_ID(sensor_temp), fd2, &temp_data2);sleep(1);}// 关闭文件描述符orb_close(fd1);orb_close(fd2);
}
示例:使用队列
#include <uORB/uORB.h>
#include <sensor/accel.h>void example_queue(void) {struct sensor_accel accel_data = {0};int fd;// 设置数据accel_data.timestamp = orb_absolute_time();accel_data.x = 0.0f;accel_data.y = 0.0f;accel_data.z = 9.8f;// 广告带队列的主题 (保存最近10条消息)fd = orb_advertise_queue(ORB_ID(sensor_accel), &accel_data, 10);if (fd < 0) {printf("广告加速度计主题失败\n");return;}// 快速发布多条消息 (模拟高频传感器)for (int i = 0; i < 20; i++) {accel_data.timestamp = orb_absolute_time();accel_data.x = sinf((float)i * 0.1f);accel_data.y = cosf((float)i * 0.1f);accel_data.z = 9.8f + (rand() % 100) * 0.01f;orb_publish(ORB_ID(sensor_accel), fd, &accel_data);usleep(10000); // 10毫秒}orb_close(fd);
}
示例:使用自动发布
#include <uORB/uORB.h>
#include <sensor/humi.h>void example_publish_auto(void) {struct sensor_humi humi_data = {0};int fd = -1; // 初始化为-1,表示需要先广告int instance = 0;for (int i = 0; i < 10; i++) {// 设置数据humi_data.timestamp = orb_absolute_time();humi_data.humidity = 50.0f + (rand() % 100) * 0.1f;// 自动广告和发布int ret = orb_publish_auto(ORB_ID(sensor_humi), &fd, &humi_data, &instance);if (ret != 0) {printf("自动发布失败: %d\n", ret);}sleep(1);}// 如果文件描述符有效,关闭它if (fd >= 0) {orb_close(fd);}
}
3.3 订阅和获取数据
// 订阅主题
int orb_subscribe(const struct orb_metadata *meta);// 订阅多实例主题
int orb_subscribe_multi(const struct orb_metadata *meta, unsigned instance);// 取消订阅
int orb_unsubscribe(int fd);// 复制数据
int orb_copy(const struct orb_metadata *meta, int fd, void *buffer);// 检查更新
int orb_check(int fd, bool *updated);
3.3.1 订阅和获取数据详细说明
在 uORB 系统中,订阅者通过订阅主题来接收数据。订阅操作会打开相应的设备节点,使应用程序能够读取主题的最新数据。以下是各个函数的详细解释和示例:
订阅函数说明
-
orb_subscribe
功能:订阅单实例主题
参数:
meta
:主题的元数据,通常使用 ORB_ID(topic_name) 获取
返回值:成功返回文件描述符,失败返回负值
用途:订阅没有多个实例的主题
-
orb_subscribe_multi
功能:订阅多实例主题的特定实例
参数:
meta
:主题的元数据
instance
:要订阅的实例编号
返回值:成功返回文件描述符,失败返回负值
用途:当需要订阅特定实例的主题时使用
-
orb_unsubscribe
功能:取消订阅,释放资源
参数:
fd
:订阅时返回的文件描述符
返回值:成功返回0,失败返回负值
用途:当不再需要订阅时调用,释放系统资源
数据获取函数说明
-
orb_copy
功能:从已订阅的主题复制最新数据
参数:
meta
:主题的元数据
fd
:订阅时返回的文件描述符
buffer
:存储复制数据的缓冲区
返回值:成功返回0,失败返回负值
用途:获取主题的最新数据
-
orb_check
功能:检查主题是否有更新
参数:
fd
:订阅时返回的文件描述符
updated
:指向布尔值的指针,如果有更新则设置为true
返回值:成功返回0,失败返回负值
用途:在调用 orb_copy 前检查是否有新数据,避免不必要的复制操作
示例:基本订阅和数据获取
#include <uORB/uORB.h>
#include <sensor/temp.h>void example_subscribe_basic(void) {struct sensor_temp temp_data;bool updated;int fd;// 订阅温度传感器主题fd = orb_subscribe(ORB_ID(sensor_temp));if (fd < 0) {printf("订阅温度主题失败\n");return;}// 读取10次数据或等待10秒for (int i = 0; i < 10; i++) {// 检查是否有更新int ret = orb_check(fd, &updated);if (ret != 0) {printf("检查更新失败: %d\n", ret);break;}if (updated) {// 有更新,复制数据ret = orb_copy(ORB_ID(sensor_temp), fd, &temp_data);if (ret != 0) {printf("复制数据失败: %d\n", ret);break;}// 打印数据printf("温度: %.2f°C, 时间戳: %" PRIu64 "\n", temp_data.temperature, temp_data.timestamp);} else {printf("没有新数据\n");}sleep(1);}// 取消订阅orb_unsubscribe(fd);
}
示例:订阅多实例主题
#include <uORB/uORB.h>
#include <sensor/temp.h>void example_subscribe_multi(void) {struct sensor_temp temp_data1, temp_data2;bool updated1, updated2;int fd1, fd2;// 订阅第一个温度传感器实例fd1 = orb_subscribe_multi(ORB_ID(sensor_temp), 0);if (fd1 < 0) {printf("订阅第一个温度传感器失败\n");return;}// 订阅第二个温度传感器实例fd2 = orb_subscribe_multi(ORB_ID(sensor_temp), 1);if (fd2 < 0) {printf("订阅第二个温度传感器失败\n");orb_unsubscribe(fd1);return;}// 监听两个传感器for (int i = 0; i < 5; i++) {// 检查第一个传感器orb_check(fd1, &updated1);if (updated1) {orb_copy(ORB_ID(sensor_temp), fd1, &temp_data1);printf("传感器1: %.2f°C\n", temp_data1.temperature);}// 检查第二个传感器orb_check(fd2, &updated2);if (updated2) {orb_copy(ORB_ID(sensor_temp), fd2, &temp_data2);printf("传感器2: %.2f°C\n", temp_data2.temperature);}if (!updated1 && !updated2) {printf("两个传感器都没有新数据\n");}sleep(1);}// 取消订阅orb_unsubscribe(fd1);orb_unsubscribe(fd2);
}
示例:阻塞式等待和轮询
#include <uORB/uORB.h>
#include <sensor/accel.h>
#include <poll.h>void example_poll_wait(void) {struct sensor_accel accel_data;struct pollfd fds[1];int fd;// 订阅加速度计主题fd = orb_subscribe(ORB_ID(sensor_accel));if (fd < 0) {printf("订阅加速度计失败\n");return;}// 设置轮询结构fds[0].fd = fd;fds[0].events = POLLIN;// 等待5次数据更新for (int i = 0; i < 5; i++) {// 阻塞等待,超时5秒int ret = poll(fds, 1, 5000);if (ret < 0) {// 出错printf("轮询出错: %d\n", errno);break;} else if (ret == 0) {// 超时printf("等待数据超时\n");continue;}// 检查事件if (fds[0].revents & POLLIN) {// 有数据可读orb_copy(ORB_ID(sensor_accel), fd, &accel_data);printf("加速度: X=%.2f, Y=%.2f, Z=%.2f\n",accel_data.x, accel_data.y, accel_data.z);}}// 取消订阅orb_unsubscribe(fd);
}
示例:设置更新间隔
#include <uORB/uORB.h>
#include <sensor/baro.h>void example_set_interval(void) {struct sensor_baro baro_data;bool updated;int fd;// 订阅气压计主题fd = orb_subscribe(ORB_ID(sensor_baro));if (fd < 0) {printf("订阅气压计失败\n");return;}// 设置更新间隔为200毫秒 (5Hz)int ret = orb_set_interval(fd, 200);if (ret != 0) {printf("设置更新间隔失败: %d\n", ret);orb_unsubscribe(fd);return;}// 监听数据更新for (int i = 0; i < 20; i++) {orb_check(fd, &updated);if (updated) {orb_copy(ORB_ID(sensor_baro), fd, &baro_data);printf("气压: %.2f hPa, 高度: %.2f m\n", baro_data.pressure, baro_data.altitude);}usleep(50000); // 检查频率比更新频率高 (50ms)}// 取消订阅orb_unsubscribe(fd);
}
完整示例:发布者和订阅者同时运行
#include <uORB/uORB.h>
#include <sensor/temp.h>
#include <pthread.h>// 发布者线程
void *publisher_thread(void *arg) {struct sensor_temp temp_data = {0};int fd;// 广告主题temp_data.timestamp = orb_absolute_time();temp_data.temperature = 25.0f;fd = orb_advertise(ORB_ID(sensor_temp), &temp_data);if (fd < 0) {printf("广告主题失败\n");return NULL;}// 发布数据for (int i = 0; i < 20; i++) {temp_data.timestamp = orb_absolute_time();temp_data.temperature = 25.0f + sinf(i * 0.5f) * 5.0f;orb_publish(ORB_ID(sensor_temp), fd, &temp_data);printf("[发布者] 温度: %.2f°C\n", temp_data.temperature);usleep(500000); // 500毫秒}orb_close(fd);return NULL;
}// 订阅者线程
void *subscriber_thread(void *arg) {struct sensor_temp temp_data;bool updated;int fd;// 给发布者一些时间来启动usleep(100000);// 订阅主题fd = orb_subscribe(ORB_ID(sensor_temp));if (fd < 0) {printf("订阅主题失败\n");return NULL;}// 监听数据for (int i = 0; i < 30; i++) {orb_check(fd, &updated);if (updated) {orb_copy(ORB_ID(sensor_temp), fd, &temp_data);printf("[订阅者] 温度: %.2f°C\n", temp_data.temperature);}usleep(300000); // 300毫秒}orb_unsubscribe(fd);return NULL;
}// 主函数
void example_pub_sub(void) {pthread_t pub_thread, sub_thread;// 创建线程pthread_create(&pub_thread, NULL, publisher_thread, NULL);pthread_create(&sub_thread, NULL, subscriber_thread, NULL);// 等待线程结束pthread_join(pub_thread, NULL);pthread_join(sub_thread, NULL);
}
3.4 控制函数
// 设置发布频率
int orb_set_interval(int fd, unsigned interval);// 获取发布频率
int orb_get_interval(int fd, unsigned *interval);// 设置批处理间隔
int orb_set_batch_interval(int fd, unsigned batch_interval);// 获取批处理间隔
int orb_get_batch_interval(int fd, unsigned *batch_interval);// 获取主题状态
int orb_get_state(int fd, struct orb_state *state);// 清空主题数据
int orb_flush(int fd);
3.5 工具函数
// 获取绝对时间
orb_abstime orb_absolute_time(void);// 获取经过的时间
orb_abstime orb_elapsed_time(const orb_abstime *then);// 检查主题是否存在
int orb_exists(const struct orb_metadata *meta, int instance);// 获取主题组计数
int orb_group_count(const struct orb_metadata *meta);
4. 事件循环支持
uORB 提供了事件循环支持,可以使用基于 epoll 的机制来处理多个主题事件:
// 初始化循环
int orb_loop_init(struct orb_loop_s *loop, enum orb_loop_type_e type);// 运行循环
int orb_loop_run(struct orb_loop_s *loop);// 销毁循环
int orb_loop_deinit(struct orb_loop_s *loop);// 初始化句柄
int orb_handle_init(struct orb_handle_s *handle, int fd, int events,void *arg, orb_datain_cb_t datain_cb,orb_dataout_cb_t dataout_cb, orb_eventpri_cb_t pri_cb,orb_eventerr_cb_t err_cb);// 启动句柄
int orb_handle_start(struct orb_loop_s *loop, struct orb_handle_s *handle);// 停止句柄
int orb_handle_stop(struct orb_loop_s *loop, struct orb_handle_s *handle);
4.1 事件循环详细说明
uORB 的事件循环功能是基于 epoll
机制实现的,它允许应用程序同时监听多个主题,并在数据更新时触发相应的回调函数,而不必使用轮询或多线程方式。这种异步事件驱动的方式更加高效,尤其是在需要监听多个主题的复杂系统中。
核心概念
-
循环 (Loop):
事件循环的核心容器,用于管理和执行多个事件句柄
通过 struct orb_loop_s
结构体表示
-
句柄 (Handle):
表示对特定主题的监听,包含回调函数和相关参数
通过 struct orb_handle_s
结构体表示
-
回调函数 (Callback):
当特定事件发生时被调用的函数
主要类型包括:数据输入回调、数据输出回调、优先事件回调和错误事件回调
函数详细说明
-
orb_loop_init
功能:初始化一个事件循环
参数:
loop
:指向循环结构体的指针
type
:循环类型,目前只支持 ORB_EPOLL_TYPE
返回值:成功返回0,失败返回负值
用途:创建一个新的事件循环
-
orb_loop_run
功能:运行事件循环,开始处理事件
参数:
loop
:已初始化的循环结构体的指针
返回值:成功返回0,失败返回负值
用途:启动循环,进入事件处理模式
-
orb_loop_deinit
功能:销毁事件循环,释放资源
参数:
loop
:循环结构体的指针
返回值:成功返回0,失败返回负值
用途:清理循环资源
-
orb_handle_init
功能:初始化事件句柄
参数:
handle
:句柄结构体的指针
fd
:要监听的文件描述符
events
:感兴趣的事件类型,如 POLLIN
(有数据可读)
arg
:传递给回调函数的参数
datain_cb
:数据可读时的回调函数
dataout_cb
:数据可写时的回调函数
pri_cb
:优先级事件的回调函数
err_cb
:错误事件的回调函数
返回值:成功返回0,失败返回负值
用途:创建一个事件句柄,定义事件发生时的处理方式
-
orb_handle_start
功能:启动事件句柄,将其添加到循环中
参数:
loop
:循环结构体的指针
handle
:已初始化的句柄结构体的指针
返回值:成功返回0,失败返回负值
用途:开始监听特定句柄的事件
-
orb_handle_stop
功能:停止事件句柄,将其从循环中移除
参数:
loop
:循环结构体的指针
handle
:已启动的句柄结构体的指针
返回值:成功返回0,失败返回负值
用途:停止监听特定句柄的事件
4.2 事件循环使用示例
基本事件循环示例
以下示例展示了如何创建一个事件循环,并使用它来同时监听多个传感器主题:
#include <uORB/uORB.h>
#include <sensor/temp.h>
#include <sensor/baro.h>
#include <sensor/humi.h>
#include <signal.h>// 全局变量,用于信号处理
static bool g_running = true;// 温度传感器回调函数
static int temp_callback(struct orb_handle_s *handle, void *arg) {struct sensor_temp data;int fd = handle->fd;// 复制数据int ret = orb_copy(ORB_ID(sensor_temp), fd, &data);if (ret != 0) {printf("复制温度数据失败: %d\n", ret);return ret;}// 处理数据printf("温度更新: %.2f°C, 时间戳: %" PRIu64 "\n", data.temperature, data.timestamp);return 0;
}// 气压计回调函数
static int baro_callback(struct orb_handle_s *handle, void *arg) {struct sensor_baro data;int fd = handle->fd;// 复制数据int ret = orb_copy(ORB_ID(sensor_baro), fd, &data);if (ret != 0) {printf("复制气压数据失败: %d\n", ret);return ret;}// 处理数据printf("气压更新: %.2f hPa, 高度: %.2f m\n", data.pressure, data.altitude);return 0;
}// 湿度传感器回调函数
static int humi_callback(struct orb_handle_s *handle, void *arg) {struct sensor_humi data;int fd = handle->fd;// 复制数据int ret = orb_copy(ORB_ID(sensor_humi), fd, &data);if (ret != 0) {printf("复制湿度数据失败: %d\n", ret);return ret;}// 处理数据printf("湿度更新: %.2f%%\n", data.humidity);return 0;
}// 信号处理函数
static void signal_handler(int sig) {g_running = false;
}// 主函数
void event_loop_example(void) {struct orb_loop_s loop;struct orb_handle_s temp_handle;struct orb_handle_s baro_handle;struct orb_handle_s humi_handle;int temp_fd, baro_fd, humi_fd;// 设置信号处理signal(SIGINT, signal_handler); // Ctrl+Csignal(SIGTERM, signal_handler); // 终止信号// 订阅主题temp_fd = orb_subscribe(ORB_ID(sensor_temp));baro_fd = orb_subscribe(ORB_ID(sensor_baro));humi_fd = orb_subscribe(ORB_ID(sensor_humi));if (temp_fd < 0 || baro_fd < 0 || humi_fd < 0) {printf("订阅主题失败\n");goto cleanup;}// 初始化循环if (orb_loop_init(&loop, ORB_EPOLL_TYPE) != 0) {printf("初始化事件循环失败\n");goto cleanup;}// 初始化句柄if (orb_handle_init(&temp_handle, temp_fd, POLLIN, NULL, temp_callback, NULL, NULL, NULL) != 0) {printf("初始化温度句柄失败\n");goto cleanup;}if (orb_handle_init(&baro_handle, baro_fd, POLLIN, NULL, baro_callback, NULL, NULL, NULL) != 0) {printf("初始化气压句柄失败\n");goto cleanup;}if (orb_handle_init(&humi_handle, humi_fd, POLLIN, NULL, humi_callback, NULL, NULL, NULL) != 0) {printf("初始化湿度句柄失败\n");goto cleanup;}// 启动句柄if (orb_handle_start(&loop, &temp_handle) != 0 ||orb_handle_start(&loop, &baro_handle) != 0 ||orb_handle_start(&loop, &humi_handle) != 0) {printf("启动句柄失败\n");goto cleanup;}printf("事件循环已启动,按 Ctrl+C 停止...\n");// 事件循环处理while (g_running) {if (orb_loop_run(&loop) != 0) {printf("运行事件循环失败\n");break;}}cleanup:// 停止句柄orb_handle_stop(&loop, &temp_handle);orb_handle_stop(&loop, &baro_handle);orb_handle_stop(&loop, &humi_handle);// 销毁循环orb_loop_deinit(&loop);// 取消订阅if (temp_fd >= 0) orb_unsubscribe(temp_fd);if (baro_fd >= 0) orb_unsubscribe(baro_fd);if (humi_fd >= 0) orb_unsubscribe(humi_fd);printf("事件循环已停止\n");
}
使用自定义参数的事件循环示例
以下示例展示了如何在回调函数中使用自定义参数,实现更灵活的事件处理:
#include <uORB/uORB.h>
#include <sensor/accel.h>
#include <sensor/gyro.h>// 定义传感器处理器结构体
struct sensor_processor {const char *name; // 传感器名称float threshold; // 阈值uint64_t last_time; // 上次处理时间uint32_t update_count; // 更新计数
};// 通用传感器回调函数
static int sensor_callback(struct orb_handle_s *handle, void *arg) {struct sensor_processor *processor = (struct sensor_processor *)arg;int fd = handle->fd;// 更新计数processor->update_count++;// 获取当前时间orb_abstime now = orb_absolute_time();// 计算时间间隔uint64_t dt = now - processor->last_time;processor->last_time = now;// 检查是否是加速度计传感器if (strcmp(processor->name, "accel") == 0) {struct sensor_accel data;int ret = orb_copy(ORB_ID(sensor_accel), fd, &data);if (ret != 0) {return ret;}// 计算加速度大小float mag = sqrtf(data.x * data.x + data.y * data.y + data.z * data.z);// 检查是否超过阈值if (mag > processor->threshold) {printf("[%s] 检测到强烈运动: %.2f m/s² (阈值: %.2f)\n", processor->name, mag, processor->threshold);}// 定期打印更新信息if (processor->update_count % 10 == 0) {printf("[%s] 加速度: X=%.2f, Y=%.2f, Z=%.2f m/s², 间隔: %" PRIu64 " us\n", processor->name, data.x, data.y, data.z, dt);}}// 检查是否是陀螺仪传感器else if (strcmp(processor->name, "gyro") == 0) {struct sensor_gyro data;int ret = orb_copy(ORB_ID(sensor_gyro), fd, &data);if (ret != 0) {return ret;}// 计算角速度大小float mag = sqrtf(data.x * data.x + data.y * data.y + data.z * data.z);// 检查是否超过阈值if (mag > processor->threshold) {printf("[%s] 检测到快速旋转: %.2f rad/s (阈值: %.2f)\n", processor->name, mag, processor->threshold);}// 定期打印更新信息if (processor->update_count % 10 == 0) {printf("[%s] 角速度: X=%.2f, Y=%.2f, Z=%.2f rad/s, 间隔: %" PRIu64 " us\n", processor->name, data.x, data.y, data.z, dt);}}return 0;
}// 主函数
void custom_param_example(void) {struct orb_loop_s loop;struct orb_handle_s accel_handle;struct orb_handle_s gyro_handle;int accel_fd, gyro_fd;// 创建处理器实例struct sensor_processor accel_processor = {.name = "accel",.threshold = 12.0f, // 12 m/s².last_time = orb_absolute_time(),.update_count = 0};struct sensor_processor gyro_processor = {.name = "gyro",.threshold = 1.5f, // 1.5 rad/s.last_time = orb_absolute_time(),.update_count = 0};// 订阅主题accel_fd = orb_subscribe(ORB_ID(sensor_accel));gyro_fd = orb_subscribe(ORB_ID(sensor_gyro));if (accel_fd < 0 || gyro_fd < 0) {printf("订阅主题失败\n");goto cleanup;}// 设置更新间隔orb_set_interval(accel_fd, 100); // 100msorb_set_interval(gyro_fd, 100); // 100ms// 初始化循环if (orb_loop_init(&loop, ORB_EPOLL_TYPE) != 0) {printf("初始化事件循环失败\n");goto cleanup;}// 初始化句柄,传递自定义处理器参数if (orb_handle_init(&accel_handle, accel_fd, POLLIN, &accel_processor, sensor_callback, NULL, NULL, NULL) != 0) {printf("初始化加速度计句柄失败\n");goto cleanup;}if (orb_handle_init(&gyro_handle, gyro_fd, POLLIN, &gyro_processor, sensor_callback, NULL, NULL, NULL) != 0) {printf("初始化陀螺仪句柄失败\n");goto cleanup;}// 启动句柄if (orb_handle_start(&loop, &accel_handle) != 0 ||orb_handle_start(&loop, &gyro_handle) != 0) {printf("启动句柄失败\n");goto cleanup;}printf("事件循环已启动,将运行10秒...\n");// 运行10秒for (int i = 0; i < 10; i++) {orb_loop_run(&loop);sleep(1);}cleanup:// 停止句柄orb_handle_stop(&loop, &accel_handle);orb_handle_stop(&loop, &gyro_handle);// 销毁循环orb_loop_deinit(&loop);// 取消订阅if (accel_fd >= 0) orb_unsubscribe(accel_fd);if (gyro_fd >= 0) orb_unsubscribe(gyro_fd);printf("事件循环已停止\n");
}
错误处理示例
以下示例展示了如何处理事件循环中可能出现的错误:
#include <uORB/uORB.h>
#include <sensor/gnss.h>
#include <errno.h>// 数据可读回调
static int gnss_data_callback(struct orb_handle_s *handle, void *arg) {struct sensor_gnss data;int fd = handle->fd;int ret = orb_copy(ORB_ID(sensor_gnss), fd, &data);if (ret != 0) {printf("复制GNSS数据失败: %d\n", ret);return ret;}printf("GNSS位置: 纬度=%.6f, 经度=%.6f, 高度=%.2f\n",data.latitude, data.longitude, data.altitude);return 0;
}// 错误事件回调
static int gnss_error_callback(struct orb_handle_s *handle, void *arg) {int fd = handle->fd;printf("GNSS传感器发生错误,fd=%d, 错误码=%d\n", fd, errno);// 尝试恢复操作printf("尝试重新订阅GNSS主题...\n");// 关闭旧的文件描述符orb_close(fd);// 重新订阅int new_fd = orb_subscribe(ORB_ID(sensor_gnss));if (new_fd < 0) {printf("重新订阅失败: %d\n", new_fd);return -1;}// 更新句柄中的文件描述符handle->fd = new_fd;printf("GNSS传感器已恢复\n");return 0;
}// 主函数
void error_handling_example(void) {struct orb_loop_s loop;struct orb_handle_s gnss_handle;int gnss_fd;// 订阅主题gnss_fd = orb_subscribe(ORB_ID(sensor_gnss));if (gnss_fd < 0) {printf("订阅GNSS主题失败: %d\n", gnss_fd);return;}// 初始化循环if (orb_loop_init(&loop, ORB_EPOLL_TYPE) != 0) {printf("初始化事件循环失败\n");orb_unsubscribe(gnss_fd);return;}// 初始化句柄,设置数据和错误回调if (orb_handle_init(&gnss_handle, gnss_fd, POLLIN | POLLERR, NULL,gnss_data_callback, NULL, NULL, gnss_error_callback) != 0) {printf("初始化GNSS句柄失败\n");orb_loop_deinit(&loop);orb_unsubscribe(gnss_fd);return;}// 启动句柄if (orb_handle_start(&loop, &gnss_handle) != 0) {printf("启动GNSS句柄失败\n");orb_loop_deinit(&loop);orb_unsubscribe(gnss_fd);return;}printf("开始监控GNSS数据,将运行30秒...\n");// 运行30秒for (int i = 0; i < 30; i++) {orb_loop_run(&loop);sleep(1);}// 清理资源orb_handle_stop(&loop, &gnss_handle);orb_loop_deinit(&loop);orb_unsubscribe(gnss_handle.fd);printf("GNSS监控已停止\n");
}
4.3 事件循环的最佳实践
在使用 uORB 事件循环时,以下是一些建议的最佳实践:
资源管理:
-
始终在不再需要时正确清理资源(取消订阅、停止句柄、销毁循环)
-
使用
goto cleanup
或类似方式确保在错误发生时也能清理资源
回调函数设计:
-
保持回调函数简短高效,避免长时间阻塞
-
将复杂的处理逻辑放在单独的函数中
-
使用自定义参数传递上下文信息
错误处理:
-
为关键操作实现错误回调函数
-
考虑实现恢复机制,例如在传感器错误时重新订阅
性能考虑:
-
合理设置更新间隔,避免过高的更新频率
-
避免在回调函数中执行 CPU 密集型任务
-
只监听真正需要的事件类型
通过使用事件循环,您可以构建响应式的应用程序,高效地处理来自多个主题的数据,而无需手动轮询或创建多个线程。这对于需要同时处理多种传感器数据的复杂应用尤为有用。
5. 传感器主题
uORB 内置了大量传感器相关的主题,位于 apps/system/uorb/sensor/
目录下,包括:
-
加速度计 (accel)
-
角度传感器 (angle)
-
气压计 (baro)
-
电容传感器 (cap)
-
二氧化碳传感器 (co2)
-
尘埃传感器 (dust)
-
心电图 (ecg)
-
引擎传感器 (eng)
-
力传感器 (force)
-
气体传感器 (gas)
-
全球导航卫星系统 (gnss)
-
陀螺仪 (gyro)
-
手势识别 (gesture)
-
霍尔传感器 (hall)
-
心跳传感器 (hbeat)
-
甲醛传感器 (hcho)
-
心率传感器 (hrate)
-
湿度传感器 (humi)
-
电阻抗传感器 (impd)
-
红外传感器 (ir)
-
光传感器 (light)
-
磁力计 (mag)
-
运动检测 (motion)
-
噪声传感器 (noise)
-
物体温度传感器 (ots)
-
PH值传感器 (ph)
-
PM1.0/PM2.5/PM10 颗粒物传感器
-
6自由度姿态传感器 (pose_6dof)
-
光电容积脉搏波传感器 (ppgd/ppgq)
-
接近传感器 (prox)
-
RGB颜色传感器 (rgb)
-
旋转传感器 (rotation)
-
计步器 (step_counter)
-
温度传感器 (temp)
-
总挥发性有机化合物传感器 (tvoc)
-
紫外线传感器 (uv)
6. 工具应用
uORB 模块提供了一个监听工具应用 listener.c
,可以用来监控和调试主题消息:
uorb_listener <command> [arguments...]Commands:<topics_name> Topic name. Multi name are separated by ','[-h ] Listener commands help[-f ] Record uorb data to file[-n <val> ] Number of messages, default: 0[-r <val> ] Subscription rate (unlimited if 0), default: 0[-b <val> ] Subscription maximum report latency in us(unlimited if 0),default: 0[-t <val> ] Time of listener, in seconds, default: 5[-T ] Top, continuously print updating objects[-l ] Top only execute once.
示例用法:
-
打印一次所有主题快照:
uorb_listener -n 1
-
持续监控特定主题:
uorb_listener sensor_temp,sensor_humi
-
以特定速率订阅:
uorb_listener sensor_temp -r 10
(10Hz) -
记录数据到文件:
uorb_listener sensor_temp -f
7. 参考资源
uORB头文件: /include/nuttx/uorb.h
uORB应用层头文件: /apps/system/uorb/uORB/uORB.h
传感器示例代码: /apps/examples/bmi160/sixaxis_uorb_main.c