开头我就不绕弯子了,直接说结论:ego-planner在Gazebo仿真里跑得再顺,离真机上能稳定自主穿树林还差着十万八千里。这篇东西不是给你讲原理的,是给你一条从零到一、把Jetson Orin-NX和Pixhawk 6C这套组合真正飞起来的路。
我见过太多人卡在同一个地方:仿真里一切正常,一上真机就各种"灵异现象"——要么无人机原地抽风,要么高度突然往下掉,要么压根切不进offboard。这背后大部分不是算法问题,而是通信链路、坐标系、时间戳、参数这些"脏活"没处理干净。这篇文章就是把我在这个过程中走过的弯路、踩过的坑、最后验证有效的方法全部写出来,照着做能让你少折腾至少两个星期。
1. 为什么是"Orin-NX + Pixhawk 6C + ego-planner"这组搭档
1.1 算力与飞控的分工逻辑
先说清楚这套系统的角色划分。Pixhawk 6C是飞控,管的是最底层的事:IMU数据读取、姿态解算、电机控制、PWM输出,它跑的是PX4固件,控制频率通常在250Hz到1kHz之间。Jetson Orin-NX是机载计算机,管的是"脑子"层面的事:相机图像处理、VIO里程计、点云生成、路径规划,最后输出一个期望位置或者期望速度给飞控。飞控再把这个期望值翻译成具体的油门和舵量。
这就像开车:Pixhawk是手脚,负责执行;Orin-NX是眼睛和大脑,负责看路和决定往哪走。你不可能让飞控去跑ego-planner这种需要大量优化的算法——它的CPU根本扛不住,也没那个必要。反过来,你也不能让Orin-NX直接去控制电机——实时性没法保证。
1.2 ego-planner到底解决什么问题
ego-planner全称是Edge-aware Gradient-based Online planner,从名字就能看出来两个关键点:基于梯度的优化、在线生成。核心就是在保证轨迹平滑可飞的同时,尽可能贴近障碍物边缘飞行,这样无人机不会像个惊弓之鸟一样离障碍物八丈远就绕路,而是能"贴边"穿过去。
和传统的A*、RRT这类基于搜索的规划器不同,ego-planner不构建显式的障碍物地图,而是直接把传感器点云当成"斥力源"来做梯度优化。这意味着它天然适合机载场景——不需要先建一张全局栅格图,省掉了大量计算和内存开销。它的前端有基于快速搜索随机树(RRT)的路径发现,后端有B样条轨迹优化,两者配合,使得算法在Orin-NX这种级别的算力上能跑到20Hz以上的规划频率。
1.3 这套组合适合谁
如果你是想做无人机编队、复杂的全局自主导航、长距离勘探,那ego-planner并不适合你——它是个局部规划器,没有全局地图的概念,飞丢了大概率救不回来。但如果你想做的是近距离避障、穿林飞行、快速自主探索未知环境,这套组合就是目前开源社区里最成熟的方案之一。
还有一个前提:你得对ROS、Linux基础操作有一定了解,至少知道catkin_make是干嘛的、什么是话题(topic)、什么是TF变换。完全零基础的话,建议先把ROS入门教程过一遍再回来。
2. ego-planner的代码脉络与仿真闭环搭建
2.1 代码仓库与依赖准备
代码这块建议直接用Fast-Drone-250这个仓库,它是ego-planner团队(浙大FAST-Lab)出的配套教程工程,自带精简版ego-planner实现、PX4飞控仿真环境、以及一个专门为无人机设计的VIO适配层。仓库里给的说明文档是中文的,对新手极其友好。
依赖方面,你需要准备:
- Ubuntu 18.04或20.04(推荐20.04)
- ROS Melodic或Noetic(对应Ubuntu版本,别装错)
- PX4-Autopilot固件源码(Fast-Drone-250自带编译脚本,版本锁定在1.13.x左右)
- Gazebo(PX4官方推荐版本为9或11,具体看仓库要求)
装依赖的时候最容出问题的就是PX4固件和Gazebo插件的版本匹配。我的建议是严格按照仓库的README装,不要手痒去拉最新版PX4,否则大概率会遇到mavlink消息类型对不上、插件加载不进来之类的问题。环境变量记得写入~/.bashrc,每次开新终端都要能直接跑gazebo才行。
2.2 仿真闭环的三个"不等于"
仿真跑通之后,不要急着欢呼,先对着下面三条自查一遍,这是我从惨痛教训里总结出来的:
第一,"仿真能跑"不等于"你理解了整个链路"。我在仿真阶段其实很早就跑通了无人机在随机障碍物里穿梭的demo,但后来真机调参时才发现,我连ego-planner订阅的是什么话题、发布的是什么坐标系都说不清楚。仿真环境里隐藏了大量你"不知道但不需要处理"的细节,比如mavros帮你做好的坐标转换、PX4仿真环境下自动处理的姿态话题,这些在真机上全部要自己搭。
第二,"参数设置合理"不等于"真机也能用同样的参数"。Fast-Drone-250的仿真默认参数是给一个理想化的小型四旋翼调的,机体质量、电机响应、传感器延迟都和你的真机不一样。我在仿真里把最大速度设到3m/s飞得飞快,第一次上真机试了试,直接吓得手心出汗——真机到3m/s的时候,那种机动性带来的压迫感完全不是仿真能模拟的。
第三,"能稳定起降"不等于"能安全切换offboard"。仿真里切换offboard模式(就是让飞控交给外部程序控制)是零成本的,但真机上切换的瞬间如果期望位置和当前位置偏差过大,飞控会用力补偿,结果是直接飞走。这个后面在参数迁移的章节详细讲。
2.3 仿真阶段应该做的事:跑通全链路+学会看日志
仿真阶段真正的目标只有一个:全链路无死角地跑通,同时知道每个节点在干什么、输出什么、依赖什么。我建议你在仿真里做这几件事:
- 用
rosnode list+rqt_graph看清所有节点之间的通信关系,确认ego_planner_node订阅的点云、里程计、状态话题分别来自哪里。 - 用
rostopic echo逐个确认消息内容。比如/odom的坐标系是odom还是world,/pointcloud的PointCloud2消息的frame_id是什么。仿真环境里这些往往不显眼,但对后续真机有决定性影响。 - 手动修改一次规划参数(比如把
max_vel从1.5改成3.0),观察飞行轨迹的变化,建立"参数→行为"的直觉。
3. 真机前夜:硬件接线、PX4配置与通信链路
3.1 机载电脑与飞控的连接方式
首先明确一个底层原则:Jetson和Pixhawk之间是串口通信,走MAVLink协议。两边的连接就两根线——TX、RX,Pixhawk的TELEM口(一般是TELEM2)接Jetson的UART口。要注意的是,Jetson的GPIO引脚电平是3.3V,不要直接接5V的串口设备,最好通过电平转换芯片,否则有烧毁GPIO的风险。
我这里用的方案是USB转TTL:Pixhawk的TELEM2口接到一个USB-TTL转接模块,模块另一头插Jetson的USB口。这样做的原因很简单——USB口即插即用,用ls /dev/ttyUSB0就能看到设备,不需要配置设备树,排查起来也方便。代价是理论上USB的实时性不如原生UART,但实际跑起来没遇到瓶颈。
3.2 PX4端要改的参数
PX4固件这边需要配置的参数,一条条列给你:
MAV_1_CONFIG = TELEM2:设置MAVLink通道使用的物理串口。MAV_1_MODE = Onboard:设置MAVLink模式为机载模式,此时飞控会以较高的频率发送机载需要的消息。SYS_COMPANION = Companion:告诉PX4连接了一个机载计算机,这会调整部分消息发送策略。SYS_AUTOSTART:根据你的机架类型设置机型(比如400是四旋翼X布局)。BAT*系列参数:根据你的电池型号和容量设置电压阈值。这个务必设置准确,否则飞行中可能直接触发低压保护降落。
改完参数后重启飞控,在QGroundControl里确认能看到MAVLink连接已经在TELEM2上工作。
3.3 通信层:mavros的配置与自检
Jetson端跑的是mavros节点,把MAVLink协议翻译成ROS话题。mavros配置需要注意几个关键点:
在px4_config.yaml里,把fcu_url改成你的设备路径:
fcu_url: /dev/ttyUSB0:921600波特率要飞控端匹配。PX4的TELEM2默认波特率是921600,如果你飞控端没改,这里就填921600,两边不一致会导致通信乱码。
mavros配置好之后,用roslaunch mavros px4.launch启动,然后依次确认:
rostopic echo /mavros/state:能看到connected: True以及armed、guided、mode的变化。rostopic echo /mavros/local_position/odom:有数据,说明飞控在发布本地位置。rostopic hz /mavros/imu/data:频率稳定在100Hz左右,说明链路通畅。
有一个非常容易踩的坑:mavros的setpoint_raw/local话题在飞控解锁前就能发布,但飞控不会执行。如果你发现程序在跑、话题有数据、但电机不动,别慌,先检查飞控状态机是否处于可解锁状态,QGroundControl里有没有弹出解锁限制条件。
3.4 TF树与时间同步——90%的"灵异现象"源头
这是整个链路里最枯燥也最容易出问题的一环。ego-planner规划出来的轨迹是有坐标系语义的,如果TF树不对,飞控收到的期望位置就可能是在一个错误的参考系下,轻则无人机悬停位置偏,重则直接失控。
一个正常的TF树应该长这样:
world -> odom -> base_link -> camera_link但实际中,有些VIO方案输出的里程计本身就在world系下,此时就变成:
world(=odom) -> base_link -> camera_link需要根据你的视觉里程计方案灵活处理,核心是保证ego_planner订阅的里程计消息和点云消息在同一个参考系里对齐。
时间同步也是个隐形杀手。VIO节点产生的位姿时间戳如果和点云时间戳对不上,规划器算出来的轨迹就是基于一个过期状态的估计,轻则轨迹抖动,重则撞障。我的解决方案是让VIO帧率(图像帧率)和点云发布频率都对齐到30Hz,用message_filters在ego_planner节点里做时间同步,超过10ms的丢帧直接丢弃。
4. 从仿真到真机的参数迁移——一个参数一个参数地抠
4.1 控制器增益:先让你的飞机"听话"
Fast-Drone-250真机适配里有一个单独的控制器节点px4_pos_controller,它把ego-planner规划的期望位置转换成速度指令,再通过mavros发给PX4。默认参数是为仿真调的,真机上通常需要重新调。
核心参数是三个:
traj_xy_gain:水平位置误差的增益。traj_z_gain:垂直方向增益。hover_xy_gain、hover_z_gain:悬停时的平滑增益。
我的经验是从仿真默认值的一半开始,飞起来之后观察悬停精度和跟踪误差。如果无人机在悬停时频繁修正、有一冲一冲的感觉,说明traj_xy_gain偏大,降下来;如果跟踪轨迹时总是慢半拍、转弯明显落后,说明增益偏小。真机上调参要"小步快跑",每次改10%左右,悬停稳定了再试小范围飞行,千万别一上来就往大了调。
4.2 规划器参数:限制梦想,保护真机
ego-planner节点(ego_planner_node)里的参数在仿真里可以很激进,真机上必须收敛。我整理了一份自己的配置基准:
| 参数 | 仿真默认 | 真机推荐 | 影响 |
|---|---|---|---|
max_vel | 3.0 m/s | 1.5 ~ 2.0 m/s | 最大飞行速度,过高容易来不及避障 |
max_acc | 3.0 m/s² | 2.0 m/s² | 最大加速度,过高会让电机饱和,轨迹变形 |
flight_height | 1.0 m | 1.5 ~ 2.0 m | 默认飞行高度,真机上太低了地面噪点干扰大 |
obstacles_inflation | 0.15 m | 0.3 ~ 0.5 m | 障碍物膨胀半径,真机安全性全靠它 |
pointcloud_hit | 0.2 | 0.4 | 点云命中阈值,调高能减少误报点 |
特别注意obstacles_inflation这个参数,室外环境下激光雷达或者深度相机扫到的点云噪声远比仿真多,膨胀半径过小的话规划的轨迹会离障碍物太近,稍有风或者定位偏差就可能蹭上去。我最终是取0.4m,保证树叶、细枝这种点云噪点不会被当作威胁。
4.3 传感器朝向:一个不起眼但致命的参数
很多VIO方案(比如VINS-Fusion、Realsense的T265)初始化时会输出一个初始朝向,如果你的相机安装方向和机体坐标轴不一致,VIO输出的里程计会带一个固定偏移。这个偏移如果不补偿,ego-planner规划出来的轨迹在真机上就是歪的。
解决方案是在VIO启动前做一个静态TF标定:把camera_link到base_link的变换写死在launch文件里,同时用static_transform_publisher发布出来。具体怎么标定,拿一个量角器加一把直尺量出相机光心相对机体中心的三轴偏移和三个欧拉角就行。注意,不同安装方式(朝前、朝前下45度)对应的roll/pitch/yaw值差异很大,不要凭感觉填。
5. 首飞实测与一次完整的炸机排查
5.1 炸机现场:无人机在offboard模式下突然急速下坠
那次炸机发生在我第三次真机测试时。流程是:可以正常解锁,切到定高模式起飞,悬停到1.2m高度,然后在地面站里切换offboard模式。切换前一切都正常,切完不到两秒,无人机直接往下坠——不是缓慢下沉,是动力全部输出为0、自由落体级别的下坠。好在只砸在草地上,桨叶断了两根,机架没大伤。
5.2 排查链路:从现象反推根因
我复盘时没有直接去翻代码,而是先列了一个"可能导致下坠"的清单,逐项排查:
第一嫌疑:PX4的offboard切换条件不满足。PX4有一个安全机制,从自稳模式切到offboard时,如果期望位置和当前位置相差超过某个阈值,飞控会拒绝切换,或者切换后有一个"强制追踪"的过渡。但我的排查结果是,QGroundControl里显示已经成功进入offboard,而且无人机是先进入offboard、然后才下坠的,所以这个排除。
第二嫌疑:ego-planner发布的期望位置错误。我用rostopic echo /command/pos看发布的位置,发现数值一直在正常范围内(高度1.2m附近抖动),没有跳变。这个也排除。
第三嫌疑:mavros的setpoint_raw话题频率不够或者丢失。PX4手册明确要求offboard模式下必须连续收到设定点消息(推荐10Hz以上),如果超过1秒没收到,飞控会退出offboard回到上一个模式。我录了包回放,发现mavros在切换offboard后大约0.5秒内就不再收到ego-planner的设定点了,而ego-planner节点本身还在正常运行。到这里,基本锁定问题出在ego-planner → mavros之间的通信链路上。
第四嫌疑:坐标系/消息类型不匹配导致mavros丢弃消息。打开mavros的log,发现它报了一个TRANSFORM相关的warning,然后拒收了一条setpoint_raw/local消息——因为这条消息的frame_id和mavros期望的不一致。原来我在ego-planner的发布端把frame_id写成了base_link,而mavros要求必须是local_origin(也就是PX4的本地坐标系),二者差了一个机体航向角。在仿真里PX4用的local系和世界系对齐,所以从来没有暴露过这个问题,真机上机体是旋转的,消息一来就直接冲突。
5.3 修复方案与复盘教训
修复很粗暴但有效:在ego-planner发布setpoint的代码里,把frame_id从base_link改成map,然后在TF树里把map到local_origin的变换在mavros侧做好。改完重新编译、跑通信自检、离地悬停测试,再切offboard,问题消失。
这次炸机让我明白三件事:
- 仿真环境里"恰好能工作"的参数和坐标系,恰恰是最危险的盲区。很多问题之所以在仿真里不出现,是因为仿真环境做了太多隐含假设。
- offboard切换必须是最高优先级的功能测试。绝对不要在没验证offboard切换的情况下就去跑完整路径规划。先把切offboard、悬停、手动接管这一整套动作练到肌肉记忆,再谈自主飞行。
- 录包是排查问题的最好手段。那次炸机我能这么快定位,完全得益于当时开着
rosbag record -a。炸机之后回放数据,几分钟就找到了线索。
6. ego-planner的边界条件与安全兜底
6.1 规划器的自我保护机制
ego-planner的代码里其实有几个内置的"刹车"机制,我在读代码时发现它们非常关键:
第一,状态机的异常处理。ego-planner有一个状态机,在"还未收到有效里程计"和"已经正常规划"之间切换。如果它长时间收不到里程计或者点云,会停在原地发布悬停指令,而不是继续按最后一条轨迹飞。这是保命的机制——但前提是你没把异常状态下的指令当正常指令用。
第二,轨迹刷新率限制。规划器默认在新轨迹生成前,会沿用上一条轨迹的剩余部分。这就意味着,如果上一条轨迹规划时离障碍物太近,即使现在障碍物已经不在视野里,无人机还是会"盲飞"一段。所以真机上max_vel设小一点、obstacles_inflation设大一点,本质上是给这个"盲飞段"留安全缓冲。
6.2 手动接管:你的最后一道防线
不管ego-planner多成熟,手动接管永远是你最重要的逃生通道。我的做法是:遥控器的飞行模式开关,一个档位是自稳(Stabilized),另一个档位是offboard。遇到任何不对劲,先把"模式开关"打回自稳,同时直接把油门杆拉到底——这样至少能保证无人机不会继续执行错误的自主指令。
这里有个细节:飞控从offboard切回自稳后,会丢失外部期望位置,此时需要你先手动稳住姿态,再决定下一步怎么办。不要指望切回去之后飞控还帮你维持高度——自稳模式的油门是"你给多少它飞多少",给少了就下沉,给多了就抬头,需要你手动接管。
6.3 电量管理与飞行边界
真机飞行前一定要检查电池电压,我建议用一块满电的4S或者6S电池,电压低于3.8V/cell就直接换。ego-planner在室外飞行的功率消耗比室内高不少——频繁加减速、急转弯、来回调整位置,电机的瞬时电流可以逼近峰值。我测试时飞了8分钟左右电池就到3.75V了,规划器还在正常跑,但飞控已经在报警低压,这时候退出offboard手动降落已经是必须的。
另外,真机测试场地选择也很重要。初期测试尽量选开阔的草地,周围没有铁丝网、电线、树木,即便失控也有缓冲区域。可以在场地四周拉一圈安全线,安排两个人在对角位置站着,一人盯飞机,一人盯遥控器和地面站屏幕。
7. 从仿真到真机的最后一公里:跑通一次完整的自主飞行
真机自主飞行和仿真不同,不是"启动launch文件等飞机自己飞"就完了。我的标准流程,写给你作参考:
起飞机检清单:
- 电池电压在安全范围内,桨叶安装方向正确,螺丝拧紧。
- QGroundControl里确认飞控能收到GPS或光流数据(室内光流也需要),确保本地位置估计正常。
- Jetson上确认所有节点running,
rosnode list里能看到ego_planner_node和mavros,rqt_tf_tree里TF树完整。 rostopic echo /mavros/state里确认connected: True,模式处于手动/自稳。
起飞流程:
- 遥控器开锁,切到自稳模式,手动起飞到1.5m高度。
- 观察悬停是否稳定(这个高度下如果抖动明显,先降下来调参数)。
- 切换offboard模式,观察无人机是继续稳在一个点上还是开始飘。我的经验是:如果一切正常,切过去后机身会有一次非常轻微的"点头"动作,那是飞控从内部姿态控制切换到外部位置控制的正常过渡。
- 确认悬停稳定之后,在另一个终端里运行预先写好的路径发布脚本(或者手动给出一组目标点),观察无人机是否按预期沿着轨迹飞。
- 全程手握遥控器,拇指放在模式开关上,任何异常立刻切回自稳。
飞行中观察什么:
- 轨迹是否平滑:真机上飞出来的轨迹应该是连续、圆润的曲线,如果出现锯齿形修正,说明参数太激进。
- 是否有"贴边"效果:ego-planner的招牌能力是贴近障碍物飞行,真机测试时可以和障碍物保持一个安全距离再观察,如果规划轨迹离障碍物太远了,说明膨胀半径设得偏大。
- 高度是否能保持:如果飞行中高度慢慢漂移,说明VIO的高度估计在退化(室外GPS辅助或者室内光流都会影响),需要检查传感器融合。
落地流程:
- 从offboard切回自稳。
- 手动控制降落,不要依赖自主降落。
- 落地后立即解锁(其实就是把油门杆拉到最低,飞控会自动上锁)。
- 保存所有节点日志和
rosbag,回头看曲线调整参数。
这套流程跑通之后,你才算真正意义上"用ego-planner在真机上自主飞行"了一次。
关于后续的扩展方向,我个人的建议是:先把Fast-Drone-250自带的VIO方案(Robust-VIO)彻底摸透,再去尝试换Realsense、换Livox激光雷达,或者把规划器换成Fast-Planner做全局规划。不要一上来就追求"大而全",ego-planner这套系统的精髓在于每个环节都能单独调试和验证,逐层把底层打牢,上层才会稳定。我踩过最大的坑就是跳过底层联调直接跑整机测试,最后花在排查问题上的时间远大于重新走一遍流程的时间。希望你不用再交这个学费。