Lazy loaded image
二、基础使用
Words 6282Read Time 16 min
2025-5-6
 
📃
本节主要介绍MAVLink常见使用方法。首先,我们演示MAVLink源码下载、编译生成 MAVLink2 C库,并对C库作大致介绍;接着详细分析ArduPilot项目中MAVLink消息收发过程;最后一小节介绍MAVLink消息格式(协助本节内容理解,为下一节做准备)。
 

一、MAVLink下载、编译

若在C/C++项目使用标准 dialects,可直接下载预编译版本 pre-built MAVLink source files。C库在如下开源库中使用:PX4ArduPilot、MAVSDK。但建议阅读1.2节,里面有基础概念讲解
开发环境建议使用Ubuntu 20.04。第一个优点是使用apt install 命令安装的python版本默认大于3.7,避免低版本系统安装高版本python的麻烦(若你已经遇到,可参考如何在 Ubuntu 24.04 LTS 中安装 Python 3.12 或指定版本文章操作,该文章列出三种方法,但本人未全部测试过)。第二个优点相较于windows,环境搭建更简单(若你已经选择Windows 进行开发,参考Install MAVLink · MAVLink Developer Guide)。
notion image
另外,强烈建议安装前,更换apt软件源、pip软件源。此类介绍文章俯拾皆是,详细说明不在本节范畴。

