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 协议层内置了续传机制。流程是这样的:
- 地面站发 MISSION_COUNT,告诉飞控「我要传 200 条」。
- 飞控回 MISSION_REQUEST,带 seq=0,意思是「给我第 0 条」。
- 地面站发 MISSION_ITEM,seq=0。
- 飞控收到后,发 MISSION_REQUEST,seq=1。
- 如此循环,直到 seq=199 传完。
- 飞控发 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 的交互流程画了张图,方便你理解:
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 的核心内容就这些。帧结构、参数映射、坐标系、续传机制——这四个点吃透了,航点任务的上半场你就拿下了。