4、SITL仿真基础:工作原理、启动脚本、时间与日志
这一章,我们正式进入SITL仿真的核心地带。说实话,很多初学者把SITL当成一个黑盒子,敲个命令飞起来就完事了。但作为工程师,你得知道它背后怎么转的。我在项目中遇到过好几次,仿真跑得挺好,一上真机就炸,最后发现是SITL的时间模型没搞对。所以,咱们得把底层的逻辑理清楚。
4.1 SITL到底是怎么工作的?
SITL的全称是Software In The Loop。说白了,就是把飞控的代码,直接在电脑上编译成一个可执行程序。它不依赖任何真实的硬件。
你想想看,真实的飞控板上有STM32芯片,有传感器(IMU、GPS、气压计),有PWM输出。SITL干了什么事?它用你电脑的CPU模拟了那个STM32,用操作系统的时间模拟了传感器数据流,用UDP网络包模拟了PWM信号和遥控器输入。
核心流程其实就三步:
- 编译成PC程序:把原本跑在ARM架构上的PX4/ArduPilot代码,用本地编译器(gcc/clang)编译成x86架构的可执行文件。
- 模拟传感器驱动:代码里原本读取I2C总线获取IMU数据的地方,被替换成了从UDP端口读取仿真数据。
- 对接仿真引擎:通过MAVLink协议,把飞控算出来的电机控制量(比如油门值)发给Gazebo或JSBSim,然后仿真引擎把新的姿态和位置反馈回来。
一句话总结:SITL就是一个“硬件抽象层”。它让飞控代码以为自己在操作真实的传感器和电机,实际上是在跟操作系统里的网络端口和文件描述符打交道。
下面这张图,是我自己画的一个SITL内部数据流,你看一眼就明白了。
4.2 启动脚本分析:到底执行了什么?
我们平时用make px4_sitl gazebo或者直接运行./ArduCopter.elf,背后其实有一整套脚本在干活。我建议你花10分钟读一下启动日志,里面信息量很大。
启动脚本主要干了这几件事:
- 设置环境变量:比如
PX4_SIM_MODEL定义了用哪款无人机模型(iris、plane、rover)。 - 创建虚拟传感器设备:在
/dev下创建一些虚拟文件,或者直接通过UDP端口模拟。 - 初始化参数系统:加载默认的
.param文件,把飞控参数设置好。 - 启动MAVLink通道:默认在UDP端口14550上监听地面站连接。
- 等待仿真引擎握手:飞控会一直尝试连接仿真引擎的UDP端口,直到收到第一个传感器数据包。
我的小技巧:如果你用sitl_multiple_run.sh启动多机仿真,记得检查每个实例的-i参数。我曾经因为两个无人机用了同一个实例ID,结果遥控器信号串了,两架飞机一起抽风。
下面是一个典型的启动日志片段,我加了注释:
# 启动命令
cd ~/PX4-Autopilot
make px4_sitl gazebo
# 日志输出(关键部分)
[INFO] [px4] Startup script: /home/user/PX4-Autopilot/ROMFS/px4fmu_common/init.d/rcS
[INFO] [px4] Setting simulator: gazebo
[INFO] [px4] Sensor model: iris (四旋翼标准模型)
[INFO] [px4] Waiting for simulator to connect on UDP port 14560...
[INFO] [px4] Simulator connected! (收到第一个IMU数据包)
[INFO] [px4] EKF starting in GPS fusion mode
[INFO] [px4] MAVLink: port 14550 (QGC) is now open
嗯,这里要注意。如果卡在Waiting for simulator这一步,99%的情况是Gazebo没启动,或者端口被占用了。我一般先killall gzserver清一下场。
4.3 仿真时间 vs 真实时间
这是SITL里最容易踩坑的地方。真实世界里,1秒就是1秒。但在仿真里,时间是可以被“操纵”的。
SITL支持两种时间模式:
| 模式 | 描述 | 典型场景 | 注意事项 |
|---|---|---|---|
| 实时模式 | 仿真时间 = 真实时间 | 硬件在环测试、飞手训练 | CPU负载过高时会变慢 |
| 加速模式 | 仿真时间 > 真实时间 | 长时间航线测试、参数调优 | 日志时间戳会压缩 |
为什么会这样?因为SITL的时钟源是CLOCK_MONOTONIC,它完全由CPU的时钟周期驱动。如果你在代码里调用了hrt_absolute_time(),拿到的其实是仿真时间,不是墙上时间。
避坑指南:我曾经在写一个长时间悬停测试脚本时,用了sleep(10)来控制仿真步长。结果因为CPU跑得太快,仿真里的10秒实际只过了2秒。最后日志分析出来的电池消耗曲线完全不对。正确的做法是用hrt_absolute_time()做时间同步。
如果你想在SITL里加速时间,可以设置环境变量:
# 设置仿真时间倍率为2倍
export PX4_SIM_SPEED_FACTOR=2
make px4_sitl gazebo
但要注意,加速后地面站的显示会变快,而且EKF的收敛速度也会受影响。我一般只在做统计性测试(比如100次起飞降落)时才开加速。
4.4 日志系统:你的飞行黑匣子
做仿真,不会看日志等于白做。SITL的日志系统继承了真实飞控的架构,但多了很多仿真特有的字段。
日志主要分两类:
- ULog(二进制日志):存储在
~/PX4-Autopilot/log/目录下,用ulog2csv可以转成CSV。 - SDLog(文本日志):实时打印在终端上,或者重定向到文件。
我个人习惯在仿真时同时开三个窗口:一个跑SITL,一个跑地面站,一个用tail -f盯着SDLog。这样能第一时间发现异常。
下面是一个典型的ULog分析流程:
- 运行仿真,执行你的测试动作(比如一个自动航线)。
- 仿真结束后,在
log/目录下找到最新的.ulg文件。 - 用
ulog2csv转换:ulog2csv xxx.ulg。 - 用Python或Excel打开生成的CSV,分析
vehicle_attitude、vehicle_gps_position等话题。
关键日志话题:
sensor_combined:原始IMU数据,检查是否有异常跳变。vehicle_local_position:本地坐标系下的位置和速度。actuator_outputs:电机输出值,看是否饱和。estimator_status:EKF的健康状态,看是否发散。
嗯,最后说一句。SITL的日志和真机日志是通用的。你在仿真里调好的日志分析脚本,直接拿到真机上也能用。这就是全栈仿真的价值——一次调试,到处复用。