☰
ArduPilot备降点机制解析:从RTL返航到安全落点
2026/10/12 5:47:37 网站建设 项目流程

开了四轴的朋友,几乎都遇到过这种“灵异事件”:明明起飞点是一块干净空地,炸鸡后(很抱歉这个词不吉利,但这是圈子里的日常用语)飞控却偏要往几十米外飞,最后落在一片灌木丛里。第一反应肯定是GPS飘了,但老飞手会瞟一眼地面站,淡淡说一句:“它去备降点了。”

备降点这三个字,在ArduPilot源码里对应的就是Rally Point,代码模块叫AP_Rally,Copter的RTL返航逻辑会把它放在和HOME点同等重要的位置。它解决的痛点是:当飞机因为遥控信号丢失、低电压、丢星、紧急情况需要自动返航时,起飞点(HOME点)不一定是安全落点——可能是人群、公路、水塘,也可能已经被飞手自己移到了一个不适合降落的临时位置。备降点就是提前存好的“安全备胎”,让飞控在一堆预设坐标里挑一个最合适的飞过去落下来。

这篇文章我打算从一次“返航落偏”事故讲起,把备降点的数据存储、选点算法、RTL状态机调用链、故障保护触发逻辑全部拆开,最后给出可落地的调试方案和避坑经验。适合被“自动降落找不着机”折磨的新手,也适合想改RTL选点策略、让飞控按自己业务逻辑挑备降点的开发者。

1. 备降点机制与设计动机

1.1 为什么不能永远依赖HOME点

HOME点不是“家”,它更像是飞机解锁时记录的一个经纬度坐标。很多飞行器场景里,起飞点其实是最不适合落地的位置:场地中央只有一块勉强可以起降的碎石地,周围全是高压线;或者起飞点是临时清出来的上楼平台,下楼通道早就被杂物堵死;再比如在河边起飞,返航时水位上涨,落下去就是泡水。

如果返航逻辑只认HOME点,那这些场景都等于给飞机判了死刑。备降点的本质是给飞控多提供几个“候选答案”,让它不要死磕起点。

1.2 从“返航点”到“备降点”的视角转换

你可以把HOME点想象成手机默认连接的Wi-Fi,备降点则是周围几个信号更好的热点。手机不是非得连着自家路由器才能上网,哪个信号强连哪个;飞控也不一定非要落在起飞点,哪个坐标更安全就选哪个。

这套设计在ArduPilot里不是后来打补丁加上的,而是一开始就埋在了架构里。RTL模式做的是:先算一个返航目标点,这个点可以是HOME,也可以是某个备降点,然后按状态机把飞机送过去。至于到底落到哪个点,取决于一件事——备降点是否被启用,以及哪个备降点在当前条件下更“合适”。

1.3 备降点不是普通航点

不要把备降点理解成一串普通航点。普通航点是任务规划的一部分,飞机飞到就执行动作,飞完继续下一个任务;备降点则是一个独立存储、独立管理的资源,它的生命周期贯穿飞行前、故障中和故障后三个阶段:

  • 飞行前由地面站写入飞控的EEPROM,断电不掉。
  • 故障触发时,RTL状态机会主动检索可用备降点。
  • 落地点确定后,飞控按照备降点附带的高度、航向、改出高度等参数执行降落。

这个“资源化”的设计非常关键,它意味着备降点不依赖任务文件,换了任务、清了航点,它还在。很多团队做航测规划时只上传任务,忘记看备降点是否被清空,等到真出事才知道备降点早就被地面站清掉了,这是后话,第六节我会专门讲。

2. 源码地图与核心数据结构

2.1 源码文件与服务模块

ArduPilot的备降点逻辑主要散在三个地方:

  • libraries/AP_Rally/AP_Rally.h与AP_Rally.cpp:备降点的数据结构、存储、读取、检索核心库。
  • ArduCopter/mode_rtl.cpp:Copter的RTL模式状态机,负责备降点的实际调用和飞行策略切换。
  • ArduPlane/下的对应逻辑:固定翼分支,会额外增加RALLY_ALT这样的导航高度参数。

