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

张开发
2026/4/7 18:14:05 15 分钟阅读

分享文章

【Python 教程】无人机 MAVLink 通信完整实战:连接飞控、接收数据与发送指令
很多人在做无人机项目时卡在“能连上飞控但不知道如何稳定收发消息”。这篇文章基于 UAV-Stack-Knowledge-Base 中的通信模版代码带你从环境准备、飞控连接、遥测接收、控制命令发送到航点任务与日志下载快速搭起可运行的 MAVLink 通信程序。很多人刚接触无人机开发时第一道坎不是算法也不是控制器参数而是通信链路怎么打通。飞控能不能连上遥测数据怎么读起飞命令怎么发航点任务怎么传如果这些基础能力不稳定后面的导航、感知、行业应用基本都无从谈起。我自己在看很多无人机项目时发现一个很常见的问题资料很多但真正能“拿来就跑”的通信示例并不多。尤其是面对 MAVLink 这种消息协议初学者往往知道它很重要却不知道应该从哪一段代码开始下手。这篇文章我就结合开源项目UAV-Stack-Knowledge-Base无人机航拍与低空经济全栈知识库中的通信模版内容带大家一步一步完成一个典型的 MAVLink 通信流程连接飞控、接收遥测、发送控制指令、上传航点任务、下载飞控日志。如果你是无人机开发者、系统集成商或者正在做低空经济相关应用这篇内容可以直接作为你的入门模板。一、先搞清楚什么是 MAVLink为什么你一定会用到它MAVLink 是无人机领域非常常见的一种轻量级通信协议广泛用于地面站与飞控通信机载计算机与飞控通信仿真器与控制程序通信日志、状态、任务、命令的统一传输简单理解它就是无人机系统里的“通用语言”。我们在实际开发里最常见的几个使用场景是接收无人机的位置信息、姿态信息、电池信息下发解锁、起飞、切换模式、降落等控制命令上传航点任务让飞控自动执行航线导出飞行日志方便排障和复盘如果你后面准备做这些方向自动巡检航拍任务编排低空物流试验无人机集群调度边缘计算与飞控联动那么 MAVLink 几乎绕不过去。二、环境准备先把通信链路跑起来这一部分我们使用 Python 的pymavlink库因为它足够轻量调试效率也高特别适合快速验证。1. 安装依赖pip install pymavlink安装完成后就可以开始连接飞控了。2. 连接飞控的 3 种方式实际项目里我们常见三种连接方式串口连接适合直连数传、电台、USB 转串口设备UDP 连接适合连接仿真器、地面站转发数据TCP 连接适合某些网桥或远程调试环境示例代码如下from pymavlink import mavutil # 通过串口连接 master mavutil.mavlink_connection(/dev/ttyUSB0, baud57600) # 或通过 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系统 IDtarget_component组件 ID是否持续收到后续遥测消息4. 一个真实的调试案例我之前帮朋友排查一个“飞控连不上”的问题最后发现不是代码有问题而是串口设备名写错了Linux 下不是/dev/ttyUSB0而是/dev/ttyACM0波特率不匹配飞控端配置是115200程序写成了57600地面站已经占用了串口导致 Python 程序抢不到设备所以你如果连不上建议按这个顺序排查设备名是否正确波特率是否一致飞控是否真的在输出 MAVLink端口是否被别的程序占用是否收到心跳包三、接收遥测数据位置、姿态、电池状态怎么读连上飞控之后下一步通常就是收数。遥测数据是无人机应用层的基础输入很多业务逻辑都依赖这些字段。1. 持续监听 MAVLink 消息下面这段代码可以持续接收几类典型消息GLOBAL_POSITION_INT全球坐标位置ATTITUDE姿态角SYS_STATUS系统状态、电池信息# 持续接收消息 while True: msg master.recv_match(type[GLOBAL_POSITION_INT, ATTITUDE, SYS_STATUS], blockingTrue) 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纬度单位是1e7lon经度单位是1e7alt高度单位通常是毫米所以代码里需要做单位换算。2.2 姿态信息ATTITUDE中roll横滚角pitch俯仰角yaw偏航角这些值通常是弧度制。如果你准备做可视化展示往往还要转成角度。2.3 电池状态SYS_STATUS中voltage_battery毫伏current_battery百分之一安培battery_remaining剩余电量百分比3. 实战建议不要一上来就无限打印很多人调试时喜欢 while True 里疯狂print结果控制台刷屏真正异常信息反而被淹没。我更建议你这样做初期先只监听一种消息类型增加时间戳便于判断刷新频率用日志模块替代裸print对异常值做过滤比如纬度经度是否为 0比如你可以先从位置消息开始验证确认无误后再加姿态和电池数据。四、发送控制命令解锁、起飞、模式切换怎么做收得到消息只能说明链路通了。真正进入“控制”环节才是无人机通信的核心。这里我们用模板代码演示三个动作解锁无人机起飞到 10 米切换到 Loiter 模式1. 控制命令示例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) # 5Loiter # 降落 # print(降落...) # master.arducopter_disarm()2. 控制命令为什么“发了没反应”这是实战里非常常见的问题。我总结下来通常有这几种原因2.1 飞控安全检查没通过比如GPS 未定位电池异常未通过预检模式不允许解锁2.2 模式编号并不通用master.set_mode(5)这种写法虽然简单但不同飞控固件、不同机型配置下模式编号可能不同。如果你项目里要做通用化建议通过模式映射表或读取飞控支持模式来处理。2.3 命令发送后没有等待 ACK真实项目里不能只负责“发”还要负责“确认”。我建议在发送重要命令后增加 ACK 监听例如def wait_cmd_ack(master, timeout3): msg master.recv_match(typeCOMMAND_ACK, blockingTrue, timeouttimeout) if msg: print(f收到 ACK: command{msg.command}, result{msg.result}) return msg.result print(未收到 ACK) return None发送起飞命令后调用result wait_cmd_ack(master)这样你就能明确知道是命令没发出去还是飞控拒绝执行。3. 我的一个经验先在仿真里验证再上真机这类控制代码尤其是解锁、起飞、模式切换强烈建议先在仿真环境中跑通。比如 SITL、Gazebo 或配套模拟器先验证流程再连接真实飞控。因为很多新人第一次上真机测试时连模式含义、参数约束都没弄清楚直接解锁很容易把问题从“代码 bug”升级成“炸机风险”。五、航点任务上传让无人机自动执行路线如果你在做巡检、测绘、航拍航点任务基本就是绕不开的能力。模板代码里已经给了一个航点上传函数我们直接看核心实现。1. 航点上传代码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航点{i1}: {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. 这段代码能帮你完成什么它完成了一个最基础的任务上传流程清空历史任务逐个发送航点设置航点总数启动任务执行对于入门学习来说这个模板已经很有价值因为你能快速理解 MAVLink 任务机制的基本结构。3. 一个巡检场景的小案例假设我们要做一个园区电力巡检任务需要无人机按固定路线飞过三处目标点A 点变压器区域B 点输电接口区域C 点配电柜上空这时候我们只需要把三组(lat, lon, alt)坐标组织成列表就能让飞控按顺序执行。这一类能力在以下场景里都非常常见电力巡检农业测绘工地进度巡查河道与林区监测4. 实际工程里还要补哪些东西如果你准备把它写进正式项目我建议继续补充航点上传握手机制任务执行状态监听断点续传航点合法性校验任务完成后的返航或降落逻辑也就是说模板代码适合你快速入门和验证通信逻辑但真正落地行业项目还要在可靠性上继续加固。六、日志下载排障、复盘、定位问题的关键能力做无人机开发有时最怕的不是报错而是“看起来飞了但不知道哪里出问题”。这时候飞控日志就非常重要。1. 日志下载模板种子内容里给出了日志下载的前半部分我们可以先理解它的主要流程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], blockingTrue, timeout5) if msg: print(f日志{msg.id}: {msg.num_logs}条) break这段代码的核心思路是先向飞控请求日志列表接收日志元信息再按日志 ID 请求具体数据将日志内容写入文件保存2. 为什么日志能力很重要我在调试无人机系统时遇到过很多“现场看不出问题回去分析日志才发现根因”的情况比如某个时刻 GPS 信号突然变差电池电压在大负载下瞬间下跌模式切换失败其实是预检状态未满足飞控实际执行了不同于预期的任务命令这些信息往往只有日志里最完整。3. 日志功能适合哪些人如果你是以下角色我建议尽早把日志下载能力纳入工具链无人机应用开发者飞控调试工程师运维排障人员行业集成商售后团队因为很多问题靠实时 telemetry 只能看到表象靠日志才能看到全过程。七、把模板代码串起来一个适合初学者的学习路径如果你是第一次系统接触 MAVLink我建议你按这个顺序学习而不是一上来就写完整项目。第 1 步先连通目标能通过串口或 UDP 连接到飞控能收到心跳包你只要把连接代码跑通就已经完成了第一阶段。第 2 步再收数目标读取位置读取姿态读取电池状态建议先做一个简易监控台把这些值打印出来。第 3 步再发命令目标能解锁能切模式能发送起飞命令这里一定要先用仿真验证。第 4 步再上任务目标能上传航点能启动任务能监听任务执行状态这样你就从“通信测试”迈向“业务流程”。第 5 步最后补日志和异常处理目标出错时能定位超时后能重试命令失败能回退这一步做完你的通信模块就不再只是 demo而是一个可演进的工程基础件。八、结语通信能力是无人机系统的第一块地基无人机开发里很多人把注意力都放在导航、控制、视觉识别上但真正做项目时你会发现稳定的通信能力才是第一块地基。连不上、收不稳、命令没确认、任务传不全这些问题不解决越往后成本越高。这篇文章基于UAV-Stack-Knowledge-Base里的《04 通信模版代码》做了拆解适合你快速建立对 MAVLink 通信流程的整体认识。如果你最近正在补无人机开发基础或者想找一套覆盖协议、开发、行业应用的学习资料我建议你去看看这个开源项目。里面不只是这一篇通信模板还有更完整的无人机航拍与低空经济相关知识内容。你可以直接到 GitHub 搜索UAV-Stack-Knowledge-Base把它当成自己的学习索引和项目参考手册。

更多文章