3. MISSION_PROTOCOL详解:任务协议交互流程、MISSION_COUNT/MISSION_ITEM消息解析

讲QGC航点任务,绕不开MAVLink的MISSION_PROTOCOL。说白了,这就是地面站和飞控之间“商量”任务的那套规矩。我最早接触这个协议时,觉得不就是传几个点嘛,有啥复杂的?后来真踩了坑才发现——细节全在交互流程里。

今天咱们就把MISSION_PROTOCOL掰开揉碎。重点看两件事:交互流程和核心消息。你搞懂了这两个,后面做性能优化才有根基。

核心观点:MISSION_PROTOCOL不是简单的“上传-下载”,而是一个带状态机的握手协议。每个消息的收发顺序、超时处理、重传机制,都直接影响任务执行的可靠性。

3.1 协议交互流程:一次完整的任务上传

先看整体流程。我习惯把任务上传分成四个阶段:

  1. 写请求阶段:地面站告诉飞控“我要写任务”
  2. 计数协商阶段:飞控确认“好,你要写几个点”
  3. 逐点传输阶段:地面站一个一个发任务点
  4. 完成确认阶段:飞控说“收完了,没问题”

下面这张图把整个流程串起来了。你盯着看两分钟,后面讲消息就好理解了。

MISSION_PROTOCOL 任务上传交互流程 MISSION_PROTOCOL 任务上传交互流程 地面站 (GCS) 飞控 (Autopilot) 阶段1:写请求 MISSION_COUNT (count=5) → MISSION_REQUEST (seq=0) ← 请求第0个点 阶段2:逐点传输(循环) MISSION_ITEM (seq=0) → 发送第0个点 MISSION_ACK (type=ACCEPTED) ← 确认接收 ... 重复直到 seq = count-1 ... 阶段3:完成确认 MISSION_ITEM (seq=4) → 发送最后一个点 MISSION_ACK (type=ACCEPTED) ← 全部接收完成 ✅ 任务上传完成

嗯,这里要注意:飞控收到MISSION_COUNT后,不是直接说“好”,而是先检查当前状态。如果飞控正在执行任务,或者存储空间不够,它会拒绝写请求。我遇到过一台老Pixhawk,EMMC快满了,写10个点的任务都报错——查了半天才发现是存储问题。

3.2 MISSION_COUNT消息解析

MISSION_COUNT是任务上传的“敲门砖”。地面站先发这个,告诉飞控“我要给你传N个点”。

字段名 类型 说明
target_system uint8_t 目标系统ID(飞控的ID)
target_component uint8_t 目标组件ID(通常为MAV_COMP_ID_AUTOPILOT)
count uint16_t 任务点总数(包括航点、命令等)
mission_type uint8_t 任务类型:0=MISSION, 1=GEOCACHE, 2=FENCE, 3=RALLY

个人经验:count字段是uint16_t,最大65535。但实际飞控的存储有限。我建议单次上传不超过500个点。超过这个数,飞控处理时间会明显变长,甚至触发看门狗复位。

飞控收到MISSION_COUNT后,会做两件事:

  • 校验count值:如果count=0,飞控认为你要清空任务。如果count超过飞控容量,返回MISSION_ERROR。
  • 发送MISSION_REQUEST:请求第0个任务点。注意,飞控不会一次性请求所有点,而是一个一个要。

为什么会这样?说白了,飞控的内存有限。一次处理一个点,处理完确认,再要下一个。这种“滑动窗口”机制虽然慢,但可靠。

3.3 MISSION_ITEM消息解析

MISSION_ITEM是任务的核心载体。每个任务点、每个命令(比如起飞、降落、拍照)都封装在这个消息里。

// MAVLink MISSION_ITEM 消息结构(简化版)
uint8_t     target_system;      // 目标系统
uint8_t     target_component;   // 目标组件
uint16_t    seq;                // 序号(从0开始)
uint8_t     frame;              // 坐标系:MAV_FRAME_GLOBAL_RELATIVE_ALT等
uint16_t    command;            // 命令:MAV_CMD_NAV_WAYPOINT等
uint8_t     current;            // 是否为当前任务点(0/1/2)
uint8_t     autocontinue;       // 是否自动继续到下一个点
float       param1;             // 参数1(如停留时间)
float       param2;             // 参数2(如接受半径)
float       param3;             // 参数3(如通过半径)
float       param4;             // 参数4(如偏航角)
float       x;                  // 纬度(度)
float       y;                  // 经度(度)
float       z;                  // 高度(米,相对或绝对)

