引言
Microsoft AirSim 是无人机仿真领域最成熟的开源平台之一。虽然微软已于2023年将其归档,但 AirSim 的架构设计和工程实现仍然是仿真系统开发的重要参考。本文通过逐文件阅读 AirSim 核心源码(AirLib 部分),从物理引擎、传感器噪声建模到飞行控制器,完整剖析其技术架构。
分析方法:本文所有结论均基于对 AirSim 源码(main 分支)的直接阅读,涉及约 20 个头文件和实现文件,涵盖 AirLib 的物理层、传感器层、API 层和固件层。
一、总体架构:五层分层设计
AirSim 采用清晰的分层架构,每层职责单一,通过接口解耦:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| ┌─────────────────────────────────────────────────┐ │ Python Client (RPC API) │ │ moveByVelocity / moveToPosition / ... │ └────────────────────┬────────────────────────────┘ │ RPC (TCP/IP) ┌────────────────────▼────────────────────────────┐ │ MultirotorApiBase (C++ API 层) │ │ takeoff / land / moveOnPath / 安全管理 │ └────────────────────┬────────────────────────────┘ │ ┌────────────────────▼────────────────────────────┐ │ SimpleFlightApi (控制器适配层) │ │ Board + CommLink + Estimator + Firmware │ │ commandVelocity → setGoalAndMode() │ └────────────────────┬────────────────────────────┘ │ ┌────────────────────▼────────────────────────────┐ │ Firmware (固件层) │ │ OffboardApi → CascadeController → Mixer │ └────────────────────┬────────────────────────────┘ │ motor PWM [0~1] ┌────────────────────▼────────────────────────────┐ │ FastPhysicsEngine (物理引擎) │ │ RotorActuator + DragFaces → Verlet 积分 │ │ 状态写回 Kinematics::State │ └────────────────────┬────────────────────────────┘ │ ground truth ┌────────────────────▼────────────────────────────┐ │ SensorCollection (传感器层) │ │ IMU / GPS / 气压计 / 磁力计 │ │ 读 ground truth → 加噪声/延迟 → 输出 │ └─────────────────────────────────────────────────┘
|
五层之间的数据流向:
- 物理引擎 每帧更新动力学状态,将
Kinematics::State(位置、速度、加速度、姿态)和 Environment::State(气压、密度、重力)写入 ground truth
- 传感器层 读取 ground truth,叠加噪声模型后输出带噪声的传感器数据
- 固件层 的
StateEstimator 读取传感器数据,CascadeController 根据目标模式计算控制输出
- Mixer 将四轴控制量(roll/pitch/yaw/throttle)转换为四个电机的 PWM 值
- 物理引擎 的
RotorActuator 将 PWM 转为推力和扭矩,施加到动力学模型
二、物理引擎层:Verlet 积分与 box 阻力模型
2.1 核心架构
物理引擎的核心是 FastPhysicsEngine(AirLib/include/physics/FastPhysicsEngine.hpp),实现了一个基于 Verlet 积分的多体物理仿真器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class FastPhysicsEngine : public PhysicsEngineBase { public: virtual void update() override { updatePhysics(world_, dt); } private: void updatePhysics(TTimePoint time, TTimeDelta dt) { for (auto& body : bodies_) { body->resetWrench(); body->updateKinematics(dt); } } };
|
2.2 旋翼执行器:常数 C_T 模型
RotorActuator(AirLib/include/vehicles/multirotor/RotorActuator.hpp)是旋翼动力学模型的核心实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void RotorActuator::update() { control_signal_filtered_ = control_signal_filtered_ + (control_signal_ - control_signal_filtered_) * (dt / (dt + params_.filter_time_constant)); auto thrust = control_signal_filtered_ * params_.max_thrust; auto torque = control_signal_filtered_ * params_.max_torque * params_.turning_direction; thrust *= air_density_ratio; torque *= air_density_ratio; }
|
默认使用的螺旋桨参数来自 UIUC 数据库(RotorParams.hpp):
1 2 3 4 5 6 7 8 9
| struct RotorParams { real_T C_T = 0.109919f; real_T C_P = 0.040164f; real_T max_rpm = 6396.667f; real_T D = 9.0f * 0.0254f; };
|
重要发现:AirSim 的旋翼模型使用常数推力/功率系数,这意味着它不支持进距比(advance ratio)校正。对于高速前飞场景(如前飞速度超过 15 m/s),桨叶效率会显著下降,但 AirSim 的常数系数模型无法捕捉这一效应。这是高速截击机仿真需要考虑补充的关键模型。
2.3 阻力模型:六面 box drag face
AirSim 使用简化的 box 阻力模型——将机身近似为长方体,每个面产生独立的阻力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Wrench getDragWrench(const PhysicsBody* body) { Wrench drag_wrench; for (auto& vertex : body->getDragVertices()) { Vector3r velocity = body->getVelocityAtVertex(vertex.position); Vector3r drag_force = -0.5f * air_density * velocity.squaredNorm() * vertex.drag_coefficient * vertex.area * velocity.normalized(); drag_wrench.force += drag_force; drag_wrench.torque += vertex.position.cross(drag_force); } return drag_wrench; }
|
局限性:
- 不区分前飞/悬停气动特性
- 无升力面建模(固定翼/倾转旋翼无法仿真)
- 无攻角/侧滑角相关气动系数
- 高速场景下精度不足
2.4 积分方法:Verlet(半隐式 Euler)
1 2 3 4 5 6 7
| linear_velocity += (force / mass + gravity) * dt; angular_velocity += inertia_inv * (torque - angular_velocity.cross(inertia * angular_velocity)) * dt;
position += linear_velocity * dt; orientation = integrate_quaternion(orientation, angular_velocity, dt);
|
相比 RK4,Verlet 积分的优势是计算量小,劣势是在高速机动(>100°/s 角速度)时精度下降。
三、传感器层:教科书级噪声建模
AirSim 的传感器实现是其最值得学习的部分。所有传感器遵循统一的 SensorBase 架构。
3.1 传感器基类设计
1 2 3 4 5 6 7 8 9 10
| class SensorBase : public UpdatableObject { protected: struct GroundTruth { const Kinematics::State* kinematics; const Environment* environment; }; public: virtual void initialize(const Kinematics::State*, const Environment*); };
|
设计要点:
- Ground truth 在所有传感器之间共享,不拷贝数据
- 构造函数不做重活(不访问 ground truth),初始化在
reset() 中完成
SensorCollection 通过 unordered_map<SensorType, Container> 管理所有传感器
3.2 IMU:ARW + Bias 随机游走
ImuSimple(AirLib/include/sensors/imu/ImuSimple.hpp)实现了标准的惯性传感器噪声模型,参考 Oliver J. Woodman 的经典论文《An introduction to inertial navigation》:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void ImuSimple::addNoise(Vector3r& linear_accel, Vector3r& angular_vel) { real_T sqrt_dt = sqrt(std::max(dt, params_.min_sample_time)); real_T gyro_sigma_arw = params_.gyro.arw / sqrt_dt; angular_vel += gauss_dist.next() * gyro_sigma_arw + state_.gyroscope_bias; real_T gyro_sigma_bias = gyro_bias_stability_norm * sqrt_dt; state_.gyroscope_bias += gauss_dist.next() * gyro_sigma_bias; real_T accel_sigma_vrw = params_.accel.vrw / sqrt_dt; linear_accel += gauss_dist.next() * accel_sigma_vrw + state_.accelerometer_bias; real_T accel_sigma_bias = accel_bias_stability_norm * sqrt_dt; state_.accelerometer_bias += gauss_dist.next() * accel_sigma_bias; }
|
噪声组分三要素:
| 组分 |
物理意义 |
建模方式 |
| ARW / VRW |
角度/速度随机游走(白噪声) |
σ_arw = arw / √dt,叠加高斯噪声 |
| Bias 稳定性 |
零偏随时间漂移(随机游走) |
σ_bias = bias_stability_norm × √dt,积分累计 |
| Turn-on Bias |
上电时的初始偏置 |
固定常值,在 reset() 时设置 |
输出格式(与 ROS sensor_msgs/Imu 兼容):
1 2 3 4 5
| Output { orientation: 四元数(真值,未加噪) angular_velocity: 体轴系角速度(已加噪) linear_acceleration: 体轴系加速度(已减重力,已加噪) }
|
3.3 GPS:频率限制 + 延迟线 + HDOP 收敛
GpsSimple(AirLib/include/sensors/gps/GpsSimple.hpp)模拟了真实 GPS 的关键特性:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void GpsSimple::update() { freq_limiter_.update(); eph_filter.update(); epv_filter.update(); if (freq_limiter_.isWaitComplete()) { addOutputToDelayLine(eph, epv); } delay_line_.update(); setOutput(delay_line_.getOutput()); }
|
GPS 仿真特性:
| 特性 |
实现方式 |
| 更新频率 |
FrequencyLimiter,可配置(默认 5Hz) |
| 信号延迟 |
DelayLine<Output>,模拟信号处理和传输延迟 |
| HDOP/VDOP 收敛 |
一阶低通滤波器,从 100 逐步收敛到 ~0.3 |
| 启动延迟 |
startup_delay,模拟冷启动时间 |
| Fix 类型切换 |
基于 eph 阈值:GNSS_NO_FIX → 2D_FIX → 3D_FIX |
3.4 气压计:Gauss-Markov 漂移模型
BarometerSimple 使用 Gauss-Markov 过程模拟气压漂移:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Output getOutputInternal() { auto altitude = environment_->getState().geo_point.altitude; auto pressure = EarthUtils::getStandardPressure(altitude); pressure_factor_.update(); pressure += pressure * pressure_factor_.getOutput(); pressure += uncorrelated_noise_.next(); output.altitude = (1 - pow(pressure / SeaLevelPressure, 0.190284)) * 145366.45 * 0.3048; output.pressure = pressure - SeaLevelPressure + qnh * 100.0; }
|
3.5 磁力计:偶极子模型 + 体轴变换
MagnetometerSimple 支持两种磁场参考源:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void updateReference(const GroundTruth& ground_truth) { switch (params_.ref_source) { case ReferenceSource_Constant: magnetic_field_true_ = Vector3r(0.34252, 0.09805, 0.93438); break; case ReferenceSource_DipoleModel: magnetic_field_true_ = EarthUtils::getMagField( ground_truth.environment->getState().geo_point ) * 1E4; break; } }
Output getOutputInternal() { output.magnetic_field_body = VectorMath::transformToBodyFrame( magnetic_field_true_, kinematics_->pose.orientation, true ) * params_.scale_factor + noise_vec_.next() + bias_vec_; }
|
3.6 传感器共同模式总结
所有 Simple 传感器有统一的模式:
- 继承
SensorBase,通过 GroundTruth 获取真值
update() 中:读真值 → 加噪声 → (可选:频率限制+延迟线) → 写输出
- 噪声生成:使用
RandomVectorGaussianR(高斯向量随机数生成器)
- 与 ROS 兼容:所有 Output 结构与 ROS 标准消息对齐
四、固件层:Cascade 级联 PID 控制器
4.1 Firmware 主循环
Firmware::update()(firmware/Firmware.hpp)实现了完整的飞行控制主循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void Firmware::update() { board_->update(); offboard_api_.update(); controller_->update(); const Axis4r& output_controls = controller_->getOutput(); if (controller_->isLastGoalModeAllPassthrough()) { for (int i = 0; i < motor_count; ++i) motor_outputs_[i] = output_controls[i]; } else { mixer_.getMotorOutput(output_controls, motor_outputs_); } for (int i = 0; i < motor_count; ++i) board_->writeOutput(i, motor_outputs_[i]); comm_link_->update(); }
|
4.2 CascadeController:四层级联架构
这是 AirSim 控制系统的核心。CascadeController(firmware/CascadeController.hpp)为每条控制轴独立选择控制模式:
1 2
| PositionWorld → VelocityWorld → AngleLevel → AngleRate → Motor (PID) (PID) (PID) (PID)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| void CascadeController::update() { const auto& goal_mode = goal_->getGoalMode(); for (unsigned int axis = 0; axis < 4; ++axis) { if (goal_mode[axis] != last_goal_mode_[axis]) { switch (goal_mode[axis]) { case GoalModeType::AngleRate: axis_controllers_[axis].reset(new AngleRateController(params_, clock_)); break; case GoalModeType::AngleLevel: axis_controllers_[axis].reset(new AngleLevelController(params_, clock_)); break; case GoalModeType::VelocityWorld: axis_controllers_[axis].reset(new VelocityController(params_, clock_)); break; case GoalModeType::PositionWorld: axis_controllers_[axis].reset(new PositionController(params_, clock_)); break; case GoalModeType::Passthrough: axis_controllers_[axis].reset(new PassthroughController()); break; } } axis_controllers_[axis]->update(); output_[axis] = axis_controllers_[axis]->getOutput(); } }
|
六种控制模式:
| 模式 |
输入 |
输出 |
子控制器 |
Passthrough |
直接值 |
直通 |
无 |
AngleRate |
目标角速率 (rad/s) |
电机指令 |
PID |
AngleLevel |
目标角度 (rad) |
角速率指令 |
PID + AngleRate |
VelocityWorld |
目标世界速度 (m/s) |
角度指令 |
PID + AngleLevel |
PositionWorld |
目标世界位置 (m) |
速度指令 |
PID + VelocityWorld |
ConstantOutput |
常值 |
恒定输出 |
无 |
位置控制器的级联实现(PositionController):
1 2 3 4 5 6 7 8 9 10 11 12
| void PositionController::update() { pid_->setGoal(goal_position_world[axis_]); pid_->setMeasured(measured_position_world[axis_]); pid_->update(); velocity_goal_[axis_] = pid_->getOutput() * max_limit; velocity_controller_->update(); output_ = velocity_controller_->getOutput(); }
|
4.3 Mixer:QuadX 混控器
1 2 3 4 5 6 7 8 9 10 11 12 13
| const motorMixer_t mixerQuadX[4] = { { 1.0f, -1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, -1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, -1.0f }, { 1.0f, -1.0f, -1.0f, -1.0f }, };
motor[i] = throttle * mixer[i].throttle + pitch * mixer[i].pitch + roll * mixer[i].roll + yaw * mixer[i].yaw;
|
混控后执行安全限幅:
1 2 3 4 5 6 7
| if (min_motor < min_motor_output) 所有电机 += (min_motor_output - min_motor);
if (max_motor > max_motor_output) 所有电机 /= (max_motor / max_motor_output);
|
4.4 增益热更新
AirSim 支持运行时通过 API 修改 PID 增益:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void SimpleFlightApi::setControllerGains(uint8_t type, const vector<float>& kp, const vector<float>& ki, const vector<float>& kd) { switch (type) { case GoalModeType::AngleRate: params_.angle_rate_pid.p.setValues(kp_axis4); params_.angle_rate_pid.i.setValues(ki_axis4); params_.angle_rate_pid.d.setValues(kd_axis4); params_.gains_changed = true; break; } }
|
当 gains_changed = true 时,CascadeController 在下一次 update() 中自动重建所有轴控制器,使新增益生效。这极大便利了参数调优和在线自适应控制。
五、API 层与状态机
5.1 OffboardApi 状态管理
OffboardApi(firmware/OffboardApi.hpp)是飞行状态的核心管理器:
状态机转换:
1 2
| Disarmed → [arm()] → Armed → [takeoff detected] → Active Active → [land detected] → Disarmed
|
API 超时保护:起飞后如果超过 api_goal_timeout 未收到新指令,自动进入 hover 模式:
1 2 3 4 5 6 7 8
| if (takenoff_ && (now - goal_timestamp_ > params_->api_goal_timeout)) { if (!is_api_timedout_) { comm_link_->log("API timeout, entering hover mode"); goal_mode_ = GoalMode::getPositionMode(); goal_ = Axis4r::xyzToAxis4(current_position, true); is_api_timedout_ = true; } }
|
着陆检测:
1 2 3 4 5 6 7 8 9 10 11 12
| void detectLanding() { if (takenoff_) { float throttle = rc_.getMotorOutput(); if (!isGreaterThanArmedThrottle(throttle)) { auto angular = state_estimator_->getAngularVelocity(); auto velocity = state_estimator_->getLinearVelocity(); if (isAlmostZero(angular) && isAlmostZero(velocity)) landed_ = true; } } }
|
5.2 MultirotorApiBase:高级路径规划
MultirotorApiBase(api/MultirotorApiBase.cpp)实现了高层次飞行功能:
路径跟随(moveOnPath):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| bool moveOnPath(const vector<Vector3r>& path, float velocity, ...) { for (auto& point : path) { path_segs.push_back(PathSegment(prev_point, point, velocity, length)); } while (!waiter.isTimeout()) { setNextPathPosition(path, segs, cur_loc, lookahead + error, next_loc); moveToPathPosition(next_loc.position, seg_velocity, ...); lookahead_error = computeLookaheadError(cur_loc, next_loc, actual_pos); } }
|
关键特性:
- 自适应 lookahead:无人机偏离路径时自动增大 lookahead 距离
- 末端减速:接近路径终点时自动切换为
breaking_vel
- 安全检查:lookahead < 精度距离时抛出异常,防止失控
六、技术评估与适用边界
6.1 设计优势
| 特性 |
评价 |
| 分层架构 |
⭐⭐⭐⭐⭐ 五层解耦清晰,接口设计合理 |
| 传感器噪声模型 |
⭐⭐⭐⭐⭐ 教科书级实现,ARW/Bias/延迟一应俱全 |
| 级联控制器 |
⭐⭐⭐⭐⭐ 四层级联 + 独立轴模式 + 热更新增益 |
| GoalMode 架构 |
⭐⭐⭐⭐ 灵活的多模式控制(Position/Velocity/Angle/Rate/Passthrough) |
| 路径规划 |
⭐⭐⭐⭐ 自适应 lookahead + 末端减速 |
6.2 局限性
| 局限 |
影响场景 |
| 常数 C_T/C_P(无进距比校正) |
高速前飞(>15 m/s)时旋翼效率建模不准 |
| Box drag 模型(无升力面) |
固定翼、倾转旋翼、高速机动仿真不适用 |
| Verlet 积分(非高阶) |
>100°/s 角速度机动精度不足 |
| 无气动系数表 |
无法模拟失速、大攻角等非线性气动效应 |
| SimpleFlight 控制器 |
仅适用于四旋翼,延伸性有限 |
6.3 适用边界
AirSim 适合:
- 四旋翼通用仿真(侦察、航拍、物流等 ≤15 m/s 场景)
- 传感器算法开发(视觉 SLAM、避障、目标跟踪)
- 控制参数调优(PID 增益搜索)
- 多机编队仿真
- 强化学习训练环境
AirSim 不适合:
- 高速飞行器(>20 m/s 前飞速度)
- 固定翼/垂直起降/倾转旋翼
- 需要精确气动模型(如失速恢复、螺旋机动)
- 导弹/截击机等大机动飞行器
七、传感器噪声模型参数参考
下表汇总了 AirSim 中四类传感器的默认噪声参数,可供自主开发仿真器时参考:
| 传感器 |
噪声参数 |
默认值 |
含义 |
| IMU 陀螺 |
arw |
2.0E-4 rad/s/√Hz |
角度随机游走 |
|
bias_stability |
2.5E-4 rad/s |
Bias 稳定性 |
|
tau |
100.0 s |
Bias 相关时间常数 |
| IMU 加计 |
vrw |
1.4E-3 m/s/√Hz |
速度随机游走 |
|
bias_stability |
3.0E-3 m/s² |
Bias 稳定性 |
| GPS |
update_frequency |
5 Hz |
更新频率 |
|
eph_final |
0.3 |
最终水平精度因子 |
|
epv_final |
0.6 |
最终垂直精度因子 |
| 气压计 |
pressure_factor_sigma |
1.0E-5 |
压力漂移率 (Pa/s) |
|
uncorrelated_noise_sigma |
2.0 |
非相关噪声 (Pa) |
| 磁力计 |
noise_sigma |
5.0E-4 Gauss |
测量噪声 |
|
scale_factor |
1.0 |
比例因子 |
八、总结
通过对 AirSim 源码的逐文件分析,可以得出以下核心结论:
分层架构是仿真系统的基石。AirSim 的 Physics → Sensors → Firmware → API → Client 五层设计使得各模块独立演化、可替换。
传感器噪声建模是 AirSim 最值得学习的部分。IMU 的 ARW+Bias 随机游走、GPS 的 HDOP 收敛、气压计的 Gauss-Markov 漂移,都是经过学术验证的经典模型。
级联 PID 的 GoalMode 设计极具灵活性。四轴独立模式 + 运行时切换 + 增益热更新,为控制算法开发提供了良好基础。
旋翼模型的常数 C_T/C_P 是最大的精度瓶颈。对于高速前飞场景,需要补充进距比(advance ratio)校正模型,建议参考 C_T(J) 查表法或 blade element theory。
AirSim 的适用边界清晰——它是四旋翼通用仿真的优秀参考,但不是高速飞行器仿真的完整解决方案。
参考