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覆盖范围标了出来,方便你理解校验逻辑。
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消息帧就是「头+数据+尾巴」的结构。头告诉你怎么解析,数据是你关心的内容,尾巴保证数据没被篡改。搞懂这三块,电调校准的通信基础就稳了。