mavlink协议详解与多通道支持

一、MAVLINK协议详解
MAVLink(Micro Air Vehicle Link)是一种轻量级的消息传输协议和串行化格式,主要用于飞行控制器(如 Pixhawk、ArduPilot)与地面控制站(GCS)、配套计算机或其他系统之间的通信。它设计之初就考虑到了资源受限的嵌入式系统和带宽有限的通信链路(如无线电遥测)。
1、核心设计思想
- 基于消息(Message-Based):通信的基本单位是预定义或自定义的"消息"(Message)。
- 轻量级(Lightweight):协议开销小,编码高效,适合低带宽环境。
- 高效(Efficient):使用紧凑的二进制表示。
- 灵活(Flexible):支持消息扩展(自定义消息)。
- 点对点(Peer-to-Peer):理论上节点可以互相通信,但实际中常以飞控为中心。
- 可扩展(Extensible):核心协议定义了一组标准消息,同时允许用户定义私有消息。
2、通信模型
典型的 MAVLink 网络包含:
- 飞行控制器(FCU / Autopilot):系统的核心,运行飞控软件(如 PX4, ArduPilot)。
- 地面控制站(GCS):运行在地面的软件(如 QGroundControl, Mission Planner),用于监控和控制无人机。
- 配套计算机(Companion Computer):搭载在无人机上的辅助计算机,用于运行更复杂的任务(如计算机视觉、高级导航)。
- 其他传感器/执行器:通过配套计算机或直接与飞控通信。
通信通常通过串口(UART)、USB、或无线链路(如数传电台、Wi-Fi)进行。
3、消息结构与编码
Mavlink 2.0 的消息结构主要由以下几个部分组成,按字节顺序排列:
-
起始分隔符 (Start Delimiter / Magic Byte):
- 长度:1 字节
- 值:固定为
0xFD(十进制 253) - 作用:标识一个 Mavlink 2.0 消息帧的开始。接收方通过检测这个字节来判断新消息的到来。
-
负载长度 (Payload Length):
- 长度:1 字节
- 范围:0 - 255 字节
- 作用:指示紧随其后的
负载字段的实际字节数。这是 Mavlink 2.0 支持可变长度消息的关键。最大负载长度为 255 字节,远大于 Mavlink 1.0 的 255 - 6 = 249 字节(因为 V1 的负载长度包含了头部部分字段)。
-
不兼容标志 (Incompatibility Flags):
- 长度:1 字节
- 作用:指示消息中包含接收方可能无法理解的扩展字段或特性。如果接收方无法处理这些标志所代表的特性,它可以选择忽略整个消息。这有助于协议的向前兼容性。
- 常见标志位:
0x01: 表示该消息包含签名字段。0x02: 表示该消息使用了协议版本字段(用于区分 V1 和 V2 格式的消息,但在标准 V2 帧中通常不使用)。- 其他位保留。
-
兼容标志 (Compatibility Flags):
- 长度:1 字节
- 作用:指示消息中包含接收方即使不理解也可以安全忽略的扩展字段或特性。接收方应处理消息的核心部分,忽略不理解的扩展部分。
- 目前保留供将来使用,通常设置为
0x00。
-
序列号 (Packet Sequence Number):
- 长度:1 字节
- 范围:0 - 255
- 作用:由发送方维护并递增(每发送一个新消息加 1,溢出后回绕)。用于检测消息丢失或乱序。同一个发送方发出的不同消息应具有不同的序列号(除非重传)。
-
系统 ID (System ID / SYSID):
- 长度:1 字节
- 范围:1 - 255
- 作用:标识发送该消息的系统(如无人机、地面站、某个子系统)。
0通常保留为广播地址或未知地址。
-
组件 ID (Component ID / COMPID):
- 长度:1 字节
- 范围:1 - 255
- 作用:标识发送该消息的系统内的特定组件(如飞控、相机、电池管理系统)。
0通常保留为广播地址或未知地址。SYSID和COMPID共同定义了消息的源地址。
-
消息 ID (Message ID / MSGID):
- 长度:3 字节(Mavlink 2.0 新增特性)
- 范围:0 - 16777215
- 作用:唯一标识消息的类型(例如心跳包、GPS 位置、姿态信息等)。Mavlink 2.0 使用 3 字节的 MSGID,极大地扩展了可定义的消息类型数量(从 V1 的 256 种增加到 1600 多万种),支持更多设备和更复杂的应用。
-
负载 (Payload):
- 长度:由
Payload Length字段指定 (0 - 255 字节)。 - 作用:包含消息的实际数据内容。数据的排列方式(字节顺序、字段偏移)由对应
消息 ID的消息定义决定。这是协议传输的核心信息。
- 长度:由
-
校验和 (Checksum - CRC16/X25):
- 长度:2 字节
- 作用:用于检测消息在传输过程中是否发生错误(比特翻转)。
- 计算:对整个消息帧(从
起始分隔符到负载结束)以及一个额外的CRC_EXTRA字节进行计算。使用的算法是 CRC-16-CCITT-FALSE (也称为 X25 CRC)。 - 公式:
checksum = CRC16 ( magic ∣ ∣ len ∣ ∣ incomp_flags ∣ ∣ comp_flags ∣ ∣ seq ∣ ∣ sysid ∣ ∣ compid ∣ ∣ msgid ∣ ∣ payload , crc_extra ) \text{checksum} = \text{CRC16}(\text{magic} || \text{len} || \text{incomp\_flags} || \text{comp\_flags} || \text{seq} || \text{sysid} || \text{compid} || \text{msgid} || \text{payload}, \text{ crc\_extra}) checksum=CRC16(magic∣∣len∣∣incomp_flags∣∣comp_flags∣∣seq∣∣sysid∣∣compid∣∣msgid∣∣payload, crc_extra) - 接收方计算相同的 CRC 值并与接收到的校验和进行比较,如果不匹配则丢弃该消息。
-
签名 (Signature - Optional):
- 长度:6 字节 (如果存在)
- 存在性:由
不兼容标志的0x01位指示。 - 作用:提供消息认证和完整性保护。签名基于发送方的私钥生成,接收方可以使用发送方的公钥进行验证,确保消息确实来自声称的发送方且未被篡改。签名算法通常为 ECDSA (P-256)。
- 内容:通常包含时间戳 (或链接序列号) 和实际的 ECDSA 签名值 (r, s)。
- 位置:紧跟在
校验和之后。
消息结构图示 (不含签名):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 起始分隔符 | 负载长度 | 不兼容标志 | 兼容标志 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序列号 | 系统 ID | 组件 ID | 消息 ID (低) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 消息 ID (中) | 消息 ID (高) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ 负载 (变长) +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 (低) | 校验和 (高) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
消息结构图示 (含签名):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 起始分隔符 | 负载长度 | 不兼容标志 | 兼容标志 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序列号 | 系统 ID | 组件 ID | 消息 ID (低) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 消息 ID (中) | 消息 ID (高) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ +
| |
+ 负载 (变长) +
| |
+ +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和 (低) | 校验和 (高) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ 签名 (6 字节) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
总结 Mavlink 2.0 的关键改进:
- 可变长度负载 : 通过独立的
Payload Length字段实现,最大支持 255 字节负载。 - 扩展的消息 ID (24 位): 支持海量的消息类型。
- 兼容性标志: 支持协议扩展,同时保持与旧实现的兼容性。
- 可选的消息签名: 提供身份认证和消息完整性保护。
4、数据类型与消息定义
MAVLink 消息的 有效载荷 由一系列字段组成。字段可以是基本数据类型:
uint8_t,int8_tuint16_t,int16_tuint32_t,int32_tuint64_t,int64_tfloat(单精度浮点数)double(双精度浮点数,较少用)char(用于固定长度的字符串)- 数组(如
float[4])
消息在 XML 文件中定义。例如,一个简化的 ATTITUDE 消息定义可能如下:
xml
<mavlink>
<messages>
<message id="30" name="ATTITUDE">
<description>Current attitude information.</description>
<field type="uint32_t" name="time_boot_ms">Timestamp (milliseconds since system boot)</field>
<field type="float" name="roll">Roll angle (radians)</field>
<field type="float" name="pitch">Pitch angle (radians)</field>
<field type="float" name="yaw">Yaw angle (radians)</field>
<field type="float" name="rollspeed">Roll angular speed (radians/s)</field>
<field type="float" name="pitchspeed">Pitch angular speed (radians/s)</field>
<field type="float" name="yawspeed">Yaw angular speed (radians/s)</field>
</message>
</messages>
</mavlink>
工具链(如 mavgen)根据这些 XML 文件生成特定编程语言(如 C, C++, Python, Java)的编解码库。
5、 标准消息与自定义消息
- 标准消息 (Common Messages) :由 MAVLink 项目定义和维护的核心消息集(定义在
common.xml)。例如:HEARTBEAT:系统状态心跳包,包含类型、状态等信息。SYS_STATUS:系统状态报告(电池、CPU负载、通信链路状态等)。ATTITUDE:飞行器姿态(欧拉角)。GLOBAL_POSITION_INT:全局位置(经纬高)。COMMAND_LONG:发送命令(如起飞、改变模式、设置参数)。PARAM_VALUE:参数值响应。
- 自定义消息 (Custom Messages / Dialects) :用户或特定厂商可以根据需要在私有命名空间或自定义的 XML 文件中定义自己的消息。这些消息的
MSGID通常从较高值开始(如 180 以上)以避免冲突。例如,一个厂商可以定义MY_COMPANY_CUSTOM_DATA = 300来传输特定的传感器数据。
6、典型工作流程
- 连接建立:物理链路建立(如串口打开)。
- 心跳交换 :双方(如飞控和 GCS)开始周期性发送
HEARTBEAT消息。接收方据此判断对方的存在和类型。 - 参数同步(可选) :GCS 可能请求飞控的参数列表 (
PARAM_REQUEST_LIST),飞控逐个回复PARAM_VALUE。 - 数据流请求 :GCS 发送
REQUEST_DATA_STREAM命令,指定所需数据的流号(如原始 IMU 数据、姿态数据、位置数据)和更新频率。飞控开始定期发送相应消息。 - 命令与控制 :GCS 发送
COMMAND_LONG或COMMAND_INT消息给飞控,执行起飞、返航、模式切换等动作。飞控回复COMMAND_ACK确认命令执行结果。 - 状态监控 :飞控持续发送状态消息(
SYS_STATUS,GPS_RAW_INT,BATTERY_STATUS等)给 GCS。 - 任务上传/下载 :使用
MISSION相关消息(MISSION_COUNT,MISSION_ITEM,MISSION_ACK)传输航点任务。
7、应用场景
- 无人机飞行控制:飞控与 GCS、配套计算机、遥控器之间的核心通信协议。
- 仿真环境 (HITL/SITL):软件在环 (SITL) 或硬件在环 (HITL) 仿真中,模拟飞控与 GCS 的交互。
- 地面站软件开发:QGroundControl, Mission Planner, MAVProxy 等软件都基于 MAVLink。
- 机器人控制:协议设计通用,也可用于地面、水面机器人。
- 传感器集成:将非标准传感器数据通过配套计算机打包成 MAVLink 消息发送给飞控或 GCS。
8、总结
MAVLink 是一个高效、灵活、广泛应用于无人机和机器人系统的通信协议。其核心在于预定义和可扩展的二进制消息结构,确保了在资源受限环境下的可靠通信。理解其帧结构、消息定义方式和典型工作流程是进行无人机系统开发、集成和调试的基础。
二、多通道支持
1、mavlink协议修改
mavlink协议是支持多通道解析的,什么是多通道么,比如你四个串口接了四个设备都通过mavlink协议进行通信,这时就需要多通道支持了。用一个通道会出现解析错误。
怎么修改通道呢?
首先打开mavlink_channel_t这个枚举,添加

