4. MAVLink 参数读写流程:从单参数到全列表的实战拆解

参数读写,说白了就是地面站和飞控之间的“讨价还价”。你告诉飞控“我要看这个参数”,飞控把值发回来;你告诉飞控“把这个参数改成那个值”,飞控确认“改好了”。

整个过程看起来简单,但坑不少。我最早做飞控调试时,就因为在参数写入后没等确认就直接断电,结果参数没存住,炸了一架机。嗯,从那以后我对这个流程就特别较真。

这一章,我们把参数读写的四个核心场景拆开揉碎:

  • 读取单个参数
  • 读取全部参数列表
  • 写入单个参数
  • 参数写入确认机制

每个场景我都会结合 MAVLink 协议栈的实际报文交互来讲,顺便带上我踩过的坑。

MAVLink 参数读写整体流程 MAVLink 参数读写核心流程 地面站 (GCS) 飞控 (FC) ① 读取单个参数 PARAM_REQUEST_READ (param_id) PARAM_VALUE (param_id, value) ② 读取全部参数列表 PARAM_REQUEST_LIST PARAM_VALUE (逐个发送,直到 param_count) ③ 写入单个参数 PARAM_SET (param_id, value) ④ 写入确认机制 PARAM_VALUE (回传写入后的值) 💡 关键要点 • 所有参数交互都基于 param_id(参数名)和 param_index(参数索引) • 飞控必须对每个 PARAM_SET 回复 PARAM_VALUE,否则地面站应重试 • 参数写入后不会立即写入持久存储,需要飞控主动保存

4.1 读取单个参数的完整流程

读取单个参数,最常见的场景就是调试时想看看某个关键参数当前是多少。比如我想看 MIS_ALT_DEF 这个高度默认值。

流程其实就两步:

  1. 地面站发送请求:PARAM_REQUEST_READ 报文,里面带上参数名或参数索引。
  2. 飞控回复参数值:PARAM_VALUE 报文,包含参数名、参数值、参数类型和总参数数量。

这里有个细节——你可以用参数名请求,也可以用参数索引请求。我个人习惯用参数名,因为可读性强,调试时一眼就知道在查什么。

报文结构示例(读取单个参数)

// 地面站 -> 飞控
PARAM_REQUEST_READ {
    target_system: 1,
    target_component: 1,
    param_id: "MIS_ALT_DEF",   // 参数名,16字节
    param_index: -1             // -1 表示使用 param_id
}

// 飞控 -> 地面站
PARAM_VALUE {
    param_id: "MIS_ALT_DEF",
    param_value: 10.0,          // 浮点数值
    param_type: MAV_PARAM_TYPE_REAL32,
    param_count: 256,           // 总参数数量
    param_index: 42             // 该参数在列表中的索引
}

我的经验:如果 param_index 传 -1,飞控会忽略 param_id 吗?不会。MAVLink 协议规定,当 param_index 为 -1 时,飞控必须使用 param_id 来查找参数。反过来,如果 param_id 为空字符串,则使用 param_index。两者不能同时为空。

4.2 读取全部参数列表的流程

读取全部参数列表,说白了就是“把飞控里所有参数都拉一遍”。地面站启动时,或者飞控固件升级后,都需要做这个操作。

流程是这样的:

  1. 地面站发送 PARAM_REQUEST_LIST,告诉飞控“我要全部参数”。
  2. 飞控从索引 0 开始,逐个发送 PARAM_VALUE 报文,直到 param_index == param_count - 1。
  3. 地面站收到所有参数后,构建本地参数缓存。

你想想看,如果飞控有 500 个参数,那就得收 500 个 PARAM_VALUE 报文。这中间要是丢了一个,地面站就得重新请求整个列表。

我曾经踩过的坑:有一次在弱信号环境下做参数读取,飞控发了 500 个参数,地面站只收到了 498 个。我以为是飞控没发完,结果发现是链路层丢包。后来我在地面站加了超时重传机制——如果 3 秒内没收到下一个参数,就重新发送 PARAM_REQUEST_LIST。嗯,从那以后就再没出过问题。