老版本的皮克斯hawk固件里,RTL逻辑还在commands_logic.cpp这类“大杂烩”文件中集中管理,备降点选择也直接内联在里面。4.x之后做了模块化重构,备降点自己的读取和选点逻辑收归到AP_Rally库,RTL模式只负责“向AP_Rally要一个点”,两者职责分离。你如果网上搜到老的源码博客,看到函数名对不上很正常,先分清楚版本再往下读。

2.2 RallyPoint结构体逐字段解读

备降点不是只存一个经纬度,它带了一整套降落参数。ArduPilot中核心数据结构类似下面这样:

// libraries/AP_Rally/AP_Rally.h 中定义的备降点结构(不同版本字段名略有差异) struct RallyPoint { uint8_t land_mode; // 着陆模式:0 自动,1 使用自定义航向 int32_t lat; // 纬度,单位 1e7 度,也就是小数后7位 int32_t lng; // 经度,单位 1e7 度 int16_t alt; // 到达备降点时的高度(米),多数分支是相对HOME的高度 int16_t break_alt; // 改出高度(米),下降过程中先稳住的高度 uint16_t land_heading; // 降落航向,单位度(0~360) };

lat和lng的1e7单位是这个项目里面的老传统,它避免了浮点误差,而且压缩后能让一个坐标点只占8字节。alt字段不是海拔,多数分支存的是相对HOME的高度偏移,这也是新手最容易误解的地方——在Mission Planner里填的备降点高度是相对于家的高度,不是ASML高程。

break_alt是很容易被忽略但非常实用的字段。飞控到达备降点后如果直接一路降到地面,在低空遇到乱流或者地形起伏时容易砸地。设置了break_alt之后,飞控会先悬停在改出高度,完成水平位置微调和机头朝向校准,再继续下降落地。就像你倒车入库,不是一脚油门怼进车位,而是先停到路口观察一下再慢慢进去。

2.3 备降点的EEPROM持久化

备降点存的是长期数据,飞控断电重启它必须还在。ArduPilot的做法是把它写到EEPROM中,通过一个简单但严格的格式来防错:

// 写入EEPROM时的大致流程(逻辑还原) eeprom_write_byte(offset++, RALLY_FORMAT_VERSION); eeprom_write_byte(offset++, total); for (uint8_t i = 0; i < total; i++) { eeprom_write_block(&points[i], offset, sizeof(RallyPoint)); offset += sizeof(RallyPoint); }

每次先写一个格式版本号,再写备降点总数,接着逐块写入每个点。读取时如果发现版本号不匹配,宁可丢弃整块数据也不盲目恢复,这个“读到脏数据宁可不读”的思路值得学习。很多奇怪的“备降点消失”问题,十有八九就是版本号对不上或者总数被地面站清零了。

2.4 关键参数对照

源码里没有单独为每个备降点搞一堆参数,而是用少数几个全局参数控制行为。我整理了一个常用对照表,实际固件分支之间会有细微差异:

参数默认值作用
FS_RALLY_ENABLE0故障保护触发RTL时,是否允许使用备降点
RALLY_LIMIT4最大允许存储并参与选择的备降点数量
RALLY_INCL_HOME0选点时是否把HOME点也列入候选列表
RALLY_ALT0固定翼分支使用备降点时的导航高度参数

多说一句,RALLY_LIMIT和AP_RALLY_RALLYPOINTS_MAX常量配合,限制备降点不能存太多,避免EEPROM被刷爆。RALLY_INCL_HOME这个参数很狡猾:如果设为1,HOME点会混在候选列表里一起被比较,最后选出来的可能不是备降点而是家。你想强制走备降点,就把它设为0。

3. 备降点选择逻辑与校验规则

3.1 选点函数入口:find_rally_point

备降点选择的核心动作发生在AP_Rally库内部的find_rally_point函数,它的任务很简单——从一堆备降点里挑一个“最合适”的返回给调用者。这里“最合适”的评判标准在绝大多数固件分支里都是“距离最近”。

// 选择最近有效备降点(逻辑还原,具体函数名以分支为准) bool AP_Rally::find_rally_point(const Location &current_loc, Location &rally_loc) const { float best_dist = FLT_MAX; bool found = false; for (uint8_t i = 0; i < total; i++) { Location rp_loc; if (!location_from_rally_point(points[i], rp_loc)) { continue; // 坐标非法 } float dist = current_loc.get_distance_m(rp_loc); if (dist < best_dist) { best_dist = dist; rally_loc = rp_loc; found = true; } } return found; }