1.1 Install MAVLink

  • --recursive :下载仓库中包含的模块 pymavlink
    • python3 -m pip install -r pymavlink/requirements.txt:安装pymavlink依赖,比如GUI工具所需的TkInter
    • 下载后,mavlink 文件夹结构。

      1.2 Generate Language-Specific Source Files

      可以用过两种方法编译生成源文件(MIT-licensed),第一种命令行 Command Line Tool第二种图形界面 GUI
      初次使用有如下常见问题:
      第一个XML文件怎么选?XML文件分为三类,Standard definitions,Test definitions 和Dialects。
      • Standard definitions分类下包括minimal.xml、standard.xml 和common.xml三个文件,其三者消息集范围逐渐变大,依次包含,比如standard.xml包含minimal.xml,common.xml包含standard.xml ;
      • Test definitions用于测试使用;
      • Dialects用于特定语言和板子使用。
      下面针对所有xml文件做简要备注(详细解释打开xml文件,或进入网址查看),知道每个文件含义,XML怎么选的问题就迎刃而解了
      第二个GUI文件中输出文件夹怎么选择?输出文件夹可以直接在Output输入,比如下图输入include,会直接在mavlink根文件夹下创建include文件夹,存放生成源码。若include已经存在,则会覆盖。建议生成多种语言指定不同的文件夹
      notion image
      notion image
      第三个命令行具体使用方法和含义?
      参考如下:

      1.3 Generate Source Files for ROS

      参考下面链接,在ROS中添加MAVLink messages 和 Dialects。

      二、编译生成的 MAVLink2 C库解析

      自动生成C库只包含头文件,包含dialect文件夹以及一些公用 “.h” 文件。

      2.1 Dialect文件夹说

      以ardupilotmega为例
      • 头文件(.h):每个 mavlink_msg_.h 文件对应一个特定的 MAVLink 消息类型。这些头文件定义消息的结构,包括其字段和数据类型。还包括对消息进行编码和解码的函数,以适应 MAVLink 数据格式。
      • ardupilotmega.h :ardupilotmega dialects 特有的枚举enum定义和消息宏定义。
      • mavlink.h :作为一个网关,用于包含ardupilotmega.h。定义了一些确保ardupilotmega 正确使用的宏定义。
      • 消息定义文件: mavlink_msg_*.h 文件提供特定消息的定义。例如,mavlink_msg_ahrs.h 将定义与 AHRS(姿态和航向参考系统)消息相关的结构和函数。
      • testsuite.h :包含测试用例或验证函数,以确保消息定义和编码/解码函数的正确性。它可能在开发过程中用于验证消息处理的完整性。
      • version.h :这个文件通常包含 MAVLink dialect或协议的版本信息。

      2.2 “.h” 文件说明

      • mavlink_conversions.h:包含不同单位或数据类型之间转换的函数。可能包括坐标系、测量单位或数据格式之间的转换。
      • mavlink_get_info.h:提供检索MAVLink协议相关信息的函数,比如支持的消息类型、字段和其他元数据。
      • mavlink_helpers.h:包括一些辅助函数,以便于使用MAVLink协议。这些函数可能包括编码和解码消息、计算校验和或管理消息缓冲区的功能。
      • mavlink_sha256.h:包含与SHA-256哈希相关的函数,这可能用于消息完整性检查或MAVLink协议内部的安全通信。
      • mavlink_types.h:包含MAVLink协议中使用的基本数据类型,例如消息结构、枚举和常量。它作为定义和处理MAVLink消息的基础组件。
      • protocol.h:包含与MAVLink协议本身相关的核心定义和函数。它可能定义了协议的版本、消息格式以及任何特定于协议的操作。
      • checksum.h:包含计算和验证MAVLink消息校验和的函数。校验和用于确保传输过程中的数据完整性,帮助检测消息中的错误或损坏。

      三、APM中MAVLink 消息收发过程

      3.1 APM 消息发送过程

      MAVLink 的接收和解码分为几个阶段:
      1. APM调度表周期调用循环接收,多通道同时更新 scheduler_tasks | update_send void GCS::update_send()void GCS_MAVLINK::update_send()
      1. 处理特殊消息(存在被阻塞消息等)和不同ID消息do_try_send_message()try_send_messagequeued_param_send

      3.1.1 调度器循环发送

      首先在飞行器(这里以ArduSub为例)任务调度表中定义了周期收发任务update_receive update_send。完成 MAVLINK 消息的接收和发送,执行频率 400Hz。
      我们来分析libraries\GCS_MAVLink\GCS_Common.cppupdate_send源码,内部调用通道接口chan(i)->update_send() ,轮询发送。
      void GCS_MAVLINK::update_send() 5ms 时间内发送消息,处理推迟发送等消息。其中由do_try_send_message 函数实现核心功能。

      3.1.2 处理飞行器相关特例

      do_try_send_message 首先判断调度器是否处于delay_callback,两种情况下直接跳出函数体。第一种ID消息并非为delay_callback处理;第二种在处于遥测系统被阻塞。其他情况则调用 try_send_message 接口,继续执行。try_send_message 函数switch-case 语句映射不同消息ID发送函数。

      3.1.3 映射不同ID发送函数

      3.1.4 Payload-协议帧-串口发送

      不同ID消息发送接口,由自动生成的头文件提供。比如MSG_SCALED_PRESSURE3由 build| YOUR_BOARD| libraries| GCS_MAVLink| include | mavlink | v2.0 | common | mavlink_msg_scaled_pressure3.h 提供。
      notion image
      notion image

      3.2 APM消息接收过程

      MAVLink 的接收和解码分为几个阶段:
      1. APM调度表周期调用循环接收,多通道同时更新void GCS::update_receive(void)GCS_MAVLINK::update_receive()
      1. 将接收到的流解析为表示每个数据包的对象(mavlink_message_t)MAVLINK_HELPER uint8_t mavlink_frame_char_buffer(mavlink_message_t* rxmsg,mavlink_status_t* status,uint8_t c,mavlink_message_t* r_message,mavlink_status_t* r_mavlink_status)
      1. 将数据包payload中的MAVLink消息解码为一个C结构体(具有与原始XML定义相对应的字段)void GCS_MAVLINK::packetReceived()void handle_message(const mavlink_message_t &msg)
      以下步骤将进行演示。

      3.2.1 调度器循环接收

      void GCS::update_receive(void) 对GCS绑定通道执行更新update_receive()。chan(i)->update_receive()才是真正执行 MAVLink 消息接收的地方。
       

      3.2.2 解析数据包

      MAVLink库编译生成 build/linux/libraries/GCS_MAVLink/include/mavlink/v2.0/mavlink_helpers.h,在内部有三个解析数据包接口:
      mavlink_parse_char 是用于从字节流中解析 MAVLink 消息的主要函数,而 mavlink_frame_charmavlink_frame_char_buffer 是处理解析过程细节的低层级函数。mavlink_frame_char_buffer 提供了额外的灵活性,以管理多个解析上下文。下面详细解释mavlink_parse_char接口。
      特性:
      1. 字节流解析
      1. 返回值指示解析状态(完成一帧解析)
      1. r_mavlink_status 表示当前字节的解码状态/错误(一个字节解析状态)
      参数:
      • uint8_t chan: 当前通道的ID。
      • uint8_t c: 要解析的字符。
      • mavlink_message_t* r_message: 成功时,返回解码后的消息。如果消息无法解码,则为NULL。
      • mavlink_status_t* r_mavlink_status: 通道统计信息,包括当前解析状态的信息。
      如果数据包解码不完整,返回0;如果数据包成功解码,返回1。
      官方文档在分析MAVLink解包过程,使用 mavlink_parse_char 最为字节流解析主要函数,但实际在追踪下面源码, 内部使用 mavlink_frame_char_buffer接口。可能原因是官方文档基于较旧版本,目前分析ArduPilot 版本为Github上最新,但三者接口差异不大,不影响我们源码分析。

      3.2.3 解码有效载荷

      在上一节中获取的消息/数据包对象(mavlink_message_t)包含 MAVLink 数据包/序列化格式表示的字段,包括消息 ID(msgid)和有效载荷(payload64)。解析有效载荷的范式如下:
      • 提供case 声明,将希望解码的消息ID 映射到适当的解码函数
      • 两种类型消息解码函数(第一种将整个消息解码为 C 结构体,而第二种仅获取单个字段
      实际GCS_MAVLINK库中,通过GCS_MAVLINK::packetReceived处理一些特殊情况,比如排除用于命令行操作的radio 包、排除路由处理的包等。进一步调用handle_message(msg),基于不同ID,调用解码函数。
      handle_message 定位到该函数定义于库内的GCS.h文件中
      在GCS_MAVLINK类中实现为一个虚函数,因此我们必须在各个具体的飞行器类型中找到对应派生类中的具体实现。以ArduSub为例,在ArduSub文件夹 GCS_Mavlink.h 中继承基类GCS_MAVLINK ,实现 GCS_MAVLINK_Sub 派生类,并且重写handleMessage()。void GCS_MAVLINK_Sub::handle_message(const mavlink_message_t &msg) 内部通过switch-case语句根据mavlink消息id进行不同的事件处理。

      3.2.4 命令解码

      3.2.5 额外检查

      库里有一些 #define 宏定义,你可以设置来启用各种功能:
      • MAVLINK_CHECK_MESSAGE_LENGTH:启用此选项以检查每个消息的长度。这样可以更早捕捉到无效消息。如果传输媒介容易丢失(或多出)字符(例如,信号时强时弱),可以使用此选项。

      四、MAVLink XML 文件框架/格式

      MAVLink 消息通过XML格式文件定义,每个XML文件定义一组消息集,common.xml是参考消息集(reference message set),大多的对话(dialects)都是基于该定义。

      4.1 文件结构

      4.2 枚举定义 (枚举)

      4.3 MAVLink 命令 (列举 MAV_CMD)

      个别 enum 命名为 MAV_CMD,正在使用MAVLink Commands 含义。 每个命令都有 value(其命令编号),并指定最多 7 参数。

      5.4 消息定义(消息)

      主要消息标签/字段是:
      • message: 每个消息被 message 标签封装,包含以下属性
        • id“id”属性是这一信息的独特索引编号(见上文:147)。
          • 对于 MAVLink 1:
          • 有效数字介于 0 到 255。
          • ID 0-149 和 230-255 为common.xml保留。 语支可以使用180-229 用于自定义消息 (除非这些信息没有被其他包括语支使用)。
          • 对于 MAVLink 2 :
          • 有效数字介于0-1677215。
          • 255以下所有值都被认为是保留的,除非报文也打算用于 MAVLink 1。 >注意 ID 在 MAVLink 1 中很宝贵!
        • name:名称属性为消息提供了人类可读的表格 (比如 "BATERY_STATUS") 它用于在生成的库命名辅助功能,但并没有通过总线发送。
      • description(可选):用户界面和代码评论中显示的信息可读描述。 这应当包含所有信息(以及超链接),以便充分了解信息。
      • field: 对消息的一个字段进行编码。字段值是地面控制站GUI界面显示的名称/文本字符串(但不通过网络发送)。每条消息必须至少有一个字段。
        • type: 类似于 C struct - 存储/代表数据类型所需数据大小。
          • 字段可签名/无签名,大小 8、16、23、64位({u)int8_t(u)int16_t(u>(u)int 32_t(u>(u)int64_int), 单一/双精度精度IEEE754 浮点数。 它们也可以是其他类型,例如uint16_t[10]
        • name:字段名称 (在代码中使用)。
        • number(可选):一个enum 定义字段可能的值的名称 (例如:MAV_BATERY_CHRRE_STATE)。
        • units(可选):信息 字段对应数字的单位(未列出)。 这些定义在 schema(搜索 name="SI_Unit")
        • display(可选):这应当设置为 display="bitmask" 用于 bitsword 字段 (指向必须作为复选框显示数字的地面站)。
        • print_format(可选):TBD。
        • default(可选):TBD。
        • instance: If true, this indicates that the message contains the information for a particular sensor or battery (e.g. Battery 1, Battery 2, etc.) and that this field indicates which sensor. Default is false.
          • 此字段允许接收者自动将特定传感器的消息关联起来,并将它们绘制在同一个系列中。
        • invalid: Specifies a value that can be set on a field to indicate that the data is invalid: the recipient should ignore the field if it has this value. For example, BATTERY_STATUS.current_battery specifies invalid="-1", so a battery that does not measure supplied current should set BATTERY_STATUS.current_battery to 1.
          • Where possible the value that indicates the field is invalid should be selected to outside the expected/valid range of the field (0 is preferred if it is not an acceptable value for the field). For integers we usually select the largest possible value (i.e. UINT16_MAXINT16_MAXUINT8_MAXUINT8_MAX). For floats we usually select invalid="NaN".
            Arrays represent multiple elements, some (or all) of which may need to be marked as invalid. The following notation is used to specify the values that indicate elements of the array are invalid:
          • invalid="[value]": Array elements that contain value are invalid.
          • invalid="[value:]": All array elements are invalid if the first array element is set to value.
          • invalid="[value1,,value3,]": Array elements are invalid if they contain the value specified in the corresponding position of the comma separated list. If the a position in the list is empty, there is no way to indicate the corresponding array element is invalid. The example above indicates that elements 1 and 3 are invalid if they contain value1 and value3, respectively. For element 2 and any elements >4 the invalid property of the field cannot be set.
          • invalid="[value1,]": The first array element is invalid if it contains value1: the invalid property cannot be set for all other elements.
      • deprecated / wip (optional): A tag indicating that the message is deprecated or "work in progress".
      • 扩展 (可选):此自闭标签被用于表明随后的字段仅适用于 MAVLink 2。
        • 标记应该用于MAVLink 1 信息 (id < 256) 已扩展到MAVLink2。
       

      引用

      1. Adding a new MAVLink Message — Dev documentation (ardupilot.org)
      1. QGC添加自定义组件和发送自定义MAVLINK消息
      1. ardupilot之mavlink消息--从飞控发出--单向_通过mavlink发送数据到missionplanner
       
      上一篇
      模板设计模式:让你的代码结构更清晰
      下一篇
      Guide to Linux System

      Comments
      Loading...