引言

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 → 加噪声/延迟 → 输出 │
└─────────────────────────────────────────────────┘

五层之间的数据流向

  1. 物理引擎 每帧更新动力学状态,将 Kinematics::State(位置、速度、加速度、姿态)和 Environment::State(气压、密度、重力)写入 ground truth
  2. 传感器层 读取 ground truth,叠加噪声模型后输出带噪声的传感器数据
  3. 固件层StateEstimator 读取传感器数据,CascadeController 根据目标模式计算控制输出
  4. Mixer 将四轴控制量(roll/pitch/yaw/throttle)转换为四个电机的 PWM 值
  5. 物理引擎RotorActuator 将 PWM 转为推力和扭矩,施加到动力学模型

二、物理引擎层:Verlet 积分与 box 阻力模型

2.1 核心架构

物理引擎的核心是 FastPhysicsEngineAirLib/include/physics/FastPhysicsEngine.hpp),实现了一个基于 Verlet 积分的多体物理仿真器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// FastPhysicsEngine 核心接口(简化)
class FastPhysicsEngine : public PhysicsEngineBase {
public:
virtual void update() override {
updatePhysics(world_, dt);
}

private:
void updatePhysics(TTimePoint time, TTimeDelta dt) {
for (auto& body : bodies_) {
// 1. 重置力和力矩
body->resetWrench();
// 2. 施加外力(旋翼推力 + 重力)
body->updateKinematics(dt);
// 3. 碰撞检测与响应
// 4. Verlet 积分步进
}
}
};

2.2 旋翼执行器:常数 C_T 模型

RotorActuatorAirLib/include/vehicles/multirotor/RotorActuator.hpp)是旋翼动力学模型的核心实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// RotorActuator::update() 核心逻辑(简化)
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
// GWS 9X5 桨的默认参数
struct RotorParams {
real_T C_T = 0.109919f; // 推力系数(@6396.667 RPM)
real_T C_P = 0.040164f; // 功率系数
real_T max_rpm = 6396.667f;
real_T D = 9.0f * 0.0254f; // 直径 (m)
// 推力公式: T = C_T * ρ * n² * D⁴
// 扭矩公式: τ = C_P * ρ * n² * D⁵ / (2π)
};

重要发现: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
// 阻力计算(FastPhysicsEngine::getDragWrench() 简化)
Wrench getDragWrench(const PhysicsBody* body) {
Wrench drag_wrench;
for (auto& vertex : body->getDragVertices()) {
// 计算该面在体轴系的速度分量
Vector3r velocity = body->getVelocityAtVertex(vertex.position);
// 阻力 = 0.5 * ρ * v² * Cd * Area(沿面法向)
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
// 速度更新(半隐式 Euler)
linear_velocity += (force / mass + gravity) * dt;
angular_velocity += inertia_inv * (torque - angular_velocity.cross(inertia * angular_velocity)) * dt;

// 位置更新(显式 Euler)
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*);
// 子类实现 resetImplementation() 和 update()
};

设计要点

  • Ground truth 在所有传感器之间共享,不拷贝数据
  • 构造函数不做重活(不访问 ground truth),初始化在 reset() 中完成
  • SensorCollection 通过 unordered_map<SensorType, Container> 管理所有传感器

3.2 IMU:ARW + Bias 随机游走

ImuSimpleAirLib/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; // ARW → 标准差
angular_vel += gauss_dist.next() * gyro_sigma_arw + state_.gyroscope_bias;

// 陀螺仪 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 收敛

GpsSimpleAirLib/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(); // 频率限制(典型 5Hz)
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);

// Gauss-Markov 压力漂移(约 10m/hr 漂移率,默认设置)
pressure_factor_.update();
pressure += pressure * pressure_factor_.getOutput();

// 非相关噪声(约 0.2m σ)
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); // Gauss
break;
case ReferenceSource_DipoleModel:
// 地球偶极子磁场模型(随经纬度变化)
magnetic_field_true_ = EarthUtils::getMagField(
ground_truth.environment->getState().geo_point
) * 1E4; // Tesla → Gauss
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 传感器有统一的模式:

  1. 继承 SensorBase,通过 GroundTruth 获取真值
  2. update():读真值 → 加噪声 → (可选:频率限制+延迟线) → 写输出
  3. 噪声生成:使用 RandomVectorGaussianR(高斯向量随机数生成器)
  4. 与 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(); // 读取 RC 输入
offboard_api_.update(); // 状态机 + RC/API 模式切换
controller_->update(); // 级联 PID 计算

const Axis4r& output_controls = controller_->getOutput();

if (controller_->isLastGoalModeAllPassthrough()) {
// Passthrough 模式:直接输出到电机(如 moveByMotorPWMs)
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 控制系统的核心。CascadeControllerfirmware/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) {
// 如果 GoalMode 改变,重新创建该轴的控制器
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(); // 内部再级联 AngleLevel → AngleRate

output_ = velocity_controller_->getOutput();
}

4.3 Mixer:QuadX 混控器

1
2
3
4
5
6
7
8
9
10
11
12
13
// QuadX 布局的混控矩阵
const motorMixer_t mixerQuadX[4] = {
{ 1.0f, -1.0f, 1.0f, 1.0f }, // FRONT_R
{ 1.0f, 1.0f, -1.0f, 1.0f }, // REAR_L
{ 1.0f, 1.0f, 1.0f, -1.0f }, // FRONT_L
{ 1.0f, -1.0f, -1.0f, -1.0f }, // REAR_R
};

// 混控公式
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; // 触发 CascadeController 重建
break;
// ... 其他模式同理
}
}

gains_changed = true 时,CascadeController 在下一次 update() 中自动重建所有轴控制器,使新增益生效。这极大便利了参数调优和在线自适应控制。


五、API 层与状态机

5.1 OffboardApi 状态管理

OffboardApifirmware/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();
// 油门低 + 角速度≈0 + 线速度≈0 → 判定着陆
if (isAlmostZero(angular) && isAlmostZero(velocity))
landed_ = true;
}
}
}

5.2 MultirotorApiBase:高级路径规划

MultirotorApiBaseapi/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));
}

// 自适应 lookahead 控制
while (!waiter.isTimeout()) {
// 计算下一个 lookahead 目标点
setNextPathPosition(path, segs, cur_loc, lookahead + error, next_loc);
// 发送位置指令
moveToPathPosition(next_loc.position, seg_velocity, ...);
// 更新当前位置并调整 lookahead 误差
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 源码的逐文件分析,可以得出以下核心结论:

  1. 分层架构是仿真系统的基石。AirSim 的 Physics → Sensors → Firmware → API → Client 五层设计使得各模块独立演化、可替换。

  2. 传感器噪声建模是 AirSim 最值得学习的部分。IMU 的 ARW+Bias 随机游走、GPS 的 HDOP 收敛、气压计的 Gauss-Markov 漂移,都是经过学术验证的经典模型。

  3. 级联 PID 的 GoalMode 设计极具灵活性。四轴独立模式 + 运行时切换 + 增益热更新,为控制算法开发提供了良好基础。

  4. 旋翼模型的常数 C_T/C_P 是最大的精度瓶颈。对于高速前飞场景,需要补充进距比(advance ratio)校正模型,建议参考 C_T(J) 查表法或 blade element theory。

  5. AirSim 的适用边界清晰——它是四旋翼通用仿真的优秀参考,但不是高速飞行器仿真的完整解决方案。


参考