当前位置: 首页 > news >正文

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/ 目录下,包括:

  1. 加速度计 (accel)

  2. 角度传感器 (angle)

  3. 气压计 (baro)

  4. 电容传感器 (cap)

  5. 二氧化碳传感器 (co2)

  6. 尘埃传感器 (dust)

  7. 心电图 (ecg)

  8. 引擎传感器 (eng)

  9. 力传感器 (force)

  10. 气体传感器 (gas)

  11. 全球导航卫星系统 (gnss)

  12. 陀螺仪 (gyro)

  13. 手势识别 (gesture)

  14. 霍尔传感器 (hall)

  15. 心跳传感器 (hbeat)

  16. 甲醛传感器 (hcho)

  17. 心率传感器 (hrate)

  18. 湿度传感器 (humi)

  19. 电阻抗传感器 (impd)

  20. 红外传感器 (ir)

  21. 光传感器 (light)

  22. 磁力计 (mag)

  23. 运动检测 (motion)

  24. 噪声传感器 (noise)

  25. 物体温度传感器 (ots)

  26. PH值传感器 (ph)

  27. PM1.0/PM2.5/PM10 颗粒物传感器

  28. 6自由度姿态传感器 (pose_6dof)

  29. 光电容积脉搏波传感器 (ppgd/ppgq)

  30. 接近传感器 (prox)

  31. RGB颜色传感器 (rgb)

  32. 旋转传感器 (rotation)

  33. 计步器 (step_counter)

  34. 温度传感器 (temp)

  35. 总挥发性有机化合物传感器 (tvoc)

  36. 紫外线传感器 (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

相关文章:

  • 适配器模式Adapter Pattern
  • Java中如何使用lambda表达式分类groupby
  • STL容器分类总结
  • 探索RAGFlow:解锁生成式AI的无限潜能(2/6)
  • 第二十章 Centos8的使用
  • DP刷题练习(三)
  • linux thermal framework(4)_thermal governor
  • Linux 忘记root密码如何解决-linux025
  • 2.1 Windows VS2019编译FFmpeg 4.4.1
  • PCL 生成圆柱面点云
  • 人工智能-准确率(Precision)、召回率(Recall) 和 F1 分数
  • YOLO优化之双池化下采样融合块、注意力引导逆残差块
  • 第20篇:数据库中间件的热点 Key 缓存一致性策略与分布式协调机制
  • Spring Boot 整合 Swagger 快速生成 API 文档的最佳实践
  • Axure应用交互设计:中继器数据向多种类型元件赋值
  • jxWebUI--简单易用的webUI库
  • Iceberg与Hive集成深度
  • linux多线程之条件变量
  • 学习昇腾开发的第三天--将服务器连接网络
  • Android 与 ESP-01 WIFI模块通信