整个函数是O(n)的线性扫描,n最大也就十几个点,性能上完全没压力,也非常容易被看明白:遍历、算距离、比较、留下最近的。对性能追求不高的场景做朴素遍历,很多时候就是最稳的方案。

3.2 合法性校验与“看不见”的限制

距离比较之前还有一个很容易被忽略的步骤——坐标是否合法。Location结构体里的is_valid()会检查纬度、经度是否在合理范围内,高度是否超出有效区间。一个备降点如果被地面站错误写入,比如经纬度是0,或者高度填了一个超出传感器量程的值,它根本不会进入候选列表。

这个校验在几个特殊场景下会直接导致“明明存了备降点,却飞回了HOME”的尴尬:

  • 地面站插件版本太老,写入的备降点字段被截断。
  • 手动改参数时把alt改成了负数且超过下限。
  • GPS冷启动后飞控还没拿到位置,备降点坐标的基准被污染。

遇到这种问题别急着怀疑源码,先去看看log里备降点检索是否被标记为“invalid”。

3.3 高度与改出高度在选点中的权重

选点只看距离,但落点高度同样关键。备降点里存的alt,在RTL状态机里会参与目标高度的计算。飞控会取RTL_ALT和备降点高度中更高的那个作为返航巡航高度,这是为了防止飞控为了落一个低海拔备降点而贴着山体飞。

等到了备降点附近,break_alt才会介入。飞控从巡航高度开始下降,如果当前高度还远高于break_alt,它会先降到改出高度,稳住,再执行最后的着陆流程。这个两段式下降逻辑在低空风切变和复杂地形场景里非常可靠,代价就是降落时间更长。所以备降点的break_alt不要随便填,填太高会让飞机在落地区域上空悬停很久,反而增加失速和风扰风险。

3.4 备降点被临时排除的场景清单

总结一下,备降点明明存在,却不会被选中的场景主要集中在这几类:

  1. 总数是0,EEPROM里没有有效点。
  2. FS_RALLY_ENABLE未开启,故障场景不允许走备降点。
  3. 所有备降点坐标校验失败(经纬度为零、高度越界)。
  4. 备降点距离当前飞机位置太远,超出了故障安全模式下允许切换的边界。
  5. RALLY_INCL_HOME=0但HOME点是唯一合法坐标。

其中第4条很多人没注意。飞控是会计算“这个备降点远到离谱,去了回不来”这种情况的,远距离备降点会被直接丢弃,转而回到HOME点逻辑。设置备降点时,离当前作业区太远的点,实际上永远不会生效。

4. RTL模式调用链与状态机

4.1 从模式入口到compute_return_target

备降点选出来之后,剩下的事交给RTL模式。Copter的RTL模式被封装成ModeRTL,入口是ModeRTL::run(),每周期调用一次。它先会计算返航目标点,这个动作在compute_return_target()里完成,流程可以简化成一句话:先取HOME点,再检查是否允许用备降点,允许就用备降点覆盖掉HOME。

// ModeRTL 中计算返航目标的流程(逻辑还原) void ModeRTL::compute_return_target() { // 默认目标点是HOME Location loc = AP::ahrs().get_home(); if (copter.position_ok() && copter.rally_point_enabled() && AP::rally().find_rally_point(copter.current_loc, loc)) { // 备降点检索成功,用它覆盖默认目标 } _target = loc; // 根据RTL_ALT和备降点参数计算最终巡航高度 _target.alt = MAX(get_RTL_alt(), _target.alt); }

这里的position_ok()尤其重要——如果飞控连自身位置都不确定,备降点检索结果没有意义。所以GPS丢星但备降点逻辑还好的情况下,飞控更倾向用上一帧位置或直接原地降落,而不是冒险飞向备降点。

4.2 RTL状态机的阶段演进

拿到_target之后,RTL不是直接飞过去,而是按状态机分阶段执行:

// RTL状态机的常见状态(不同分支名称不同) typedef enum { RTL_InitialClimb, // 初始爬升 RTL_Returning, // 空中巡航返回 RTL_Loiter, // 到达备降点上空后盘旋等待 RTL_Descent, // 下降 RTL_Land // 落地 } RTL_State;
  • 初始爬升阶段:飞控把飞机拉到RTL_ALT或者更高,防止返航路上撞树撞楼。
  • 空中巡航阶段:直线飞向_target,目标高度已经包含了备降点高度约束。
  • 盘旋/下降阶段:到点后切到降落引导,break_alt在这里生效。
  • 落地阶段:触发自动着陆。

很多人以为RTL就是“直线飞回家”,实测中你看到的RTL轨迹往往是先往高处拉,再水平走直线,最后垂直往下降。这背后就是状态机在分阶段干活。

4.3 切换条件的边界值

状态切换不是靠时间,而是靠距离和高度的边界值判断:

  • 从初始爬升切到巡航返回:当前高度达到目标高度的95%以上。
  • 从巡航返回切到下降:距离备降点小于预设半径,一般取几十米;同时高度低于某个阈值。
  • 从下降切到落地:垂直接近率降到可接受范围,且水平误差小于落点精度要求。

这些边界值直接影响降落表现。有的团队把备降点设在楼顶,高度设置很高,结果飞控在“下降阶段”悬停很久不敢落地,就是因为下降边界高度没匹配上备降点的break_alt。

4.4 从日志视角看链路

备降点是否真的参与RTL,日志里一眼就能看出来:

  • 事件日志中会出现RTL模式进入记录。
  • 若备降点被选中,目标点位置会落在备降点坐标附近,而不是HOME点。
  • 高度变化曲线中会有一个“先爬升到巡航高度、再降到break_alt、最后落地”的三段式特征。

如果你现场没接数传,落地后立刻拔SD卡看log,重点看EV事件的模式切换时间和NAV类日志的目标点经纬度,就能确认备降点是不是背锅侠。

5. 故障保护触发:什么场景才会真正走到备降逻辑

5.1 常见故障源与模式切换

备降点不是备降点开关,它是被RTL模式“选用”的目标点。要让备降点发挥作用,得先有事件把飞控切到RTL。最常见的几个触发源:

  • 遥控信号丢失(RC Failsafe):遥控器被撞、电池没电、远距离飞出遥控范围。
  • 电池电量过低(Battery Failsafe):到达一级保护阈值后切RTL。
  • GPS信号丢失(GPS Failsafe):部分固件配置为丢失后切RTL或Auto。
  • 地面站主动控制:飞手或地面站在线时手动输入返航指令。

触发源虽然不同,最终都会进入同一套RTL逻辑,这个“各自触发、殊途同归”的设计,降低了备降点逻辑的复杂度,也让故障链路的排查变得清晰。

5.2 FS_RALLY_ENABLE的约束

这里必须强调一个参数:FS_RALLY_ENABLE。它的默认值是0,意味着故障保护触发RTL时,默认不启用备降点,飞控只会飞回HOME。你不把这个参数设成1,前面存再多备降点,故障场景下也用不上。

有一个经典误区是:手动在遥控器上切RTL模式时,飞行器可以直接使用备降点;但遥控信号都丢了,故障保护切到RTL时,如果FS_RALLY_ENABLE没开,备降点就不参与。很多飞手在模拟器里手动测试备降点正常,真机炸鸡后却发现落回了HOME,原因是没设置故障保护专用的开关。

5.3 备降点参数推荐配置

我尝试过多套配置,下面这组参数适合大多数城市周边和近郊航拍场景:

参数推荐值说明
FS_RALLY_ENABLE1允许故障保护使用备降点
RALLY_LIMIT6多存几个,选择余地大
RALLY_INCL_HOME0强制只在备降点里选,不混入HOME
RALLY_ALT(固定翼)高于周边最高障碍20米避免返航途中撞山

固定翼用户记得额外关注RALLY_ALT,这个参数单独控制使用备降点时的巡航高度。多旋翼则主要看每个备降点自己的alt字段,参数表里没有统一的“RALLY_ALT”,别按多旋翼的惯性思维去找。

5.4 多个备降点时的容灾策略

