MavLink通信协议 C++应用详细教程

MavLink C++应用教程

0.前言

网上的各类MavLink通信协议教程,往往只对协议本身进行介绍,而不对开发中如何应用进行详述。有介绍应用的往往也都浅尝辄止,讲完HeartBeat然后就让你自己触类旁通。笔者在自学时遇到了不少困难,一步步摸爬滚打后,总算对其有了一定的认识,在这里将心得与大家分享。

如果你读到这篇博文,说明你至少已经对MavLink有了一定了解,其各种特征笔者就不再赘述了,但是值得一提的是,MavLink通信协议是与通信方式密切相关的,本文仅介绍MavLink协议的应用,并不会介绍通信链路相关的知识。根据你的应用场景,可能会需要串口通信、TCP、UDP等不同通信方式,本文会对不同通信方式下使用MavLink协议的差异略作介绍,但不会详述。

博文所介绍的内容均为本人个人理解,可能会有错误、纰漏,因此仅供交流、讨论。如因参照本人的方法导致开发出现了各种问题,本人概不负责。

本文主要章节:

第一章介绍使用mavlink协议前的准备

第2章主要介绍处理接收的mavlink消息的相关问题

第3章主要介绍发送mavlink消息的相关问题

1.开始前的准备

1.1开发环境

本文使用visual studio 2019作为开发工具,C++为程序语言。

mavlink库配置教程参考我的另一篇博文。

1.1在项目中包含mavlink库

  1. 在vs2019开发环境中使用mavlink通信协议,首先需要将下载好的mavlink库添加至项目包含目录。

    即项目->属性->配置属性->VC++目录->包含目录->选择你的文件路径,如下图,然后点击确定,应用。

MavLink通信协议 C++应用详细教程_第1张图片

  1. 然后在头文件中包含如下文件
#include

接下来就可以在项目使用mavlink库提供的功能了。


1.2根据通信方式预处理数据(串口/TCP/UDP/…)

1.2.1 接收mavlink消息时的预处理

这一步一般在教程中极少提到,但在实际应用中却极为重要。我们使用通信协议一定是为了通信服务的,那就不可避免的涉及各种不同的通信方式。不论是哪种通信方式,你所接收到的数据几乎都无法直接拿来进行解析。因为mavlink库提供的消息解析功能需要的是mavlink_message_t这种数据类型,而我们通过各种串口获得的数据类型千奇百怪,可能是各种uint,可能是各种string或char*,这就需要我们先统一将其转为mavlink能操作的数据类型

找到官方的mavlink_message_t的定义如下,可以看到其基本单元就是8位无符号整型(uint8_t/unsigned char),也就是1字节

MAVPACKED(
typedef struct __mavlink_message {
	uint16_t checksum;      ///< sent at end of packet
	uint8_t magic;          ///< protocol magic marker
	uint8_t len;            ///< Length of payload
	uint8_t incompat_flags; ///< flags that must be understood
	uint8_t compat_flags;   ///< flags that can be ignored if not understood
	uint8_t seq;            ///< Sequence of packet
	uint8_t sysid;          ///< ID of message sender system/aircraft
	uint8_t compid;         ///< ID of the message sender component
	uint32_t msgid:24;      ///< ID of message in payload
	uint64_t payload64[(MAVLINK_MAX_PAYLOAD_LEN+MAVLINK_NUM_CHECKSUM_BYTES+7)/8];
	uint8_t ck[2];          ///< incoming checksum bytes
	uint8_t signature[MAVLINK_SIGNATURE_BLOCK_LEN];
}) mavlink_message_t;

因此我们必须将串口接收到的千奇百怪的数据类型转换为以uint8/unsigned char为基本单元的形式,一般是将串口接收到的消息转换成一个uint8数组。本文无法对每种转换具体操作详细说明,读者可以自行在网上找到各种数据类型转换的方法。建议转换之后先自己print/cout/…验证一下,正确的输出应为形如0xff这样的格式或0-255之间的整数。

转换成uint8_t/uint8_t数组后预处理就算完成了,将这些单字节数据进一步解析为mavlink消息的过程将在章节2.3继续完成。

1.2.2 发送mavlink消息时的预处理

有了前文对接收时预处理的理解,这里的操作就简单多了。mavlink库提供了功能函数mavlink_msg_to_send_buffer(),该函数可以将mavlink_message_t转换为uint8_t数组并返回其指针。因此只要使用此函数然后再将uint8_t转换为你要发送的格式即可。往往直接发送该uint8_t数组即可,毕竟接收方也是要解析此数据将其转为mavlink_message_t的。

2.接收mavlink消息

