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

PX4中的uavcan进程

概述

PX4 中的 uavcan 模块梳理起来是有点杂的,就像 commander 中的内容一个较为杂乱。如果要梳理划分的话,可以按照主题消息的类型来划分,PX4 中的 uavcan 模块需要接收处理的消息主题非常多,因此将其主要分为三类:

1)需要收发的非传感器主题消息

2)只需接收的传感器主题消息

3)服务器消息(用于固件升级)

框图如下:

在 PX4 中的 uavcan 进程中,在 uavcan_main 中创建主节点,并且在创建主节点时,会将所有的主题消息挂载在主节点上,主节点也就是飞控。

服务器消息有一定的条件,即需要根据 UAVCAN_ENABLE 参数设置,大于 1 时,才会开启。固件升级功能需要动态节点 ID 分配,因此在服务器文件 uavcan_servers 中,包含的就是这两个功能,固件升级与动态节点 ID 分配,不过也可以理解为就是一个固件升级功能,因为动态节点 ID 分配是为固件升级服务的。

传感器消息是非常多的,传感器模块都在 uavcan/sensors 目录下,正是由于种类多,因此 PX4 单独为传感器创建了一个管理器 sensor_bridge ,在管理器中统一创建各个传感器的实例,并且创建了一个默认4个通道的列表,管理所有传感器消息的 uORB 主题发布。

在 px4 中的目录对应如下:

主流程

要梳理主流程的脉络的话,对照 DroneCAN 的标准流程就可以。按照 DroneCAN 的流程,创建节点一般需要几个步骤:

1)创建节点,并且在创建节点时需要指定 CAN 底层驱动实例以及系统时钟实例

2)创建消息订阅器与发布器

3)创建进程运行节点

主流程的步骤相比上面的三步要复杂一些,一方面在主循环中进行时间同步的操作,另一方面也有更多的主题消息,不过按照上述的步骤,可以大致梳理出 PX4 的 uavcan 创建节点的主流程步骤:

1)创建节点,指定 CAN 底层实例与系统时钟实例

2)为节点创建各类主题消息

3)创建进程运行节点

4)在主循环中定时进行时间同步

根据上述的主流程步骤,uavcan_main 文件中的主要内容与功能:

1.UavcanNode::start 函数中创建节点,并指定 CAN 底层实例与系统时钟实例;

2.UavcanNode::UavcanNode 构造函数中,会创建需要收发的主题消息各控制器并挂在节点上;

3.UavcanNode::init 函数中:

1)设置节点名称与 ID 基础信息

2)初始化需要收发的各消息控制器(在构造函数中已经创建,此时只初始化)

3)创建传感器主题消息链表(由于传感器较多,所以使用一个链表来节省资源)

4)获取 UAVCAN_ENABLE 参数设置,大于1时,启动服务器,用于固件升级

4.UavcanNode::Run() 函数中进行线程创建,时间同步,参数获取等

在构造函数中,就可以看出需要收发的主题消息控制器有哪些,这些消息,在PX4中是跟着构造函数一起创建的:

在初始化函数 init 中,会设置节点名称与 ID :

/*******************设置节点名称与ID 基础信息*******************/
_node.setName("org.pixhawk.pixhawk");  //设置节点名称
_node.setNodeID(node_id);   //设置节点ID
fill_node_info();

 

之后创建主题消息控制器:

/********************初始化各个其他模块的主题消息控制器***********************/
ret = _beep_controller.init();  //蜂鸣器控制器

if (ret < 0) {
        return ret;
}

// Actuators
ret = _esc_controller.init();   //电调控制器

if (ret < 0) {
        return ret;
}
...

而传感器消息太多,因此传感器消息由一个管理器类 IUavcanSensorBridge,初始化函数中只需要调用此管理器类的一个成员函数 make_all 即可完成全部的创建:

// Sensor bridges
//创建所有传感器的消息链表(由于传感器太多,因此创建一个链表节省资源)
IUavcanSensorBridge::make_all(_node, _sensor_bridges);

