4、ArduPilot SITL 深度解析:架构、启动、脚本与 ROS2 桥接

好,咱们进入正题。这一章我打算把 ArduPilot SITL 的底裤扒开给你看。说白了,SITL 就是一套能在你电脑上完整运行的飞控软件。你不需要任何硬件,就能跑起一架无人机。我当年刚入行时,就是靠 SITL 熬过了无数个调试的夜晚。

4.1 ArduPilot 架构概览:别被代码吓到

很多人一打开 ArduPilot 的源码就懵了。其实它的核心架构非常清晰。我习惯把它分成三层:

  • 硬件抽象层(HAL):负责跟传感器、执行器打交道。SITL 就是 HAL 的一个特殊实现,它把真实的硬件接口换成了操作系统提供的网络和文件接口。
  • 核心库(AP_* 库):这是 ArduPilot 的灵魂。包括姿态估计(EKF)、导航控制、PID 控制器、通信协议(MAVLink)等。这些代码在 SITL 和真机上完全一样。
  • 飞行模式与任务逻辑:比如 Loiter、Auto、RTL 等模式。它们调用核心库的接口,实现具体的飞行行为。

关键点:SITL 和真机共享同一套核心算法。你仿真里调好的 PID,真机上基本不用大改。这就是“无缝迁移”的底气。

为了让你更直观地理解,我画了张图:

ArduPilot 软件架构(SITL 视角) 地面站 / ROS2 节点 MAVLink 通信层 核心库(AP_*) EKF 姿态估计 | PID 控制 | 导航 | 任务规划 硬件抽象层(HAL) SITL 仿真(本机) 真机硬件(Pixhawk 等) SITL 替换了硬件驱动 使用网络接口模拟传感器

你看,SITL 只是把最底层的 HAL 换掉了。上面的核心逻辑纹丝不动。这就是为什么你在仿真里验证过的代码,可以直接烧到飞控板上。

4.2 SITL 启动与配置:从零跑起来

启动 SITL 其实就一行命令。但我建议你先理解它的配置机制。

4.2.1 基本启动命令

# 最简单的启动方式(以四旋翼为例)
sim_vehicle.py -v ArduCopter --map --console

# 参数说明:
# -v:指定车型(ArduCopter, ArduPlane, ArduRover 等)
# --map:打开地图界面
# --console:打开控制台

我第一次跑这个命令时,看到地图上出现一架无人机,心里还是挺激动的。但别急着高兴,你得学会配置它。

4.2.2 关键配置文件

SITL 的配置主要通过参数文件实现。我个人习惯在启动时指定一个自定义参数文件:

sim_vehicle.py -v ArduCopter --map --console -P my_params.parm

这个 .parm 文件里可以写任何 ArduPilot 参数。比如:

参数名 值 说明
SYSID_THISMAV 1 飞控系统 ID
ARMING_CHECK 0 跳过解锁检查(仿真时常用)
SIM_SPEEDUP 10 仿真速度倍率(10倍速)
LOG_DISARMED 1 允许未解锁时记录日志

我的习惯:仿真时我一般把 ARMING_CHECK 设为 0,省得每次解锁都报错。但真机上一定要开,那是保命用的。

4.3 使用脚本控制仿真环境

光靠命令行启动还不够。真正的效率提升来自脚本化控制。我常用的场景是:批量跑测试、自动重置仿真、注入故障。

4.3.1 使用 MAVProxy 脚本

MAVProxy 是 ArduPilot 自带的命令行地面站。它支持脚本扩展:

# 在 MAVProxy 控制台中执行脚本
script run my_test_script.py

# my_test_script.py 示例
from pymavlink import mavutil

# 等待飞控就绪
master.wait_heartbeat()

# 解锁
master.arducopter_arm()

# 起飞到 10 米
master.mav.command_long_send(
    master.target_system, master.target_component,
    mavutil.mavlink.MAV_CMD_NAV_TAKEOFF,
    0, 0, 0, 0, 0, 0, 10
)

