ROS2与PX4协同控制无人机画圆:从Offboard通信到C++节点实战
2026/9/21 7:09:24 网站建设 项目流程

很多人第一次听说“用ROS2控制PX4飞个圆”这个需求时,心里想的是“写个坐标,发进去就完事了”。等真正把环境装完、消息发出去、无人机在Gazebo里冲出去炸机之后,才知道这条路的水有多深。这篇文章不是帮你把代码抄一遍,而是把我自己踩过的坑、验证过的流程、以及每个关键决策背后的原因,完整地讲给你听。

这篇文章适合两类人:一类是刚接触ROS2和PX4,想通过一个具体任务把整个开发链路跑通的新手;另一类是已经在用MAVLink或MAVSDK,想切到ROS2生态,但不太清楚Offboard模式在ROS2消息层应该怎么发的人。文中的所有流程都在Ubuntu 22.04 + ROS2 Humble + PX4 v1.14.3 + Gazebo下实际跑过,你可以直接照着复现。

1. 项目全景:ROS2、PX4、Gazebo三兄弟的协作关系

1.1 为什么偏要用这个组合

你可能想问:控制无人机画圆的方案很多,用MAVSDK写个Python脚本也行,用QGC手动规划航线也行,为什么非要用ROS2 + C++ + PX4 + Gazebo这一套?

因为这几样东西组合起来,恰好覆盖了从“算法设计”到“真机部署”的完整链路。ROS2提供分布式通信和丰富的感知、规划库,C++保证实时性,PX4提供经过验证的飞行控制栈,Gazebo则让你在不用炸真机的前提下验证算法。尤其是落地到实际项目里,视觉避障、多机编队、室内定位这些需求,基本都跑在ROS2生态上。用MAVSDK脚本控制一次飞行不难,但要把视觉、路径规划、集群协同这些模块接到一起,就成了“把非ROS系统硬塞进ROS架构”的别扭事。

另外,PX4的Offboard模式本身就是为外部计算机设计的高级控制接口。配合uXRCE-DDS协议,ROS2可以直接以话题的形式向飞控发送期望位置、速度、姿态,精度和实时性都比传统MAVLink串口转发好不少。可以说,这个组合就是当前开源无人机二次开发的事实标准。

1.2 通信链路拆解:从C++节点到飞控的“数据管道”

先看整体数据流向,我用文字画一条链路:

ROS2 C++节点 │ ├─ 发布 /fmu/in/offboard_control_mode (OffboardControlMode 消息) └─ 发布 /fmu/in/trajectory_setpoint (TrajectorySetpoint 消息) │ ▼ Micro XRCE-DDS Agent(运行在Ubuntu宿主机上) │ ▼ uXRCE-DDS Client(运行在PX4固件内部) │ ▼ PX4 Offboard模式状态机 → 位置/姿态控制器 → 混控器 → 电机输出

如果我们默认PX4是跑在Gazebo仿真里的SITL(Software In The Loop)模式,那么PX4固件本身就是一个Linux进程,它内部会启动一个uXRCE-DDS Client,默认通过UDP连接宿主机的8888端口。宿主机这边运行一个Micro XRCE-DDS Agent,负责把PX4发出的uORB消息转成标准DDS/ROS2话题,也把ROS2发来的话题转回uORB消息。

这里有个容易懵的地方:ROS2节点到底是在跟谁说话?实际上ROS2节点从来没有直接“接触”PX4,它只是往/fmu/in/前缀的话题上发消息。Micro XRCE-DDS Agent收到后,会把消息序列化,通过网络传给PX4内部的Client,Client再把它翻译成PX4能读懂的uORB消息。整个流程对ROS2节点来说是透明的,你只要按照PX4定义的消息格式发就行。

还有一个关键点:PX4的Offboard是一个“模式”,不是一条指令。你发出的所有期望值,都只是给Offboard状态机参考的输入。PX4要真正进入Offboard模式,必须主动发送MAV_CMD_DO_SET_MODE(或通过QGC、RC切换)执行模式切换动作。这一点很多人会忽略,结果就是消息发了一堆,但无人机纹丝不动,原因就是正反馈——模式根本没切进去。

下面这张表可以帮你快速梳理各层的职责:

层级组件协议/机制主要职责
决策层我们的C++节点ROS2/DDS话题生成圆轨迹期望值,发布控制指令
桥接层Micro XRCE-DDS AgentXRCE-DDS over UDP/TCP在Linux宿主机和PX4之间转发消息
飞控内部uXRCE-DDS ClientuORB将外部消息映射为PX4内部消息
执行层PX4控制器位置控制、姿态控制计算电机指令,驱动仿真模型

