3. MAVLink消息结构:消息帧格式、CRC校验、消息ID分配规则

聊完电调校准的宏观流程,咱们得钻到协议内部看看了。MAVLink消息在线上怎么跑?说白了,就是一串字节流。但光有字节不行,你得知道哪段是头、哪段是数据、哪段是校验。这就是消息帧格式要解决的问题。

我刚开始接触MAVLink时,直接拿现成库用,没仔细看帧结构。结果有一次调试自定义消息,死活收不到正确数据。折腾了两天,最后发现是CRC算错了。嗯,从那以后,我老老实实把协议栈啃了一遍。

3.1 消息帧格式

MAVLink目前有两个主流版本:v1和v2。v2向后兼容v1,但帧头更长,功能也更丰富。咱们做电调校准,强烈建议直接用v2,因为v2支持更大的payload和更灵活的签名机制。

先看v2的帧结构,我习惯把它拆成三段:

偏移(字节) 字段名 长度(字节) 说明
0 STX (起始标志) 1 v2固定为0xFD,v1为0xFE
1 LEN (载荷长度) 1 payload字节数,0~255
2 INC_FLAGS (不兼容标志) 1 接收方必须理解的标志位
3 CMP_FLAGS (兼容标志) 1 接收方可忽略的标志位
4 SEQ (序列号) 1 每个组件独立计数,用于丢包检测
5 SYS_ID (系统ID) 1 标识发送设备,如飞控=1
6 COMP_ID (组件ID) 1 标识发送组件,如电调=MAV_COMP_ID_ESC
7~9 MSG_ID (消息ID) 3 v2扩展到24位,支持更多消息类型
10~(10+LEN-1) PAYLOAD (载荷) LEN 实际数据,最大255字节(v2)
10+LEN CRC (校验码) 2 16位CRC,覆盖整个包
10+LEN+2 SIGNATURE (签名) 13 可选,用于安全认证

关键点:v2的MSG_ID是3字节,v1只有1字节。这意味着v2最多支持16,777,216种消息ID,而v1只有256种。做电调校准自定义消息时,v2的ID空间几乎用不完。

3.2 CRC校验

CRC是MAVLink消息的「防错锁」。线上环境干扰多,一个比特翻转就可能让电调收到错误的校准指令。CRC的作用就是让接收方自己算一遍,跟发送方附带的校验码比对,不一致就直接丢包。

MAVLink用的CRC-16-CCITT,多项式是0x1021。但有个坑——它不只是对payload算CRC,还把整个帧头(从LEN到MSG_ID)也算进去了。你想想看,如果帧头里的LEN字段被干扰了,接收方读到的payload长度是错的,那后面所有数据都废了。

我给大家一个实用的CRC计算片段,基于MAVLink官方C库:

// CRC-16-CCITT 查表法
static const uint16_t crc_table[256] = {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
    // ... 完整256项表,这里省略
};

uint16_t mavlink_crc_calculate(const uint8_t* buf, uint16_t len) {
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < len; i++) {
        crc = (crc << 8) ^ crc_table[((crc >> 8) ^ buf[i]) & 0xFF];
    }
    return crc;
}

// 注意:计算时先算帧头(从LEN到MSG_ID),再算payload
uint16_t calc_full_crc(const mavlink_message_t* msg) {
    uint16_t crc = mavlink_crc_calculate(&msg->len, 10); // 帧头10字节
    crc = mavlink_crc_calculate(msg->payload, msg->len); // payload
    // 最后异或消息的额外CRC种子
    crc ^= msg->crc_extra;
    return crc;
}

我曾经踩过的坑:在STM32上移植MAVLink时,发现CRC总是算不对。查了半天,原来是字节序问题——MAVLink的CRC计算要求小端序,而我的payload是多字节字段,没有做字节序转换。记住:MAVLink所有多字节字段都是小端序,包括CRC本身。

3.3 消息ID分配规则

消息ID是MAVLink的「身份证」。每个消息类型都有一个唯一的ID。MAVLink官方维护了一套分配规则,咱们做电调校准,主要关心三类ID:

  • 标准消息(0~255):v1时代就定义好的,比如HEARTBEAT (0)、SYS_STATUS (1)、COMMAND_LONG (76)。这些ID所有设备都必须支持。
  • 扩展消息(256~511):v2引入的,用于特定协议扩展,比如ESC_INFO (290)、ESC_STATUS (291)。电调校准主要用这个区间。
  • 厂商自定义消息(512~65535):留给厂商自己玩。如果你做的是非标电调,可以在这里定义私有消息。但我不建议这么做——除非你确定上下游设备都是自家的。

电调校准过程中,最常用的几个消息ID我列一下:

消息名 ID (十进制) 方向 用途
COMMAND_LONG 76 飞控→电调 发送校准指令(如MAV_CMD_PREFLIGHT_CALIBRATION)
COMMAND_ACK 77 电调→飞控 确认指令已收到
ESC_INFO 290 电调→飞控 上报电调型号、固件版本
ESC_STATUS 291 电调→飞控 上报转速、温度、电流
PROTOCOL_VERSION 300 双向 协商协议版本(v2特有)

我的建议:写电调固件时,优先用标准消息ID。实在不够用,再考虑扩展区间。厂商自定义ID尽量别碰——你想想看,如果以后要对接别人的飞控,对方不认识你的私有ID,那就尴尬了。

3.4 消息帧结构图

下面这张图把v2帧结构画清楚了。我特意把CRC覆盖范围标了出来,方便你理解校验逻辑。

MAVLink v2 消息帧结构 MAVLink v2 消息帧结构 帧头 (10字节) STX LEN INC CMP SEQ SYS COMP MSG_ID (3B) 载荷 (LEN字节) PAYLOAD (0~255B) CRC 2字节 CRC 覆盖范围:帧头(10B) + 载荷(LEN) + CRC_extra 可选签名 (13字节) — 仅当 INC_FLAGS 的 bit7=1 时存在 说明: • v1帧头只有6字节(无INC/CMP,MSG_ID仅1字节),STX=0xFE • 电调校准建议使用v2,充分利用3字节MSG_ID和签名功能

3.5 实际调试中的注意事项

讲完理论,说点实战经验。做电调校准调试时,我建议你准备一个MAVLink抓包工具。我自己常用的是mavlink-router自带的日志功能,或者用QGroundControl的MAVLink Inspector。看到原始字节流,很多问题就一目了然了。

几个常见问题:

  • CRC不匹配:先检查字节序,再检查crc_extra值。每个消息类型都有一个固定的crc_extra,定义在XML里。忘了加这个,CRC永远算不对。
  • 消息ID冲突:如果你自定义了消息ID,确保跟官方ID不重叠。我见过有人把自定义消息ID设为77,结果跟COMMAND_ACK冲突了,飞控端直接解析错误。
  • 帧头解析失败:v2的STX是0xFD,v1是0xFE。如果混用版本,接收方可能把v2帧当成v1来解析,导致LEN和MSG_ID全读错。

一句话总结:MAVLink消息帧就是「头+数据+尾巴」的结构。头告诉你怎么解析,数据是你关心的内容,尾巴保证数据没被篡改。搞懂这三块,电调校准的通信基础就稳了。