
配平这件事做飞控和飞行仿真的朋友一定不陌生。很多时候你把一架固定翼飞机的非线性模型搭好了满怀期待地扔进仿真环境里结果跑不到两秒飞机就开始侧滑、掉高度、姿态乱成一团整条仿真曲线跟醉汉走路一样没法看。八成问题不是模型写错了而是你没有先做配平。配平这个动作说白了就是给飞机找一个“稳稳当当待住”的工作点在给定的速度、高度和飞行状态下找到一组舵面偏角、油门开度和姿态角组合让气动力、推力、重力刚好互相抵消合力为零、合力矩为零。这样仿真才能从这个平衡点起步控制器设计、动态响应分析也才有基准。我最近做了一个固定翼飞机仿真程序核心就是解决配平问题用MATLAB的航空工具箱和优化工具箱当数值引擎再靠脚本把流程自动化。这篇文章就把整个实现思路、工具取舍和调试踩坑从头到尾写一遍给同样在做飞机仿真、飞控算法、半物理验证的朋友做个参考。1. 配平到底在解什么方程先把物理模型这一层理清楚很多人一开始思路混乱是因为没有把配平问题准确翻译成数学语言。配平不是一个“按钮”而是求解一组非线性方程。要写对程序先得弄清楚飞机模型里每个量是从哪来的。1.1 六自由度方程里的力平衡和力矩平衡固定翼飞机在空间里的运动可以用六自由度方程描述三个平动自由度前后、上下、左右加三个转动自由度滚转、俯仰、偏航。在体轴系下外力和力矩主要由三部分贡献气动力、发动机推力、重力。平动方程的核心是m * (dV/dt ω × V) F_aero T G转动方程的核心是I * dω/dt ω × (I · ω) M_aero M_T配平要找的状态就是让上面两个方程左侧的加速度项全部为零换句话说飞机可以保持一个恒定状态运动。这里要特别注意“恒定状态”不一定是水平直线飞行。定直平飞是一个状态稳态爬升是另一个状态定常盘旋又是另一个状态。不同状态下需要“配”的量不一样边界条件和约束也不一样。以最常见的定直平飞为例理想情况下要满足升力近似等于重力推力等于阻力俯仰力矩为零。这只是三个主方程实际上完整的配平还包括侧向方程侧滑角为零滚转力矩为零偏航力矩为零。对于常规布局、对称飞行状态侧向自然解耦很多教程会省略但你自己写程序的时候最好完整保留因为只要模型中存在哪怕一点点不对称侧向方程就会影响结果。1.2 配平结果在仿真程序里扮演什么角色配平结果通常是一组六维或更多维的状态向量加控制向量速度、攻角、俯仰角、滚转角、升降舵偏角、油门位置可能还有副翼、方向舵偏角。这组数据看起来平平无奇但它决定了你整个仿真的起点。如果直接跳过配平从一个拍脑门的初始状态开始跑会造成两类问题。第一类是数值问题非线性模型在一定迎角、侧滑角范围内有定义初始状态远离配平点仿真步长稍大一点姿态积分就直接飞出了合理值范围气动力插值查表系数跑到定义域外最后算出NaN或者无穷大。第二类是物理问题即使数值没爆飞机也会以很大的加速度修正起始力不平衡整个动态响应从一开始就是畸形的。控制器如果在这个基础上做验证带宽算出来都是错的因为你连工作点都站不住。所以配平是仿真程序的地基。地基没打牢后面所有楼都白盖。这也是为什么我在项目里把配平单独做成一个子系统而不是顺手写在主仿真脚本里。2. 工具箱加脚本两条可行的技术路线怎么选解决配平问题市面上的常用做法大致分两种一种走航空工具箱里的现成函数一种走“自己写目标函数加优化工具箱”的路子。这两条路各有适用场景我项目中实际是两条都搭了一遍最后互为验证。2.1 路线AMATLAB航空工具箱里的trim函数Aerospace Toolbox里有一个trim函数专门用来做飞机配平。这个函数的本质是把Simulink模型当成黑盒通过数值迭代调整状态和控制输入让模型输出端的微分量尽可能接近零。使用它需要模型能正常初始化、输入输出端口设置正确。trim函数的基本调用格式类似[x_trim, u_trim, y_trim, dx_trim, options] trim(AircraftModel, x0, u0, y0, ix, iu, iy, dx0, idx)第一次看到这一长串参数可能会头大但搞清楚含义就好办了。x0是状态初值猜测u0是控制输入初值猜测y0是期望的输出值ix用于标记哪些状态需要调整iu标记哪些控制输入可以调整iy标记哪些输出要参与约束dx0和idx则指定哪些状态导数为零。这条路线最大的优点是省事只要Simulink模型搭建规范trim会自动从模型中提取需要的雅可比信息不需要你手写气动力导数函数。缺点是黑盒当它收敛不了的时候报错信息往往比较隐晦你得回头检查模型接线和初始猜测。而且trim一次只能算一个工作点做全包线扫描时要写循环脚本包住它。2.2 路线B自己写配平脚本交给优化工具箱找解另一条路线是我个人更偏爱的把飞机动力学写成一个普通的MATLAB函数然后构造代价函数调用优化工具箱的fmincon或者fsolve来求解配平点。为什么我更偏爱这条路原因有三。第一调试方便。你可以直接在这个函数里设置断点看清楚每个力、每个力矩在每一步迭代时是多少而不是让一切事情发生在Simulink的黑盒里。第二约束表达自然。配平经常有工程约束升降舵偏角不能超过正负30度油门在0.3到1.0之间攻角不能太大否则失速。fmincon天生支持这类边界约束和线性约束而自己在Simulink模型里硬加约束逻辑反而麻烦。第三批量扫描速度快。做包线配平时我需要连续求解几十个速度点下的配平状态在for循环里调用这个脚本比一次次触发Simulink仿真省下大量时间。这两条路线并不冲突。我在项目里的做法是先用脚本加优化工具箱验证气动模型的合理性再把这个模型封装成Simulink模块用trim做交叉验证。两者结果如果一致说明模型构建没有低级错误如果对不上多半是Simulink模型的输入输出定义和脚本函数不一致定位问题很快。2.3 为什么把平衡方程包装成优化问题而不是直接解方程这里有个很多人问过我的问题配平方程本来就是一组非线性方程直接用fsolve解不就行了为什么非要包装成优化问题理论上是可以用fsolve直接解但实际工程中直接解方程容易翻车。原因在于配平问题是高度非线性的气动系数查表、三角函数耦合、推力模型非线性这些因素导致方程组可能有多个解也可能在某个初值附近根本收敛不了。fsolve在无解或者解不唯一时不给你调整余地。而把问题包装成代价函数的优化问题最大的好处是可控性强。即使找不到残差完全为零的精确解你也能得到一个在物理意义下误差最小的近似解而且这个解的收敛范围比直接求根要大得多。打个比方直接解方程像是要求你闭着眼一步跨过沟优化则允许你一步一步试探着走过去虽然慢一点但更稳妥。3. 脚本实现的几个关键细节变量选择、代价函数和单位这部分是实操核心。我把自己代码里最容易被新手忽略的几个地方拆开讲。3.1 配平变量和约束的选择要有物理直觉写配平程序第一个要确定的就是“让哪些变量参与求解”。这里不是把状态空间里所有量都拿出来扔给优化器那样维度太高收敛慢解出来也未必合理。定直平飞配平典型做法是固定速度和高度把下面这些量作为待求变量攻角 alpha或者俯仰角 theta二者在中性状态下近似相等油门开度 throttle升降舵偏角 elevator再考虑侧向完全对称的情况下副翼、方向舵可以直接置零。把代价函数建立在三个力分量和三个力矩分量的残差平方和上。变量个数少了求解速度会快很多收敛稳定性也好很多而且解出来物理意义直观。边界约束也要给足。油门通常限制在0.3到1.0之间巡航状态下低于0.3说明推力模型可能不对高于1.0说明飞机在这个速度下根本平飞不了。升降舵偏角限制在正负30度以内这是常规舵面作动器输出极限超过这个值说明配平点已经接近飞行包线边缘。3.2 代价函数的正确打开方式归一化比纯量纲更重要配平的核心代价函数理论上是三轴力余量和三轴力矩余量的平方和。直接写J Fx^2 Fy^2 Fz^2 Mx^2 My^2 Mz^2在数学上没问题实际跑起来你会发现一个尴尬情况力和力矩数量级差得太远往往力矩项在代价函数里完全不占权重。力的量级通常是几百到几千牛顿力矩量级可能只有几十牛·米。平方之后力项会彻底淹没力矩项导致优化器只会把力配平了力矩随便弄弄就算“最优”。我的做法是对每一项做归一化处理J (Fx / F_ref)^2 (Fy / F_ref)^2 (Fz / F_ref)^2 (Mx / M_ref)^2 (My / M_ref)^2 (Mz / M_ref)^2其中F_ref取当前状态下重力的数量级M_ref取气动力合力乘以平均气动弦长的数量级。这样每一项的贡献大致在同一个尺度范围优化器才会兼顾加速度和角加速度两类约束。3.3 配平脚本的核心框架示例下面是一个简化但能跑的脚本框架我用的是自定义气动模型加fmincon求解。这个模型把飞机当成刚体气动系数用查表函数逼近。%% 配平脚本定直平飞 % 目标在给定高度和速度下求攻角、油门、升降舵偏角 % 使得合力为零、合力矩为零 V0 35.0; % 目标速度 m/s h0 1000; % 目标高度 m rho 1.196; % 该高度下空气密度 kg/m^3简化取值 x0 [4*pi/180; 0.5; -2*pi/180]; % 初值猜测 [alpha; throttle; elevator] lb [0*pi/180 ; 0.3 ; -30*pi/180]; % 下边界 ub [12*pi/180; 1.0 ; 30*pi/180]; % 上边界 options optimoptions(fmincon, ... Display, iter, ... Algorithm, sqp, ... OptimalityTolerance, 1e-8, ... StepTolerance, 1e-10); % 包装配平余量函数 fun (x) trim_cost_function(x, V0, rho); [x_trim, J_min, exitflag] fmincon(fun, x0, [], [], [], [], lb, ub, [], options); alpha_trim x_trim(1); throttle_trim x_trim(2); elevator_trim x_trim(3; fprintf(配平结果: alpha%.3f deg, throttle%.3f, elevator%.3f deg\n, ... alpha_trim*180/pi, throttle_trim, elevator_trim*180/pi);对应的代价函数大概是这个样子function J trim_cost_function(x, V0, rho) alpha x(1); throttle x(2); elevator x(3); % 计算当前状态下的力和力矩 [Fx, Fy, Fz, Mx, My, Mz] aircraft_forces(V0, alpha, throttle, elevator, rho); % 归一化权重示例 F_ref 9.8 * 500; % 假设飞机重量500kg M_ref F_ref * 0.3; % 假设平均气动弦长0.3m J (Fx/F_ref)^2 (Fy/F_ref)^2 (Fz/F_ref)^2 ... (Mx/M_ref)^2 (My/M_ref)^2 (Mz/M_ref)^2; end这里的aircraft_forces函数就是你的气动力模型里面的具体公式取决于你用的气动导数形式。我强烈建议在这个函数里把每个力和力矩分量都作为变量保留下来方便调试时单独查看。3.4 Simulink模型里用trim的坑端口定义和初始猜测如果你选择走trim路线最常遇到的坑是模型输出端定义不对。trim函数默认的是“模型输出的微分量等于零”来作为收敛准则。所以你的Simulink模型输出端口一定要把运动方程里的状态导数引出来三个速度导数加三个角速度导数。少引一个配平结果就不会完整。另外初值猜测也很关键。x0里给的攻角、俯仰角如果偏离真实配平点太远trim很可能陷入局部极小或者直接报错。我的经验是先用脚本快速估一遍大致解再用trim细化这样两边配合效率最高。4. 一个能跑的定直平飞算例从求解到验证理论说再多不如跑一个具体的算例。这里我用一架小型通用飞机模型气动参数做了简化处理方便大家直接复现思路。4.1 算例气动模型参数飞机基本参数取质量m500kg翼面积S10m²平均气动弦长c0.3m巡航推力假设正比于油门开度最大推力800N。升力系数和阻力系数简化为攻角和升降舵偏角的线性函数加失速修正俯仰力矩系数也是线性化处理。在这个简化模型下定直平飞在35m/s速度下的配平结果算出来大概是变量配平值攻角4.2度油门开度0.62升降舵偏角-2.5度俯仰角3.1度注意这里的俯仰角并不完全等于攻角因为实际还有很小的爬升角残差。如果严格要求γ等于零需要在代价函数里把爬升角也作为约束加进去。4.2 把配平结果带回仿真模型里做验证配平算完不能直接信必须回灌模型验证。我在脚本里把配平状态和控制量自动写入Simulink工作区然后初始化模型跑5秒仿真监控速度、高度和姿态角的变化。如果配平正确这几个量应该基本保持不变最多有微小的数值漂移。如果高度明显往下掉或者速度在慢慢变快说明配平点有残差。不要急先看代价函数残差再看是哪个力或力矩还在失衡。之前就碰到过一次油门解到0.7代价函数几乎为零但实际仿真高度还是缓慢下降后来发现是推力模型里没有考虑进气速度对推力的影响导致动态推力比静态推力小重新修正推力模型后配平结果就对了。4.3 做一组全包线配平扫描你会有大收获单点配平只是入门。把脚本包在for循环里对速度从20m/s到50m/s每隔5m/s取一个点就能得到一组完整的配平曲线不同速度下需要的油门、升降舵偏角、攻角。这些曲线拿来画图观察能直观看到飞机有没有反操纵问题、有没有速度稳定性问题。比如正常情况下速度越高所需的攻角越小、升降舵偏角越小油门变化则要看推力模型和阻力极曲线的关系。如果某段速度区间内速度越高反而需要更大的攻角那基本说明模型里存在静不稳定或者气动导数改错了这时候就要回头检查模型而非继续调配平脚本。这条配平扫描曲线在后续做控制器调参时会成为非常有价值的参考。很多飞行品质分析和LQR控制器设计都是在一组配平点基础上做线性化再逐点设计控制器参数的。5. 调试配平时最容易踩的五个坑实测排障记录写配平程序看起来是一个小时的事真正调通可能会花你一天。下面这几个坑是我自己反复踩过的整理成一个速查清单供参考。5.1 攻角和俯仰角混用导致无解这是新手最容易犯的经典错误。攻角alpha是速度矢量与机体轴之间的夹角俯仰角theta是机体轴与水平面的夹角。定直平飞时爬升角gamma等于theta减去alpha。很多人初始赋值时直接让theta等于alpha忽略了重力分量在体轴系的分解差异结果力的平衡方程永远差一项无论如何迭代都收敛不了。解决办法是把速度坐标系和机体坐标系的转换矩阵写清楚不要在这上面偷懒。5.2 量纲不统一收敛到一个“看起来合理但实际荒诞”的解英制单位、公制单位混用是仿真程序里的顽疾。气动系数表如果是从风洞数据或者老外那儿拿来的大概率是英制速度可能是ft/s翼面积可能是ft²。你如果忘了换算解出来的配平油门可能变成0.05或者5.0这种离谱边界值。建议在程序一开始做严格的单位制声明所有计算内部统一用国际单位只在输入输出接口做转换。5.3 代价函数权重失衡力矩永远配不平前面讲了归一化的问题这里再强调一遍。如果你的气动力数量级是几千牛俯仰力矩数量级是十几牛米不做归一化代价函数基本等于力和力矩之和除以几千牛力矩项会被优化器自动无视。配平结果就会是飞机受大力矩但旋转趋势被忽略的荒谬状态。检查方法很简单看配平后的力矩残差如果大于气动力残差两个数量级以上基本就是权重配错。5.4 trim函数报无法初始化模型但脚本法能算出来Simulink模型在trim运行前要执行一次初始化如果你的模型里有积分器模块初始状态和你给的x0不匹配trim会直接报错。我自己碰到过一次模型里积分器的初始状态是写死在模块参数里的而不是从工作区读取导致无论怎么给x0模型内部起点都纹丝不动。解决办法是把所有积分器初始状态都抽成模型参数由工作区统一赋值。5.5 局部极小和初值敏感需要“多起点搜索”兜底固定翼的配平方程组往往不是单峰函数。同一个速度下可能同时存在小攻角解和大攻角解优化器给了哪个取决于初值猜测。fmincon本质上找的是局部最优不是全局最优。我的处理方式是在脚本里做一个简单的多起点搜索对攻角初值从0度到10度等间隔取五个点分别跑一遍优化选择代价函数最小且物理意义合理的结果。注意如果两次不同初值跑出来的结果差距很大而且代价函数都很小那基本可以断定模型存在多平衡点这本身就是一个非常重要的飞行品质信号值得专门研究。6. 把配平脚本变成仿真流程中一个可靠的基础设施配平程序写完以后建议不要当成一次性脚本用完就丢。我在项目里把配平函数、包线扫描、结果可视化统一封装成了一个工具包主仿真脚本每次运行第一步都会自动调用配平子系统确保任何飞行状态开始前都站在正确的平衡点上。这个自动化带来的好处不止是省时间。你会发现后续做线性化分析、控制器参数扫描、模态分析时所有的基准状态都能一键获取不用手动去翻三维模型输出。整个仿真程序的可靠性也上了一个台阶因为配平成了流程里一个可复用的基础设施其他模块都在它之上搭建。最后提一个容易被忽视的事配平不是只做一次就永远正确的事。当气动模型参数调整、质量特性改变、飞行状态变化时配平点都会跟着变。把配平和模型改版放在一起做联动而不是等仿真跑挂了才想起来重新配平经验之谈能让你省下大量无效的排查时间。