这里有个坑:frame字段决定了z的解释方式。MAV_FRAME_GLOBAL_RELATIVE_ALT表示z是相对起飞点的高度;MAV_FRAME_GLOBAL_TERRAIN_ALT表示z是相对地面的高度。我见过有人把相对高度当绝对高度传,结果飞机飞到100米高空——还好是在模拟器里。

避坑指南:我曾经在项目中遇到一个诡异问题:任务上传后,飞控总是跳过第3个点。查了两天,发现是seq字段写错了——我传了seq=0,1,2,2,4。飞控收到两个seq=2,直接覆盖了前一个。记住:seq必须连续且唯一。

3.4 任务下载流程

下载流程和上传是对称的,但方向相反。我简单列一下:

  1. 地面站发MISSION_REQUEST_LIST,请求任务列表
  2. 飞控回复MISSION_COUNT,告诉地面站有多少个点
  3. 地面站发MISSION_REQUEST(带seq),请求第N个点
  4. 飞控回复MISSION_ITEM,发送任务点数据
  5. 重复步骤3-4,直到所有点下载完成
  6. 地面站发MISSION_ACK,确认下载完成

你想想看,下载和上传的区别在哪?谁主动谁被动。上传是地面站主动推,飞控被动收;下载是地面站主动拉,飞控被动发。但核心消息是一样的。

3.5 协议中的超时与重传

实际通信中,丢包是常态。MISSION_PROTOCOL有简单的超时重传机制:

  • 发送方:发送消息后启动定时器(通常500ms-1s)。超时未收到响应,重发上次消息。
  • 接收方:收到重复消息(比如seq相同),直接丢弃,但重新发送ACK。

我遇到过最头疼的情况:WiFi链路下,丢包率10%左右,任务上传经常卡住。后来发现是QGC默认超时时间太短(300ms)。我改成800ms,配合重传3次,问题解决了。

优化建议:在QGC源码中,超时时间定义在MissionManager::_missionTransferTimeout。你可以根据链路质量动态调整。比如数传链路用1.5s,WiFi链路用800ms,4G链路用500ms。

3.6 协议状态机

飞控内部维护一个任务传输状态机。我画个简化的状态图:

飞控任务传输状态机 飞控任务传输状态机(简化) IDLE(空闲) RECEIVING(接收中) SENDING(发送中) DONE(完成) 收到MISSION_COUNT 收到MISSION_REQUEST_LIST 所有点接收完成 所有点发送完成 超时/错误 → 回到IDLE

这个状态机看着简单,但实际实现时有很多边界情况。比如:接收过程中飞控突然收到MISSION_REQUEST_LIST,应该怎么处理?按规范,飞控应该先完成当前传输,再响应新请求。但有些飞控固件实现得不好,直接中断当前传输——这就导致任务上传一半卡死了。

3.7 常见问题与排查方法

现象 可能原因 排查方法
任务上传到一半卡住 超时时间过短、链路丢包 抓MAVLink日志,看是否收到MISSION_ACK
飞控拒绝写任务 存储空间不足、飞控正在执行任务 检查飞控状态,清空当前任务
任务点顺序错乱 seq字段不连续、重复 打印所有seq,检查是否递增
高度值异常 frame字段错误、坐标系混淆 确认frame类型,检查z值单位

调试技巧:我习惯在QGC的MAVLink Inspector里实时看MISSION_PROTOCOL消息。重点关注MISSION_ACK的type字段。type=0表示ACCEPTED,type=1表示ERROR,type=2表示UNSUPPORTED。看到非0值,基本就是协议出问题了。

好了,MISSION_PROTOCOL的核心内容就这些。你记住三件事:交互流程是握手式的、MISSION_COUNT是总纲、MISSION_ITEM是血肉。后面讲性能优化时,我们会基于这些原理,讨论如何减少传输延迟、提高任务上传成功率。