这张表也对排错很有用——消息发不出去,先判断节点有没有发布;话题能看到但飞控不响应,那大概率是Agent和Client之间的桥接问题;如果飞控接收了但飞行动作怪异,那是坐标系或控制参数的问题。

2. 环境搭建:一套能少折腾一个月的基础配置

2.1 版本搭配建议

环境搭建是整个流程里耗时最长、最容易放弃的一步。版本不匹配导致编译失败,或者启动仿真时界面疯狂闪退,这些问题十有八九是版本搭配不当。我自己实测下来比较稳的组合是这样的:

组件推荐版本备注
Ubuntu22.04 LTS环境最成熟,坑最少
ROS2Humble Hawksbill和Ubuntu 22.04全兼容
PX4源码v1.14.3 tag对ROS2/XRCE-DDS支持完善
Gazebo Simgz-garden (新Gazebo)PX4 v1.14默认使用
Micro XRCE-DDS Agent最新release用于宿主机桥接
px4_msgs与PX4 v1.14.3配套ROS2侧消息定义

如果你看到网上很多教程用Gazebo Classic 11,那是因为PX4 v1.13之前的老教程。v1.14.3依然保留了gazebo-classic的启动方式,但新版世界模型和仿真插件做得更好,直接用gz_x500模型就行。这里我强烈建议使用v1.14.3而不是main分支。PX4的main分支代码变动非常快,今天能编译的接口过两周可能就不兼容了,学习阶段千万别追新。

2.2 操作步骤:从零到能起飞

我按自己能稳定复现的顺序给你梳理一遍,每一步都写清楚为什么要这么做。

第一步:安装ROS2 Humble。

按照官方文档装desktop版本就行,不用纠结选哪个版本。有个老生常谈但总有人踩的坑:装完后一定要执行source /opt/ros/humble/setup.bash,并且把这句话写进~/.bashrc,否则新终端里ros2命令永远找不到。测试一下:

ros2 --version

能输出版本号就说明ROS2本体OK。

第二步:安装Gazebo仿真工具链。

对于PX4 v1.14.3,我推荐直接用它的自动脚本把依赖一次装齐,而不是手动一个个装gazebo。在源码目录下执行:

bash ./Tools/setup/ubuntu.sh

这个脚本会检测并安装CMake、Python、Gazebo、OpenCV等一堆依赖。执行完以后最好重启一次shell,让新添加的环境变量生效。

第三步:拉取PX4源码并切到稳定版本。

git clone --recursive https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot git checkout v1.14.3 git submodule update --init --recursive

--recursive非常重要。PX4有不少子模块,比如mavlinkuavcan,不更新子模块,后面编译一定报错。

第四步:编译PX4 SITL。

make px4_sitl gz_x500

第一次编译会很久,建议挂机去喝杯茶。如果编译过程中提示缺少某个Python包,用pip install 包名补上就行,基本不用慌。

第五步:创建ROS2工作空间,放置px4_msgs。

mkdir -p ~/ws/src cd ~/ws/src git clone https://github.com/PX4/px4_msgs.git cd ~/ws colcon build --packages-select px4_msgs source install/setup.bash

这里有个细节:px4_msgs的版本最好和PX4固件版本对齐。如果你用v1.14.3固件,但px4_msgs是main分支,消息字段可能有差异。我建议直接克隆后也切到对应release tag。

第六步:安装Micro XRCE-DDS Agent。

这步通常有两种方式,源码编译或者ros2包安装。我推荐用ROS2方式:

sudo apt install ros-humble-micro-ros-agent

这样装完以后可以直接用ros2 run micro_ros_agent micro_ros_agent启动,省去手动配置的麻烦。

到这里,环境基础就算打好了。启动完这些之后,建议先在终端里验证一下PX4能否正常启动:

cd ~/PX4-Autopilot make px4_sitl gz_x500

如果弹出Gazebo 3D窗口,并且终端提示Ready for takeoff,那就可以进入下一步了。

