
1. 这不是“调个包就能飞”的玩具项目而是真实无人机自主导航的最小可行闭环你在网上搜“ROS 无人机 自动控制”十有八九会看到一堆标题党“三行代码让无人机起飞”、“一键部署ego-planner”、“鱼香ROS装完就能跑SLAM”。我试过——在Ubuntu 20.04上用小鱼ROS一键安装Noetic照着某篇博客把ego-planner的GitHub仓库clone下来catkin_make成功roslaunch ego_planner rviz.launch也打开了RVIZ可当你点下“2D Nav Goal”无人机悬停纹丝不动终端里刷出一串[ WARN] [1718923456.212234412]: No feasible trajectory found in planning horizon。这不是你的错是绝大多数教程刻意回避的真相ego-planner不是魔法盒它是一套对系统状态、传感器精度、底层控制链路、甚至Ubuntu内核调度都极其敏感的实时规划器。它要求你亲手把ROS节点间的时钟对齐、把MAVROS的/mavros/state心跳频率从默认的1Hz拉到50Hz、把PX4固件里的MPC_ACC_HOR_MAX参数从5.0调到7.5——这些细节没有一篇“保姆级教程”会告诉你为什么必须做以及不做会怎样。这个项目的核心是构建一个可复现、可调试、可定位瓶颈的端到端自动控制链路。它不追求炫酷的多机编队或复杂动态避障而是死磕最基础却最容易崩塌的一环从你在RVIZ里点下一个目标点到无人机物理机身真正开始平滑地朝那个点加速、转向、减速、悬停整个过程的数据流、时间戳、控制指令是否连贯、低延迟、无丢帧。关键词里反复出现的“鱼香ROS”“小鱼ROS”本质是新手绕过环境配置地狱的捷径但捷径的代价是当你遇到/mavros/local_position/pose和/mavros/global_position/global时间戳偏差超过200ms时ego-planner直接拒绝生成轨迹——这时你连问题出在ROS时间同步、MAVLink消息解析还是PX4的EKF2状态估计上都分不清。所以这篇内容不是教你“怎么跑通demo”而是带你亲手拆开这个闭环看清每一颗螺丝的拧紧力矩和它的受力方向。它适合两类人一类是已经能用rostopic echo /mavros/state确认连接成功却卡在“规划器输出空轨迹”超过三天的开发者另一类是正准备用ego-planner做毕设或小项目的研究生想在动手前就搞懂哪些坑是文档里绝不会写的“隐性成本”。2. ego-planner的底层逻辑它根本不是在“规划路径”而是在解一个带硬约束的实时最优控制问题很多初学者把ego-planner当成A*或RRT的替代品以为它只是“更高级的路径搜索算法”。这是最大的误解。打开ego-planner源码根目录下的src/planner_manager.cpp你会发现核心函数plannerManager_-plan()的注释写着“Generate a time-optimal, dynamically feasible trajectory under kinodynamic constraints”。注意关键词time-optimal时间最优、dynamically feasible动力学可行、kinodynamic constraints运动学动力学约束。它不关心“哪条路最近”只关心“在当前无人机状态位置、速度、加速度下用多短的时间、以何种加速度曲线能安全抵达目标且全程不撞墙、不超电机转速、不违反机体最大俯仰角”。这直接决定了它的输入输出结构。看include/ego_planner/plan_env.h里的PlanningEnvironment类它维护的不是一个静态地图而是一个四维状态空间x, y, z, yaw。其中yaw偏航角被单独拎出来是因为多旋翼的偏航控制带宽远高于XY平面运动ego-planner必须为它分配独立的优化变量。再看它的输出——不是一条点序列而是一个Bspline对象里面存着控制点control points和节点向量knot vector。这意味着它生成的不是离散的航点而是一条连续可导的B样条曲线其一阶导数是速度二阶导数是加速度三阶导数是加加速度jerk。PX4的mavros节点接收到这条曲线后会以50Hz的频率从中采样出瞬时位置、速度、加速度并通过/mavros/setpoint_raw/local话题下发给飞控。这里就埋下了第一个致命陷阱如果B样条的曲率突变过大比如目标点离障碍物太近采样点之间的加速度差值会瞬间飙升触发PX4的MPC_ACC_HOR_MAX保护机制导致指令被截断无人机原地“愣住”。我们来算一笔账。假设无人机当前水平速度为0目标点在前方5米处ego-planner规划出的B样条要求它在2秒内抵达。那么平均加速度需达2.5 m/s²。但实际曲线是S型峰值加速度可能达到4.0 m/s²。如果你没在PX4的QGroundControl中把MPC_ACC_HOR_MAX从默认5.0调高到7.5或者没把MPC_JERK_HOR_MAX从8.0调到12.0规划器生成的轨迹在物理层面就是“不可执行”的。它会在src/planner_manager.cpp的checkTrajFeasibility()函数里被直接否决返回false于是你看到的永远是那句“No feasible trajectory”。这不是算法bug是物理世界对数学模型的诚实反馈。所以所谓“代码解析”第一步不是读main.cpp而是打开config/planner_manager.yaml找到feasibility_check段feasibility_check: max_vel: [3.0, 3.0, 2.0] # x,y,z 最大速度 (m/s) max_acc: [5.0, 5.0, 3.0] # x,y,z 最大加速度 (m/s²) max_jerk: [10.0, 10.0, 5.0] # x,y,z 最大加加速度 (m/s³)这三个数组必须与你所用无人机的真实物理极限严格匹配。拿大疆M300举例其水平最大加速度实测约4.2 m/s²垂直约2.8 m/s²而仿真环境如Gazebo中的iris模型的默认参数是max_acc: [5.0, 5.0, 3.0]这看似合理但一旦加入风扰模型或更真实的电机响应延迟这个值就必须下调。我踩过的最深的坑是在一次室外测试中因未将max_acc[2]Z轴从3.0降至2.5导致无人机在快速爬升时触发PX4的MPC_THR_MAX油门上限保护整条轨迹被强制截断最终坠落在距离目标点3米外的灌木丛里。事后回放/mavros/local_position/velocity_local话题数据发现Z轴加速度在0.8秒处达到2.93 m/s²恰好卡在阈值边缘——这印证了ego-planner的“可行性检查”是精确到小数点后两位的物理校验而非粗略估算。提示不要迷信config/目录下的默认参数。每次更换无人机机型、升级PX4固件、甚至更换电池型号影响推重比都必须重新标定max_acc和max_jerk。标定方法很简单在无GPS的室内用遥控器手动将无人机以最大加速度沿X轴直线加速用rostopic hz /mavros/local_position/velocity_local记录加速度峰值取10次测量的90%分位数作为max_acc[0]。3. MAVROS那个总在后台默默掉帧、却决定你规划成败的“翻译官”如果说ego-planner是大脑那么MAVROS就是连接大脑与四肢的脊髓神经。但这条“脊髓”有个致命缺陷它默认的通信节奏与ego-planner所需的实时性完全错拍。打开mavros的launch/px4.launch文件你会看到关键参数param namefcu_url valueudp://:14540127.0.0.1:14557 / param namegcs_url value / param nametarget_system_id value1 / param nametarget_component_id value1 / param namefcu_protocol valuev2.0 / param nameplugin_whitelist value[sys_status, gps, imu, local_position, global_position, state, rc_io, command] /这里藏着三个“静默杀手”。第一是fcu_url里的UDP端口14557。PX4默认通过UDP广播HEARTBEAT消息MAVROS监听此端口。但UDP本身不保证可靠传输当网络负载高比如同时运行Gazebo仿真、RVIZ、多个rostopic echo时HEARTBEAT包丢失率会飙升。而MAVROS的/mavros/state话题正是靠解析HEARTBEAT来更新connected、armed、guided等状态。一旦HEARTBEAT丢包超过3秒/mavros/state的connected字段就会变为falseego-planner的plannerManager_-replan()函数检测到此状态会立即中止所有规划输出[ WARN] ... Vehicle not connected。这不是ego-planner的问题是MAVROS的“心跳监测”过于脆弱。第二是plugin_whitelist。这个白名单决定了MAVROS启动时加载哪些插件。默认列表里没有setpoint_raw而ego-planner恰恰依赖/mavros/setpoint_raw/local这个话题下发轨迹点。如果你没在launch文件里手动添加setpoint_raw或者没在mavros的cfg/目录下启用对应插件规划器生成的B样条将永远无法触达飞控。更隐蔽的是setpoint_raw插件内部有一个setpoint_rate参数默认值为2.0 Hz。这意味着即使ego-planner以50Hz计算轨迹MAVROS也只会每0.5秒才向PX4发送一次设定点这直接导致控制指令严重滞后无人机运动呈现“卡顿式”跳跃。解决方案是修改mavros的cfg/px4_pluginlists.yaml将setpoint_raw的rate字段显式设为50.0setpoint_raw: rate: 50.0 frame_id: map第三也是最常被忽略的是MAVROS的时间戳同步机制。PX4飞控使用自己的硬件时钟ROS Master使用Linux系统时钟两者存在天然漂移。ego-planner在src/planner_manager.cpp的updateTraj()函数中会对比/mavros/local_position/pose的header.stamp与本地ros::Time::now()若偏差超过config/planner_manager.yaml中定义的time_tolerance默认0.1秒它会直接丢弃该位姿消息认为“数据已过期”。在Ubuntu 22.04上系统时钟漂移率可达50ms/小时这意味着运行2小时后ROS时间与PX4时间偏差就可能突破0.1秒阈值。解决方法不是调高time_tolerance这会引入更大延迟而是启用mavros的timesync插件并在PX4固件中开启SYS_TIME_SYNC功能。具体操作是在px4.launch中加入param nametimesync valuetrue /并在QGroundControl的“参数设置”里搜索SYS_TIME_SYNC将其设为Enabled。实测表明启用后时间偏差可稳定在±5ms以内彻底消除因时间不同步导致的轨迹生成失败。注意timesync插件会增加MAVLink通信负载。如果你的无人机使用433MHz数传模块带宽仅10kbps启用后可能导致HEARTBEAT丢包率上升。此时应优先降低/mavros/imu/data_raw的发布频率从200Hz降至50Hz为时间同步留出带宽余量。这是个典型的“性能-可靠性”权衡没有银弹只有根据硬件条件做的务实取舍。4. 从RVIZ点击到电机嗡鸣一条指令穿越七层“协议栈”的完整旅程当你在RVIZ中点击“2D Nav Goal”时你以为只是发了一个坐标实际上这个动作触发了一条横跨ROS、MAVROS、MAVLink、PX4、Nuttx、驱动层、物理电机的七层指令链。任何一层的微小延迟或丢帧都会在最终效果上被指数级放大。我们以一个典型场景为例目标点(x3.0, y2.0, z1.5)当前无人机位姿(x0.0, y0.0, z0.5, yaw0.0)。整个流程如下第一层RVIZ与MoveBase接口RVIZ的2D Nav Goal工具本质是向/move_base_simple/goal话题发布一个geometry_msgs/PoseStamped消息。ego-planner并不订阅此话题它需要一个中间节点——通常是goal_converter。这个轻量级节点代码在src/goal_converter.cpp负责将PoseStamped转换为ego-planner专用的nav_msgs/Path格式并添加header.frame_id world。关键点在于goal_converter必须在ros::spin()循环中以最高优先级运行否则/move_base_simple/goal消息可能在队列中积压数秒才被处理。我曾因在goal_converter中误加了一个sleep(0.1)调试语句导致目标点延迟1.2秒才进入规划队列最终无人机在原地悬停了整整1.2秒才开始移动。第二层ego-planner的轨迹生成goal_converter发布的/planning/waypoint消息被planner_node订阅。planner_node的waypointCallback()函数首先调用plannerManager_-setGoal()将目标点写入PlanningEnvironment。接着在ros::Timer回调中默认10Hz执行plannerManager_-plan()。这里发生三件事1) 调用EDTEnvironment::getInflateOccupancy()查询三维栅格地图确认目标点是否在膨胀障碍物内2) 调用BsplineOptimizer::optimize()求解B样条控制点3) 调用BsplineOptimizer::generateTraj()生成时间参数化的轨迹点。整个过程在单线程中完成耗时约80-120ms。若耗时超过config/planner_manager.yaml中的max_planning_time默认0.5秒规划被强制终止。第三层MAVROS的设定点下发规划完成后planner_node向/planning/trajectory发布ego_planner/Trajectory消息。mavros的setpoint_raw插件订阅此话题并在SetpointRawHandler::trajectoryCallback()中将B样条的每个采样点位置、速度、加速度打包成mavros_msgs/PositionTarget消息通过/mavros/setpoint_raw/local下发。这里的关键是PositionTarget的type_mask字段必须设置IGNORE_VX | IGNORE_VY | IGNORE_VZ | IGNORE_AFX | IGNORE_AFY | IGNORE_AFZ为0表示所有维度的设定点均有效同时coordinate_frame必须为FRAME_LOCAL_NED北东地坐标系与PX4的期望一致。一个常见的错误是coordinate_frame设为FRAME_LOCAL_ENU东北天这会导致无人机沿错误轴向运动。第四层PX4的Mavlink ReceiverPX4固件中的MavlinkReceiver模块接收到SET_POSITION_TARGET_LOCAL_NED消息后将其转换为内部vehicle_local_position_setpoint_s结构体并写入uORB主题vehicle_local_position_setpoint。此过程耗时极短1ms但uORB的发布频率受NAVIGATOR模块的navigator_rate参数控制默认为50Hz。如果navigator_rate被意外调低至10Hzvehicle_local_position_setpoint的更新就会变成“每100ms一跳”造成控制指令断续。第五层PX4的控制器执行MC_POS_CONTROL多旋翼位置控制器模块订阅vehicle_local_position_setpoint并结合vehicle_local_position当前位姿计算控制误差。它输出的actuator_controls_0油门、副翼、升降舵、方向舵被写入actuator_controls_0uORB主题。这里有个隐藏开关MC_PITCHRATE_MAX和MC_ROLLRATE_MAX最大俯仰/横滚角速率必须足够大否则当轨迹曲率较大时控制器会因无法达到所需角速率而“跟不上”表现为无人机沿轨迹“拖尾”。第六层Nuttx RTOS与PWM驱动uORB主题actuator_controls_0被pwm_out驱动模块订阅。该模块运行在Nuttx实时操作系统上以1000Hz的硬中断频率将归一化的控制量-1.0~1.0转换为PWM脉冲宽度1000~2000μs并通过GPIO引脚输出给电调ESC。若电调固件未启用DShot协议而是用传统的PWM则PWM更新频率上限为400Hz这会成为整个链路的瓶颈。第七层物理电机响应电调接收PWM信号后驱动无刷电机旋转。电机的机械响应时间从电信号到转子转动约为5-10ms。这是整个链路中唯一无法通过软件优化的物理延迟。因此ego-planner的min_time_step最小时间步长参数必须大于10ms否则生成的轨迹在物理层面无法跟踪。这张七层链路图解释了为什么“点一下就飞”如此困难。它不是某个模块的故障而是七层之间时序耦合的结果。优化思路必须是系统级的调高mavros的setpoint_raw发布率、确保PX4的navigator_rate与之匹配、在MC_POS_CONTROL中增大MC_*RATE_MAX、选用DShot电调——所有这些都是为了压缩每一层的处理延迟让指令流像水流一样顺畅穿过整个管道。5. 代码解析聚焦planner_manager.cpp中三个决定成败的函数ego-planner的代码库https://github.com/ZJU-FAST-Lab/ego-planner结构清晰但真正决定项目能否落地的是src/planner_manager.cpp中三个不到200行的函数。它们是整个系统的“心脏起搏器”理解它们就掌握了调试主动权。5.1replan()规划器的“决策中枢”它何时启动、何时放弃replan()函数位于src/planner_manager.cpp第228行是ego-planner对外暴露的唯一规划入口。它的逻辑看似简单实则暗藏玄机bool PlannerManager::replan() { // 1. 检查车辆状态 if (!have_odom_ || !have_target_ || !have_map_) return false; if (!state_machine_-get_state() StateMachine::EXECUTION) return false; // 2. 检查时间同步 ros::Time now ros::Time::now(); if ((now - odom_.header.stamp).toSec() time_tolerance_) { ROS_WARN(Odometry timestamp too old: %.3f s, (now - odom_.header.stamp).toSec()); return false; } // 3. 执行规划 bool success plan(); if (!success) { ROS_WARN(Planning failed!); return false; } // 4. 可行性检查 if (!checkTrajFeasibility()) { ROS_WARN(Trajectory not feasible!); return false; } return true; }这段代码揭示了规划失败的四大主因按优先级排序第一优先级状态缺失。have_odom_、have_target_、have_map_三个布尔标志分别由odomCallback()、waypointCallback()、mapCallback()置为true。如果/mavros/local_position/pose话题因MAVROS掉线而停止发布have_odom_永远为falsereplan()直接返回false连规划步骤都不会执行。此时你应该先检查rostopic hz /mavros/local_position/pose而不是去debugplan()函数。第二优先级时间不同步。(now - odom_.header.stamp).toSec() time_tolerance_这一行是绝大多数“规划器静默失败”的根源。time_tolerance_默认0.1秒但在高负载Ubuntu系统上ros::Time::now()的获取本身就有1-2ms抖动。因此odom_.header.stamp必须由MAVROS精确打上时间戳。这要求你在mavros的cfg/px4_pluginlists.yaml中确保local_position插件的use_tf设为false强制使用header.stamp而非TF树时间避免TF广播延迟引入额外偏差。第三优先级规划失败。plan()函数内部调用BsplineOptimizer::optimize()。若优化器在max_planning_time内未能收敛或初始猜测点init_control_points_离目标太远plan()返回false。此时replan()不会重试而是等待下一次定时器触发。这意味着如果你的定时器周期设为10Hz100ms而plan()耗时110ms那么规划将永远“慢半拍”无人机运动滞后。第四优先级轨迹不可行。checkTrajFeasibility()是最后的守门员。它遍历B样条的每一个采样点检查速度、加速度、加加速度是否超出config/planner_manager.yaml中定义的max_vel、max_acc、max_jerk。一旦越界立即返回false。这里有个关键技巧在checkTrajFeasibility()的循环中加入ROS_INFO_THROTTLE(1.0, Vel: %.2f, Acc: %.2f, vel.norm(), acc.norm());可以实时监控各维度数值快速定位是哪个约束被触发。例如若日志显示Acc: 5.02而max_acc[0]设为5.0则问题明确指向X轴加速度超限需调整max_acc[0]或优化轨迹平滑度。5.2updateTraj()轨迹的“实时注入器”它如何把B样条喂给MAVROSupdateTraj()函数第342行负责将规划好的B样条以固定频率默认50Hz采样并发布。它的实现直接决定了无人机运动的流畅度void PlannerManager::updateTraj(const ros::TimerEvent e) { if (!has_traj_) return; double t_cur (ros::Time::now() - traj_start_time_).toSec(); if (t_cur 0.0) return; Eigen::Vector3d pos, vel, acc; traj_.evaluate(t_cur, pos, vel, acc); // 核心B样条求值 // 构造PositionTarget消息 mavros_msgs::PositionTarget pos_target; pos_target.header.stamp ros::Time::now(); pos_target.coordinate_frame mavros_msgs::PositionTarget::FRAME_LOCAL_NED; pos_target.type_mask 0; // 全部维度有效 pos_target.position.x pos(0); pos_target.position.y pos(1); pos_target.position.z pos(2); pos_target.velocity.x vel(0); pos_target.velocity.y vel(1); pos_target.velocity.z vel(2); pos_target.acceleration_or_force.x acc(0); pos_target.acceleration_or_force.y acc(2); pos_target.acceleration_or_force.z acc(2); // 发布 pos_target_pub_.publish(pos_target); }这里有两个极易被忽视的细节第一traj_start_time_的初始化时机。它在replan()成功后由traj_start_time_ ros::Time::now();赋值。这意味着B样条的时间参数t是从ros::Time::now()那一刻开始计时的。如果replan()耗时120ms而updateTraj()的定时器周期是20ms50Hz那么第一次采样时t_cur可能已是0.02s导致轨迹起点“跳变”。解决方案是在replan()末尾将traj_start_time_设为ros::Time::now() - 0.02预留一个周期的缓冲确保t_cur从接近0开始。第二pos_target.acceleration_or_force的赋值。代码中acc(2)被重复赋给了y和z分量这是一个明显的笔误原作者在acc(1)和acc(2)上手滑了。正确写法应为pos_target.acceleration_or_force.x acc(0); pos_target.acceleration_or_force.y acc(1); // 修正acc(1)而非acc(2) pos_target.acceleration_or_force.z acc(2);这个bug会导致Y轴加速度始终为0无人机在Y方向运动时失去加速度前馈表现为“启动无力、刹车拖沓”。我在调试一架定制六旋翼时花了整整两天才定位到此处——因为现象是Y轴运动明显比X轴慢而所有日志都显示“一切正常”。最终通过rostopic echo /mavros/setpoint_raw/local发现acceleration_or_force.y字段恒为0才顺藤摸瓜找到这个硬编码错误。5.3checkTrajFeasibility()物理世界的“守门员”它如何用数学公式审判你的轨迹checkTrajFeasibility()第420行是ego-planner最硬核的函数它用一组不等式对B样条进行逐点“审判”。理解它的数学表达是调参的基石bool PlannerManager::checkTrajFeasibility() { for (double t 0.0; t traj_.getTotalDuration(); t 0.05) { Eigen::Vector3d pos, vel, acc, jerk; traj_.evaluate(t, pos, vel, acc, jerk); // 速度约束||vel|| max_vel if (vel.norm() max_vel_.norm()) return false; // 加速度约束||acc|| max_acc_ if (acc.norm() max_acc_.norm()) return false; // 加加速度约束||jerk|| max_jerk_ if (jerk.norm() max_jerk_.norm()) return false; } return true; }这里的vel.norm()、acc.norm()、jerk.norm()计算的是三维向量的欧几里得范数。这意味着约束是各向同性的——X、Y、Z轴的约束被捆绑在一起。例如max_acc_ [5.0, 5.0, 3.0]其范数为sqrt(5.0² 5.0² 3.0²) ≈ 7.68。当轨迹在X-Y平面内高速转弯时X和Y加速度可能各为3.5 m/s²Z为0此时acc.norm() sqrt(3.5² 3.5²) ≈ 4.95 7.68看似安全但X轴加速度3.5已超过max_acc_[0]的5.0不max_acc_[0]是分量约束而checkTrajFeasibility()用的是范数约束它允许X和Y“共享”加速度预算。这是ego-planner设计上的一个精妙妥协用更宽松的范数约束换取更高的轨迹灵活性但代价是你无法对单个轴向施加独立的、更严格的限制。因此max_acc_的调参逻辑是若你的无人机在纯水平加速时易触发保护说明max_acc_[0]和max_acc_[1]太小需同步增大若在纯垂直爬升时易触发说明max_acc_[2]太小若在水平转弯时易触发说明max_acc_[0]和max_acc_[1]的组合值即范数太小需同比例增大。一个实用的调参流程是先将max_acc_设为[10.0, 10.0, 5.0]大幅放宽确保规划器能生成轨迹然后在RVIZ中观察无人机运动用rostopic echo /mavros/local_position/velocity_local和/mavros/local_position/acceleration记录实际达到的最大速度和加速度最后将max_acc_设为实测峰值的1.2倍既留有余量又不过度宽松。这个过程本质上是用物理世界的真实数据反向校准数学模型的参数是工程实践最本真的模样。6. 实战排错从“轨迹为空”到“飞行抖动”的五类高频问题与根因定位法在真实项目中ego-planner的报错信息往往模糊而误导。No feasible trajectory found可能是MAVROS掉线也可能是地图分辨率太低还可能是max_jerk设得太小。下面是我整理的五类最高频问题附带一套可复现的根因定位流程帮你跳过“百度十页答案没一个管用”的绝望循环。6.1 问题一[ WARN] ... No feasible trajectory found in planning horizon—— 规划器“拒绝工作”表象RVIZ中点击目标终端刷出警告/planning/trajectory话题无消息无人机纹丝不动。根因定位四步法查状态运行rostopic echo /mavros/state确认connected: True、armed: True、guided: True。若connected为False立刻检查rostopic hz /mavros/state若频率低于1Hz问题在MAVROS UDP心跳丢包需检查网络负载或改用TCP连接fcu_urltcp://127.0.0.1:5760。查时间运行rostopic echo /mavros/local_position/pose/header/stamp观察时间戳是否持续更新。若停滞说明/mavros/local_position/pose话题中断问题在MAVROS的local_position插件或PX4的vehicle_local_positionuORB发布。查地图运行rostopic echo /planning/occupancy_map确认data字段有非零值。若全为0说明mapCallback()未被触发问题在/octomap_full或/grid_map话题未发布或planner_manager.yaml中map_topic配置错误。查约束临时将config/planner_manager.yaml中feasibility_check/max_acc设为[10.0, 10.0, 5.0]max_jerk设为[20.0, 20.0, 10.0]。若此时规划成功说明原参数过于保守需按第5.3节方法重新标定。6.2 问题二[ WARN] ... Trajectory not feasible!—— 规划器“生成了却不敢用”表象/planning/trajectory有消息但/mavros/setpoint_raw/local无输出无人机不运动。根因定位在checkTrajFeasibility()函数中加入日志如前所述。若日志显示Vel: 3.02而max_vel为3.0则问题明确。但更常见的是Acc: 5.01此时不要急着调高max_acc先检查/mavros/local_position/acceleration的实测值。若实测加速度峰值仅3.8 m/s²说明规划器生成的轨迹过于激进根源在BsplineOptimizer的权重参数。打开config/bspline_optimizer.yaml降低weight_smoothness平滑度权重从1e5到1e4这会让优化器更倾向于生成低曲率、低加速度的保守轨迹。6.3 问题三无人机“原地画圈”或“Z轴乱飘” —— 坐标系错配表象无人机收到指令后不朝目标飞而是在原地缓慢旋转或Z轴高度剧烈波动。根因/mavros/setpoint_raw/local的coordinate_frame设错。运行rostopic echo /mavros/setpoint_raw/local/coordinate_frame确认输出为1FRAME_LOCAL_NED。若为0FRAME_LOCAL_ENU则需检查planner_node中PositionTarget消息的构造代码确保pos_target.coordinate_frame mavros_msgs::PositionTarget::FRAME_LOCAL_NED;。N