3. 飞行模式切换指令:MAV_CMD_DO_SET_MODE 深度拆解

说实话,在飞控系统里,切换飞行模式这件事看起来简单——不就是发个指令吗?但实际做起来,坑不少。我见过不少新手,上来就照着文档发 MAV_CMD_DO_SET_MODE,结果飞机没反应,或者直接切到了意想不到的模式。

这一节,我们就把它彻底讲透。

3.1 指令结构:COMMAND_LONG 与 MAV_CMD_DO_SET_MODE

在 MAVLink 协议里,MAV_CMD_DO_SET_MODE 是 COMMAND_LONG 消息的一个子命令。说白了,COMMAND_LONG 是一个“大信封”,里面装了很多种指令,而 MAV_CMD_DO_SET_MODE 就是其中专门用来切换飞行模式的。

它的参数定义如下:

参数编号 字段名 说明 典型值
Param1 base_mode 基础模式掩码,定义系统状态 1 (MAV_MODE_FLAG_CUSTOM_MODE_ENABLED)
Param2 custom_mode 自定义模式编号,即具体的飞行模式 4 (AUTO), 5 (GUIDED), 3 (LOITER) 等
Param3~7 保留/未使用 通常填 0 或 NaN 0

核心要点:Param1 必须设置为 MAV_MODE_FLAG_CUSTOM_MODE_ENABLED(值为 1),否则飞控会忽略 Param2 的内容。这是很多人踩过的坑。

3.2 参数详解:base_mode 与 custom_mode

这两个参数,我分开讲。

3.2.1 base_mode:系统基础状态

base_mode 是一个 8 位的掩码,每一位代表一个系统状态标志。常用的标志位如下:

  • MAV_MODE_FLAG_CUSTOM_MODE_ENABLED (0x01):启用自定义模式。这是切换飞行模式的必须标志。
  • MAV_MODE_FLAG_TEST_ENABLED (0x02):测试模式,一般不用。
  • MAV_MODE_FLAG_AUTO_ENABLED (0x04):自动模式标志,通常与自定义模式配合使用。
  • MAV_MODE_FLAG_GUIDED_ENABLED (0x08):引导模式标志。
  • MAV_MODE_FLAG_STABILIZE_ENABLED (0x10):增稳模式标志。
  • MAV_MODE_FLAG_HIL_ENABLED (0x20):硬件在环仿真标志。
  • MAV_MODE_FLAG_MANUAL_INPUT_ENABLED (0x40):手动输入标志。
  • MAV_MODE_FLAG_SAFETY_ARMED (0x80):解锁标志。

对于飞行模式切换,我们通常只设置 MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,即 base_mode = 1。

我的经验:我曾经在项目里尝试过同时设置多个标志位,比如 base_mode = 1 | 4 | 8,结果飞控的行为变得很奇怪。后来查了源码才发现,ArduPilot 和 PX4 对 base_mode 的解析方式不同。所以,老老实实只设 1 就对了。

3.2.2 custom_mode:具体的飞行模式

custom_mode 是一个 32 位的整数,具体值由飞控固件定义。不同固件,同一个数字可能代表不同的模式。

以 ArduPilot 为例,常用的模式编号如下:

模式名称 custom_mode 值 说明
STABILIZE 0 增稳模式
ACRO 1 特技模式
ALT_HOLD 2 定高模式
AUTO 3 自动模式
GUIDED 4 引导模式
LOITER 5 悬停模式
RTL 6 返航模式
CIRCLE 7 环绕模式
LAND 9 降落模式

注意:PX4 的 custom_mode 定义与 ArduPilot 完全不同。例如,PX4 的 AUTO 模式编号是 4,而 ArduPilot 是 3。跨固件开发时,一定要查对应固件的源码或文档。

3.3 发送流程:从地面站到飞控