2.3 常见的版本坑

  • 子模块缺失:很多人的gazebo模型加载不出来,原因是PX4的Tools/simulation-gazebo子模块没拉全。重新执行git submodule update --init --recursive就能解决。
  • Gazebo界面狂闪:如果GPU驱动不太好,特别是虚拟机环境里,Gazebo渲染会一直闪。临时解决方案是设置环境变量走软件渲染:export LIBGL_ALWAYS_SOFTWARE=1,但性能会下降。最好装一下GPU驱动,或换一台有独立显卡的机器跑仿真。
  • ROS2话题从零开始没内容:这通常是Agent没有启动,或者PX4内部的uXRCE-DDS客户端没连上Agent。启动顺序上,先启动Agent,再启动PX4 SITL,连接更稳定。

3. C++节点设计:把一个“圆”翻译成飞控指令

3.1 先理解Offboard模式下的控制接口

在写代码前,必须搞清楚PX4的Offboard模式到底接收什么格式的期望值。在ROS2接口层,我们需要往两个话题上发消息:

  • /fmu/in/offboard_control_mode:告知飞控本次期望值的类型(位置/速度/加速度/姿态),当前阶段我们需要的就是位置。
  • /fmu/in/trajectory_setpoint:具体的期望数值,比如期望位置坐标、期望速度、期望偏航角。

关键一点:PX4在Offboard模式下会屏蔽掉所有不属于当前控制类型的其他字段。也就是说,如果你的OffboardControlMode里只把position置为true,那么TrajectorySetpoint里的velocity字段即使填了,也不会参与控制。这点在调试时很重要,不要同时开位置和速度控制,容易让内环和外环打架。

另外,PX4内部使用的坐标系是NED(北东地),即X轴朝北、Y轴朝东、Z轴朝下。ROS2默认坐标系在很多场景下是ENU(东北天),X轴朝东、Y轴朝北、Z轴朝上。如果你在ROS2里拿到的局部位置是ENU,直接塞进PX4的topic里,无人机会往完全错误的方向飞。所以拿到数据后,要么在节点内部统一做NED转换,要么明确知道自己手里的坐标就是NED。后面我给代码时直接按NED生成圆轨迹,反而少一层转换负担。

3.2 圆的数学:参数方程与控制频率

要“画圆”,本质上就是在NED坐标系下周期地给出圆的期望位置。假设圆心在起飞点上方的H高度,半径R,飞行角速度为ω,那么NED位置满足:

x(t) = x0 + R * cos(θ(t)) y(t) = y0 + R * sin(θ(t)) z(t) = -H // NED的Z轴向下,期望高度取负 θ(t) = θ0 + ω * t

这里θ0是初始相位,决定无人机起点在圆上的哪个位置。ω的单位是rad/s,想要多飞完一圈,由目标线速度决定:目标线速度v = R * ω。

更要紧的是,PX4的Offboard模式允许你同时发位置和速度设定值。只发位置也能画圆,但控制效果会比较“拖沓”:飞机总是一点点追着目标走,轨迹更像是跟随而非精确飞行。所以在TrajectorySetpoint里,我建议把位置和速度都填上。速度期望可以直接用位置参数方程对时间求导得到:

vx(t) = -R * ω * sin(θ(t)) vy(t) = R * ω * cos(θ(t)) vz(t) = 0

飞控拿到位置和速度期望后,位置环输出前馈速度,速度环再跟踪,跟踪效果会平滑很多。

再说发布频率。PX4文档里有个硬性要求:Offboard模式下设定值最低要高于某频率,通常建议至少10Hz,推荐20Hz~50Hz。我实测20Hz已经完全够用,而且CPU占用不会太高。我个人习惯用50Hz作为默认值,因为Gazebo仿真模型动力学本身比较简单,50Hz下轨迹更平滑,视觉上看不出锯齿。

最后说一下给定参数的具体数值。如果你希望无人机在10秒内飞完一整圈,半径R=5m,那根据圆周运动公式,这个组合也可以这样拆:可以先定角速度ω=2π/10≈0.628 rad/s,线速度就是5×0.628≈3.14m/s。这个线速度对四旋翼来说比较适中,太慢会让位置控制环难以收敛,太快则可能触发限幅导致轨迹打折。参数设定这块,我的建议永远是“低参数起步,好稳定后再加大”。

3.3 写C++代码:核心循环

下面是核心类和一个10Hz~50Hz的发布循环。这个代码我实际跑通过,直接放在你创建的ROS2包里就能编译运行。为了不占太多篇幅,我贴核心部分,构造函数和话题创建的模板代码略。