传感器类主题消息的内容在下面的内容详细叙述,在此先跳过。

服务器的创建需要条件,需要根据 UAVCAN_ENABLE 参数:

/*获取 UAVCAN_ENABLE 参数设置,一共有0,1,2,3四种情况,大于1时就需要开启服务节点来支持升级*/
if (uavcan_enable > 1) {
        _servers = new UavcanServers(_node, _node_info_retriever);

        if (_servers) {
                int rv = _servers->init();

                if (rv < 0) {
                        PX4_ERR("UavcanServers init: %d", ret);
                }
        }
}

UAVCAN_ENABLE 参数的定义如下:

0 - UAVCAN 禁用
1 - UAVCAN 对传感器支持,但没有动态节点ID分配与固件升级
2 - UAVCAN 对传感器支持,同时也支持动态节点ID分配与固件升级
3 - UAVCAN 对传感器支持,同时支持动态节点ID分配与固件升级,并且将电机控制信号也用UAVCAN输出

时间同步与固件升级

时间同步的流程可以参照《DroneCAN的实现库Libuavcan及基础功能示例》一文中的示例程序。主要需要注意的地方是主机的冗余机制: 如果当前模块是主机的话,会同时再创建一个从机运行,当检测到网络中有更高优先级的主机时,当前节点会启用从机 (suppress(false)),之后与新主机进行同步:

/*
 * 检查网络中是否又更高优先级的主机
 * 如果有的话,激活本地的从机节点,当前模块由主机变从机,与新主机同步
 */
if (_time_sync_slave.isActive()) { // "Active" 表示从设备网络中至少有一个远程主机
        if (_node.getNodeID() < _time_sync_slave.getMasterNodeID()) {
                /*
                 * 当前节点是优先级最高的主机节点  调用 suppress 函数,压制从机模式
                 */
                _time_sync_slave.suppress(true);  // SUPPRESS

        } else {
                /*
                 * 网络中存在更高优先级的主机,因此取消压制从机,允许从机调整本地时钟
                 */
                _time_sync_slave.suppress(false);  // UNSUPPRESS
        }

} else {
        /*
         * 当网络中没有主机时,必须压制从机,本主机是唯一时钟源
         */
        _time_sync_slave.suppress(true);
}

/*
 * 发布时间同步主题消息
 */
_time_sync_master.publish();

固件升级的流程也同样可以参照《DroneCAN的实现库Libuavcan及基础功能示例》一文中的示例程序。

需要收发的主题消息流程

此部分也是两种类型,不过大体上的类成员函数框架都是复制的。伪代码如下:

类名::类名(uavcan::INode &node) :
        要发布的主题消息(node),
        要订阅的主题消息(node),
        _timer(node)  //软件定时器
{
        要发布的消息.setPriority(uavcan::TransferPriority::Default);  //设置优先级
}

int 类名::init()
{
        //启动要订阅的主题消息的回调函数
        int res = 要订阅的消息.start(StatusCbBinder(this, &类名::回调函数名));
        if (res < 0) {
                return res;
        }    
    
        // 设置软件定时器的周期与回调函数,用于定时广播主题消息
        if (!_timer.isRunning()) {
                _timer.setCallback(TimerCbBinder(this, &UavcanSafetyState::periodic_update));
                _timer.startPeriodic(uavcan::MonotonicDuration::fromMSec(1000 / MAX_RATE_HZ));
        }

        return 0;
}

//周期更新状态,然后发布消息
void 类名::广播回调函数(const uavcan::TimerEvent &)
{
    更新消息
    (void)要发布的主题消息.broadcast(cmd);
}

//处理接受到的主题消息
void 类名::订阅回调函数()
{
}

如果是仅仅广播消息,不接收消息的控制器的话,例如安全开关,就不需要订阅,其代码也基本上是上述的模板套上去即可:

1.构造函数中创建主题消息并设置优先级:

2.初始化函数中设置软件定时器的周期与回调函数:

3.回调函数中更新数据并调用 broadcast 库函数来发送消息:

如果是订阅与发布都需要的话,例如电调,则在初始化过程中会再多增加一个启动主题消息的订阅,并指定回调函数:

传感器类主题消息流程

传感器类的主题消息控制器,在 PX4 中,又创建了一个管理器来管理这么多的传感器消息控制器,即 sensor_bridge。在 sensor_bridge 中,创建了一个列表,在 make_all 函数中,将创建的传感器对象都添加到列表中:

不过更重要的是,在 sensor_bridge 中,创建了一个默认为4的通道:

_channels(new uavcan_bridge::Channel[max_channels]),

由于传感器种类太多,PX4 不仅要接收传感器发送的主题数据,同时在接收之后,还要根据数据更新相应的 uORB 主题消息,因此 PX4 使用这 4 个通道来发布相应的 uORB 主题消息,避免同时发布的数据太多,节省资源,在 publish 函数中实现:

当通道中有 ID 与要发布的消息的传感器 ID 相同时,优先选择此通道,如果没有,则需按照空闲通道,当有空闲通道时,则发布 uORB 主题消息,如果没有空闲通道,则退出函数,暂不发布数据。

其余的单传感器的消息控制器的框架与上面一节的框架是一样的,只是没有广播消息,只订阅消息,例如电池,也是在构造函数中创建消息类型:

UavcanBatteryBridge::UavcanBatteryBridge(uavcan::INode &node) :
        UavcanSensorBridgeBase("uavcan_battery", ORB_ID(battery_status)),
        ModuleParams(nullptr),
        _sub_battery(node),
        _sub_battery_aux(node),
        _warning(battery_status_s::BATTERY_WARNING_NONE),
        _last_timestamp(0)
{
}

在初始化函数中,开启消息订阅,并指定回调函数:

int UavcanBatteryBridge::init()
{
        /*启动主题消息订阅器,并指定其回调函数*/
        int res = _sub_battery.start(BatteryInfoCbBinder(this, &UavcanBatteryBridge::battery_sub_cb));

        if (res < 0) {
                PX4_ERR("failed to start uavcan sub: %d", res);
                return res;
        }
        ...

在回调函数中,更新数据,并且调用 sensor_bridge 的发布函数来发布 uORB 主题:

if (battery_aux_support[instance] == false) {
        publish(msg.getSrcNodeID().get(), &battery_status[instance]);
}

px4自身id与消息订阅使能

px4自身的id与各类外设的消息是否订阅的使能,在uavcan_param.c中,如果要修改飞控的id,以及是否订阅相关外设的消息,调节参数即可,或者在QGC中修改:

相关文章:

  • python全栈-Linux基础
  • 策略模式处理
  • AI工具:deepseek+阶跃视频,生成好玩的视频
  • 教育强国建设“三年行动计划“分析
  • 如何快速上手RabbitMQ 笔记250304
  • docker-compose安装anythingLLM
  • 2000-2020年各省地方财政一般预算支出数据
  • 鸿蒙5.0实战案例:基于ArkUI的透明页面效果
  • c++中什么时候应该使用extern关键字?
  • 全栈(Java+vue)实习面试题(含答案)
  • Django项目实战
  • 基于opencv消除图片马赛克
  • 项目工坊|Python驱动淘宝信息爬虫
  • Python和PyQt5写的密码记录工具
  • 三方库总结
  • 模块11_面向对象
  • NLP如何训练AI模型以理解知识
  • C# IComparable<T> 使用详解
  • Hi3516CV610电瓶车检测 电动自行车检测 人脸检测 人形检测 车辆检测 宠物检测 包裹检测 源码
  • MWC 2025 | 移远通信大模型解决方案加速落地,引领服务机器人创新变革
  • 本地环境建设网站/培训学校机构
  • 网站建设销售专业术语/推广业务平台
  • wordpress 安全 插件/长沙seo平台
  • 怎么做坑人网站/创意营销新点子
  • 舟山建设银行网站/公司网站怎么优化
  • 浙江建设厅建委网站/网络营销和网络销售的关系