4. MISSION_ITEM消息详解:帧结构、参数映射、坐标系选择、自动续传机制

MISSION_ITEM 这条消息,可以说是整个航点任务系统的「细胞」。你想想看,无论任务多复杂,最终都要拆成一条一条的 MISSION_ITEM 发给飞控。我最早接触 MAVLink 时,觉得这消息挺简单——不就发个坐标嘛。后来在项目中踩了坑才发现,里面的门道比想象中多得多。

4.1 帧结构:一条消息里到底装了什么?

先看原始定义。MAVLink 2 协议里,MISSION_ITEM 的 payload 固定 37 字节。我习惯把它分成三块来看:

偏移 字段 类型 说明
0 target_system uint8_t 目标系统 ID,通常填 1
1 target_component uint8_t 目标组件 ID,飞控一般是 1
2 seq uint16_t 航点序号,从 0 开始
4 frame uint8_t 坐标系枚举,后面细讲
5 command uint16_t 命令编号,比如 MAV_CMD_NAV_WAYPOINT
7 current uint8_t 是否为当前航点(0/1/2)
8 autocontinue uint8_t 是否自动进入下一航点
9 param1 ~ param4 float × 4 命令参数,共 16 字节
25 x / lat float 纬度或 X 坐标
29 y / lon float 经度或 Y 坐标
33 z / alt float 高度或 Z 坐标

关键点:seq 字段是 uint16_t,意味着最多支持 65535 个航点。实际项目中我建议别超过 500 个,否则上传时间太长,容易断链。

4.2 参数映射:param1~param4 到底怎么用?

很多人第一次看到 param1~param4 会懵——这四个 float 到底填什么?其实它完全取决于 command 字段。

举个例子,MAV_CMD_NAV_WAYPOINT(编号 16):

  • param1:停留时间(秒)。0 表示飞过即走,不停留。
  • param2:到达半径(米)。飞机进入这个范围就算到达。
  • param3:通过半径(米)。用于曲线绕行,一般设 0。
  • param4:偏航角(度)。-1 表示自动对准下一个航点。

再比如 MAV_CMD_DO_CHANGE_SPEED(编号 178):

  • param1:速度类型(0=空速,1=地速)。
  • param2:目标速度(m/s)。
  • param3:油门百分比(-1 表示不改变)。
  • param4:保留,填 0。

我的习惯:写代码时,我会在注释里把每个 param 的物理含义标清楚。曾经接手过一个项目,param2 和 param3 填反了,飞机在航点附近一直绕圈……排查了两天才发现。

4.3 坐标系选择:frame 字段的坑

frame 字段只有 1 个字节,但选错了,飞机可能飞到天上去。常见的坐标系有这些:

枚举值 名称 适用场景
0 MAV_FRAME_GLOBAL 经纬度 + 海拔高度(气压计)
3 MAV_FRAME_GLOBAL_RELATIVE_ALT 经纬度 + 相对起飞点高度(最常用)
6 MAV_FRAME_GLOBAL_TERRAIN_ALT 经纬度 + 地形跟随高度
11 MAV_FRAME_LOCAL_NED 局部坐标系,北东地
14 MAV_FRAME_LOCAL_OFFSET_NED 相对当前位置偏移

避坑指南:我曾经在项目中用了 MAV_FRAME_GLOBAL(海拔高度),结果飞机在高原地区起飞,海拔 3000 米,任务高度设了 100 米,飞控直接爬升到 3100 米——因为海拔高度是绝对高度。后来全部改成 MAV_FRAME_GLOBAL_RELATIVE_ALT,问题解决。

我个人建议:户外飞行一律用 GLOBAL_RELATIVE_ALT。室内或视觉定位场景用 LOCAL_NED。地形跟随场景用 GLOBAL_TERRAIN_ALT,但前提是飞控必须有地形数据。

4.4 自动续传机制:断点续传是怎么实现的?

上传 200 个航点,传到一半信号断了,难道要重头再来?MAVLink 的 MISSION_ITEM 协议层内置了续传机制。流程是这样的:

  1. 地面站发 MISSION_COUNT,告诉飞控「我要传 200 条」。
  2. 飞控回 MISSION_REQUEST,带 seq=0,意思是「给我第 0 条」。
  3. 地面站发 MISSION_ITEM,seq=0。
  4. 飞控收到后,发 MISSION_REQUEST,seq=1。
  5. 如此循环,直到 seq=199 传完。
  6. 飞控发 MISSION_ACK,表示全部收到。

如果第 50 条丢了,飞控会一直发 MISSION_REQUEST seq=50。地面站收到后重新发送第 50 条即可。这就是自动续传的核心——基于 seq 的逐条确认机制。

实际经验:我遇到过一种情况——飞控发了 MISSION_REQUEST seq=50,但地面站已经发到 seq=60 了。这时候地面站必须丢弃 seq=51~60 的缓存,回退到 seq=50 重新发送。有些地面站没做这个回退逻辑,就会导致任务上传失败。

嗯,这里要注意:MISSION_REQUEST 的超时时间一般设 500ms。如果飞控 500ms 内没收到 MISSION_ITEM,会重发 MISSION_REQUEST。连续重试 3 次失败,飞控就会认为上传失败,发 MISSION_ACK 带错误码。

4.5 核心流程一览

我把整个 MISSION_ITEM 的交互流程画了张图,方便你理解:

MISSION_ITEM 上传与续传流程 MISSION_ITEM 上传与自动续传流程 地面站 (GCS) 飞控 (FCU) MISSION_COUNT (count=200) MISSION_REQUEST (seq=0) MISSION_ITEM (seq=0) MISSION_REQUEST (seq=1) ... MISSION_ITEM (seq=50) 丢失 MISSION_REQUEST (seq=50) 续传核心:飞控只请求缺失的 seq,地面站回退到该 seq 重新发送

4.6 实际编码中的几个细节

写代码时,有几个点我建议你特别留意:

  • seq 必须严格递增:飞控收到 seq=5 后,如果突然收到 seq=7,会直接丢弃并重发 MISSION_REQUEST seq=6。
  • current 字段:上传时全部填 0。飞控收到所有航点后,会把第一个设为 current=1。地面站不要自己设。
  • autocontinue 字段:绝大多数场景填 1。填 0 的话,飞控到达航点后会悬停等待指令,不会自动飞下一个。
  • 坐标系一致性:同一个任务里,所有 MISSION_ITEM 的 frame 必须一致。混用会导致飞控计算错误。

小技巧:调试时,可以在地面站打印飞控返回的 MISSION_REQUEST seq 值。如果 seq 一直在某个值附近反复请求,说明那条 MISSION_ITEM 没被正确接收。这时候检查一下 payload 长度和校验和。

好了,MISSION_ITEM 的核心内容就这些。帧结构、参数映射、坐标系、续传机制——这四个点吃透了,航点任务的上半场你就拿下了。