仿真跑得欢,上机就翻车——这句话在无人机和机器人圈子里流传了很多年。我自己也经历过几次“仿真里稳如老狗、实机上一飞冲天(然后炸机)”的尴尬阶段。后来把 HITL(Hardware-In-The-Loop,硬件在环)真正用起来,才算是把仿真和实机之间的鸿沟填上了一大半。今天这篇就专门聊聊,从 ROS2 到 PX4 的 HITL 全链路是怎么一步步搭起来、跑通、再到真机迁移的,以及我踩过的那些坑。
这套方案适合谁?就是想用 PX4 飞控做二次开发、但不想每次都拿真机试错的开发者,尤其是已经会用 Gazebo / jMAVSim 做纯软件仿真、正准备往真机上搬的朋友。HITL 的本质是让真实的飞控硬件(比如 Pixhawk 系列)跑真实固件,而把传感器数据、电机执行器交给仿真器模拟。这样你验证的不仅仅是算法,还有底层驱动、传感器融合、控制链路,甚至编译固件时出的那些莫名其妙的问题,都能在室内提前炸完。
1. 为什么非要折腾 HITL:SITL 和 HITL 的差别在哪里
1.1 SITL 的“虚假繁荣”
很多人一开始入门用的都是 SITL(Software-In-The-Loop),也就是纯软件仿真。PX4 固件直接在电脑上编译运行,Gazebo 或 jMAVSim 模拟一个飞机,MAVLink 消息在本地回环。这套方案的优点是快、免费、不用硬件,非常适合学 ROS2 消息机制、调 MAVSDK 代码、跑视觉算法。
但 SITL 有个致命问题:它验证的只是“算法逻辑”,而不是“嵌入式系统本身”。你用的是电脑的 CPU 和内存,跑的是 x86 架构的编译产物,而真机上飞控板是 ARM 架构,传感器是真实的 IMU / 磁力计 / 气压计,电机通过 ESC 和 PWM 信号控制。SITL 里一切正常,不代表固件烧进 Pixhawk 里就能跑。真实的调度延迟、传感器噪声、总线通信错误,SITL 全都没有。
1.2 HITL 到底模拟了什么
HITL 的做法是:真实飞控板接了电脑,固件是编好烧进板子的原生固件(ARM 架构),但 IMU、GPS、气压计这些传感器数据不是来自真实硬件,而是来自仿真器通过 MAVLink 发送过来的模拟值。同时,飞控输出的 PWM / 电机指令也不会真的驱动电机,而是通过 MAVLink 送回仿真器,换算成飞机模型在虚拟世界里的力和力矩。
所以 HITL 覆盖了从“真实固件”到“真实控制输出”的完整闭环,唯一缺的就是物理世界的空气动力学和传感器真实噪声。这也是为什么 HITL 被称为“最接近真机的地面测试”。
1.3 什么时候该用 HITL
我的建议是,以下场景优先考虑 HITL 而不是 SITL:
- 验证飞控固件层面的改动,比如修改了 PX4 内部模块、驱动、姿态估计器参数;
- 需要确认硬件初始化流程正常,比如传感器校准参数是否被正确读取;
- 联调 ROS2 节点和飞控的串口 / CAN 通信,排查波特率、帧格式、丢包问题;
- 做航点任务、Offboard 控制逻辑、故障保护逻辑的预演。
说句大实话,如果你只是写写上层的 ROS2 节点、不发愁底层驱动,SITL 完全够用。但一旦开始碰“固件和硬件交互”的边界,HITL 就是必须迈过去的一道坎。
2. 整套 HITL 环境搭建:版本选型和准备工作
2.1 版本组合的“黄金搭配”
HITL 环境最怕版本不匹配。ROS2、PX4、Gazebo、QGroundControl 各自版本之间有着微妙的依赖关系,用错版本经常会出现 MAVLink 解析错误、参数读不到、日志时间戳错乱等奇奇怪怪的问题。
我这里推荐的组合是经过实际验证的:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Ubuntu | 22.04 LTS | 稳定性最高,社区资源最多 |
| ROS2 | Humble Hawksbill | 22.04 的官方配套版本 |
| PX4-Autopilot | v1.14.3 | 1.14 系列最稳定的版本之一,文档全 |
| Gazebo | Gazebo 11(经典版) | PX4 官方工具链适配最好 |
| QGroundControl | 最新稳定版 | 地面站配置 HITL 参数的入口 |
| MAVSDK | v2.x | 后续 ROS2 上层开发用 |
注意,不要一上来就追最新版 PX4 或 ROS2 Jazzy。HITL 是一个非常吃生态的东西,老版本踩坑记录多、教程多,遇到问题更容易搜到答案。我在 1.15 和 1.16 上遇到过 Gazebo 插件编译不通过的问题,最后退回 1.14.3 一切安静。
2.2 安装 ROS2 Humble 的实操记录
ROS2 安装本身不复杂,但有几个细节要注意。先设置软件源,再装包。我用的是鱼香ROS的一键安装脚本,省去了很多手动配置的麻烦,但事后建议还是自己核对一下环境变量。
# 设置编码 sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8 # 添加 ROS2 源 sudo apt install software-properties-common curl sudo add-apt-repository universe sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update sudo apt install ros-humble-desktop python3-argcomplete装完之后在~/.bashrc里加上 source 命令,然后记得装 colcon,这是 ROS2 的编译工具:
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> ~/.bashrc source ~/.bashrc sudo apt install python3-colcon-common-extensions2.3 PX4 源码和 Gazebo 的编译
PX4 官方提供了Tools/setup/ubuntu.sh一键安装脚本,它会帮你装好所有编译依赖和 Gazebo。但我的经验是,这个脚本偶尔会因为网络问题在某个包上卡住。建议分段执行,先装基础依赖再单独装 Gazebo。
git clone https://github.com/PX4/PX4-Autopilot.git --recursive -b v1.14.3 cd PX4-Autopilot git submodule update --init --recursive make px4_sitl gazebo-classic如果make px4_sitl gazebo-classic能成功启动一个仿真环境,说明工具链没问题。这个时候不要急着跑 HITL,先在 SITL 下验证一遍 Gazebo 和 QGC 的通信,确认地面站能收到消息,再做硬件接入。
提示:PX4 编译时特别吃内存,建议至少 8GB 内存,否则容易 OOM 崩溃。编译过程中出现
c++: internal compiler error: Killed,一般是内存不够,可以临时加 swap。
3. HITL 硬件接入和参数配置全流程
3.1 飞控硬件准备:固件版本要匹配
接入 HITL 之前,先确认你的飞控板是 PX4 官方支持的型号。我用的是 Pixhawk 6C 和 Holybro Durandal,两者都可以直接刷 PX4 原生固件。上一代的 Pixhawk 4 也支持,但需要注意端口的映射差异。
我建议在 HITL 之前先给飞控刷一遍真实固件,然后接上 QGC 做一次完整的传感器校准。这一步虽然和仿真无关,但能确保飞控板本身是健康的。如果这一步就出现传感器读数异常,排除硬件问题后再说 HITL。
飞控通过 USB 线连接到电脑,正常情况下 QGC 会识别到设备。命令行里可以用ls /dev/ttyACM*或ls /dev/ttyUSB*确认设备节点。在 Linux 下如果遇到权限问题,需要把用户加入dialout组:
sudo usermod -a -G dialout $USER3.2 HITL 参数设置:关键参数逐一说明
PX4 的 HITL 模式切换是通过参数控制的。连接飞控到 QGC,进入参数界面,需要修改以下几项:
SYS_HITL是最核心的参数。将这个参数设为 1,飞控就会进入 HITL 模式,传感器数据不再从真实的 IMU / GPS 读取,而是从 MAVLink 的仿真数据通道获取。改完这个参数,飞控会要求重启。
MAV_USEHILGPS建议设为 1,表示接收来自 HITL 仿真器的 GPS 数据。如果不设,飞控可能仍尝试读取真实 GPS,导致室内无 GPS 信号时定位丢失。
MAV_HIL_STATE相关的参数,在 PX4 1.14 里已经不是必须手动配置的了,仿真器会自动通过 MAVLink 发送 HIL_STATE 消息。但如果你发现姿态角没有反应,需要检查这条消息是否在传输。
SYS_AUTOSTART要和机型匹配。比如你要模拟四旋翼,就设为 4001(Quadrotor X),对应你未来真机的机型。机型参数不对,HITL 里飞机可能直接起不来,或者电机输出异常。
然后需要设置仿真机架的类型。在 jMAVSim 里,可以通过以下命令加载特定的机架模型:
make px4_hitl jmavsim这个命令会用默认的四旋翼模型启动 jMAVSim,同时飞控进入 HITL 模式。如果是 Gazebo:
make px4_hitl gazebo-classic其实 HITL 的核心流程是:QGC 先把 HITL 模式参数写入飞控,然后启动仿真器,仿真器通过 MAVLink 与飞控建立双向通信。我在实际中遇到过一个问题:QGC 本身也会占用 MAVLink 连接,导致仿真器和 QGC 抢串口。解决办法是让 QGC 通过 UDP 连接仿真器的 14550 端口,而仿真器通过串口 / USB 连接飞控。
3.3 MAVLink 通信链路的连接方式
PX4 的 HITL 通信链路主要有两种接法,我用一个表格来对比:
| 连接方式 | 飞控连接 | 仿真器连接 | 适用场景 |
|---|---|---|---|
| 串口直连 | USB / UART | QGC 转发 UDP 14550 | 快速验证,推荐新手 |
| MAVLink Router | USB / UART | TCP / UDP 定向转发 | 多机、复杂拓扑,推荐进阶 |
第一种方式是 QGC 同时作为 HITL 的“总调度”,它连接到飞控后,再把 MAVLink 转发给本机的 jMAVSim / Gazebo。PX4 官方在make px4_hitl的脚本里已经自动处理了这个转发关系,你只需要确保 QGC 在运行、并识别到了飞控。
第二种方式是使用 MAVLink Router 或 mavp2p 这类工具,建立一条专门的转发通道,避免 QGC 参与数据链路的“搬运”。我在多机 HITL 仿真里用 mavp2p 比较多,它延迟更低,配置也直观:
# mavp2p 配置示例 protocol: udp listen: 0.0.0.0:14550 # 飞控的串口路径 endpoints: - name: fc protocol: serial path: /dev/ttyACM0 baudrate: 1152003.4 启动顺序和验证流程
这一步是关键,启动顺序错了,很容易出现“地面站能看到飞机,但姿态不动”的情况。我的标准启动顺序是:
- 先连接飞控到电脑,打开 QGC,确认飞控状态正常;
- 在 QGC 参数界面设置
SYS_HITL = 1,重启飞控; - 启动仿真器(
make px4_hitl gazebo-classic或 jMAVSim); - 观察 QGC 的虚拟仪表盘,如果姿态、高度、位置数据开始随仿真模型运动,说明 MAVLink 链路已通;
- 在 QGC 里尝试发送起飞指令,观察仿真模型是否响应。
验证的时候,我最常用的方法是手动摆动飞机模型(虚拟摇杆),看 QGC 的姿态指示器是否同步变化。如果姿态不动,先看终端有没有 MAVLink 报错,再用mavlink status检查消息流。这里还有一个比较容易忽略的点:如果启动 jMAVSim 后它自动加载了一个默认模型,而这个模型的机型参数和SYS_AUTOSTART不一致,飞控会拒绝解锁。此时需要手动指定模型参数。
4. ROS2 层接入:让 HITL 不再是“孤岛”
4.1 ROS2 与 PX4 通信的两种主流方式
HITL 跑通之后,接下来就是把 ROS2 纳入整个链路,这也是大多数人做 HITL 的最终目的:在仿真环境里调试自己的 ROS2 算法节点。
ROS2 和 PX4 之间没有原生的 MAVLink 接口,需要借助中间层。主流方案有两种:
第一种是 MAVSDK,它提供了 Python 和 C++ 库,通过 MAVLink 和飞控通信。ROS2 节点通过 rclpy 或 rclcpp 调用 MAVSDK,间接控制飞控。优点是抽象程度高、API 友好,适合快速开发;缺点是多了一层封装,调试底层问题时不够直观。
第二种是 PX4-ROS2 接口,官方在px4_ros_com仓库里提供了针对 Offboard 模式的 ROS2 消息定义和接口,可以直接在 ROS2 节点里订阅飞控发布的VehicleOdometry、VehicleAttitude等消息,也可以发送TrajectorySetpoint控制指令。这个方式更“原生”,也更贴近 PX4 的内部机制。
我这里重点说第二种,因为 HITL 下调试 Offboard 控制是最常见的需求。
4.2 px4_ros_com 和 px4_msgs 的编译
首先拉取并编译两个核心包:
mkdir -p ~/ws_offboard/src && cd ~/ws_offboard/src git clone https://github.com/PX4/px4_msgs.git git clone https://github.com/PX4/px4_ros_com.git cd ~/ws_offboard colcon build source install/setup.bash编译完成后,在终端启动 MicroXRCEAgent,这是 PX4 和 ROS2 DDS 通信的桥接程序:
MicroXRCEAgent serial --dev /dev/ttyACM0 -b 921600注意,如果飞控是通过 USB 连接的,默认的 UART 波特率有时会匹配不上。PX4 1.14 默认的SER_TEL2_BAUD或 USB 的波特率通常在make px4_hitl时已经设置好了,但如果 MicroXRCEAgent 启动后没有任何数据上来,先确认飞控的XRCE_*参数。
在实际项目里,我遇到过 MicroXRCEAgent 一直报
No data received,排查了一天发现是飞控的UXRCE_DDS_CFG参数没有设为 1(Telemetry 2 端口)。在 HITL 模式下,这个参数有时不会和固件默认值保持一致。
4.3 Offboard 控制和机载电脑仿真链路验证
等 MicroXRCEAgent 正常输出消息后,可以用ros2 topic list看到大量/fmu/out/*类型的主题,比如/fmu/out/vehicle_attitude、/fmu/out/vehicle_odometry。这说明 ROS2 已经能收到飞控的估计状态。
接下来测试上行控制。在 px4_ros_com 里自带一个 offboard 控制示例,我简单跑一下手动控制模式验证链路:
ros2 run px4_ros_com offboard_control如果能看到飞控的模式切换到 Offboard,并且仿真模型开始执行位置指令,说明 ROS2 -> PX4 -> 仿真器 -> 模型 -> 飞控估计的完整闭环已经打通。这个时候,你完全可以在 HITL 下先跑一遍自己的路径规划算法、避障节点、视觉里程计融合,再决定要不要上真机。
4.4 仿真时钟和 rviz2 的联动
在 ROS2 里做机器人开发,很多人习惯开 rviz2 看可视化。HITL 模式下,PX4 的位姿数据是从话题里来的,直接在 rviz2 里添加一个RobotModel或Odometry显示,就能看到仿真飞行器的实时位姿。但有个细节要注意:ROS2 节点的使用时间基准要和仿真时间同步。
如果在 rviz2 里看到模型“飘”或者轨迹不连续,大概率是 TF 坐标系问题。PX4 的位姿话题本身带的是机体坐标系到 NED(北东地)系的变换,你需要发布对应的 TF 关系,rviz2 才能正确显示。也可以直接用px4_ros_com里自带的vehicle_odometry来发布 TF,省去自己写变换的麻烦。
5. HITL 常见问题排查和实机迁移前最后的检查
5.1 高频问题速查表
下面这张表是我做 HITL 部署时反复遇到的高频问题,按出现频率排序:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| QGC 能看到飞机但姿态不动 | HITL 参数没生效或 MAVLink 路由错误 | 确认 SYS_HITL=1,重启飞控,检查终端输出 |
| jMAVSim 启动后黑屏闪退 | 显卡驱动 / OpenGL 问题 | 尝试软件渲染,export LIBGL_ALWAYS_SOFTWARE=1 |
| 解锁失败,提示 Preflight Fail | 传感器校准数据无效 | 在 HITL 模式下手动跳过预检,或重新校准 |
| MicroXRCEAgent 无数据 | UXRCE_DDS_CFG 参数未开启 | 设为 1,重启飞控 |
| 仿真飞行器乱飞、数值发散 | Gazebo 模型或 EKF 参数异常 | 检查 SYS_AUTOSTART 机型,重置 EKF2 参数 |
| USB 连接不稳定导致掉线 | 供电不足或线材质量差 | 换一根短的高质量 USB 线,尽量用独立供电的 USB Hub |
5.2 实机迁移时的“最后一公里”
HITL 跑通不代表真机一定安全,但至少能过滤掉大部分低级问题。从 HITL 向实机迁移时,我有几个固定的检查项:
第一,参数导出与对比。把 HITL 模式下调好的参数导出,烧完真机固件后再导入一遍,并逐项确认没有仿真专用参数残留。比如SYS_HITL必须改回 0,MAV_USEHILGPS必须改回 0。
第二,传感器方向检查。HITL 里传感器方向是仿真器给的,不存在“装反”问题。但真机上,飞控的安装方向、GPS 的朝向、磁力计的干扰源,每一项都可能让飞机原地打转。所以上真机前,在完全安全的环境下(比如室内系留或桨叶拆卸),先做一次手动模式的小油门测试,确认电机方向和混控输出无误。
第三,安全机制的检查。地理围栏、返航高度、失控保护、电量保护这些参数,HITL 里只验证了逻辑,但实机上要重新确认阈值合理。电池电压报警设太低了,可能在飞行中直接触发紧急降落。
第四,EKF 和传感器融合的差异。HITL 里即使你模拟了各种噪声,也无法完全复现真实 IMU 的温漂、磁干扰、振动。实机上第一步就是把EKF2_*参数按飞控推荐值重新梳理一遍,尤其是加速度计和陀螺仪的噪声参数,需要使用 QGC 的振动检测功能实际测量。
5.3 一个值得留意的 PX4 卡尔曼滤波参数细节
讲一个我在 HITL 和实机上花了很多时间研究的点:PX4 的 EKF2(扩展卡尔曼滤波器)参数。HITL 下我经常觉得 EKF2 收敛速度太慢,飞机起飞时会有几秒钟的位置漂移,当时想着到真机上应该也一样。结果真机一测,反而更差,因为真实的 GPS 信号在室内根本收不到,EKF2 直接在 GNSS 丢星的边缘疯狂试探。
后来我意识到,HITL 里 GPS 是理想模型,没有丢星、没有多路径效应,所以 EKF2 的参数不需要激进。而真机上在复杂的电磁环境里,反而需要调高 EKF2 对加速度计的信任度、降低 GPS 速度噪声。这些参数如果不做针对性修改,实机首飞很容易出现定位跳变。所以我的建议是:HITL 只用来验证“逻辑对不对”,不要用它来精调“传感器参数”。
注意:飞控的传感器参数本质上是硬件相关的,不是算法逻辑相关的。仿真里调出来的最佳参数,换一块板子就可能不适用。
5.4 实机首飞安全检查清单
最后分享一份我每次实机首飞前都会走一遍的清单,内容很朴素,但每条都来自于真实教训:
- 桨叶完好、螺丝上紧,机身重心与飞控中心基本重合;
- 所有电机转向正确,桨叶安装方向与电机转向匹配;
- 遥控器模式正确,通道映射与飞控设置一致,解锁开关处于安全位置;
- GPS 固定后检查定位精度,HDOP 小于 1.5 再做起飞准备;
- 在 QGC 里确认所有传感器校准完成,无报错;
- 设置好地理围栏和返航高度,测试一下失控保护触发;
- 先小油门测试电机响应,解锁后 1 米悬停 10 秒,观察姿态稳定性和振动水平;
- 首飞一定要有专人看守紧急开关,随时准备切回手动模式。
6. 几点实用技巧总结
在实际操作中,我还有几个很小的技巧想分享。HITL 里最容易被忽略的就是日志分析。PX4 的 ulog 文件在 HITL 模式同样会记录,QGC 的 Analyze 视图可以看到完整的振动、GPS 健康度、EKF 状态变化。我在排查一次离线模式失效问题时,就是通过日志发现 EKF 在仿真过程中一度进入“惯性静止”状态,才定位到了参数设置的问题。
另外,在 HITL 跑 ROS2 节点时,我建议把RMW_IMPLEMENTATION环境变量固定下来。如果系统里同时装了 Fast DDS 和 Cyclone DDS,MicroXRCEAgent 和 ROS2 节点使用不同的 DDS 实现时,会出现互相发现不了的问题。统一用一个实现,能少很多莫名其妙的麻烦。
最后还有一点心得:HITL 不是 SITL 的替代品,也不完全等于真机。它是两者之间最重要的“炼狱”环节。每次从 HITL 过渡到实机,我都会把“HITL 能不能覆盖这个改动”作为第一评判标准。能覆盖,先在 HITL 里跑一遍;不能覆盖,就老老实实做真机小油门验证。流程虽然繁琐,但正是这些繁琐,让那些“仿真里稳如老狗、实机上一飞冲天”的翻车事故,再也没在我这里发生过。