【Python 教程】无人机 MAVLink 通信完整实战:连接飞控、接收数据与发送指令

很多人在做无人机项目时,卡在"能连上飞控,但不知道如何稳定收发消息"。这篇文章基于 UAV-Stack-Knowledge-Base 中的通信模版代码,带你从环境准备、飞控连接、遥测接收、控制命令发送到航点任务与日志下载,快速搭起可运行的 MAVLink 通信程序。

很多人刚接触无人机开发时,第一道坎不是算法,也不是控制器参数,而是通信链路怎么打通。飞控能不能连上?遥测数据怎么读?起飞命令怎么发?航点任务怎么传?如果这些基础能力不稳定,后面的导航、感知、行业应用基本都无从谈起。

我自己在看很多无人机项目时,发现一个很常见的问题:资料很多,但真正能"拿来就跑"的通信示例并不多。尤其是面对 MAVLink 这种消息协议,初学者往往知道它很重要,却不知道应该从哪一段代码开始下手。

这篇文章我就结合开源项目 UAV-Stack-Knowledge-Base(无人机航拍与低空经济全栈知识库) 中的通信模版内容,带大家一步一步完成一个典型的 MAVLink 通信流程:连接飞控、接收遥测、发送控制指令、上传航点任务、下载飞控日志。如果你是无人机开发者、系统集成商,或者正在做低空经济相关应用,这篇内容可以直接作为你的入门模板。


一、先搞清楚:什么是 MAVLink,为什么你一定会用到它

MAVLink 是无人机领域非常常见的一种轻量级通信协议,广泛用于:

  1. 地面站与飞控通信
  2. 机载计算机与飞控通信
  3. 仿真器与控制程序通信
  4. 日志、状态、任务、命令的统一传输

简单理解,它就是无人机系统里的"通用语言"。

我们在实际开发里,最常见的几个使用场景是:

  • 接收无人机的位置信息、姿态信息、电池信息
  • 下发解锁、起飞、切换模式、降落等控制命令
  • 上传航点任务,让飞控自动执行航线
  • 导出飞行日志,方便排障和复盘

如果你后面准备做这些方向:

  • 自动巡检
  • 航拍任务编排
  • 低空物流试验
  • 无人机集群调度
  • 边缘计算与飞控联动

那么 MAVLink 几乎绕不过去。


二、环境准备:先把通信链路跑起来

这一部分我们使用 Python 的 pymavlink 库,因为它足够轻量,调试效率也高,特别适合快速验证。

1. 安装依赖

bash 复制代码
pip install pymavlink

安装完成后,就可以开始连接飞控了。

2. 连接飞控的 3 种方式

实际项目里,我们常见三种连接方式:

  1. 串口连接:适合直连数传、电台、USB 转串口设备
  2. UDP 连接:适合连接仿真器、地面站转发数据
  3. TCP 连接:适合某些网桥或远程调试环境

示例代码如下:

python 复制代码
from pymavlink import mavutil

# 通过串口连接
master = mavutil.mavlink_connection('/dev/ttyUSB0', baud=57600)

# 或通过 UDP 连接
# master = mavutil.mavlink_connection('udpin:127.0.0.1:14550')

# 或通过 TCP 连接
# master = mavutil.mavlink_connection('tcp:127.0.0.1:5760')

# 等待心跳包
master.wait_heartbeat()
print(f"连接成功:系统={master.target_system}, 组件={master.target_component}")

3. 如何判断连接是否真的成功

很多初学者看到程序没报错,就以为连上了。其实不够。

我一般会用 心跳包 做第一层确认。只有 wait_heartbeat() 返回后,才能说明对端飞控确实在发 MAVLink 消息。

你可以重点观察以下内容:

  • target_system:系统 ID
  • target_component:组件 ID
  • 是否持续收到后续遥测消息

4. 一个真实的调试案例

我之前帮朋友排查一个"飞控连不上"的问题,最后发现不是代码有问题,而是:

  • 串口设备名写错了,Linux 下不是 /dev/ttyUSB0,而是 /dev/ttyACM0
  • 波特率不匹配,飞控端配置是 115200,程序写成了 57600
  • 地面站已经占用了串口,导致 Python 程序抢不到设备

所以你如果连不上,建议按这个顺序排查:

  1. 设备名是否正确
  2. 波特率是否一致
  3. 飞控是否真的在输出 MAVLink
  4. 端口是否被别的程序占用
  5. 是否收到心跳包

