搞无人机开发这些年,我最大的教训就是:千万别在没验证逻辑的情况下直接上真机。一次炸机,轻则损失几千块,重则把周围人都搭进去。所以现在我的工作习惯很固定——任何新的飞行任务、航点脚本、应急返航策略,先在一套纯软件模拟环境里反复跑几十遍,确认没问题再谈实飞。今天要聊的这套组合,就是我用了很久、也踩过不少坑之后沉淀下来的模拟飞行方案:DRONEKIT-SITL 负责在电脑上假装是一台完整的飞控,MAVProxy 作为命令行链路调试工具,QGroundControl 提供可视化地面站面板。三者配合,能在一台普通电脑上完整走完起飞、巡航、航点任务、异常返航这一整套飞行流程。
这套环境能帮你解决什么问题?一句话概括:不花钱、不冒险、不占场地,就能把飞控上要跑的逻辑先彻底跑熟。它特别适合三类人:正在学 MAVLink 协议和 ArduPilot 架构的入门玩家、需要为测绘或巡检任务编写自动飞行脚本的开发者、以及想测试地面站航线和参数调优功能的工程人员。下面我就把这套环境的搭建过程、链路原理、真实踩坑记录全部写下来,跟着操作,你也能在半小时内拥有一套属于自己的“空中模拟场”。
1. 模拟飞行环境搭建前,先看懂这条数据链路
很多人一上来就急着装软件,结果 SITL 起来了、QGC 也装了,但就是连不上,最后卡在“为什么没反应”上。根源在于没搞清楚这三个组件到底怎么协同工作。我先花点篇幅把架构讲透,后面你排查问题会省很多力气。
1.1 不飞真机,为什么还要这么认真地搭环境
先聊聊 SITL 到底是什么。SITL 全称 Software In The Loop,软件在环仿真。它不是一个普通的三维飞行游戏,也不是简单的“模拟器”,而是把 ArduPilot 或 PX4 这类真实飞控固件,编译成能在普通操作系统里直接运行的二进制程序。你通过 MAVLink 协议跟它通信时,它输出的心跳包、姿态数据、GPS 坐标、电机转速,全部是真实飞控代码处理出来的结果——只是传感器数据被虚拟模型代替了。
这意味着什么?你在真实飞机上跑的同一个 ArduCopter 固件,在电脑上跑的是同一条代码路径。你今天在 SITL 里验证过的模式切换、RC 覆盖、地理围栏逻辑,上真机后行为基本一致。差别只在于:真实世界有风、有磁场干扰、有 GPS 漂移,而 SITL 里这些都可以建模和注入。所以,用 SITL 做逻辑验证是可靠的,它是从“代码写完”到“真机起飞”之间最重要的一道关卡。
那 MAVProxy 和 QGroundControl 又是什么角色?MAVProxy 是一个基于命令行的 MAVLink 地面站,轻量、灵活,适合调试协议和链路。QGroundControl(习惯叫 QGC)是更完善的可视化地面站,负责航线规划、参数调参、仪表显示。它们不参与仿真计算,只负责跟 SITL 通信、监控和控制。简单说,SITL 是“被仿真的飞机”,MAVProxy 和 QGC 是“地面端”。这两者可以同时存在、同时连接,也可以只选其一。实际使用时,我通常让 MAVProxy 常驻,因为它诊断链路和注入故障指令非常方便。
1.2 三个组件如何串成一条完整的仿真链路
搞懂数据流,是这套环境最核心的部分。ArduPilot SITL 启动后,默认会在本机打开两个通信入口:一个是 TCP 端口 5760,专门给 MAVProxy 或 DroneKit 这类需要主动发起连接的客户端使用;另一个是 UDP 端口 14550,用于向地面站广播遥测数据。MAVProxy 连接 SITL 之后,又可以作为一个“数据转卖商”,把自己收到的 MAVLink 数据包再转发给其他端口,这样 QGC 就能通过 MAVProxy 间接拿到数据。
我常用的链路是这样的:
- SITL 监听
tcp:127.0.0.1:5760 - MAVProxy 用
--master tcp:127.0.0.1:5760连接 SITL - MAVProxy 通过
--out 127.0.0.1:14550将数据转发到 UDP 14550 - QGC 新建一个 UDP 连接,监听
127.0.0.1:14550,就能看到飞机了
还有一种更粗暴的方式:SITL 启动时直接加--out 127.0.0.1:14551,QGC 监听 14551,绕开 MAVProxy。这种方式适合只测地面站功能、不需要命令行干预的场景。但我个人建议不要省掉 MAVProxy,因为它在排查“心跳断了”“数据没转发”“消息类型不对”这些问题时,简直是神器。后面我在故障排查部分会详细举例子。
端口和数据流心里有数之后,安装和启动就变成了一件顺理成章的事。
2. 环境部署:从零跑通 SITL + MAVProxy + QGC
搭建这套环境,依赖的工具并不多:Python 3、pip、DroneKit-SITL、MAVProxy,以及一个 QGroundControl 桌面端。整个过程我按“安装依赖 → 启动 SITL → 连接 MAVProxy → 检查健康状态”四步来走。
2.1 依赖安装与版本选择
先说大前提:强烈建议在 Linux 环境下跑这套东西,尤其是 SITL。Windows 虽然也能跑,但经常会遇到路径含中文、防火墙拦截、超时连接这类莫名其妙的怪问题。如果手头只有 Windows 电脑,优先开启 WSL(Windows Subsystem for Linux),在 Ubuntu 环境里操作。我这边的操作全部基于 Ubuntu 22.04。
Python 环境推荐用虚拟环境,避免把系统搞乱。命令如下:
sudo apt update sudo apt install -y python3 python3-pip python3-venv mkdir ~/drone-sim && cd ~/drone-sim python3 -m venv venv source venv/bin/activate接着安装核心工具:
pip install dronekit dronekit-sitl mavproxy pymavlink这里有个版本选择的关键点:dronekit-sitl会自动下载并缓存对应版本的 ArduPilot 固件,默认机型是 ArduCopter。下载完成后,它会提示你 SITL 二进制放在哪个目录。如果你之前装过旧版,建议先pip uninstall dronekit-sitl再重装,否则可能启动旧固件。
MAVProxy 本身依赖 pymavlink,在某些新架构机器上可能出现编译失败。如果遇到这种情况,先装好编译工具链再重试:
sudo apt install -y build-essential python3-dev libxml2-dev libxslt1-devQGroundControl 的安装就简单多了,去官方仓库下载对应系统的 AppImage 或安装包。Linux 下给 AppImage 加上执行权限即可:
chmod +x QGroundControl.AppImage ./QGroundControl.AppImage2.2 启动 SITL 模拟器与 home 点设置
依赖装好后,第一步就是启动 SITL。我喜欢在启动时指定好 home 点坐标,因为 SITL 默认的 home 点固定在一个海外坐标,如果你的任务脚本里写死了相对位置,默认坐标会导致数据看起来很奇怪。
我常用的启动命令是:
dronekit-sitl copter-3.3 --home=31.2304,121.4737,10,0 --out=127.0.0.1:14550拆开解释一下:
copter-3.3:指定 ArduPilot 机型与版本。copter 表示多旋翼,3.3 是 ArduPilot 的经典稳定版本;也可以换成plane-3.3模拟固定翼,或rover-3.3模拟小车。--home:后跟四个参数,依次是纬度、经度、海拔高度、朝向角。我这里是上海某地的坐标,海拔 10 米,朝向 0 度。设置成你本地的位置,写任务脚本时更直观。--out:额外输出一个 UDP 遥测端口。SITL 默认已经有 5760 和 14550 了,这里再加一个 14551,留给 QGC 备用。
启动成功后,终端会滚动打印模拟飞控的启动日志,包括传感器校准、GPS 锁定、EKF 初始化等。等到日志稳定出现EKF2 IMU0 is using GPS之类的内容,说明飞控已经进入待命状态。此时再开一个终端窗口,进入虚拟环境,准备连接 MAVProxy。
2.3 MAVProxy 连接与链路健康检查
MAVProxy 连接 SITL,最核心的命令就这一条:
mavproxy.py --master=tcp:127.0.0.1:5760 --out=127.0.0.1:14550--master指定数据源,--out指定转发目标。如果你想同时把数据转发给 QGC 和另一个调试脚本,可以再加一个--out。MAVProxy 启动后,下方会有一个STABILIZE>之类的命令行提示符。此时在里面输入status,你会看到类似这样的输出:
APM: ArduCopter V3.3 GPS: GPS OK, fix 3 Vcc: 4.97 Rel: 0.00字段含义可以直接看字面:GPS 锁定状态、供电电压、相对高度。如果GPS显示fix 3而没有报错,说明 SITL 和 MAVProxy 之间的链路已经通了。
再用module list查看当前加载的模块,help查看可用命令。我习惯在 MAVProxy 里先执行几个基础命令验证控制能力:
STABILIZE> mode GUIDED STABILIZE> arm throttle STABILIZE> takeoff 10如果一切正常,飞控会从 STABILIZE 切到 GUIDED,电机解锁,并自动起飞到 10 米高度。我用这种方式确认 MAVProxy 已经拥有完整的“地面站权限”。注意,arm throttle后会看到Throttle armed的提示,直接起飞之前建议先取消:
STABILIZE> mode LAND等飞机落地后再进行下一步。这一步走通,就说明整个命令行链路已经 OK,剩下的就是让 QGC 进来看可视化界面了。
3. 让 QGroundControl 接管模拟飞行的可视化监控
命令行能干活,但对大多数人来说,千里之外看不见飞机,心里还是不踏实。QGC 的价值就是把整个仿真环境变成一个可视化的操控台:你能看到飞机在地图上的位置、姿态仪、空速地速、电池电压,还能在地图上画航线、传任务、切换飞行模式。下面讲 QGC 的连接、界面验证和航线规划。
3.1 添加连接:UDP 还是 TCP
QGC 连接 SITL 有两条路:直接连 SITL 的 UDP 14550,或者连 MAVProxy 转发出来的端口。两种方式我都在用,场景不同而已。
如果你只开了 SITL,没有启动 MAVProxy,可以在 QGC 右上角点开“应用设置”,进入“通信连接”,添加一个连接:
- 类型:UDP
- 监听端口:14550
保存并连接后,QGC 应该能在几秒内识别到 SITL 飞控,顶部状态灯变绿,并显示 ArduCopter V3.3 的固件版本。
如果你和我一样常驻 MAVProxy,则更简单。MAVProxy 已经通过--out=127.0.0.1:14550把数据转发出来了,QGC 同样监听 14550 就行。这里有个容易踩的坑:如果 MAVProxy 和 QGC 都在同一台电脑上,QGC 用 UDP 监听,MAVProxy 也是 UDP 转发,一般来说没问题;但 Windows 防火墙会把 UDP 广播拦截,导致 QGC 端怎么都收不到心跳。解决方法是给防火墙放行那几个 UDP 端口,或者干脆在 MAVProxy 里转发到一个 TCP 端口,QGC 用 TCP 客户端方式连接。我一般测试时直接放行 UDP,固定开发环境后就改成 TCP,稳定很多。
3.2 在 QGC 里验证传感器数据与姿态同步
连接成功后,先不要急着规划航线。观察一下界面右侧的仪表盘:
- 高度:SITL 默认会模拟气压计,起飞前应该显示 0 米或接近于 0。
- GPS 坐标:应该对应你通过
--home设置的坐标点,地图上的飞机图标稳稳停在那里。 - 姿态角:roll、pitch、yaw 在静止状态下应该全部归零或接近零。
- 电池电量:SITL 默认模拟一个满电电池,电压 12V 以上,不会变化。
这些看起来不起眼的数据,其实是检验链路是否完整最直接的指标。有一次我在新电脑上搭环境,QGC 看到飞机却怎么都不动,高度一直是 0,后来一查是 SITL 的 EKF 没有收敛,日志里提示磁力计异常,重启 SITL 重新校准就好了。
验证数据正常后,我通常会在 MAVProxy 里发送一条控制指令,看看 QGC 仪表是否实时变化:
STABILIZE> mode GUIDED STABILIZE> arm throttle STABILIZE> takeoff 5QGC 的仪表盘上,高度开始上升,姿态也随着模拟电机的动力输出发生变化。到这一步,可视化链路就算完全打通了。
3.3 任务规划与自动飞行验证
QGC 最大的价值之一就是航点任务规划。在 SITL 模拟环境里规划航线,跟在真机里操作流程一模一样。
点击左侧工具栏的“飞行计划”,在地图上飞机现在的位置附近点几个航点,设置每个航点的高度(比如 30 米),保存并上传任务。此时注意看 MAVProxy 终端的输出,会滚动显示Got MAVLink msg: MISSION_COUNT、MISSION_ITEM_INT之类的内容,说明任务已经通过 MAVLink 协议上传到了 SITL 飞控。
上传完成后,切到 QGC 的“飞行界面”,在模式选择里选择“自动”(AUTO),飞机就会依次飞向每个航点。地图上会实时绘制出飞机的飞行轨迹,你能看到它起飞、转弯、平飞、降落的全过程。这个过程中 QGC 就相当于一个实时的空管雷达,SITL 则相当于完整执行飞控逻辑的“虚拟真机”。
这里我特别建议新手在模拟环境里多试几次航线规划,因为很多真实飞行的操作习惯——比如起飞前检查 home 点、确认任务上传完整、保证航点高度合理——就是在这种反复演练里培养出来的。
4. 实战:完整跑一遍模拟自主航线与应急返回
环境通了,功能也看过了,接下来的实战部分是整套方法最有价值的地方。我会用一个真实场景来描述:编写一个 DroneKit 飞行脚本,自动起飞、按航点飞行、最后返回起飞点并降落;然后再人为注入 GPS 故障,验证应急返航逻辑。
4.1 起飞前检查清单
虽然只是模拟环境,但我还是习惯走一遍检查流程,目的是养成肌肉记忆,真机上才不会慌。我通常在启动任何飞行任务之前,在 MAVProxy 里执行这些检查:
STABILIZE> status STABILIZE> gps STABILIZE> battery STABILIZE> ekfstatus:确认飞控版本和心跳正常。gps:确认 GPS fix 正常,星数模拟值通常为 10 以上,HDOP 小于 1。battery:确认电压、电流检查无异常。ekf:确认姿态估计没有出现严重漂移。
同时确认 QGC 地图上的 home 点位置正确。这个 home 点很重要,因为自动返航(RTL)的降落点就是它,如果设错了,应急返航就会落错地方。
4.2 编写并执行 DroneKit 自动飞行脚本
DroneKit 是 MAVLink 的 Python SDK,可以让你用几行代码控制一整个飞行流程。下面是我用来测试 SITL 环境的一套最小脚本:
from dronekit import connect, VehicleMode import time connection_string = "tcp:127.0.0.1:5760" print("Connecting to vehicle on %s" % connection_string) vehicle = connect(connection_string, wait_ready=True) print("Global Location: %s" % vehicle.location.global_frame) # 设置 GUIDED 模式并解锁 vehicle.mode = VehicleMode("GUIDED") vehicle.armed = True while not vehicle.armed: print("Waiting for arming...") time.sleep(1) print("Armed!") # 起飞到 20 米 vehicle.simple_takeoff(20) while True: alt = vehicle.location.global_relative_frame.alt print("Altitude: %.1f" % alt) if alt >= 20 * 0.95: print("Reached target altitude") break time.sleep(1) # 向前飞行 10 秒 vehicle.airframe = None # 占位,保持脚本结构完整 vehicle.simple_goto(vehicle.location.global_frame, groundspeed=5) time.sleep(10) # 返航并降落 print("Returning to launch") vehicle.mode = VehicleMode("RTL") while True: if vehicle.location.global_relative_frame.alt <= 1: print("Landed") break time.sleep(1) vehicle.close() print("Test completed")注意,simple_goto的参数是一个LocationGlobalRelative对象,我这里为了保持示例简短,只用了当前点让飞机悬停,实际任务中可以构造一个偏移航点,比如:
from dronekit import LocationGlobalRelative point = LocationGlobalRelative(lat, lon, 30)跑这个脚本之前,确认你已经启动了 SITL 和 MAVProxy。然后执行:
python test_flight.py你会看到脚本逐行打印坐标和高度,同时 MAVProxy 和 QGC 同步显示飞机状态。如果脚本运行中出现Command Failed或超时,多半是 SITL 还在初始化 EKF,等几秒再重试即可。
4.3 注入故障:GPS 丢失与应急返航验证
模拟环境里最能锻炼人的地方,就是可以肆无忌惮地制造故障。我常用 MAVProxy 来禁掉 GPS,看看飞控和脚本会怎么反应。
过程是这样的:飞机先正常起飞到 20 米悬停,稳定后我在 MAVProxy 里执行:
STABILIZE> param set GPS_TYPE 0 STABILIZE> rebootGPS_TYPE 0表示禁用 GPS 模块,重启飞控后相当于模拟器完全没了 GPS 信号。这个时候观察 QGC 上的现象:GPS 图标变灰,位置不再更新,速度读数变为 0。此时如果依赖 GPS 的自动模式(比如 AUTO、RTL)还在运行,飞控通常会给出警告或切换到自稳,具体行为取决于参数配置。
那怎么应急处理?我在实际测试中,通常会先在脚本里监听 GPS 丢失事件,然后让飞机直接切换回 GUIDED 模式并执行原地降落:
def gps_callback(self, attr_name, value): if vehicle.gps_0.fix_type < 2: print("GPS lost!") vehicle.mode = VehicleMode("GUIDED") vehicle.mode = VehicleMode("LAND") vehicle.add_attribute_listener('gps_0', gps_callback)这样一套流程跑下来,你就会深刻理解 GPS 在飞控里有多重要,以及“失去定位后立刻原地降落”为什么是多数场景下最安全的兜底策略。这套验证在真机上代价极高,但在 SITL 里也就几十秒的事。
5. 常见故障排查:连接失败、数据异常、兼容性的处理办法
最后这块内容,我整理了自己在这套组合上踩过的大多数坑。按问题类型分了三类,每一类都直接给处理方法,遇到问题时可以当成速查表用。
5.1 连接失败类问题
QGC 一直显示“等待心跳”。优先检查 SITL 是否启动成功,日志是否有异常;再检查 MAVProxy 的status是否正常。如果 MAVProxy 正常但 QGC 还是看不到,就检查 UDP 端口。在 Linux 下可以用:
ss -ulpn | grep 14550确认 14550 端口有没有进程在监听。如果什么都没有,多半是 MAVProxy 的--out参数没生效,重启 MAVProxy 时加上--out=127.0.0.1:14550即可。
DroneKit 脚本 connect 直接卡死。大概率是 SITL 还没就绪。DroneKit 的wait_ready=True会等待 MAVLink 心跳和关键消息,如果 SITL 的 EKF 还没收敛,就会一直等下去。解决方法是稍等几秒再跑脚本,或者在connect()前加一个sleep(5)。
Windows 下 UPD 连接不稳定。这个我前面提到过,多办是防火墙拦截。除了放行端口,更稳妥的方式是让 MAVProxy 额外输出一个 TCP 端口:
STABILIZE> output add 127.0.0.1:14560然后在 QGC 里添加 TCP 连接,地址127.0.0.1、端口14560,立刻稳定。
5.2 数据异常类问题
高度一直跳、GPS 坐标乱飞。通常是因为 SITL 的传感器模型没有正确初始化。请确认启动 SITL 时没有--no-rc之类的限制参数,并且等待日志出现EKF2 IMU0 is using GPS之后再开始飞。
起飞后姿态乱摆甚至自翻。先检查电机方向是否一致。在 SITL 里没有物理电机,方向问题多半来自 SITL 的参数配置。可以重置参数:
STABILIZE> param set ARMING_CHECK 0 STABILIZE> reboot这只是模拟环境下的排查手段,真机千万别这样做。
位置数据静止但高度在变。这个是 GPS 和气压计数据不一致导致的。做一次完整的传感器校准:
STABILIZE> module load compass STABILIZE> compass cal5.3 版本兼容与性能问题
DroneKit 脚本提示某些属性不存在。不同 ArduPilot 版本号的 MavLink 消息类型略有差异。SITL 的copter-3.3和 DroneKit 的新版 SDK 配合时,有时候vehicle.ekf_ok这类属性会不兼容。优先把 dronekit 和 pymavlink 升级到最新,或者固定使用官方示例里兼容的版本组合。
电脑跑 SITL 很卡,QGC 操作有延迟。SITL 和 QGC 同机运行,对 CPU 有一定压力。推荐在启动 QGC 前先调低操作系统性能模式,并且在 SITL 启动时限制仿真频率:
dronekit-sitl copter-3.3 --speedup=5--speedup表示仿真速度倍数,默认是 1 倍速,改成 5 倍后任务执行速度会加快,但 QGC 显示会感觉比较“快进”。调试逻辑时用 1 倍,跑长航线验证用 5 倍,效率高很多。
多次启动 SITL 后端口被占用。这个我会直接杀掉残留进程再重启:
lsof -i :5760 kill -9 <PID>如果是在 Windows 上,换成netstat -ano | findstr 5760,然后taskkill /F /PID。
我个人在实际操作中的体会是:这套模拟环境最大的价值,不是让你“学会用 QGC”,而是让你在零成本、零风险的前提下,把飞行逻辑、应急策略、通信机制全部验证一遍。你可以在一个下午内反复飞一百次、拉掉十次 GPS、切换二十次模式,这在真机上几乎是不可想象的。搭建这个环境装的软件不多,真正的门槛在于理解 MAVLink 数据流和飞控的行为逻辑——而一旦你把 SITL + MAVProxy + QGC 这条链路吃透,再上手真机,心里会踏实很多。
最后再分享一个小技巧:把常用的 SITL 启动命令和 MAVProxy 连接命令写成一个 shell 脚本,一键启动整套环境,省去每次敲命令的时间。我自己的脚本里还会加一个gnome-terminal同时拉起 QGC,真正做到了“一条命令,模拟飞行”。这套流程用熟了之后,你会发现写无人机代码的迭代速度,能比之前快出一个量级。