#include <rclcpp/rclcpp.hpp> #include <px4_msgs/msg/offboard_control_mode.hpp> #include <px4_msgs/msg/trajectory_setpoint.hpp> #include <px4_msgs/msg/vehicle_local_position.hpp> #include <cmath> class OffboardCircleNode : public rclcpp::Node { public: OffboardCircleNode() : Node("offboard_circle_node") { // 创建发布器和订阅器 offboard_mode_pub_ = create_publisher<px4_msgs::msg::OffboardControlMode>( "/fmu/in/offboard_control_mode", 10); trajectory_pub_ = create_publisher<px4_msgs::msg::TrajectorySetpoint>( "/fmu/in/trajectory_setpoint", 10); local_pos_sub_ = create_subscription<px4_msgs::msg::VehicleLocalPosition>( "/fmu/out/vehicle_local_position", 10, [this](const px4_msgs::msg::VehicleLocalPosition::SharedPtr msg) { current_position_ = *msg; }); // 定时器:50Hz发布 timer_ = create_wall_timer(std::chrono::milliseconds(20), [this]() { publishCircleSetpoint(); }); } private: void publishCircleSetpoint() { // 1. 先发布OffboardControlMode,标记我们使用的控制类型 px4_msgs::msg::OffboardControlMode offboard_mode{}; offboard_mode.timestamp = stamp(); offboard_mode.position = true; // 我们使用位置控制 offboard_mode.velocity = false; offboard_mode.acceleration = false; offboard_mode.attitude = false; offboard_mode.body_rate = false; offboard_mode.thrust_and_torque = false; offboard_mode.direct_actuator = false; offboard_mode_pub_->publish(offboard_mode); // 2. 计算圆轨迹设定值 double time_now = 0.0; // 实际项目中应从节点启动时刻或收到本地位置后开始计时 // 我这里用节点启动后的系统时间做演示 auto elapsed = (this->now() - start_time_).seconds(); if (elapsed < 0.0) elapsed = 0.0; time_now = elapsed; constexpr double radius = 5.0; // 5米半径 constexpr double omega = 2.0 * M_PI / 10.0; // 10秒一圈 constexpr double height = 3.0; // 3米高度 constexpr double theta0 = 0.0; double theta = theta0 + omega * time_now; float x = radius * std::cos(theta); float y = radius * std::sin(theta); float z = -height; // NED,Z轴向下 px4_msgs::msg::TrajectorySetpoint setpoint{}; setpoint.timestamp = stamp(); setpoint.position[0] = x; setpoint.position[1] = y; setpoint.position[2] = z; // 速度前馈,让轨迹更平滑 setpoint.velocity[0] = -radius * omega * std::sin(theta); setpoint.velocity[1] = radius * omega * std::cos(theta); setpoint.velocity[2] = 0.0f; setpoint.yaw = 0.0f; // 让机头保持正北 trajectory_pub_->publish(setpoint); } uint64_t stamp() const { auto time_now = this->now(); return static_cast<uint64_t>(time_now.seconds() * 1e6) * static_cast<uint64_t>(1000); // 返回纳秒级时间戳 } rclcpp::Publisher<px4_msgs::msg::OffboardControlMode>::SharedPtr offboard_mode_pub_; rclcpp::Publisher<px4_msgs::msg::TrajectorySetpoint>::SharedPtr trajectory_pub_; rclcpp::Subscription<px4_msgs::msg::VehicleLocalPosition>::SharedPtr local_pos_sub_; rclcpp::TimerBase::SharedPtr timer_; px4_msgs::msg::VehicleLocalPosition current_position_; rclcpp::Time start_time_{rclcpp::Time(0)}; };

代码里我故意保留了几个演示性质的简化点,这里说一下哪些地方需要你自己替换成实际逻辑:

第一,时间戳必须认真对待。OffboardControlModeTrajectorySetpoint都要求传入PX4固件时间戳(微秒或纳秒单位)。我上面写的stamp()函数只演示了如何从ROS2时间转换,实际运行中你会发现,如果PX4主机和ROS2主机时间不同步(比如通过多机通信跑),时间戳会比较别扭。SITL模式下两个进程在同一台机器上,时间基本一致,所以问题不大。如果你跑真机,建议用同步后的系统时间或PX4输出的timestamp统一管理。

第二,起飞前的“预置点”逻辑。代码直接从0点开始画圆,但无人机在地面时不能直接从地面跳到3米高,那样飞控会给出很大的期望加速度,导致起飞瞬间剧烈晃动甚至翻转。稳妥做法是:先发送一个当前位置的setpoint,等无人机切换到Offboard模式并且高度到期望值附近后,再开始画圆。我在完整工程里是把“进入Offboard模式”也封装成一个节点行为的,这里篇幅有限就只展示核心循环。

