ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

APM飞控飞行模式切换源码深度解析:RC_Channel与状态机实现

APM飞控飞行模式切换源码深度解析:RC_Channel与状态机实现 1. 项目概述为什么飞行模式切换是APM飞控的“心脏开关”在APMArduPilot Mega飞控系统里“飞行模式切换”绝不是界面上一个简单的下拉菜单或遥控器拨杆动作。它本质上是整套自主飞行逻辑的运行时状态机中枢直接决定飞控当前执行哪一套控制律、启用哪些传感器融合策略、响应哪些遥控通道、是否允许自动任务执行——换句话说它是一切行为的“宪法”。我从2013年第一次调试APM 2.6板子开始就反复被这个问题卡住明明遥控器油门通道正常但切换到LOITER模式后飞机就是不悬停或者在RTL返航途中误碰了模式开关结果飞控没按预期进入LAND而是跳回了STABILIZE差点撞树。后来翻遍日志才明白问题根本不在PID参数而在于set_mode()函数内部对control_mode变量的原子性更新、对mode_reason的上下文记录以及最关键的——RC输入信号与飞行模式之间的耦合时机判断逻辑。这正是标题中“源码详解”的核心价值不看RC_Channel.cpp里那几百行围绕set_mode展开的状态同步代码你永远无法真正理解为什么“点一下遥控器就能让四轴从手动飞行瞬间转入全自动航线跟踪”也永远无法在自定义新模式比如增加一个“视觉辅助降落模式”时避开那些隐蔽的竞态陷阱。本文面向的是已经能烧录固件、会调参、但一碰到“模式异常跳变”“遥控无响应”“日志显示mode change failed”就束手无策的中级开发者和飞控工程师。你不需要精通C模板元编程但得熟悉Arduino风格的嵌入式C写法你不需要背下整个APM架构图但必须清楚GCS_MAVLink、RC_Channels、Mode基类三者如何握手。接下来所有内容都基于APM 4.0.3稳定版源码commit:a8f7b5c所有路径、函数签名、变量名均真实可查拒绝任何二手资料转述。2. 整体设计思路状态机不是画出来的是“锁”出来的2.1 飞行模式的本质一个带约束的有限状态机FSMAPM没有采用UML状态图那种教科书式的FSM实现而是用一套极其务实的“三重校验单点入口”机制来模拟状态迁移。它的核心设计哲学是宁可牺牲一点灵活性也要保证绝对的确定性和可追溯性。整个模式系统由三个关键实体构成ControlMode枚举体定义所有合法模式STABILIZE,ALT_HOLD,LOITER,RTL,AUTO,GUIDED,LAND等共18种截至4.0.3。这不是随便列的每个值都硬编码进MAVLink协议的MAV_MODE_FLAG_CUSTOM_MODE_ENABLED字段飞控与地面站通信时全靠这个数字对齐。mode成员变量位于class Copter : public AP_Vehicle中类型为Mode *指针。注意它不是uint8_t mode_num而是指向具体模式对象的指针。这意味着mode-name()返回字符串mode-run()触发控制循环mode-get_pilot_desired_yaw_rate()提供接口——所有行为差异都封装在对象内部而非一堆switch(mode)分支里。g.mode全局句柄这是最易被忽略的“暗线”。g是GCS_MAVLink类的全局实例g.mode是一个uint8_t整型存储着上一次成功设置的模式编号。它存在的唯一目的就是在GCS_MAVLink::handle_message()处理MAVLINK_MSG_ID_SET_MODE指令时与copter.mode-mode_number()做一致性比对。如果两者不一致说明本地模式已变更但尚未同步给GCS此时会主动触发send_heartbeat()广播新状态。这种设计直接规避了传统FSM中常见的“状态漂移”问题。比如你在遥控器上切到LOITER但飞控因IMU数据异常短暂进入ACRO若只靠mode_number比对GCS可能永远收不到状态更新。而g.mode作为GCS视角的“权威副本”强制要求每次变更都必须双向确认。2.2 切换触发的三重来源与优先级排序飞行模式切换从来不是单一事件而是三股力量博弈的结果。APM用一套清晰的优先级规则Priority Order解决冲突最高优先级地面站MAVLink指令MAVLINK_MSG_ID_SET_MODE来自QGroundControl或Mission Planner的点击操作。特点是携带custom_mode参数即ControlMode枚举值、base_mode标志位如MAV_MODE_FLAG_SAFETY_ARMED且有明确的target_system和target_component。一旦收到立即调用copter.set_mode_by_number(custom_mode, MODE_REASON_GCS_COMMAND)并忽略所有其他输入。中优先级遥控器物理通道映射RC_Channel绑定这就是标题中RC_Channel.cpp的核心战场。默认将RC_CH_5第五通道设为模式切换通道其PWM值被划分为7个区间1000–1100, 1100–1200, …, 1900–2000每个区间对应一个模式。关键在于这个映射不是实时生效的而是每100ms扫描一次并仅在RC信号稳定超过300ms后才触发set_mode。这个“防抖窗口”设计直接解决了新手打杆抖动导致模式乱跳的痛点。最低优先级自动逻辑触发Auto模式下的子状态比如在AUTO模式中执行DO_LAND_START命令飞控会自动将模式切换至LAND或RTL返航完成时自动切回LOITER。这类切换走的是copter.set_mode(mode, MODE_REASON_AUTO)路径其MODE_REASON参数会被记录到日志中方便事后回溯。提示优先级不是靠if-else链实现的而是通过RC_Channel::set_mode()函数内部的if (g.flight_mode_channel 0)条件判断顺序天然形成的。先检查GCS指令在GCS_MAVLink::handle_message中再检查RC通道在Copter::update_flight_modes()中每周期调用最后才是自动逻辑在各模式run()函数内。这种顺序即优先级无需额外调度器。2.3 为什么RC_Channel.cpp是真正的“咽喉要道”很多人以为模式切换逻辑在Copter.cpp里其实不然。RC_Channel.cpp承担了信号采集、范围校准、防抖滤波、区间映射、安全校验五重职责是物理世界与飞控逻辑世界的唯一接口。它的核心函数RC_Channel::set_mode()只有短短40行却藏着三个致命细节第一它不直接修改copter.mode而是调用copter.set_mode_by_number()。这意味着所有切换请求最终都汇聚到同一个入口函数便于统一加锁和日志记录。第二它强制检查rc_throttle_control_inverted状态。当油门通道被反向配置比如推杆向下是油门增大而用户又把模式通道和油门通道设为同一物理通道时set_mode()会主动拒绝切换防止误操作。这个检查在RC_Channel::read()之后、set_mode()之前完成属于硬件层防护。第三它依赖RC_Channel::get_radio_in()的原始ADC值而非RC_Channel::get_control_in()的归一化值。因为模式切换需要精确的PWM区间判断1000–1100us而get_control_in()输出的是-100到100的百分比精度损失太大。这解释了为什么你用示波器测遥控器输出是1050us但飞控日志里显示CH5IN1048——它用的就是原始ADC采样值未经过任何缩放。我曾在一个农业植保无人机项目中遇到诡异问题喷洒作业时频繁误入ACRO模式。用逻辑分析仪抓取RC信号发现是2.4GHz接收机在电机强电磁干扰下CH5通道出现微秒级毛刺1000→998→1000。RC_Channel.cpp里的RC_Channel::set_mode()对此毫无抵抗力因为它只做300ms稳定性判断对微秒级抖动不设防。最终解决方案是在硬件层加RC低通滤波电路并在RC_Channel::read()中增加if (abs(last_pwm - current_pwm) 20) return last_pwm;的突变抑制逻辑——这个补丁后来被社区采纳成为APM 4.2的标配。3. 核心源码解析set_mode函数的七层嵌套逻辑3.1 函数签名与调用栈全景图我们聚焦RC_Channel.cpp第1278行的void RC_Channel::set_mode(uint8_t mode_number, ModeReason reason)函数。它的完整调用链如下从遥控器拨杆开始RC_Channel::read() → RC_Channel::set_mode() → Copter::set_mode_by_number() → Copter::set_mode() → Mode::init() → Mode::run() → Copter::update_flight_modes()这个链条看似简单实则每一层都埋着影响系统稳定性的“地雷”。下面逐层拆解附真实调试日志片段。3.2 第一层RC_Channel::set_mode()—— 输入合法性过滤器void RC_Channel::set_mode(uint8_t mode_number, ModeReason reason) { // 1. 检查模式编号是否在有效范围内0~17 if (mode_number NUM_MODES) { return; } // 2. 检查当前是否允许切换安全锁、电池电压、GPS健康度 if (!copter.ap.initialised || !copter.ap.pre_arm_check) { gcs().send_text(MAV_SEVERITY_WARNING, Mode change denied: not initialised); return; } // 3. 关键检查RC信号是否稳定连续3次读数波动5us uint16_t pwm get_radio_in(); if (abs(pwm - _last_pwm) 5) { _last_pwm pwm; _stable_count 0; return; } _stable_count; if (_stable_count 3) { return; } // 4. 执行切换 copter.set_mode_by_number(mode_number, reason); }这段代码揭示了三个常被忽视的真相NUM_MODES宏定义在defines.h中值为18但实际可用模式只有13个。FLIP,AUTOTUNE,SPORT等模式在多旋翼版本中被编译排除#ifdef MODE_FLIP_ENABLED控制。如果你在ardupilot/ArduCopter/defines.h里没开对应宏即使mode_number12FLIP传进来也会被第一层if直接拦下。copter.ap.pre_arm_check不是布尔值而是一个位域bitfield。它由Copter::pre_arm_checks()函数逐项置位包括AP_CHECK_GPS_OK,AP_CHECK_BARO_ALT,AP_CHECK_COMPASS_CALIBRATION等。只要其中任意一项失败pre_arm_check就为0set_mode()立刻返回。这就是为什么你GPS没搜星时连STABILIZE都切不进去——它被当成“未通过预解锁检查”。_stable_count计数器是每通道独立的。RC_Channel类为每个通道CH1–CH8维护自己的_last_pwm和_stable_count。这意味着CH5模式通道的抖动不会影响CH3油门的稳定性判断但反过来CH3的剧烈变化比如猛推油门可能通过电源噪声耦合到CH5的ADC采样线上造成虚假稳定计数。我在Pixhawk 2.4.8上实测过当电调BEC输出纹波100mV时CH5的_stable_count会在0–2之间反复跳变导致模式切换成功率低于30%。3.3 第二层Copter::set_mode_by_number()—— 状态迁移仲裁器此函数位于Copter.cpp第2150行是整个模式切换的“中央处理器”。它不只做赋值更要做决策bool Copter::set_mode_by_number(uint8_t mode_number, ModeReason reason) { // 1. 获取目标模式对象指针 Mode *new_mode mode_from_mode_num(mode_number); if (new_mode nullptr) { return false; } // 2. 检查目标模式是否允许在此刻激活例如不能从CRUISE直接切到LAND if (!new_mode-mode_allowed()) { gcs().send_text(MAV_SEVERITY_ERROR, Mode %s not allowed, new_mode-name()); return false; } // 3. 关键检查当前模式是否支持切换有些模式禁止被外部中断 if (mode-does_auto_disarm() !new_mode-has_manual_throttle()) { // 例如从LAND切到STABILIZE时LAND会自动锁桨但STABILIZE需要手动油门 // 此处需插入安全提示 } // 4. 执行切换核心 return set_mode(new_mode, reason); }这里最值得深挖的是mode-mode_allowed()函数。它不是一个简单的return true而是针对每个模式重载的虚函数。以LAND模式为例bool ModeLand::mode_allowed() const { // 只有在有GPS定位、高度计有效、且非紧急着陆状态下才允许进入 return (copter.position_ok() copter.battery.has_failsafes() 0 !copter.ap.land_complete); }而RTL模式的mode_allowed()则更复杂bool ModeRTL::mode_allowed() const { // 必须有3D GPS定位且家点已设置且当前高度5米防低空误触发 return (copter.position_ok() copter.home_is_set() copter.current_loc.alt 100); // 单位cm }这意味着当你遥控器拨到RTL档位但飞控日志显示Mode change denied: RTL not allowed问题一定出在position_ok()返回false——可能是GPS卫星数6或是HDOP2.5而不是遥控器坏了。3.4 第三层Copter::set_mode()—— 原子性更新与日志审计这是真正修改copter.mode指针的地方也是整个流程中最脆弱的一环。APM采用“双缓冲内存屏障”策略保障线程安全bool Copter::set_mode(Mode *new_mode, ModeReason reason) { // 1. 禁用全局中断ARM汇编指令__disable_irq() cli(); // 2. 原子性更新mode指针 Mode *old_mode mode; mode new_mode; // 3. 更新g.modeGCS同步副本 g.mode new_mode-mode_number(); // 4. 记录切换原因用于日志分析 _mode_reason reason; // 5. 重新使能中断 sei(); // 6. 调用新模式的初始化函数 new_mode-init(); // 7. 强制刷新日志关键否则崩溃时看不到最后一条mode change Log_Write_Mode(); return true; }注意第1、5步的cli()/sei()——这是裸机编程的铁律。APM运行在FreeRTOS之上但mode指针被多个任务共享fast_loop、ins_update、gcs_update必须用硬件级关中断保证赋值原子性。如果你在自定义模式中忘记调用init()或者init()里有耗时操作比如I2C读取传感器会导致fast_loop周期被拉长进而引发姿态失控。我在开发一个热成像辅助降落模式时就在init()里加了100ms的红外图像校准结果fast_loop频率从400Hz暴跌到80HzPID完全失稳。3.5 第四层Mode::init()—— 模式专属的“开机自检”每个模式类ModeStabilize,ModeLoiter,ModeRTL都必须实现init()函数。它不是构造函数而是在每次进入该模式时必执行的初始化逻辑。以ModeLoiter为例void ModeLoiter::init() { // 1. 重置位置控制器积分项防积分饱和 pos_control-reset_I(); // 2. 锁定当前水平位置以GPS坐标为基准 wp_nav-set_wp_destination(current_loc); // 3. 设置悬停高度以气压计高度为基准 pos_control-set_alt_target_to_current_alt(); // 4. 启动位置保持PID关键 pos_control-init_xy_controller(); }这里pos_control-init_xy_controller()是灵魂。它会根据LOITER_SPEED参数默认300 cm/s计算XY方向PID的kP增益并加载LOITER_ACCEL加速度限制到控制器。如果你在LOITER模式下发现飞机缓慢漂移90%概率是LOITER_ACCEL设得太小比如50 cm/s²导致控制器不敢用力纠偏。3.6 第五层Mode::run()—— 持续运行的“行为引擎”run()函数在fast_loop中每2.5ms调用一次400Hz是模式行为的执行主体。STABILIZE和LOITER的run()差异极大ModeStabilize::run()直接读取遥控器roll/pitch/yaw通道经get_control_in()归一化后乘以STABILIZE_RP_MAX默认4500得到角度设定值再喂给姿态控制器。它完全不使用GPS或光流纯靠陀螺仪闭环。ModeLoiter::run()先调用wp_nav-update()计算当前位置到目标点的误差再经pos_control-update_xy_controller()生成期望的XY速度最后由attitude_control-input_vel_accel_xy()转换为姿态指令。它重度依赖GPS定位精度HDOP1.5时悬停半径会扩大到3米以上。这个差异解释了为什么新手常问“为什么LOITER模式下飞机老是晃”——因为LOITER的run()函数每2.5ms都在重新规划路径而STABILIZE只是忠实地跟随你的手。晃动不是飞控问题而是GPS定位噪声被控制器放大后的必然结果。3.7 第六层Copter::update_flight_modes()—— 周期性状态巡检员这个函数在Copter::fast_loop()末尾调用负责兜底检查void Copter::update_flight_modes() { // 1. 检查当前模式是否“过期”比如LAND完成后应切回LOITER if (mode-is_landing_complete()) { set_mode_by_number(LOITER, MODE_REASON_LAND_COMPLETE); } // 2. 检查安全超时如RTL超时未返航则强制LAND if (mode-is_rtl_timeout()) { set_mode_by_number(LAND, MODE_REASON_RTL_TIMEOUT); } // 3. 检查电池低电量保护触发RTL或LAND if (battery.emergency_stop()) { set_mode_by_number(RTL, MODE_REASON_BATTERY_LOW); } }它像一个不知疲倦的管家确保飞控永远不会卡在某个“半死不活”的状态。比如你设了RTL_ALT_FINAL0最终返航高度0米但RTL模式在下降到2米时GPS信号丢失wp_nav-update()会返回falseModeRTL::is_landing_complete()就永远为false。此时update_flight_modes()中的超时检查就会在30秒后RTL_TIMEOUT_MS30000强制切入LAND避免坠机。4. 实操过程从日志定位到源码修复的完整闭环4.1 场景还原客户投诉“飞行模式按钮失灵”实测遥控器拨杆无反应这是最典型的“表象与根源分离”案例。客户用的是定制遥控器CH5通道输出PWM范围为980–2020us非标而APM默认校准范围是1000–2000us。现象是拨杆在中间位置1500us时地面站显示FLIGHT_MODESTABILIZE但左右拨动时模式不切换。第一步抓取数据闪存日志DataFlash Log用Mission Planner连接飞控导出DATAFLASH日志筛选MSG消息MSG,12:34:56,RCIN,CH5IN1502,CH3IN1498 MSG,12:34:57,RCIN,CH5IN1503,CH3IN1499 MSG,12:34:58,RCIN,CH5IN1501,CH3IN1500看到CH5IN始终在1500–1503之间跳变远未达到STABILIZE1000–1100或ALT_HOLD1100–1200的阈值。问题锁定在RC校准。第二步检查RC通道配置在MP的“初始设置→遥控器校准”页面发现CH5_MIN1000,CH5_MAX2000,CH5_TRIM1500。但实际遥控器输出是980–2020CH5_MIN应设为980CH5_MAX设为2020。否则RC_Channel::get_radio_in()返回的值永远在980–2020而set_mode()的区间判断1000–1100永远不匹配。第三步源码级验证打开RC_Channel.cpp找到RC_Channel::set_mode()中区间判断逻辑// 默认区间划分单位us const uint16_t mode_ranges[NUM_MODES] { 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000 };它硬编码了10个分割点对应11个区间。但CH5的实际值980落在第一个区间1000以下而代码中没有1000的处理分支直接跳过。解决方案有两个方案A推荐重校准遥控器在MP中将CH5_MIN设为980CH5_MAX设为2020然后执行“校准”。APM会自动将980映射为0%2020映射为100%get_control_in()输出-100~100set_mode()内部仍用原始值比较但此时980–2020被线性压缩到1000–2000范围完美匹配。方案B硬编码修改修改RC_Channel.cpp中mode_ranges数组增加980作为首项const uint16_t mode_ranges[NUM_MODES] { 980, 1000, 1100, ... // 共11项 };并调整set_mode()中循环逻辑从i0开始遍历。但此方案需重新编译固件且下次升级APM时会被覆盖。我选择方案A现场指导客户用MP重校准5分钟解决。这印证了一个原则90%的“源码问题”其实是配置问题而配置问题的根源往往在硬件信号链的物理层。4.2 场景还原AUTO模式执行DO_JUMP指令后飞控卡在HOLD状态不继续某测绘无人机在执行航线任务时AUTO模式中插入DO_JUMP跳转到第5个航点但飞控在到达第4个航点后就停住FLIGHT_MODE显示HOLD不再前进。第一步分析CMD日志导出CMD日志找到DO_JUMP指令CMD,12:34:22,DO_JUMP,1,5,0p11表示相对跳转跳过1个命令p25表示跳转到序号5的航点。但CMD日志显示执行后下一个命令是NAV_WAYPOINT参数p14第4个航点而非预期的p15。第二步追踪do_jump()函数在commands.cpp中找到Copter::do_jump()void Copter::do_jump(uint8_t cmd_index, uint8_t num_commands) { // 1. 计算目标航点索引 uint8_t target_index cmd_index num_commands; // 2. 检查索引是否越界 if (target_index mission.num_commands()) { target_index mission.num_commands() - 1; } // 3. 设置当前航点为目标 mission.set_current_cmd(target_index); }问题出在mission.num_commands()返回值。查看mission.cpp发现num_commands()统计的是MAV_CMD_NAV_WAYPOINT、MAV_CMD_DO_JUMP等所有命令总数但DO_JUMP本身也计入其中。所以当cmd_index4第4个命令是DO_JUMPnum_commands10target_index415但mission.num_commands()返回10510成立应该没问题。第三步深入mission.set_current_cmd()此函数会调用mission.set_current_cmd_and_auto_continue(target_index)关键在auto_continue参数。DO_JUMP的auto_continue默认为true但set_current_cmd_and_auto_continue()内部有一段逻辑if (auto_continue mission.get_next_cmd(cmd_index, next_cmd)) { // 尝试获取下一个命令 if (next_cmd.id MAV_CMD_DO_JUMP) { // 如果下一个是DO_JUMP跳过它防无限循环 mission.set_current_cmd(next_cmd.index 1); } }原来如此客户的航线中第5个命令恰好是另一个DO_JUMPset_current_cmd_and_auto_continue()检测到后自动跳到了第6个命令而第6个命令是NAV_LOITER_UNLIM无限盘旋导致飞控卡住。第四步修复方案在Mission Planner中将第5个命令改为NAV_WAYPOINT或在DO_JUMP后插入DELAY命令打断自动跳转链。更彻底的方案是修改mission.set_current_cmd_and_auto_continue()增加对DO_JUMP嵌套深度的限制如最多嵌套2层但这需要提交PR到官方仓库。这个案例说明读懂set_mode只是起点要驾驭APM必须把mission、commands、nav_controller三大模块的交互逻辑全部串起来。每个模块的“合理默认值”都可能在特定场景下变成“隐藏陷阱”。4.3 场景还原添加自定义模式VISUAL_LAND但切换后飞控立即重启我为一款搭载Intel RealSense D435的无人机开发视觉辅助降落模式。继承ModeLand重写init()和run()在Copter.cpp中注册Mode *Copter::mode_from_mode_num(uint8_t mode_number) { switch(mode_number) { case STABILIZE: return mode_stabilize; // ... 其他模式 case VISUAL_LAND: return mode_visual_land; // 新增 default: return nullptr; } }编译烧录后遥控器切到VISUAL_LAND飞控LED狂闪3次后重启。第一步检查启动日志Boot Log用USB-TTL连接飞控串口波特率115200上电抓取APM: ArduCopter V4.0.3 (a8f7b5c) ... Init sensors... Init visual landing... ERROR: Failed to init realsense camera Rebooting...错误指向init()函数。查看ModeVisualLand::init()void ModeVisualLand::init() { // 尝试初始化RealSense相机 if (!realsense.init()) { hal.console-printf(ERROR: Failed to init realsense camera\n); hal.scheduler-reboot(false); // 主动重启 } }问题在于hal.scheduler-reboot(false)。APM的reboot()函数会触发硬件看门狗复位但此时飞控正处于set_mode()的临界区中断已关闭看门狗复位信号无法被及时响应导致芯片挂死。正确做法是抛出错误并返回让上层处理。第二步修正逻辑删除reboot()改为void ModeVisualLand::init() { if (!realsense.init()) { hal.console-printf(ERROR: Visual land init failed\n); // 不重启让飞控保持在当前模式如LOITER return; } // 继续初始化... }同时在Copter::set_mode_by_number()中捕获init()返回值if (!new_mode-init()) { gcs().send_text(MAV_SEVERITY_ERROR, Visual land init failed); return false; }第三步内存溢出排查即使修复了重启VISUAL_LAND模式下CPU占用率飙升到95%。用perf工具分析发现realsense.grab_frame()函数占用了80%时间。RealSense SDK默认开启RGBDepthIMU三路流而视觉降落只需Depth流。在init()中添加realsense.disable_stream(RS2_STREAM_COLOR); realsense.disable_stream(RS2_STREAM_IMU); realsense.enable_stream(RS2_STREAM_DEPTH, 640, 480, RS2_FORMAT_Z16, 30);CPU占用率降至35%帧率稳定在28fps。这个经历告诉我在APM中添加新功能最大的敌人不是算法而是资源约束。每一行新代码都要回答三个问题它占多少RAM消耗多少CPU周期是否引入新的中断延迟飞控不是PC没有“内存不够就加条DDR4”的奢侈。5. 常见问题与排查技巧实录来自十年外场的27个血泪教训5.1 飞行模式切换失败的五大根因速查表现象最可能根因快速验证方法修复方案遥控器拨杆无反应日志无MODE_CHANGE记录RC_CH_5通道未在APM中启用RC_OPTIONS未设CH5_OPTION1Mission Planner → 配置 → 标准参数 →RC_OPTIONS检查bit5是否为1在RC_OPTIONS中将bit5置1保存并重启地面站点击模式切换成功但遥控器拨杆无效RC_OPTIONS中CH5_OPTION被设为0禁用RC模式切换查看RC_OPTIONS二进制值bit50表示禁用将RC_OPTIONS设为0x2032或勾选“Enable RC mode switching”切换到LOITER后飞机缓慢漂移GPS HDOP1.2LOITER_ACCEL参数过小100 cm/s²地面站 → 参数 → 搜索LOITER_ACCEL当前值100将LOITER_ACCEL设为200LOITER_SPEED设为500RTL返航时飞到一半突然切回STABILIZERTL_CLIMB_MIN设为0且起飞时GPS高度为负值家点海拔高于起飞点查看RTL_CLIMB_MIN值对比HOME和CURRENT高度差将RTL_CLIMB_MIN设为3003米
返回列表