基于 Renode 的 ArduPilot 物理飞行测试:CI 集成、场景原理与本地复现完整指南
【免费下载链接】ardupilotArduPlane, ArduCopter, ArduRover, ArduSub source项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot
ArduPilot 仓库通过 Renode 仿真器在 CI 中运行未修改的真实 ChibiOS 固件完成整架飞机的物理飞行任务,其中 IMU、罗盘与气压计型号均按真实 CubeOrangePlus 硬件选配。本指南以 Tools/renode/tests/README.md 为主线,逐条讲解测试的获取、运行、校验与调试手段,并结合源码说明每条命令背后的实现机制,帮助你在本地复现 CI 级别的仿真飞行验证。
一、这套测试解决什么问题
嵌入式飞控开发最痛苦的一环是"改一行代码必须上真机验证",而 Renode 飞行测试提供了一条折中路径:把真实编译产物(ArduPlane / ArduCopter 固件)放进指令级仿真器运行,再通过独立的物理引擎让它在虚拟世界里完成起飞、巡航、降落。它既保留了接近硬件的行为(寄存器、DMA、外设时序),又能跑在普通 Linux 服务器上,因此可以作为 CI 的常驻回归手段。
从仓库结构看,测试基础设施集中在 Tools/renode/tests 目录下:
| 文件 | 职责 |
|---|---|
| fetch_renode.sh | 下载并校验 Renode 运行时 |
| test_physics_flight.py | 三个物理飞行场景:plane / copter / quadplane |
| test_mission.py | CubeOrange SITL-on-hardware 四航点任务 |
| test_physics_flight_helpers.py | 物理飞行公共工具函数 |
| test_mission_helpers.py | 任务测试公共工具函数 |
其余test_*.py | 面向底层模块(DFU、FAT 镜像、启动脚本、外设模型等)的单元测试 |
二、快速开始:两条命令跑起来
文档给出的标准入口在仓库根目录执行:
Tools/renode/tests/fetch_renode.sh Tools/renode/tests/test_physics_flight.py quadplane --renode build/renode/renode第一步把 Renode 可执行包下载到build/renode/,第二步以quadplane场景执行物理飞行测试。此外 CI 还会跑整个 Python 测试目录:
python3 -m pytest -q Tools/renode/tests2.1 未提供 Renode 时的行为
reset与 flash-model 类测试使用build/renode/renode,或读取环境变量RENODE指定的可执行文件;两者都不可用时这些测试会自动跳过(skip),不会报错中断。这一点在两个测试入口的 argparse 逻辑中也有体现:test_physics_flight.py与test_mission.py都会在未显式传--renode时探测build/renode/renode是否存在,存在才将其作为默认值。
2.2 不在 CI 中、但本地可用的场景
plane、copter两个物理场景以及 CubeOrange SITL-on-hardware 任务测试不在 CI 执行范围内,但官方保留供本地使用:
Tools/renode/tests/test_physics_flight.py plane --renode build/renode/renode Tools/renode/tests/test_physics_flight.py copter --renode build/renode/renode Tools/renode/tests/test_mission.py三、Renode 与 SVD 数据获取机制(含校验)
3.1 fetch_renode.sh:按架构下载并三重校验
fetch_renode.sh 从官方固件服务器(RENODE_PACKAGE_BASE_URL,默认指向https://firmware.ardupilot.org/Tools/Renode/)下载当前主机架构的最新包。脚本内部逻辑如下:
- 平台限制:仅支持 Linux(
uname -s非 Linux 直接报错退出);架构仅接受x86_64/amd64与aarch64/arm64,分别对应renode-linux-x86_64与renode-linux-aarch64两个制品名; - 清单校验:先下载
latest.json清单,用内嵌 Python 脚本校验schema_version、制品目标(architecture/platform/runtime_identifier)、文件名合法性(仅允许[A-Za-z0-9._+-]+\.tar\.gz)、SHA-256 格式(64 位十六进制)与源码修订号格式(40 位十六进制); - 包校验:下载后先比对
stat --format=%s的文件大小,再用sha256sum --check校验校验和; - 原子安装:先解压到临时目录并确认存在可执行的
renode,才整体mv到目标目录;目标已存在时直接报错退出,避免覆盖。
3.2 三个关键环境变量
| 环境变量 | 作用 |
|---|---|
RENODE_PACKAGE_BASE_URL | 更换 Renode 包镜像源 |
RENODE_SOURCE_REVISION | 要求特定的 40 位源码修订号,不匹配即失败 |
RENODE_PACKAGE_SHA256 | 额外要求包校验和与受信任值一致 |
CI 在 .github/workflows/test_renode.yml 中同时固定后两者:
- name: Fetch Renode env: RENODE_SOURCE_REVISION: 2a060779f4e2b87d1ae7238a041d858369818805 RENODE_PACKAGE_SHA256: 0f090820a222acbef7e80168c955feccf1759b69bc891b2ad821bacc19c4bcae run: Tools/renode/tests/fetch_renode.sh文档明确提醒:更换 Renode 包时,这两个值必须同步更新,否则供应链校验会直接失败。
3.3 run.py 的 SVD 下载与缓存
测试运行的核心启动器是 Tools/renode/run.py。它每次运行时读取编译产物hwdef.dat中的精确 MCU 型号,从RENODE_DATA_BASE_URL(默认官方固件服务器的data/SVD/目录)下载匹配的 SVD 文件,校验大小与 SHA-256 后缓存到~/.cache/ardupilot/renode/data/SVD/(默认目录)。两个相关环境变量:
RENODE_DATA_CACHE:更换缓存目录;RENODE_DATA_BASE_URL:更换 SVD 镜像源。
SVD(System View Description)提供了外设寄存器布局描述,是 Renode 精确建模 STM32 外设的基础数据,因此它和 Renode 本体一样需要校验与缓存。
四、物理飞行场景深入:plane / copter / quadplane
test_physics_flight.py 的入口main()只接受三个场景名(choices=('plane', 'copter', 'quadplane'))。下表汇总三者的配置差异(均来自源码中的run_plane/run_copter/run_quadplane函数):
| 项目 | plane | copter | quadplane(CI 主场景) |
|---|---|---|---|
| 板卡 | MatekH743 | KakuteF4 | CubeOrangePlus |
| 固件 | arduino plane(./waf plane) | arducopter(./waf copter) | arduino plane(./waf plane) |
| 默认参数 | MatekH743-plane.parm | KakuteF4-copter.parm | CubeOrangePlus-quadplane.parm |
| 物理模型 | plane | bfx | quadplane |
| 物理交换速率 | 400 Hz(PHYSICS_RATE_HZ) | 125 Hz(F405_PHYSICS_RATE_HZ) | 400 Hz |
| 起始经纬高 | 堪培拉 (-35.363261, 149.165230, 584.0, 353.0) | 同上 | 同上 |
| 外设 | u-blox GPS@SERIAL3、IST8310 罗盘@I2C1、MS4525 空速@I2C1 | u-blox GPS@SERIAL3、IST8310 罗盘@I2C0 | 三颗 IMU(icm42688_ext/icm20948_ext/icm20649)、u-blox GPS@SERIAL2、MS4525 空速@I2C1 |
4.1 运行时拓扑:Renode 主进程 + 物理 sidecar
每个场景都启动两个进程:
- 物理 sidecar:
build/sitl/tool/renode-physics(由./waf configure --board sitl && ./waf --targets tool/renode-physics构建),带--physics-port与--model参数; - Renode 主进程:由
run.py启动,通过--exec 'sysbus.physics Connect <port> "<model>" <lat> <lon> <alt> <hdg> <rate>'把仿真器与 sidecar 连接起来。
两者通过 localhost 上的锁步协议(lockstep protocol)交换带时间戳的执行器/传感器状态。源码中wait_for_sidecar()会轮询 sidecar 日志直到出现PHYSICS_PORT <port>标记,确认监听就绪后才启动 Renode。
4.2 传感器身份校验:让仿真对齐真机
quadplane 场景在起飞前会做一步关键校验——check_sensor_ids()通过 MAVLink 逐个读取INS_ACC_ID、INS_GYR_ID、COMPASS_DEV_ID、BARO1_DEVID等参数,与真实 CubeOrangePlus 板卡采集到的 ID 逐一比对(源码 test_physics_flight.py 中CUBEORANGEPLUS_SENSOR_IDS常量):
| 参数 | 期望值 | 对应真机传感器 |
|---|---|---|
| INS_ACC_ID / INS_GYR_ID | 3408930 | ICM42688(SPI4 CS4) |
| INS_ACC2_ID / INS_GYR2_ID | 2883874 | ICM20948(SPI4 CS1) |
| INS_ACC3_ID / INS_GYR3_ID | 3015690 | ICM20649(SPI1 CS4) |
| COMPASS_DEV_ID | 590114 | ICM20948 内置 AK09916 |
| BARO1_DEVID | 721442 | MS5611(SPI4 CS2) |
| BARO2_DEVID | 721674 | MS5611(SPI1 CS3) |
任何一项不匹配都会以sensor IDs differ from real hardware报错。这意味着仿真模型必须精确复现 SPI 总线、片选、设备类型编码等底层细节,测试才可能通过。
4.3 三个场景的飞行任务与判据
plane(MatekH743 固定翼):TAKEOFF 模式爬升到相对高度 38 m → LOITER 模式绕圈累计 ≥350°(用 VFR_HUD 航向增量累加判断)→ AUTOLAND 模式着陆并自动解锁。着陆要求距 home ≤125 m、相对高度 ≤3 m、地速 ≤2 m/s。任务完成后从虚拟 SD 卡镜像(state/sdcard.img)用fat_image.extract_files()提取唯一的*.BIN日志,再用 pymavlink 的DFReader_binary校验:
- 模式序列必须包含 TAKEOFF(13) → LOITER(12) → AUTOLAND(26) 子序列;
- 必须出现 3 个 IMU 实例(每实例 ≥500 条记录且全程健康)与 2 个气压计(各 ≥100 条、≥20 个不同压力读数,证明气压在动态变化);
- 空速健康且最大值 ≥20 m/s、结尾 ≤3 m/s;
- 最大高度 ≥38 m、离 home 最大距离 ≥50 m、最终相对高度 ±3 m 内;
- 姿态稳定(|pitch| ≤35°、|roll| ≤70°);日志无丢帧(DSF 最大值 0)、无 ERR/IREG 错误。
copter(KakuteF4 四旋翼):上传四航点任务(起飞 10 m + 4 个航点 + 降落,由common.mission_items()生成),切 AUTO 后强制解锁(FORCE_ARM_MAGIC = 2989)飞行。落回地面解锁后校验:SYS_STATUS中 gyro/accel/mag/气压/GPS 传感器全程健康;最大高度与最大距离均 ≥8 m;着陆精度(相对高度 ±1 m、距 home ≤3 m);姿态稳定(roll/pitch ≤35°)。
quadplane(CubeOrangePlus,CI 主场景):任务由quadplane_mission_items()生成——NAV_VTOL_TAKEOFF垂直起飞至 20 m,随后 3 个航点((120,0,40)、(120,100,40)、(-300,60,25)),再经一段长距离低空进场(DO_LAND_START)后NAV_VTOL_LAND垂直降落。通过EXTENDED_SYS_STATE的vtol_state与 STATUSTEXT 中的Transition started/Land descend started判断任务确实经历了多旋翼与固定翼两种飞行状态。判据:最大高度 ≥28 m、最大距离 ≥100 m、最大空速 ≥11 m/s;着陆精度(±1 m 高、≤10 m 距离);姿态上限 roll ≤60°、pitch ≤40°。
五、SITL-on-hardware 任务测试(test_mission.py)
test_mission.py 走的是另一条构建链路——它用 Tools/scripts/sitl-on-hardware/sitl-on-hw.py 构建 CubeOrange 的 SITL-on-hardware Copter 固件(--frame quad,默认参数 CubeOrange.parm),随后:
- 通过 MAVLink 连接(
tcp:127.0.0.1:<port>); - 校验
POWER_STATUS:Vcc 必须在 4900–5100 mV 之间且只带MAV_POWER_STATUS_USB_CONNECTED标志(验证仿真电源建模); - 设置
AUTO_OPTIONS=3、上传 4 航点任务、切 AUTO、强制解锁; - 等待起飞、巡航、降落并自动解锁;
- 通过
MAV_CMD_LOG_REQUEST_LIST/LOG_REQUEST_DATA走 MAVLink 协议下载 DataFlash 日志为flight.BIN; - 用
DFReader校验日志:POS 记录 ≥50 条、包含 AUTO 模式、最大高度/距离 ≥8 m、最终相对高度 ±1 m、无丢帧(DSF 最大值 0)。
默认超时 300 秒(--timeout),所有状态、Renode 输出与下载的flight.BIN保留在build/renode-test/下。该测试同样复用run.py,并设置XDG_CONFIG_HOME与TMPDIR指向 state 目录,把仿真产生的临时文件隔离在测试产物目录内。
六、开发期调试:复用构建与自定义固件
6.1 复用已有构建:--skip-build
物理飞行测试每次默认会重新构建固件与物理 sidecar。开发迭代时可用--skip-build跳过构建,直接复用build/<board>/bin/下已有的产物:
Tools/renode/tests/test_physics_flight.py quadplane --interactive --skip-build6.2 直接喂固件:--firmware
--firmware接受APJ、BIN、HEX、ELF四种格式(源码中FIRMWARE_SUFFIXES)。提供固件后会跳过板卡固件构建,但仍会构建物理 sidecar,除非同时使用--skip-build。从源码看,main()会在解析时校验文件存在与后缀合法性。
6.3 交互模式:--interactive
Tools/renode/tests/test_physics_flight.py quadplane --interactive --skip-build该模式不执行任务校验,而是把 Renode 与物理 sidecar 保持运行,供地面站(GCS)人工接管。命令会:
- 打印 MAVLink TCP 端点(
MAVLink: tcp:127.0.0.1:<port>); - 以实时速度(paced)运行——对比之下,自动化模式会附加
--unthrottled让仿真尽可能快; - 保持进程存活直到按下 Ctrl-C(源码中
wait_interactive()循环监听 KeyboardInterrupt)。
6.4 导出 USB 设备:--usb
--usb会把仿真飞控的 USB 控制器通过 USB/IP 导出到 Linux VHCI 控制器。首次使用需一次性安装 udev 规则:
sudo Tools/renode/usbip_attach.py --install-rules测试内部通过 Tools/renode/usbip_attach.py 以--port 3240启动辅助进程,附着状态记录在测试产物目录的usbip.log中(源码start_usb_helper())。这允许你在宿主机上以真实 USB 设备的方式枚举、上传固件、验证 USB 重枚举行为。
6.5 GDB 调试:--gdb
--gdb会打开一个运行 GDB 的 xterm,固件停在 reset 处,直到在 GDB 里输入continue才继续执行。两个要点:
- 必须使用 ELF 固件:APJ、BIN、HEX 镜像不含调试器需要的 ELF 符号,
main()中会显式校验--gdb与 ELF 的组合,非 ELF 直接报错; - 由 harness 构建固件时,
--gdb会自动给 waf configure 加上-g参数;而使用--skip-build时,所选 ELF 必须已经包含调试符号。
七、测试产物与常见问题排查
7.1 产物目录结构
所有测试默认输出到build/renode-test/下按场景与时间戳命名的子目录(如CubeOrangePlus-quadplane-20260913-...),包含:
| 文件/目录 | 内容 |
|---|---|
renode.log | Renode 进程输出 |
physics.log | 物理 sidecar 输出 |
flight.BIN | 从虚拟 SD 卡 / MAVLink 下载的 DataFlash 日志 |
flight.tlog | Copter / QuadPlane 场景的 MAVLink 遥测记录 |
usbip.log | USB/IP 附着状态(使用--usb时) |
state/ | 仿真状态目录(虚拟 flash、SD 卡镜像、XDG 配置等) |
--output-dir可指定自定义目录,且要求目标目录不存在(防止覆盖历史结果)。
7.2 常见失败信号
sensor IDs differ from real hardware:仿真外设模型与真机传感器身份不一致,检查 hwdef 的传感器声明与--imu选择;timed out waiting for ...:默认 600 秒超时(CI 中 quadplane 场景用--timeout 900)不足以完成任务,或仿真卡死——排查时先看renode.log尾部;Renode stopped with status N/physics sidecar stopped with status N:任一子进程异常退出都会导致测试失败,日志尾部(各 100 行)会附加在异常信息中;expected one DataFlash log, found N:plane 场景要求 SD 卡中恰好一份日志。
7.3 供应链与一致性提醒
文档最后强调了两个易错点:一是RENODE_SOURCE_REVISION与RENODE_PACKAGE_SHA256必须与 Renode 包同步更新(CI 中两者在 .github/workflows/test_renode.yml 显式固定);二是测试行为依赖build/renode/renode或RENODE,本地复现请先执行fetch_renode.sh确保版本与 CI 一致。
八、总结:从 CI 到本地的工作流
综合文档与源码,这套测试体系的完整工作流可以归纳为:
- 准备环境:
fetch_renode.sh(或自行指定RENODE/RENODE_PACKAGE_BASE_URL镜像); - 单元级回归:
python3 -m pytest -q Tools/renode/tests覆盖 DFU、FAT 镜像、启动脚本、数据获取等基础模块; - 物理级回归:
test_physics_flight.py quadplane复现 CI 的 CubeOrangePlus 垂直起降+固定翼巡航+垂直降落全流程,含传感器身份校验与飞行日志校验; - 任务级回归:
test_mission.py验证 SITL-on-hardware 构建链路上的航点任务与 MAVLink 日志下载; - 调试:
--interactive+ 地面站、--usb+ 真实 USB 枚举、--gdb+ ELF 符号级断点,覆盖从自动化回归到人工诊断的全过程。
通过把真实固件、精确外设模型与独立物理引擎三者缝合在一起,这套测试让飞行质量回归在 CI 中成为日常动作,也为本地开发提供了一个无需硬件的可信验证环境。
【免费下载链接】ardupilotArduPlane, ArduCopter, ArduRover, ArduSub source项目地址: https://gitcode.com/GitHub_Trending/ar/ardupilot
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考