3.4 编译配置:CMakeLists和package.xml

一个干净的package.xmlCMakeLists.txt,主要就是把px4_msgs依赖加进来。

package.xml里需要声明:

<depend>rclcpp</depend> <depend>px4_msgs</depend>

CMakeLists.txt里则要加:

find_package(ament_cmake REQUIRED) find_package(rclcpp REQUIRED) find_package(px4_msgs REQUIRED) add_executable(offboard_circle src/offboard_circle_node.cpp) ament_target_dependencies(offboard_circle rclcpp px4_msgs) install(TARGETS offboard_circle DESTINATION lib/${PROJECT_NAME})

编译:

cd ~/ws colcon build --packages-select your_package_name source install/setup.bash

4. Gazebo仿真:看着无人机把圆画出来

4.1 启动PX4 SITL与DDS Agent

一切就绪后,启动顺序很关键。我的习惯是:

# 终端1:启动ROS2环境 source /opt/ros/humble/setup.bash source ~/ws/install/setup.bash ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888 -v

注意-v参数会输出详细日志,第一次调试时务必打开,能直接看到Agent和Client是否成功握手。

# 终端2:启动PX4 SITL cd ~/PX4-Autopilot make px4_sitl gz_x500

当Gazebo窗口弹出、终端打印[SITL] Ready for takeoff后,说明PX4固件已经在仿真里跑起来了。此时去终端1看一眼日志,应该能看到Agent收到了来自PX4的DDS消息:

[info] New client [client id: 7] connected

如果没看到这条,多半是端口不对。默认PX4的SITL模式会往本机8888端口发UDP包,Agent也要监听8888端口,两边改成一致即可。

4.2 运行ROS2节点,验证画圆效果

启动我们的控制节点:

# 另一个终端,同样先source ros2 run your_package offboard_circle

理论上无人机会垂直上升到3米高,然后稳定地飞一个半径5米的圆。我建议你在验证阶段做几件事:

第一,打开QGroundControl(QGC)连接到仿真。用QGC的好处是可以目视角速度和位置误差,也能直接远程切换模式。注意,SITL模式下QGC连接的是UDP端口14550,不是USB串口。

第二,确认飞行模式。要让节点真正进入Offboard,你可以通过QGC把飞行模式切到“Offboard”,或者如果不想再额外写模式切换代码,也可以用ros2 topic pub发一条MAVLink指令,但这比较绕。我更推荐先在C++节点里直接发MAVLink指令做自动模式切换,或者跑起来以后在QGC手动切。手动切最省事,适合第一次验证。切换后看页面上的飞控状态,确认当前模式是Offboard而不是Position。

第三,用ros2 topic echo观察实际位置:

ros2 topic echo /fmu/out/vehicle_local_position

这里面有几个字段值得关注:xyz是当地NED坐标系下的位置估计,vxvyvz是速度估计。如果它输出的数值和我们的圆参数一致,说明整个链路已经通了。更直观的方法是装rqt_plot,把xy画到一个二维坐标里,就能看到圆心位置和轨迹形状。

第四,看Gazebo画面。如果无人机在画面里不断画圈,并且圆半径看着稳定,这事就算大功告成。轨迹是椭圆的话,多半是半径参数或速度前馈没对,下面一节会细说。

4.3 如何让圆“飞得更圆”

你可能觉得,能飞起来就算圆了。但其实第一版代码跑出来的轨迹大概率不圆,甚至有点“方”。我的经验是从三个方向调:

一是提高发布频率到50Hz以上。Offboard模式对丢包和延迟非常敏感,如果发布频率只有5Hz,PX4会频繁触发Offboard超时,甚至自动退出Offboard。频率高一点之后,控制环的跟踪误差会明显下降。

二是用好速度前馈。前面代码里已经填了velocity字段,前提是OffboardControlModeposition=true的同时别把velocity也置true,否则PX4会尝试做速度控制,反而丢掉了位置环。实际上PX4在位置控制下,会把TrajectorySetpoint.velocity当作前馈速度。如果你发现“画出来的圆有滞后”,可以先检查这个字段是否有值。