整个发送流程,我习惯把它分成三步:

  1. 构建 COMMAND_LONG 消息:填充 target_system、target_component、command、param1~7 等字段。
  2. 序列化并发送:通过串口、UDP 或 TCP 发送 MAVLink 帧。
  3. 等待飞控确认:飞控收到后会回复 COMMAND_ACK 消息,告知指令执行结果。

下面是一个完整的 Python 示例,使用 pymavlink 库发送切换指令:

from pymavlink import mavutil

# 创建连接(假设飞控在 /dev/ttyUSB0,波特率 115200)
master = mavutil.mavlink_connection('/dev/ttyUSB0', baud=115200)

# 等待心跳包,确保连接建立
master.wait_heartbeat()
print("心跳已接收,系统ID:", master.target_system)

# 构建并发送 MAV_CMD_DO_SET_MODE 指令
# 目标:切换到 AUTO 模式(custom_mode = 3)
master.mav.command_long_send(
    master.target_system,    # target_system
    master.target_component, # target_component
    mavutil.mavlink.MAV_CMD_DO_SET_MODE,  # command
    0,                       # confirmation (0 表示首次发送)
    1,                       # param1: base_mode = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED
    3,                       # param2: custom_mode = AUTO (ArduPilot)
    0, 0, 0, 0, 0           # param3~7: 保留,填 0
)

print("指令已发送,等待飞控确认...")

# 等待 COMMAND_ACK 回复
ack = master.recv_match(type='COMMAND_ACK', blocking=True, timeout=5)
if ack:
    if ack.result == mavutil.mavlink.MAV_RESULT_ACCEPTED:
        print("指令被飞控接受,模式切换成功!")
    else:
        print(f"指令被拒绝,结果码: {ack.result}")
else:
    print("超时:未收到飞控确认")

避坑指南:我曾经在发送指令后没有等待 COMMAND_ACK,结果连续发了 10 次,飞控直接进入了保护模式。记住,每次发送后一定要等确认,不要无脑重发。

3.4 飞控端的处理逻辑

飞控收到 MAV_CMD_DO_SET_MODE 后,内部处理流程大致如下:

  1. 校验 base_mode:检查是否包含 MAV_MODE_FLAG_CUSTOM_MODE_ENABLED。如果没有,直接返回 MAV_RESULT_DENIED。
  2. 校验 custom_mode:检查该模式在当前系统状态下是否合法。例如,在未解锁状态下,某些模式可能被禁止。
  3. 执行模式切换:调用内部的状态机切换函数。
  4. 发送 COMMAND_ACK:返回执行结果。

下面我用一张 SVG 流程图来展示这个过程:

MAV_CMD_DO_SET_MODE 飞控端处理流程 飞控端处理 MAV_CMD_DO_SET_MODE 流程 接收 COMMAND_LONG 校验 base_mode base_mode & 0x01 ? 是 否 返回 DENIED 校验 custom_mode 执行模式切换 发送 COMMAND_ACK

3.5 常见问题与避坑指南

最后,我总结几个实际项目中容易遇到的问题:

  • 模式编号搞混:ArduPilot 和 PX4 的 custom_mode 值不同。我建议在代码里用枚举或常量定义,不要硬编码数字。
  • base_mode 忘记设 1:这是最常见的错误。飞控收到 base_mode=0 的指令,会直接忽略。
  • 未等待 ACK 就发下一条:飞控处理指令需要时间。连续发送会导致指令队列溢出。
  • 在非安全状态下切换:比如飞机正在执行紧急降落,此时切换模式可能被飞控拒绝。

重要提醒:在实际飞行中测试模式切换前,务必先在仿真环境(如 SITL)中验证。我曾经在真机上测试新写的切换逻辑,结果飞机直接失控——还好当时高度只有 2 米,没有造成事故。

好了,关于 MAV_CMD_DO_SET_MODE 的内容就这些。参数定义、发送流程、飞控处理逻辑,这三块你吃透了,飞行模式切换这块就算入门了。