3、MAVLink协议深度解析:消息结构、常用类型与mavlink-router配置
MAVLink 这东西,说白了就是无人机世界的“普通话”。不管你是 ArduPilot 还是 PX4,飞控和地面站、机载电脑之间怎么聊天?全靠它。我最早接触 MAVLink 是在一个多机协同项目里,两台无人机要互相传位置信息,结果发现消息格式没对齐,数据全乱套了。从那以后,我养成了一个习惯——先看协议栈,再碰硬件。
这一节,咱们就把 MAVLink 的底裤扒开看看。消息怎么组装的?常用的心跳、姿态、位置消息长什么样?还有那个 mavlink-router 到底怎么配?嗯,一个一个来。
3.1 MAVLink 消息结构:从字节流到结构化数据
MAVLink 目前有两个主流版本:v1.0 和 v2.0。v2.0 主要解决了 v1.0 的扩展性问题,比如消息 ID 范围更大、支持签名认证。我个人建议,新项目直接上 v2.0,兼容性更好。
先看 v2.0 的消息帧结构:
| 字节偏移 | 字段名 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | STX(起始标志) | 1 | 0xFD 表示 v2.0 |
| 1 | LEN(负载长度) | 1 | payload 部分的字节数 |
| 2 | INC_FLAGS(不兼容标志) | 1 | 比如是否启用签名 |
| 3 | CMP_FLAGS(兼容标志) | 1 | 扩展字段,接收方可忽略 |
| 4 | SEQ(序列号) | 1 | 每个组件独立计数 |
| 5 | SYS_ID(系统 ID) | 1 | 标识无人机或地面站 |
| 6 | COMP_ID(组件 ID) | 1 | 标识飞控、GPS、相机等 |
| 7-9 | MSG_ID(消息 ID) | 3 | v2.0 扩展到 24 位 |
| 10-... | PAYLOAD(负载) | LEN | 具体消息数据 |
| ... | CHECKSUM(校验和) | 2 | CRC16-CCITT |
| ... | SIGNATURE(签名) | 13 | 可选,INC_FLAGS 置位时存在 |
你想想看,一个完整的 MAVLink 消息,其实就是这么一串字节。飞控收到后,先找 STX,再解析长度、ID,最后校验 CRC。如果校验不过,直接丢掉。我在调试时经常遇到 CRC 错误,后来发现是波特率没对齐——115200 和 57600 混着用,数据全花了。
mavlink-inspector 或 mavproxy.py --map 可以实时查看消息流。如果看到大量 CRC 错误,先检查物理层——线缆、波特率、电平转换。
3.2 常用消息类型:HEARTBEAT、ATTITUDE、LOCAL_POSITION_NED
MAVLink 定义了上百种消息,但日常联调中,90% 的时间你只跟几个核心消息打交道。我挑三个最常用的,咱们逐个拆解。
3.2.1 HEARTBEAT(心跳)
心跳是 MAVLink 的“存在证明”。每个组件必须定期发送,告诉别人“我还活着”。地面站如果连续几秒收不到心跳,就会报连接断开。
HEARTBEAT 的消息 ID 是 0,负载结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| type | uint8_t | MAV_TYPE 枚举,如 2 表示固定翼 |
| autopilot | uint8_t | MAV_AUTOPILOT 枚举,如 3 表示 ArduPilot |
| base_mode | uint8_t | MAV_MODE_FLAG 位掩码,指示是否已武装、是否在引导等 |
| custom_mode | uint32_t | 自定义模式,ArduPilot 用此表示飞行模式 |
| system_status | uint8_t | MAV_STATE 枚举,如 4 表示已激活 |
举个例子,ArduPilot 在 Loiter 模式下,custom_mode 的值是 5。PX4 则不同,它的模式编码方式不一样。所以联调时,如果你看到地面站显示的模式不对,别慌——先查一下 custom_mode 的映射表。
custom_mode 编码不兼容。跨平台联调时,建议用 mavlink-router 做协议转换,或者在地面站端手动映射。
3.2.2 ATTITUDE(姿态)
ATTITUDE 消息(ID=30)告诉你飞机当前转到了什么角度。负载包含滚转、俯仰、偏航角,以及对应的角速度。这是飞控最核心的状态之一。
负载字段:
- roll(float):滚转角,单位弧度
- pitch(float):俯仰角,单位弧度
- yaw(float):偏航角,单位弧度
- rollspeed(float):滚转角速度,rad/s
- pitchspeed(float):俯仰角速度,rad/s
- yawspeed(float):偏航角速度,rad/s
我记得有一次在仿真环境里,飞机姿态一直乱跳。查了半天,发现是仿真时间戳没对齐,导致姿态数据被重复消费。后来我在接收端加了一个序列号检查,问题就解决了。
3.2.3 LOCAL_POSITION_NED(本地位置)
这个消息(ID=32)给出飞机在 NED 坐标系下的位置和速度。NED 就是北-东-地,原点通常是起飞点。
负载字段:
- x(float):北向位置,单位米
- y(float):东向位置,单位米
- z(float):向下位置,单位米(注意:向下为正)
- vx(float):北向速度,m/s
- vy(float):东向速度,m/s
- vz(float):向下速度,m/s
这里有个坑:z 是向下为正。也就是说,飞机高度 10 米时,z 的值是 -10。我第一次用这个数据做高度控制时,PID 参数死活调不对,就是因为符号搞反了。你想想看,一个正反馈的控制回路,飞机不炸才怪。
3.3 mavlink-router 配置:打通多设备通信
mavlink-router 是一个轻量级的 MAVLink 转发工具,专门用来连接飞控、地面站、机载电脑等多个端点。它的核心功能就是路由——把消息从一端送到另一端,同时支持 UDP、TCP、串口等多种传输方式。
为什么需要它?
- 飞控通常只有一个串口输出,但你可能同时需要连地面站(QGC)和机载电脑(ROS)。
- mavlink-router 可以同时转发到多个 UDP 端点,还能做消息过滤和带宽控制。
- 在仿真场景中,它能把 SITL 的 MAVLink 数据桥接到真实硬件上。
安装很简单:
git clone https://github.com/mavlink-router/mavlink-router.git
cd mavlink-router
meson setup build
ninja -C build
sudo ninja -C build install
配置文件通常放在 /etc/mavlink-router/main.conf,格式如下:
[General]
MavlinkDialect = ardupilotmega
TcpServerPort = 5760
ReportStats = true
[UdpEndpoint qgc]
Mode = Normal
Address = 192.168.1.100
Port = 14550
[UdpEndpoint companion]
Mode = Normal
Address = 127.0.0.1
Port = 14540
[SerialEndpoint flight_controller]
Device = /dev/ttyACM0
Baud = 115200
解释一下:
- General:全局设置,指定 MAVLink 方言(ardupilotmega 或 common),以及 TCP 监听端口。
- UdpEndpoint:定义 UDP 目标。你可以配多个,比如一个给 QGC,一个给 ROS 节点。
- SerialEndpoint:串口连接,指向飞控的物理端口。
启动命令:
mavlink-routerd -c /etc/mavlink-router/main.conf
我曾经在一个项目中,飞控通过串口连到树莓派,树莓派上跑 mavlink-router,同时转发到 QGC(WiFi)和 ROS(本地回环)。效果很稳定,延迟在 5ms 以内。唯一要注意的是,如果 UDP 目标断连,mavlink-router 会持续重试,不会阻塞其他通道。
--out 参数指向 mavlink-router 的 UDP 端口,而不是直接连地面站。这样你可以随时切换地面站,而不用重启仿真。
3.4 知识体系总览
下面这张图把 MAVLink 的核心逻辑串起来了。从消息结构到常用类型,再到路由转发,你可以看到数据是怎么从飞控一路流到地面站和机载电脑的。
从这张图可以看得很清楚:飞控通过串口把原始 MAVLink 数据发给 mavlink-router,router 再根据配置,把消息分发给地面站和机载电脑。地面站关心的是 HEARTBEAT 和 ATTITUDE,机载电脑可能更关注 LOCAL_POSITION_NED。router 本身不做协议转换,但它可以过滤消息,减少不必要的带宽占用。
嗯,MAVLink 这块内容其实挺多的,但只要你把消息结构、核心类型、路由转发这三块吃透了,后面的协同仿真联调就会顺畅很多。我当年踩过的那些坑,希望你能绕过去。