三、接收遥测数据:位置、姿态、电池状态怎么读

连上飞控之后,下一步通常就是收数。遥测数据是无人机应用层的基础输入,很多业务逻辑都依赖这些字段。

下面这段代码可以持续接收几类典型消息:

  • GLOBAL_POSITION_INT:全球坐标位置
  • ATTITUDE:姿态角
  • SYS_STATUS:系统状态、电池信息
python 复制代码
# 持续接收消息
while True:
    msg = master.recv_match(type=['GLOBAL_POSITION_INT', 'ATTITUDE', 'SYS_STATUS'], blocking=True)
    
    if msg.get_type() == 'GLOBAL_POSITION_INT':
        lat = msg.lat / 1e7
        lon = msg.lon / 1e7
        alt = msg.alt / 1000.0
        print(f"位置:{lat:.6f}, {lon:.6f}, 高度:{alt:.1f}m")
    
    elif msg.get_type() == 'ATTITUDE':
        roll = msg.roll
        pitch = msg.pitch
        yaw = msg.yaw
        print(f"姿态:横滚={roll:.2f}, 俯仰={pitch:.2f}, 偏航={yaw:.2f}")
    
    elif msg.get_type() == 'SYS_STATUS':
        battery_voltage = msg.voltage_battery / 1000.0
        battery_current = msg.current_battery / 100.0
        battery_remaining = msg.battery_remaining
        print(f"电池:{battery_voltage:.1f}V, {battery_current:.1f}A, {battery_remaining}%")

2. 这些字段分别代表什么

为了避免大家"代码能跑,但不知道值是什么意思",我把几个核心字段解释一下。

2.1 位置信息

GLOBAL_POSITION_INT 常见字段:

  • lat:纬度,单位是 1e7
  • lon:经度,单位是 1e7
  • alt:高度,单位通常是毫米

所以代码里需要做单位换算。

2.2 姿态信息

ATTITUDE 中:

  • roll:横滚角
  • pitch:俯仰角
  • yaw:偏航角

这些值通常是弧度制。如果你准备做可视化展示,往往还要转成角度。

2.3 电池状态

SYS_STATUS 中:

  • voltage_battery:毫伏
  • current_battery:百分之一安培
  • battery_remaining:剩余电量百分比

3. 实战建议:不要一上来就无限打印

很多人调试时喜欢 while True 里疯狂 print,结果控制台刷屏,真正异常信息反而被淹没。

我更建议你这样做:

  1. 初期先只监听一种消息类型
  2. 增加时间戳,便于判断刷新频率
  3. 用日志模块替代裸 print
  4. 对异常值做过滤,比如纬度经度是否为 0

比如你可以先从位置消息开始验证,确认无误后再加姿态和电池数据。


四、发送控制命令:解锁、起飞、模式切换怎么做

收得到消息,只能说明链路通了。真正进入"控制"环节,才是无人机通信的核心。

这里我们用模板代码演示三个动作:

  1. 解锁无人机
  2. 起飞到 10 米
  3. 切换到 Loiter 模式

1. 控制命令示例

python 复制代码
import time

# 解锁无人机
print("解锁...")
master.arducopter_arm()
time.sleep(1)

# 检查解锁状态
master.mav.command_ack_send(
    master.target_system,
    master.target_component,
    mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM,
    mavutil.mavlink.MAV_RESULT_ACCEPTED
)

# 起飞到 10 米高度
print("起飞...")
master.mav.command_long_send(
    master.target_system,
    master.target_component,
    mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
    0, 0, 0, 0, 0, 0, 0, 10  # 目标高度 10 米
)

# 等待起飞
time.sleep(5)

# 设置模式为 Loiter(定点)
print("切换到 Loiter 模式...")
master.set_mode(5)  # 5=Loiter

# 降落
# print("降落...")
# master.arducopter_disarm()

2. 控制命令为什么"发了没反应"

这是实战里非常常见的问题。我总结下来,通常有这几种原因:

2.1 飞控安全检查没通过

比如:

  • GPS 未定位
  • 电池异常
  • 未通过预检
  • 模式不允许解锁
2.2 模式编号并不通用

master.set_mode(5) 这种写法虽然简单,但不同飞控固件、不同机型配置下,模式编号可能不同。如果你项目里要做通用化,建议通过模式映射表或读取飞控支持模式来处理。