备降点数量多不是坏事,但策略要合理。我见过有人在方圆100米内设了6个备降点,结果选点算法每次选中的都是离飞机当前距离最近的那个,反而把飞机引导到人群附近。更合理的做法是把备降点分散布置在作业区四周,每个点之间间隔500米以上,方向覆盖主要返航路径,这样一个点被障碍物遮挡或者地面站写入错误时,其他点还能兜底。

6. 实操调试与避坑清单

6.1 用SITL模拟器验证备降点逻辑

真机测试备降点成本太高,先把逻辑跑通,再用模拟器验证是最省钱的路。ArduPilot自带SITL仿真环境,流程大致是:

cd ardupilot sim_vehicle.py -v ArduCopter --map --console

启动后在MAVProxy控制台里执行:

param set FS_RALLY_ENABLE 1 param set RALLY_LIMIT 6 param set RALLY_INCL_HOME 0 # 添加一个备降点,格式通常是:rally add 纬度 经度 高度 rally add -35.363261 149.165230 50 rally list

rally list能确认点是否写入成功。然后手动触发故障保护,比如在模拟器里断开遥控器信号模拟RC Failsafe,观察飞机的目标点是否指向备降点。能看到RTL轨迹先拉高、再水平飞向备降点、最后下降,基本就说明链路通了。

6.2 真机测试的几个安全建议

模拟器只是验证逻辑,真机测试必须谨慎:

  • 备降点选在离人群、建筑、电线至少100米的开阔地。
  • 首次实测不要设太多备降点,两个就够:一个在起飞点附近,一个在相反方向。
  • 设置FS_RALLY_ENABLE为1后,先做一次低空近距离的遥控失联测试,确保飞机不会窜向远处的备降点。
  • 带上RTK或差分基站,避免GPS漂移给测试结果盖棺定论。

真机测试真正要验证的是“降落姿态”和“落点精度”,这受风、地形、电量影响很大,建议在风力小于3级的天气进行,故障触发时电池电量保持在中高区间。

6.3 常见问题速查表

我整理了这些年团队在使用过程中最常踩的问题,做成一个小表,方便现场排查:

现象可能原因排查方法
存了备降点却不使用FS_RALLY_ENABLE为0检查该参数是否设为1
RTL落回HOME点RALLY_INCL_HOME为1,HOME点更近查看选择日志中的目标坐标
备降点坐标无效地面站写入异常查看“RALLY”格式版本号
高度一直悬停不落地break_alt设置过高降低改出高度
备降点太远导致被忽略超出安全距离边界重新规划备降点位置
重复RTL反复切换目标多个备降点距离接近调整备降点分布间距

6.4 两个容易忽略的潜规则

最后再强调两个源码里不会直接写明的潜规则。

第一个是备降点数量上限。即便RALLY_LIMIT改成了16,飞控内部对总点数和EEPROM空间都有上限约束,强行塞到20个点,后面几个点可能写不进去。地面站显示保存成功,实际上只有前几个点落到了EEPROM里。

第二个是备降点在“任务清空”时会一起被移除。很多飞手在Mission Planner的“任务”页面点清除全部航点时,备降点也会被连着清掉。每次飞行前养成检查任务列表和备降点数量的习惯,比任何参数调整都管用。

结尾:我自己踩过的坑

备降点功能我前前后后用了三年,最大的体会是:它不是让你把飞机落到更远地方的“偷懒功能”,而是一个需要提前设计的安全策略。你必须在飞之前就想清楚——如果我在这个区域的任何一点出事,飞机应该优先落在哪几个点,每个点的高度、改出高度、降落航向分别是什么,然后把这些参数一起写进去。

有一次我在河道附近做航测,起飞点在一个便于飞手站立的人行步道上,备降点设置在了对岸河滩。结果真遇到遥控失联,飞机没有落回人行道,而是直接飞到对岸河滩安全落地。当时地面的所有人都在庆幸,如果按照HOME点返航,飞机大概率会挂在河中央的高压线上。那次之后,我的所有项目都会严格执行备降点规划,也推荐大家都试试这个功能——先模拟器里跑通,再真机验证。源码里的逻辑并不复杂,真正的复杂度都在你对实际场景的理解里。

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

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

立即咨询