赞
踩
网上的各类MavLink通信协议教程,往往只对协议本身进行介绍,而不对开发中如何应用进行详述。有介绍应用的往往也都浅尝辄止,讲完HeartBeat然后就让你自己触类旁通。笔者在自学时遇到了不少困难,一步步摸爬滚打后,总算对其有了一定的认识,在这里将心得与大家分享。
如果你读到这篇博文,说明你至少已经对MavLink有了一定了解,其各种特征笔者就不再赘述了,但是值得一提的是,MavLink通信协议是与通信方式密切相关的,本文仅介绍MavLink协议的应用,并不会介绍通信链路相关的知识。根据你的应用场景,可能会需要串口通信、TCP、UDP等不同通信方式,本文会对不同通信方式下使用MavLink协议的差异略作介绍,但不会详述。
博文所介绍的内容均为本人个人理解,可能会有错误、纰漏,因此仅供交流、讨论。如因参照本人的方法导致开发出现了各种问题,本人概不负责。
本文主要章节:
第一章介绍使用mavlink协议前的准备
第2章主要介绍处理接收的mavlink消息的相关问题
第3章主要介绍发送mavlink消息的相关问题
本文使用visual studio 2019作为开发工具,C++为程序语言。
mavlink库配置教程参考我的另一篇博文。
在vs2019开发环境中使用mavlink通信协议,首先需要将下载好的mavlink库添加至项目包含目录。
即项目->属性->配置属性->VC++目录->包含目录->选择你的文件路径,如下图,然后点击确定,应用。
#include<common/mavlink.h>
接下来就可以在项目使用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继续完成。
有了前文对接收时预处理的理解,这里的操作就简单多了。mavlink库提供了功能函数mavlink_msg_to_send_buffer(),该函数可以将mavlink_message_t转换为uint8_t数组并返回其指针。因此只要使用此函数然后再将uint8_t转换为你要发送的格式即可。往往直接发送该uint8_t数组即可,毕竟接收方也是要解析此数据将其转为mavlink_message_t的。
首先给出解析一个最简单的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; } }
接下来我们来逐步分析
mavlink_parse_char(uint8_t chan, uint8_t c, mavlink_message_t* r_message, mavlink_status_t* r_mavlink_status)是解析mavlink消息的核心。
解析完成后该函数会返回一个uint8_t值,如果成功解析则返回1,解析失败则返回0
因此我们需要准备这四个参数。
uint8_t chan = MAVLINK_COMM_0;//第一个参数,默认取该值
uint8_t* buffer;//这是你接收到的原始信息,并按照2.2.1章节将其转化为了uint8_t数组形式
mavlink_message_t rmsg;//第三个参数,用于存储解析完成的内容
mavlink_status_t status;//第四个参数,用于存储解析状态
for循环的意图就是把你接收到的所有信息都送来解析,len就是你所接收到信息的字节长度。然后把上述几个参数传入该函数,等待解析。当解析成功,即返回值=1时,继续执行进一步的操作。
for (int i = 0; i <len; i++)
{
if (mavlink_parse_char(chan, buffer[i], &rmsg, &status) == 1)
{
//进一步操作
}
}
成功解析出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;
同样先给出发送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送到串口发送即可
发送消息的核心就是把你要发送的信息(也就是payload部分)打包成符合mavlink协议的数据格式。
打包使用的函数名为mavlink_msg_abc_pack,该函数会将信息打包为mavlink_message_t数据格式,函数的返回值是该信息所占的总长度(Byte)。
函数使用的参数就是你要打包的所有信息,根据你要发送的信息不同,使用的参数也是不同的。
发送的消息主要可分为一下几类,
打包成mavlink_message_t数据格式后,再使用mavlink_msg_to_send_buffer(uint8_t*,const mavlink_message_t*),将其存储到字节数组中,便于发送至串口。
发送消息涉及的参数众多,在使用时建议转到定义处详细查看各参数的含义,避免误用。这里主要介绍常用的参数。
一般打包都需要如下几个参数:
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,务必详细阅读你使用的消息的定义、坐标系定义、指令定义。
除了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值对应的含义。
码字不易,如果你觉得这篇文章对你的学习有所帮助,不妨点个赞激励一下作者!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。