2.3 命令发送后没有等待 ACK

真实项目里,不能只负责"发",还要负责"确认"。

我建议在发送重要命令后,增加 ACK 监听,例如:

python 复制代码
def wait_cmd_ack(master, timeout=3):
    msg = master.recv_match(type='COMMAND_ACK', blocking=True, timeout=timeout)
    if msg:
        print(f"收到 ACK: command={msg.command}, result={msg.result}")
        return msg.result
    print("未收到 ACK")
    return None

发送起飞命令后调用:

python 复制代码
result = wait_cmd_ack(master)

这样你就能明确知道:是命令没发出去,还是飞控拒绝执行。

3. 我的一个经验:先在仿真里验证,再上真机

这类控制代码,尤其是解锁、起飞、模式切换,强烈建议先在仿真环境中跑通。比如 SITL、Gazebo 或配套模拟器先验证流程,再连接真实飞控。

因为很多新人第一次上真机测试时,连模式含义、参数约束都没弄清楚,直接解锁,很容易把问题从"代码 bug"升级成"炸机风险"。


五、航点任务上传:让无人机自动执行路线

如果你在做巡检、测绘、航拍,航点任务基本就是绕不开的能力。

模板代码里已经给了一个航点上传函数,我们直接看核心实现。

1. 航点上传代码

python 复制代码
def upload_mission(waypoints):
    """
    上传航点任务
    
    waypoints: [(lat, lon, alt), ...]
    """
    print("上传航点任务...")
    
    # 清除现有任务
    master.mav.mission_clear_all_send(
        master.target_system,
        master.target_component
    )
    
    # 上传航点
    for i, (lat, lon, alt) in enumerate(waypoints):
        master.mav.mission_item_int_send(
            master.target_system,
            master.target_component,
            i,  # 序号
            mavutil.mavlink.MAV_FRAME_GLOBAL_RELATIVE_ALT,
            mavutil.mavlink.MAV_CMD_NAV_WAYPOINT,
            2, 0, 0, 0, 0, 0,
            int(lat * 1e7),
            int(lon * 1e7),
            alt
        )
        print(f"航点{i+1}: {lat}, {lon}, {alt}m")
    
    # 设置总航点数
    master.mav.mission_count_send(
        master.target_system,
        master.target_component,
        len(waypoints)
    )
    
    # 开始任务
    print("开始任务...")
    master.mav.command_long_send(
        master.target_system,
        master.target_component,
        mavutil.mavlink.MAV_CMD_MISSION_START,
        0, 0, 0, 0, 0, 0, 0, 0
    )

# 示例:上传 3 个航点
waypoints = [
    (30.1234, 120.5678, 50),
    (30.1245, 120.5689, 50),
    (30.1256, 120.5700, 50),
]
upload_mission(waypoints)

2. 这段代码能帮你完成什么

它完成了一个最基础的任务上传流程:

  1. 清空历史任务
  2. 逐个发送航点
  3. 设置航点总数
  4. 启动任务执行

对于入门学习来说,这个模板已经很有价值,因为你能快速理解 MAVLink 任务机制的基本结构。

3. 一个巡检场景的小案例

假设我们要做一个园区电力巡检任务,需要无人机按固定路线飞过三处目标点:

  • A 点:变压器区域
  • B 点:输电接口区域
  • C 点:配电柜上空

这时候我们只需要把三组 (lat, lon, alt) 坐标组织成列表,就能让飞控按顺序执行。

这一类能力,在以下场景里都非常常见:

  • 电力巡检
  • 农业测绘
  • 工地进度巡查
  • 河道与林区监测

4. 实际工程里还要补哪些东西

如果你准备把它写进正式项目,我建议继续补充:

  • 航点上传握手机制
  • 任务执行状态监听
  • 断点续传
  • 航点合法性校验
  • 任务完成后的返航或降落逻辑

也就是说,模板代码适合你快速入门和验证通信逻辑,但真正落地行业项目,还要在可靠性上继续加固。


六、日志下载:排障、复盘、定位问题的关键能力

做无人机开发,有时最怕的不是报错,而是"看起来飞了,但不知道哪里出问题"。这时候飞控日志就非常重要。

1. 日志下载模板

种子内容里给出了日志下载的前半部分,我们可以先理解它的主要流程:

python 复制代码
def download_logs():
    """下载飞控日志"""
    print("请求日志列表...")
    
    master.mav.log_request_list_send(
        master.target_system,
        master.target_component,
        0,  # 起始 ID
        10  # 数量
    )
    
    # 接收日志列表
    while True:
        msg = master.recv_match(type=['LOG_ENTRY'], blocking=True, timeout=5)
        if msg:
            print(f"日志{msg.id}: {msg.num_logs}条")
            break

这段代码的核心思路是:

  1. 先向飞控请求日志列表
  2. 接收日志元信息
  3. 再按日志 ID 请求具体数据
  4. 将日志内容写入文件保存

2. 为什么日志能力很重要

我在调试无人机系统时,遇到过很多"现场看不出问题,回去分析日志才发现根因"的情况,比如:

  • 某个时刻 GPS 信号突然变差
  • 电池电压在大负载下瞬间下跌
  • 模式切换失败其实是预检状态未满足
  • 飞控实际执行了不同于预期的任务命令

这些信息往往只有日志里最完整。

3. 日志功能适合哪些人

如果你是以下角色,我建议尽早把日志下载能力纳入工具链:

  • 无人机应用开发者
  • 飞控调试工程师
  • 运维排障人员
  • 行业集成商售后团队

因为很多问题,靠实时 telemetry 只能看到表象,靠日志才能看到全过程。


七、把模板代码串起来:一个适合初学者的学习路径

如果你是第一次系统接触 MAVLink,我建议你按这个顺序学习,而不是一上来就写完整项目。

第 1 步:先连通

目标:

  • 能通过串口或 UDP 连接到飞控
  • 能收到心跳包

你只要把连接代码跑通,就已经完成了第一阶段。

第 2 步:再收数

目标:

  • 读取位置
  • 读取姿态
  • 读取电池状态

建议先做一个简易监控台,把这些值打印出来。

第 3 步:再发命令

目标:

  • 能解锁
  • 能切模式
  • 能发送起飞命令

这里一定要先用仿真验证。

第 4 步:再上任务

目标:

  • 能上传航点
  • 能启动任务
  • 能监听任务执行状态

这样你就从"通信测试"迈向"业务流程"。

第 5 步:最后补日志和异常处理

目标:

  • 出错时能定位
  • 超时后能重试
  • 命令失败能回退

这一步做完,你的通信模块就不再只是 demo,而是一个可演进的工程基础件。


八、结语:通信能力是无人机系统的第一块地基

无人机开发里,很多人把注意力都放在导航、控制、视觉识别上,但真正做项目时你会发现,稳定的通信能力才是第一块地基。连不上、收不稳、命令没确认、任务传不全,这些问题不解决,越往后成本越高。

这篇文章基于 UAV-Stack-Knowledge-Base 里的《04 通信模版代码》做了拆解,适合你快速建立对 MAVLink 通信流程的整体认识。如果你最近正在补无人机开发基础,或者想找一套覆盖协议、开发、行业应用的学习资料,我建议你去看看这个开源项目。

里面不只是这一篇通信模板,还有更完整的无人机航拍与低空经济相关知识内容。你可以直接到 GitHub 搜索 UAV-Stack-Knowledge-Base,把它当成自己的学习索引和项目参考手册。

相关推荐
小辉同志2 小时前
79. 单词搜索
开发语言·c++·leetcode·回溯
娇娇爱吃蕉蕉.2 小时前
类和对象的默认成员函数
c语言·开发语言·c++·算法
广州山泉婚姻2 小时前
Python 虚拟环境 venv 在 VSCode 中的正确用法
人工智能·python
小白学大数据2 小时前
Python requests + BeautifulSoup 爬取豆瓣电影图片
开发语言·python·beautifulsoup
她说..10 小时前
Java 对象相关高频面试题
java·开发语言·spring·java-ee
花酒锄作田10 小时前
Postgres - Listen/Notify构建轻量级发布订阅系统
python·postgresql
watson_pillow11 小时前
c++ 协程的初步理解
开发语言·c++
庞轩px11 小时前
深入理解 sleep() 与 wait():从基础到监视器队列
java·开发语言·线程··wait·sleep·监视器
Thomas.Sir11 小时前
第二章:LlamaIndex 的基本概念
人工智能·python·ai·llama·llamaindex