三是调PX4位置控制器的增益。所有PX4的Offboard外部控制,最终都会落到内部位置环。位置环PID增益在QGC的MC_POSCTRL参数组里。给一个实用经验:如果无人机飞圆时有明显“追赶”感,可适当增大MC_POSCTRL_POS_P,比如从默认的1.0提到1.2。但别一次调太多,否则会有高频抖动。修改后直接改参数,无人机会在线响应,不用重启仿真。

5. 常见问题与排查实录

5.1 高频故障速查表

我把自己和身边人实测中踩过的坑整理成了表格,遇到问题先对号入座:

症状可能原因排查思路
消息发了但无人机不动没有进入Offboard模式用QGC或MAVLink确认当前模式
进入Offboard后立即退飞/降落设定值不连续或频率不足保证循环发布50Hz,预置点设在当前位置附近
无人机起飞就翻转坐标系反了/IMU标定错误检查NED vs ENU,重新校准加速度计
Gazebo画面狂闪显卡渲染驱动问题尝试LIBGL_ALWAYS_SOFTWARE=1,或升级显卡驱动
ros2 topic list里没有/fmu开头的消息Agent未启动或端口不一致运行Agent,确认8888端口,看verbose日志
圆飞成椭圆速度前馈没填/增益不合适检查速度字段,提升发布频率,微调MC_POSCTRL_POS_P
高度一直往下掉NED的Z方向填反了NED的期望高度应为负数(如-3)
无人机频繁退Offboard控制指令超时查看PX4终端是否打印“Offboard control lost”
px4_msgs编译不过版本与PX4不匹配检查px4_msgs的tag,最好和固件版本对齐

5.2 几个被忽略的细节

除了表格里的问题,还有几个细节严重影响成功率,但常规文档很少提。

第一个是预置点策略。如果无人机在地面上,代码运行后第一帧就发送3米高的圆心位置,PX4会认为你期望它瞬间到3米。这时候电机会猛加速,飞控可能直接进入保护模式。不要一上来就发目标点,应该先发“当前位置保持”的设定值,等无人机起飞并稳定在目标高度后,再开始圆的轨迹。我的做法是节点启动后先订阅vehicle_local_position,拿当前位置,把它作为前1秒的期望输出,等时间超过1秒后再切换到圆轨迹。

第二个是模式切换的“参数豁免”。如果你没有RC遥控器,SITL在启动时会默认检测到RC信号丢失,一旦无人机进入Offboard后有哪怕几百毫秒的异常,就可能导致飞行终止或落回Position模式。建议在QGC里把COM_RCL_EXCEPT设置为1(表示Offboard模式可以工作于RC丢失状态)。这个参数我在多次实物测试里也被坑过,仿真里不设置也能飞,但真机上不设置的话很容易一上天就触发保护。

第三个是不要在主循环里做耗时操作。比如把打印日志、打开文件操作放在50Hz的publish循环里,偶尔卡顿一帧可能问题不大,但当频率达到20Hz以上时,几百毫秒的卡顿可能会让PX4认为Offboard超时。我一般是发布逻辑只做数学计算和消息发送,必须做的日志用单独的线程或降频输出。

第四个值得单独说的,是**“时间戳差”的问题**。PX4 v1.14开始,OffboardControlMode消息里的timestamp必须填写,否则飞控无法正确判断消息的新鲜度。有的代码里随便填一个固定值,无人机能跑但会有偶发抖动。正确做法是每次消息都取当前时刻,并且单位要统一。PX4内部的timestamp通常是微秒,但有些消息是纳秒,比如vehicle_local_position输出是微秒时间戳。我习惯统一用微秒,避免出现消息被当作过期数据丢掉。

最后的经验分享

这个demo做完之后,最大的收获不是“我会发圆轨迹了”,而是终于把ROS2到PX4之间的这层通信彻底搞明白了。后面你再去做航线规划、视觉避障、编队飞行,核心思路都是一样的:ROS2负责算期望值,PX4负责执行,Agent负责翻译。区别只是期望值从“圆”变成“贝塞尔曲线”还是“多机协同轨迹”而已。

再分享一个小技巧:调通圆轨迹之后,可以把代码里的半径、角速度、高度改成从ROS2参数读取,这样就不用每次改代码重新编译。如果你后续打算做路径规划,把“生成轨迹”的部分抽象成接口,圆只是其中一种轨迹实现,后面接Bezier曲线或B样条就顺理成章了。ROS2的可复用性真正发挥出来,是从“让无人机飞起来”变成“让无人机按我的算法去飞”的时候。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询