这里有个性能优化点:参数列表的传输是串行的。飞控一次只能发一个参数,不能并发。所以参数越多,读取时间越长。我见过一些飞控固件,参数数量超过 1000 个,光读取参数列表就要十几秒。

参数数量 单报文耗时(典型值) 总耗时(无丢包) 总耗时(含重传)
100 10 ms 1.0 s 1.2 s
256 10 ms 2.56 s 3.0 s
500 10 ms 5.0 s 6.5 s
1000 10 ms 10.0 s 13.0 s

优化建议:如果链路质量差,可以考虑分段请求。比如先请求索引 0-99,再请求 100-199,以此类推。这样即使某段丢了,只需要重传那一段,不用全量重来。

4.3 写入单个参数的流程

写入参数比读取要复杂一点,因为涉及确认机制。流程如下:

  1. 地面站发送 PARAM_SET 报文,带上参数名和要写入的值。
  2. 飞控收到后,先校验参数是否存在、值是否合法。
  3. 如果校验通过,飞控更新内存中的参数值,然后回复 PARAM_VALUE 报文。
  4. 地面站收到 PARAM_VALUE 后,比对值是否与写入值一致。

这里有个容易忽略的点:飞控回复的 PARAM_VALUE 里的值,是写入后的值,不是写入前的值。所以地面站可以通过比对来确认写入是否成功。

报文结构示例(写入单个参数)

// 地面站 -> 飞控
PARAM_SET {
    target_system: 1,
    target_component: 1,
    param_id: "MIS_ALT_DEF",
    param_value: 15.0,          // 要写入的值
    param_type: MAV_PARAM_TYPE_REAL32
}

// 飞控 -> 地面站(确认)
PARAM_VALUE {
    param_id: "MIS_ALT_DEF",
    param_value: 15.0,          // 写入后的值
    param_type: MAV_PARAM_TYPE_REAL32,
    param_count: 256,
    param_index: 42
}

4.4 参数写入确认机制

为什么需要确认机制?说白了,就是怕丢包。你发了 PARAM_SET,飞控可能没收到,或者收到了但写入失败,或者写入成功但确认报文丢了。

MAVLink 协议本身没有强制要求重传,但实际工程中,地面站必须实现超时重传机制。我一般这样设计:

  1. 发送 PARAM_SET 后,启动一个 500ms 的超时计时器。
  2. 如果在超时前收到对应的 PARAM_VALUE,比对值是否一致。
  3. 如果一致,标记写入成功。
  4. 如果不一致或超时,重发 PARAM_SET,最多重试 3 次。
  5. 3 次都失败,向用户报告写入失败。

我曾经踩过的坑:有一次写入 MIS_ALT_DEF 后,飞控回复了 PARAM_VALUE,值也对。但我没等飞控把参数保存到持久存储(比如 Flash)就断电了。结果下次上电,参数还是旧值。后来我才知道,PARAM_VALUE 确认只表示内存中的值更新了,不代表已经写入持久存储。飞控需要额外调用保存接口(比如 param_save())才能持久化。

所以,完整的写入确认机制应该包含两层:

层级 确认方式 说明
第一层 PARAM_VALUE 回传 确认内存中的值已更新
第二层 持久化完成通知 飞控保存到 Flash/EEPROM 后,通过特定报文或状态通知地面站

第二层在标准 MAVLink 协议里没有强制要求,但很多飞控固件(比如 ArduPilot、PX4)会通过 STATUSTEXT 报文通知“参数已保存”。我个人建议,地面站收到 PARAM_VALUE 确认后,再等一个“保存完成”的通知,才算真正的写入成功。

避坑指南:如果你在写地面站软件,记得在写入参数后,给用户一个明确的反馈——是“写入成功(内存)”,还是“写入成功(已保存)”。这两个状态差别很大,用户需要知道参数是否已经持久化。