3. MAVLink XML定义文件:仓库结构、消息语法与自定义模板
好,咱们进入第三讲。前两章我们把 MAVLink 的协议栈和心跳包聊透了。这一章,我带你看看 MAVLink 的“源代码”——也就是那些 XML 定义文件。
说白了,MAVLink 里所有的消息长什么样、每个字段叫什么、取值范围是多少,全写在这些 XML 里。你想想看,没有这些定义,你连 HEARTBEAT 里哪个字节是系统状态都搞不清。
核心观点:XML 定义文件是 MAVLink 的“宪法”。解析器、代码生成器、甚至地面站里的消息列表,都是从这些 XML 里自动生成的。你改一个 XML,整个生态都跟着变。
3.1 mavlink/mavlink.git 仓库长什么样?
我第一次打开这个仓库时,说实话有点懵。目录挺多,但核心就几个。我挑重要的说。
| 目录/文件 | 作用 | 我个人的关注点 |
|---|---|---|
message_definitions/ |
存放所有 XML 定义文件,按厂商或协议版本分 | 这是咱们的主战场,自定义消息都放这里 |
message_definitions/v1.0/ |
MAVLink 1.0 和 2.0 的官方标准定义 | 别被“v1.0”骗了,MAVLink 2.0 的 XML 也在这 |
message_definitions/ardupilotmega.xml |
ArduPilot 的扩展定义,量最大 | 我经常拿它当自定义模板的参考 |
message_definitions/development.xml |
还在草案中的新消息 | 想提前尝鲜?看这里 |
generator/ |
Python 代码生成器,把 XML 转成 C/Python 源码 | 你写 XML,它帮你生成解析代码 |
include/ |
C 语言头文件,生成器的输出目标 | 实际编译时引用的就是这些 .h 文件 |
嗯,这里要注意:千万别直接改 include/ 下的 .h 文件。我见过有人这么干,结果下次重新生成时,改的全没了。正确的做法是改 XML,然后跑生成器。
3.2 XML 消息定义语法:从根到叶子
一个 MAVLink XML 文件,结构其实很清晰。我把它拆成三层:根节点 → 枚举 → 消息。
<?xml version="1.0"?>
<mavlink>
<!-- 版本和包含关系 -->
<version>3</version>
<include>common.xml</include>
<!-- 枚举定义 -->
<enum name="MY_CUSTOM_STATE">
<description>自定义状态枚举</description>
<entry value="0" name="MY_STATE_IDLE">
<description>空闲</description>
</entry>
<entry value="1" name="MY_STATE_BUSY">
<description>忙碌</description>
</entry>
</enum>
<!-- 消息定义 -->
<message id="150" name="MY_CUSTOM_MESSAGE">
<description>我的第一条自定义消息</description>
<field type="uint8_t" name="state" enum="MY_CUSTOM_STATE">当前状态</field>
<field type="float" name="temperature">温度值,单位摄氏度</field>
<field type="char[16]" name="label">标签字符串</field>
</message>
</mavlink>
这段代码我手写过无数次。咱们拆开看。
3.2.1 枚举(enum)
枚举用来定义一组有名字的常量。比如飞行模式、状态码、错误类型。好处是代码里不用记数字,直接写名字。
- name:枚举的名字,在 C 代码里会生成对应的
enum类型。 - entry:每个枚举值,
value是数字,name是字符串。 - description:可选的说明文字,生成文档时很有用。
我的习惯:枚举值从 0 开始,留一些保留值(比如 200-255)给未来扩展。我曾经因为枚举值冲突,导致地面站把“降落中”显示成“紧急迫降”,场面一度很尴尬。
3.2.2 消息(message)
消息是 MAVLink 的核心。每个消息都有一个唯一的 ID(0-255 是 MAVLink 1.0 的范围,2.0 扩展到 0-16777215)。
| 属性 | 说明 | 避坑点 |
|---|---|---|
id |
消息的唯一标识,接收方靠它区分消息类型 | 不要和已有消息 ID 冲突,查一下 common.xml |
name |
消息的名字,建议全大写加下划线 | 比如 MY_CUSTOM_MESSAGE,别用驼峰 |
field |
消息里的字段,每个字段有类型、名字和描述 | 字段顺序就是字节序,别乱排 |
3.2.3 字段(field)
字段的类型决定了它在二进制流里占几个字节。MAVLink 支持的类型我列一下:
- 整数型:
uint8_t,int8_t,uint16_t,int16_t,uint32_t,int32_t,uint64_t,int64_t - 浮点型:
float(4 字节),double(8 字节) - 数组:
char[10](字符串),uint8_t[4](字节数组) - 特殊:
uint8_t_mavlink_version(自动填充协议版本)
注意:MAVLink 2.0 默认使用小端字节序(Little-Endian)。如果你在 ARM 或 x86 上开发,这没问题。但如果你用某些大端 MCU,记得在生成代码时指定字节序。我曾经在 PowerPC 平台上踩过这个坑,解析出来的数据全是反的。
3.3 自定义消息模板:从零开始写一个
好,理论说完了。咱们动手写一个自定义消息。假设我要给无人机加一个“电池健康状态”的消息,包含电压、电流、剩余百分比和电池编号。
第一步:确定消息 ID。我查了一下 common.xml,150-249 是用户自定义范围。我选 150。
第二步:写 XML。
<?xml version="1.0"?>
<mavlink>
<version>3</version>
<include>common.xml</include>
<enum name="BATTERY_HEALTH_FLAG">
<description>电池健康标志位</description>
<entry value="0" name="BATTERY_HEALTH_OK">
<description>正常</description>
</entry>
<entry value="1" name="BATTERY_HEALTH_LOW">
<description>低电量</description>
</entry>
<entry value="2" name="BATTERY_HEALTH_CRITICAL">
<description>严重低电量</description>
</entry>
</enum>
<message id="150" name="BATTERY_HEALTH_EXTENDED">
<description>扩展电池健康信息,包含电压、电流、百分比和健康标志</description>
<field type="uint8_t" name="battery_id">电池编号,从0开始</field>
<field type="float" name="voltage">总电压,单位伏特</field>
<field type="float" name="current">总电流,单位安培</field>
<field type="int8_t" name="remaining_pct">剩余百分比,-1表示未知</field>
<field type="uint8_t" name="health_flags" enum="BATTERY_HEALTH_FLAG">健康标志位</field>
</message>
</mavlink>
第三步:保存文件。我习惯把它放在 message_definitions/custom/ 下,取名 my_battery.xml。这样和官方文件分开,方便管理。
第四步:生成代码。在仓库根目录跑:
python -m mavgenerate
或者用命令行:
python generator/mavgen.py --lang=C --output=include/custom message_definitions/custom/my_battery.xml
生成后,你会得到一个 include/custom/my_battery.h。里面会有 MAVLINK_MSG_ID_BATTERY_HEALTH_EXTENDED 这个宏,以及对应的结构体和编解码函数。
避坑指南:我曾经把 remaining_pct 定义成 uint8_t,结果发现 -1 表示未知时没法赋值。后来改成 int8_t,问题解决。记住:需要表示“无效值”的字段,用有符号类型。
3.4 本章知识结构图
下面这张图帮你理清 XML 定义文件的整体脉络。从仓库结构到消息定义,再到自定义扩展,一目了然。
3.5 小结
这一章我们干了三件事:
- 搞清楚了
mavlink/mavlink.git的仓库结构,知道 XML 放哪、生成器在哪、头文件在哪。 - 学会了 XML 消息定义语法:
<mavlink>根节点、<enum>枚举、<message>消息、<field>字段。 - 手写了一个自定义电池健康消息的 XML 模板,并生成了 C 代码。
嗯,说实话,自定义消息是 MAVLink 扩展能力里最实用的技能。你想想看,当你需要给无人机加一个官方协议里没有的传感器数据时,自己写个 XML 消息,比硬塞进现有消息里要优雅得多。