默认4个比如你有20个通道就修改为:
c
typedef enum {
MAVLINK_COMM_LPUART,
MAVLINK_COMM_WIRELESS,
MAVLINK_COMM_2,
MAVLINK_COMM_3,
MAVLINK_COMM_4,
MAVLINK_COMM_5,
MAVLINK_COMM_6,
MAVLINK_COMM_7,
...
MAVLINK_COMM_18,
} mavlink_channel_t;
其次修改MAVLINK_COMM_NUM_BUFFERS这个宏定义

2、多通道解析方法
cpp
//串口接收解析
len = rt_ringbuffer_get(&uart_recv_buff,buf,sizeof(buf));
for (uint8_t i = 0; i < len; i++)
{
if (mavlink_parse_char(MAVLINK_COMM_LPUART, buf[i], &mavlinkMsgIn, &mavlinkStatusIn))
{
msg_deal(MAVLINK_COMM_LPUART);
}
}
//无线接收解析
len = rt_ringbuffer_get(&wireless_recv_buff,buf,sizeof(buf));
for (uint8_t i = 0; i < len; i++)
{
if (mavlink_parse_char(MAVLINK_COMM_WIRELESS, buf[i], &mavlinkMsgIn, &mavlinkStatusIn))
{
msg_deal(MAVLINK_COMM_WIRELESS);
}
}
注意mavlink_parse_char的第一个参数填写对应通道,如下:
cpp
if (mavlink_parse_char(MAVLINK_COMM_LPUART, buf[i], &mavlinkMsgIn, &mavlinkStatusIn))
if (mavlink_parse_char(MAVLINK_COMM_WIRELESS, buf[i], &mavlinkMsgIn, &mavlinkStatusIn))
3、多通道发送
调用自己的发送方法就行:
c
void mavlink_ack(uint8_t chan,uint16_t command,uint8_t result, uint8_t progress, int32_t result_param2)
{
uint8_t packBuffer[256];
mavlink_command_ack_t commandAck;
commandAck.command = command;
commandAck.result = result;
commandAck.progress = progress;
commandAck.result_param2 = result_param2;
mavlink_msg_command_ack_encode(configPara.systemId,configPara.componentId,&msg,&commandAck);
uint16_t sendLen = mavlink_msg_to_send_buffer(packBuffer,&msg);
if(chan == MAVLINK_COMM_LPUART)
lpuart_send_data(packBuffer,sendLen);
else if(chan == MAVLINK_COMM_WIRELESS)
wireless_send_data(packBuffer,sendLen);
}
根据通道选择对象发送:
c
if(chan == MAVLINK_COMM_LPUART)
lpuart_send_data(packBuffer,sendLen);
else if(chan == MAVLINK_COMM_WIRELESS)
wireless_send_data(packBuffer,sendLen);