我曾经用这套脚本跑过一整夜的参数扫描。第二天早上来看结果,省了至少一周的手动测试时间。

4.3.2 使用 Python 直接控制 SITL

更灵活的方式是用 Python 脚本直接启动和控制 SITL:

import subprocess
import time
from pymavlink import mavutil

# 启动 SITL 进程
sitl_process = subprocess.Popen([
    'sim_vehicle.py', '-v', 'ArduCopter',
    '--no-mavproxy', '--no-extra-ports',
    '-I0'
])

time.sleep(5)  # 等待启动

# 连接 MAVLink
master = mavutil.mavlink_connection('tcp:127.0.0.1:5760')
master.wait_heartbeat()

# 执行任务...
print("SITL 已就绪,开始测试")

# 测试结束后关闭
sitl_process.terminate()

注意:启动 SITL 后一定要等它完全初始化。我见过有人没加 sleep 直接连,结果连接失败。你可以用 wait_heartbeat() 来确保飞控已响应。

4.4 与 ROS2 桥接:打通仿真与真机的最后一公里

这是本章的重头戏。ROS2 是目前机器人领域的标准中间件。把 ArduPilot 和 ROS2 连起来,意味着你可以用 ROS2 的生态(SLAM、规划、控制)来驱动你的无人机。

4.4.1 桥接方案对比

方案 原理 适用场景 我的评价
MAVROS MAVLink → ROS 1 桥接 ROS 1 老项目 稳定但已过时
MAVSDK + ROS2 MAVSDK 封装 MAVLink,提供高级 API 快速原型开发 上手快,但灵活性一般
mavlink-router + ROS2 轻量级路由,直接转发 MAVLink 消息 高性能、低延迟场景 我目前最推荐的方式

4.4.2 实战:使用 mavlink-router 桥接

我个人现在最常用的是 mavlink-router。它轻量、稳定,而且对 ROS2 支持很好。

步骤很简单:

  1. 启动 SITL(确保 MAVLink 端口开放)
  2. 启动 mavlink-router,把 SITL 的 MAVLink 转发到 ROS2 节点
  3. 启动你的 ROS2 节点,订阅/发布 MAVLink 消息
# 终端1:启动 SITL
sim_vehicle.py -v ArduCopter -I0 --out udp:127.0.0.1:14550

# 终端2:启动 mavlink-router
mavlink-routerd -e 127.0.0.1:14550 0.0.0.0:14550

# 终端3:启动 ROS2 桥接节点
ros2 run mavlink_ros2 mavlink_bridge --ros-args -p connection_url:=udp://127.0.0.1:14550

为什么推荐 mavlink-router? 因为它不修改 MAVLink 消息,延迟极低。我在项目中用它同时连接地面站、ROS2 节点和日志记录器,三路并行毫无压力。

4.4.3 验证桥接是否成功

跑起来之后,怎么确认连上了?我一般用这个命令:

# 查看 ROS2 话题列表
ros2 topic list

# 你应该能看到类似 /mavlink/from 和 /mavlink/to 的话题

# 查看飞控心跳
ros2 topic echo /mavlink/from | grep -c "HEARTBEAT"

如果能看到心跳包在持续输出,恭喜你,桥接成功了。这时候你就可以在 ROS2 里写节点,订阅飞控的姿态、位置信息,或者发送控制指令了。

避坑指南:我曾经在桥接时遇到端口冲突。SITL 默认使用 14550 端口,但如果你同时开了多个实例,记得用 -I 参数指定不同的实例编号,端口会自动偏移。

4.5 本章小结

这一章我们干了三件事:

  • 看懂了 ArduPilot 的架构,知道 SITL 和真机共享核心代码
  • 学会了启动和配置 SITL,包括参数文件和脚本控制
  • 打通了 SITL 到 ROS2 的桥接,为后续的复杂仿真打下基础