首先给出解析一个最简单的heartbeat消息的流程

	mavlink_status_t status;
    mavlink_message_t rmsg;
    uint8_t chan = MAVLINK_COMM_0;
   	for (int i = 0; i <len; i++)
    {
		if (mavlink_parse_char(chan, buffer[i], &rmsg, &status) == 1)
        {
            switch (rmsg.msgid)
            {
            case MAVLINK_MSG_ID_HEARTBEAT:
                mavlink_heartbeat_t heartbeat;
                mavlink_msg_heartbeat_decode(&rmsg, &heartbeat);
                std::cout() << "type:" << (int)heartbeat.type << endl;
                break;
             default:
                break;
            }
		}

2.1 接收并存储mavlink消息

接下来我们来逐步分析

  1. mavlink_parse_char(uint8_t chan, uint8_t c, mavlink_message_t* r_message, mavlink_status_t* r_mavlink_status)是解析mavlink消息的核心。

    • 第一个参数的官方解释:mavlink利用channel实现在同一个程序中同时处理多个独立的mavlink流。mavlink库提供的所有接收和发送功能都需要一个channel,因此为每个操作使用正确的channel是很重要的。可以看出,chan不是物理上的串口1串口2,而是逻辑上的channel。这点我也没有很深入的了解,只知道在仅有一条mavlink流时,取MAVLINK_COMM_0是可行的。
    • 第二个参数就是该函数每次会解析的一个字节。
    • 当解析完成后,会将解析结果的内容返回给第3个数据类型为mavlink_message_t的参数。
    • 并会将解析状态返回给第4个数据类型为mavlink_status_t的参数。

    解析完成后该函数会返回一个uint8_t值,如果成功解析则返回1,解析失败则返回0

  2. 因此我们需要准备这四个参数。

    	uint8_t chan = MAVLINK_COMM_0;//第一个参数,默认取该值
    	uint8_t* buffer;//这是你接收到的原始信息,并按照2.2.1章节将其转化为了uint8_t数组形式
        mavlink_message_t rmsg;//第三个参数,用于存储解析完成的内容
    	mavlink_status_t status;//第四个参数,用于存储解析状态
    
  3. for循环的意图就是把你接收到的所有信息都送来解析,len就是你所接收到信息的字节长度。然后把上述几个参数传入该函数,等待解析。当解析成功,即返回值=1时,继续执行进一步的操作。

    for (int i = 0; i <len; i++)
        {
    		if (mavlink_parse_char(chan, buffer[i], &rmsg, &status) == 1)
            {
            //进一步操作
            }
    	}
    

2.2对解析出的mavlink_message_t消息进行进一步操作

成功解析出mavlink消息后,我们要区分该消息的类型,依据不同的消息执行不同的操作。该部分代码即上文中"进一步操作“位置处的代码。

switch (rmsg.msgid)
            {
            case MAVLINK_MSG_ID_HEARTBEAT:
                mavlink_heartbeat_t heartbeat;
                mavlink_msg_heartbeat_decode(&rmsg, &heartbeat);
                std::cout() << "type:" << (int)heartbeat.type << endl;
                break;
             default:
                break;
  1. 判断mavlink消息类型,即判断msgid。
  2. mavlink的消息类型是用数字编号的,但一般不会直接用数字,用其枚举名即可。定义的枚举名的格式均为MAVLINK_MSG_ID_ABC
  3. 根据消息类型进行进一步解析,比如判断出了消息id是MAVLINK_MSG_ID_ABC,那么利用对应的解析函数mavlink_msg_abc_decode()进行解析,里面的参数很明显,第一个是要解析的mavlink_message,第二个是保存解析后的信息的,这个参数的数据类型名称为mavlink_abc_t
  4. 此时以mavlink_abc_t为数据类型的变量即已包含了最终可读的信息。这个数据类型本质上是一个结构体,包含了各种信息,建议转到mavlink_abc_t的定义详细查看其提供的参数的数据类型及单位等信息,避免误用。例如惯性思维会认为roll pitch yaw为角度制,但实际上进入mavlink_attitude_t的定义中可以看到,其使用的是弧度制。

2.3 常用的mavlink消息

  • mavlink_heartbeat_t 心跳包,用于确认无人机与地面站之间保持着通信连接
  • mavlink_attitude_t 储存着姿态角、角速度信息
  • mavlink_gps_raw_int_t 储存着gps相关信息,包括经纬高(wgs84坐标系下),gps精度等信息
  • mavlink_battery_status_t 电池状态,包括电压、容量等信息
  • mavlink_vfr_hud_t 储存着空速地速高度、爬升率、节气门等信息

3. 发送mavlink消息

同样先给出发送mavlink消息的通用流程

    mavlink_message_t msg_t;
    int len = mavlink_msg_mission_count_pack(255, 1, &msg_t, 1, 0, 5, MAV_MISSION_TYPE_MISSION);
    buf = new uint8_t[len];
    mavlink_msg_to_send_buffer(buf, &msg_t);
	//接下来把buf送到串口发送即可

3.1打包消息

发送消息的核心就是把你要发送的信息(也就是payload部分)打包成符合mavlink协议的数据格式。

打包使用的函数名为mavlink_msg_abc_pack,该函数会将信息打包为mavlink_message_t数据格式,函数的返回值是该信息所占的总长度(Byte)。

函数使用的参数就是你要打包的所有信息,根据你要发送的信息不同,使用的参数也是不同的。

发送的消息主要可分为一下几类,

  • command,指令消息,其函数名为mavlink_msg_command_long_pack
  • mission,任务相关的消息,其函数名为mavlink_msg_mission_abc_pack
  • set,设置相关参数,其函数名为mavlink_msg_set_abc_pack

打包成mavlink_message_t数据格式后,再使用mavlink_msg_to_send_buffer(uint8_t*,const mavlink_message_t*),将其存储到字节数组中,便于发送至串口。

3.2常用通用参数含义及介绍

发送消息涉及的参数众多,在使用时建议转到定义处详细查看各参数的含义,避免误用。这里主要介绍常用的参数。

一般打包都需要如下几个参数:

  • system_id发送端的系统id;target_system目标(接收端)的系统id。系统id是用来区分系统中的不同个体的,常用于多机编队中,因为此时需要详细确认发送方和接收方的身份。如3机编队,那么可以分别给其编号为1、2、3用于区分,并给地面站编号4。mavlink最多允许一个系统中存在255个id。如果只是单地面站操作单架无人机,那么这个编号就显得不是那么重要了。
  • component_id发送组件的id;target_component目标组件id,这是用来区分同一个体中不同组件的,如区分地面站cpu发给机载gpu,或地面站cpu发给机载imu。这个id可以明确消息的发送源和接收方。0代表发送给目标系统的所有组件。

3.3mission相关参数介绍

mission系列的消息主要就是航线任务相关的消息。例如

  • mission_count:总的任务点(航点)数量

  • mission_item:具体每个航点的信息

  • 自动补全时你还会看到如mission_item_int,mission_xxx_long的消息,其最后的int,long就是指数据类型,例如mission_item消息里的参数使用的是float,而mission_item_int使用的是int,由于浮点数在不同的平台存在数据精度问题,推荐使用int、long等形式的信息。

  • mission_request:请求获取目标的任务信息

  • mission_ack:任务状态,如任务成功接收,任务接收失败,失败原因:缺少参数。

  • mission_current:当前目标航点

  • mission_item_reached:上一个到达的航点

  • mission_change,mission_clear_all:任务改变,清空任务。

想向无人机上传航线任务有一个具体的流程,可参考其他教程学习该流程,本文不再详述流程,主要介绍上传各个消息时可能用到的参数。

  • mission_type:任务类型,官方原版mavlink提供了如航点,地理围栏等任务类型,其有如下几个枚举值。一般航点使用的是MAV_MISSION_TYPE_MISSION。

    typedef enum MAV_MISSION_TYPE
    {
       MAV_MISSION_TYPE_MISSION=0, /* Items are mission commands for main mission. | */
       MAV_MISSION_TYPE_FENCE=1, /* Specifies GeoFence area(s). Items are MAV_CMD_NAV_FENCE_ GeoFence items. | */
       MAV_MISSION_TYPE_RALLY=2, /* Specifies the rally points for the vehicle. Rally points are alternative RTL points. Items are MAV_CMD_NAV_RALLY_POINT rally point items. | */
       MAV_MISSION_TYPE_ALL=255, /* Only used in MISSION_CLEAR_ALL to clear all mission types. | */
       MAV_MISSION_TYPE_ENUM_END=256, /*  | */
    } MAV_MISSION_TYPE;
    
  • seq:你的任务有10个航点那么这10个航点的序号依次是0,1,2,3,4,…,9

  • frame:航点等使用的坐标系,如绝对gps坐标,使用相对高度的gps坐标,相对xyz坐标等等,详细可查看MAV_FRAME的枚举值。例如常用的MAV_FRAME_GLOBAL_RELATIVE_ALT=3,这个坐标系是指使用gps的经纬度坐标,但是高度使用相对home的相对高度而不是gps高度。

  • command:指令,如起飞MAV_CMD_NAV_TAKEOFF,着陆MAV_CMD_NAV_LAND,普通航点MAV_CMD_NAV_WAYPOINT,建议详细查看MAV_CMD的枚举值。mav_cmd还决定了你的消息需要的其他参数,务必详细阅读其注释。

  • autocontinue:飞过当前任务点后是否自动飞向下一个任务点。(个人测试1为自动继续,0为不继续,不确定,建议自行测试)

  • xyz:坐标值,取决于你选取的frame,需注意在使用mission_item和mission_item_int时,前者的xyz使用的是float类型;而后者xyz是int数据类型,有时需要用原始值*10^7,务必详细阅读你使用的消息的定义、坐标系定义、指令定义。

3.4 其他参数

除了mission系列的消息,还经常需要地面站上传设置指令,如设置mode,这既可以通过mavlink_msg_command_long_pack,设置其中的command参数为MAV_CMD_DO_SET_MODE打包上传,也可以通过mavlink_msg_set_mode_pack(…)打包上传。根据官方说明,推荐使用MAV_CMD_DO_SET_MODE设置

mode包含base mode和custom mode。在接收到心跳包时就可以明确当前的base mode和custom mode值。base mode可查看MAV_MODE的枚举值,而custom mode对不同的无人机是不同的,一个不成熟的测试方法是依次设置custom mode,根据返回的heartbeat信息确定custom mode值对应的含义。


码字不易,如果你觉得这篇文章对你的学习有所帮助,不妨点个赞激励一下作者!

你可能感兴趣的:(vs2019,MavLink,c++,c++,开发语言,后端)