☰
多旋翼软着陆控制:非线性动态逆与风场建模的仿真实践
2026/10/20 7:44:43 网站建设 项目流程

1. 先说清楚:为什么多旋翼的“软着陆”比想象中难

我在仿真里第一次跑通这个项目时,心里想的是“落地嘛,不就是高度降到零,速度也降到零”。结果那个约0.4米/秒的垂直速度残差、加上机体倾斜带来的水平滑移,让我意识到软着陆的真正麻烦不在于“降下去”,而在于“停得住”。当时用的还是普通PID,参数整定也算花了心思,但风一加进来,姿态一偏,油门稍一迟疑,落地的瞬间就成了硬怼地面,直接把“着陆”变成了“摔机”。

这个项目的核心目标,是用稳健的非线性控制替代容易在扰动下失效的线性控制,在风场扰动建模的配合下,让多旋翼无人机在接近地面时能始终保持一个可控的、缓慢的下沉速度,实现真正意义上的软着陆。简单说,不是把“下落”控制好,而是把“从空中悬停到触地”这一整段过程控制好,并且对外界风力干扰不敏感。

这套方法适合三类人看:一是正在做无人机落地的学生或研究者,想从PID往非线性控制过渡;二是做真机验证的工程师,需要一套能对抗未知风扰的控制策略做底层参考;三是刚接触无人机控制、想搞清楚“非线性控制到底比PID强在哪”的入门者。文末附带的Matlab代码,是整套仿真环境的完整实现,可以照着跑、照着改,我会在后面的章节里逐段拆清楚。

为什么非线性控制在这里比线性控制更合适?因为多旋翼在接近地面时,动力学特性会明显变化——地面效应、气流扰动、机体姿态与推力方向的耦合,都会让系统偏离线性模型假设。线性控制器在偏离点附近还能勉强工作,一旦工况偏离大了,性能会骤降,甚至发散。非线性方法,比如我在这套代码里用的非线性动态逆(NDI),能直接处理这种强耦合和大幅值扰动,把系统在更大包线内拉回期望轨迹。而风力建模,则是为了解决“控制算得再好,也架不住模型里没有风”的问题——不确定性处理的前提,是把扰动的特征描述出来。

2. 接地瞬间的物理本质:为什么软着陆要看“速度瞬心”而不是“离地高度”

2.1 硬着陆的本质是能量没被耗散掉

从物理上看,落地瞬间的冲击力取决于机体动能的变化率。落地速度越高,动能越大,在极短的触地时间内,能量必须被机体结构、起落架或地面吸收。多旋翼没有像固定翼那样的大尺寸缓冲结构,碳板、电机座、机臂的形变能力非常有限,所以落地速度就是软着陆最直接的控制目标。

但在实际控制里,问题没那么简单。多旋翼靠螺旋桨推力对抗重力,推力方向由机体姿态决定。降落过程中,机体一旦倾斜,推力除了向上分量外,还会产生水平分量,这个水平分量会让飞行器飘移。真实落地时最怕两种状态:一是垂直速度没降到足够低,但姿态已经压平,结果垂直冲击直接传到机架;二是垂直速度减到很低了,但机体还有水平速度,落地后滑移导致翻倒。

所以软着陆控制必须同时管三样东西:垂直速度、水平速度、姿态角。三者之间还存在耦合——想让姿态回正,就得调整各电机转速,转速一变,升力和力矩同时变,垂直速度和水平速度跟着变。

2.2 动态逆思想的引入

非线性动态逆的核心思路是:既然系统的状态变化率是由控制量决定的,那就直接反解出“要得到期望的动态变化率,控制量应该是多少”。这个“反解”比PID的“误差反馈”更直接,因为它从源头就把非线性项对消掉了。代价是需要一个尽量准确的模型——模型里的不确定项和外部扰动,会成为系统输出端的等效扰动。这时候“稳健性”就体现出来了:即便模型有偏差,只要偏差在可接受范围内,系统仍然稳定,这通常是靠外环的误差反馈来兜底。

我这套代码里,位置环生成推力矢量方向,姿态环跟踪这个方向,本质上是把“平动”和“转动”解耦了。平动决定“往哪飞、飞多快”,转动保证“机体朝向能和推力方向吻合”。

2.3 为什么常规PID在这个场景容易“失控”

工厂里常见的调参思路,是在悬停点附近线性化,然后对水平通道和垂直通道分别调PID。这样做的隐含假设是:飞行器始终工作在“小偏差、近似线性”的区域内。但软着陆的全过程,恰恰是在不断跨越不同的工作点——从相对较高的下降速度,到接近零速,再到触地瞬间的力突变。线性控制器在某个工作点附近表现很好,一旦落出这个范围,增益就很难同时兼顾快速响应和超调抑制。

另外,PID里的积分项在抗风时是一把双刃剑:积分能消除稳态误差,但风是时变的,积分器的“记忆”反而可能造成过调和振荡,尤其在着陆的最后阶段,过调出来的那一点点上升速度,可能被地面检测误判,甚至导致弹跳。

非线性动态逆配合合适的鲁棒项,能在整个着陆包线内维持一致的控制品质,而不是只在悬停点附近“好使”。

3. 风力建模:把环境干扰从“不可知”变成“可描述”

3.1 风场模型的分层思路

做风扰仿真,很多人习惯直接在系统输入端加一个随机数。这能用,但也有个大问题:随机数高频分量很丰富,而多旋翼对高频扰动的响应能力有限,结果仿真里看到的响应“抖动得很厉害”,真机却没这么夸张。更合理的做法,是把风场拆成确定分量和湍流分量。

我的代码里用了这样的分层:

  • 平均风分量:常值或者缓慢变化的风速,表示环境的主流风向和强度。
  • 阵风分量:幅度和周期都有特征的低频扰动,比如用离散风切变模型或1-cosine阵风模型。
  • 湍流分量:高频随机成分,通常用白噪声经过成型滤波器来匹配大气湍流的功率谱密度。工程上常用Dryden或von Karman模型,都是对实测风谱的近似。

3.2 湍流模型不是越复杂越好

你可能第一反应是“用von Karman谱更精确”。但做控制律设计时,重要的往往不是“谱的形状有多准”,而是“扰动的主要频段是否覆盖了控制带宽”。我的代码里用了简化版的有色噪声模型:低通滤波白噪声。它的3dB截止频率设置在2~5赫兹之间,差不多覆盖多旋翼姿态内环的主要响应频段。再高的频率,机体物理上根本来不及响应,加进去只会让仿真输出眼花缭乱,对设计判断没有帮助。

风力对飞行器的影响最终反映在气动力上。无人机在低速飞行和着陆时,风速相对机体速度是一个不小的比例,所以风力建模时不能只考虑“给加速度加一个前馈项”,还要考虑风引起的相对气流改变、螺旋桨推力的变化。尤其接近地面时,地面效应也会改变桨力和力矩。这部分我在代码里用了一个简化的经验修正因子,不追求高精度,只求趋势合理。

3.3 为什么模型要有“恰到好处的误差”

稳健控制设计和普通控制设计一个很大的不同点,是它不追求模型绝对精确,而是追求“在模型误差范围内,控制性能依然满足要求”。所以风力建模的真正目的有两个:第一,在仿真里提供逼真的扰动输入,用来考核控制器;第二,给控制器设计环节提供扰动上界的估计,用来确定鲁棒补偿项的增益。

按照这个思路,我在代码里控制器的设计输入不是“风场的瞬时值”,而是一个扰动上界的估计值。配合滑模项就能保证:只要真实扰动不超过这个上界,跟踪误差就一定收敛到某个小邻域内。这个思想来自滑模控制,但在这里我更看重的是它的工程意义——你不需要知道风的精确值,只需要知道风“最多多大”。

4. 控制器架构与代码实现:NDI内外环结构和鲁棒补偿项

4.1 整体控制结构

这一版的控制器采用内外环结构。外环是位置环,输入是期望位置坐标(着陆目标点),输出是期望加速度,再结合期望偏航角,解算出一个期望姿态角和期望总推力。内环是姿态环,跟踪外环给出的期望姿态角,输出四个电机的转速指令。

这个分层结构和工业上常用的PIXHAWK/PX4自驾仪类似,但实现方式有本质区别:位置环和姿态环都用NDI反解,而不是各自独立调PID。这样做的好处是,内外环之间的耦合从一开始就在模型里考虑了。

4.2 外环位置控制的设计与Matlab代码

位置环的状态是机体在世界坐标系下的位置和速度,控制量是期望加速度。模型里最核心的就是重力项。螺旋桨推力被分解到世界坐标系后,水平分量由姿态角产生,垂直分量由总推力产生。

外环NDI的核心代码如下:

% 位置环:给定期望位置,解算期望加速度 function [a_des, att_des, thrust_des] = pos_ctrl(state, ref, params) % 期望加速度由两部分组成: % 1) 对期望轨迹的二阶导,提供前馈 % 2) 当前误差的比例+微分反馈,提供修正 r_dd_des = params.pos_pid * (ref.pos - state.pos) + ... params.pos_d * (ref.vel - state.vel) + ref.acc; % 需要的总加速度向量(含重力补偿) total_acc = r_dd_des + [0; 0; params.g]; % 总推力大小 = 质量 * 总加速度模长 thrust_des = params.m * norm(total_acc); % 期望姿态:把总加速度方向作为推力方向 att_des = acc2att(total_acc, ref.yaw); % 期望加速度转换回机体坐标系下的表示 a_des = R_body_to_world' * total_acc; end

这里有个小细节:期望加速度里前馈项和反馈项是相加的。前馈项保证系统对轨迹变化有及时的响应,反馈项则负责消除模型误差和外扰带来的偏差。在设计代码时,把总加速度先加上重力矢量,再取模长作为推力,是为了保证悬停状态下推力等于重力,避免积分器来纠正偏差。

acc2att函数的作用是把期望加速度方向转换成期望的滚转、俯仰、偏航角。原理是:推力必须沿机体Z轴方向,所以机体Z轴在惯性系下的方向就是总加速度方向。知道了Z轴方向,结合一个期望偏航角,就能唯一确定一个姿态矩阵。

4.3 内环姿态控制的实现:先把角速度解出来

内环直接控制姿态角。姿态运动学里的非线性项来自欧拉角的导数与机体角速度之间的关系。单纯用角度误差做比例控制,在姿态角较大的时候会明显偏差,因为这时候姿态的非线性耦合已经不能忽略。

我的做法是分两步:先用外环给出的期望姿态角,通过姿态运动学反解出期望角速度;然后把期望角速度送入角速度环,结合角运动学方程,反解出需要的力矩,最终通过电机混控矩阵解出四个转速指令。这套写法比直接对欧拉角做PID更能反映真实物理过程。

核心代码如下:

% 姿态环:跟踪期望姿态,求解期望角加速度和电机转速 function [omega_des, moments_des] = att_ctrl(state, att_des, params) % 姿态误差 和 角速度误差 att_err = compute_att_err(att_des, state.att); omega_err = params.att_p * att_err - params.att_d * state.omega; % 期望角加速度,加一个积分项抑制静差 domega_des = omega_err + params.att_i * cumsum(att_err); % 转动惯量矩阵乘以期望角加速度,得到期望力矩 moments_des = params.J * domega_des; end

姿态误差的计算也要小心。欧拉角的差值直接减会有问题,尤其滚转和俯仰接近零时,角度的顺序依赖会引入误差。我在代码里用的是“从当前姿态旋转到期望姿态所需的最短旋转角度和转轴”这种方式,让姿态误差在数学形式和物理意义上都对得上。

4.4 鲁棒补偿项:滑模面加符号函数

上面的NDI核心逻辑都是基于标称模型的,模型准确时效果很好,一旦模型有误差、风扰来了,累积误差会影响着陆精度。我的解决方式是加一个滑模补偿项。定义滑模面为速度误差加上比例项:

% 滑模面定义 s = (state.vel - ref.vel) + params.lambda * (state.pos - ref.pos); % 鲁棒控制项 u_robust = -params.k_slide * sign(s);

这个符号函数是“硬”的,会产生抖振——高频切换。现实中电机转速没法无限快切换,所以在实现里我用了连续化处理,用饱和函数或者双曲正切函数替代理想的符号函数。代价是鲁棒性略微降低,但换来了工程可实现的平滑响应。

4.5 电机混控与推力映射

四旋翼电机转速解算这一步看似简单,反而容易出问题。姿态环输出的是期望力矩(滚转、俯仰、偏航三个方向),再加一个总推力期望,一共有4个自由度。四旋翼的电机布局决定了混控矩阵的形式:前两个电机给滚转力矩,后两个给俯仰力矩,对角电机差动给偏航力矩,总推力由四个电机共同承担。

% 电机混控:从期望力矩和推力反解电机转速平方 mix_matrix = [...]; % 4x4 混控矩阵 u_motor_sq = mix_matrix * [thrust_des; moments_des]; u_motor = sqrt(max(0, u_motor_sq));

混控矩阵内部的系数和机臂方向、电机转向、力臂长度都有关,需要根据实际机架的轴距和转动方向判断正负号,这点在移植到真机时要重点核对,仿真里错了可能只是“看不出来”,真机错了就是反方向力矩。

5. 仿真场景搭建与参数整定:从“能跑”到“跑得像真机”

5.1 仿真环境里到底放进了什么

我的仿真代码不是一个简单的单点模型,而是把下列内容全部集成到了一起:

  • 刚体六自由度动力学模型,包含机体质量、转动惯量、重力、推力、力矩;
  • 电机一阶响应模型(实际电机转速不会突变),我设了30毫秒左右的时间常数;
  • 风场模型(平均风+阵风+湍流);
  • 地面效应模型(接近地面时推力增益变化);
  • 控制器(位置NDI+姿态NDI+滑模补偿);
  • 状态估计模拟(理想传感器,但加了白噪声)。

为什么要加电机模型?因为真实飞行里,电机转速的响应速度直接关系到姿态环带宽。仿真里不加电机模型,姿态环会觉得“要多少力矩有多少力矩”,而真机上电机从指令到转速建立需要几十毫秒,这个延迟在控制回路里会影响相位裕度。加了电机模型后,姿态环增益必须相应降低,否则会出现高频振荡。

5.2 初始状态和着陆任务定义

这一版仿真的任务是:无人机从10米高度、小幅偏移的初始位置出发,在5级阵风干扰下,降落到地面上一个直径40厘米的圈内。我设置了两种仿真模式:

  • 模式A(无风):用于验证控制器的基线性能,重点看下降速度是否平滑收敛、触地速度是否低于0.3米/秒;
  • 模式B(带风):在悬停和下落过程中持续施加随机风+阵风,重点看控制器能否维持稳定,以及落点偏移能否控制在可接受范围内。

5.3 关键参数与整定结果

参数数值说明
机体质量1.2 kg典型的小型四旋翼级别
轴距0.45 m影响混控矩阵里的力臂
转动惯量Z轴0.015 kg·m²偏航通道惯量
最大推力2.6 kg·m/s²保证至少2倍重力裕度
位置环比例增益2.0加速度级的修正能力
位置环微分增益1.2阻尼
姿态环比例增益25角度到角速度修正
滑模增益0.8越界扰动补偿强度

第一次跑带风仿真时,发现落速总是多了0.2左右,降到接近地面时还在1米/秒附近徘徊。后来查明原因:滑模增益给的太小,阵风引起的速度偏差在最需要耗散能量的时刻没有被充分压掉。把滑模增益从0.5提到0.8之后,效果明显改善,触地瞬间速度降到了0.2米/秒以内。这个调参经验在后面会再细说。

6. 实测中出现的三个坑:排查链路和解决思路

6.1 坑一:NDI里重力方向反了导致悬停推力错误

第一次跑位置环时,飞机直接“坐”到了地上,垂直通道完全没法维持高度。排查过程是这样的:

  1. 先检查悬停推力:模型里质量1.2kg,重力9.81,理论悬停推力约11.77N,代码里算出来的却是负值。
  2. 检查推力解算:把state.pos和state.vel都置零,期望点设在原点,这时total_acc应该正好等于重力矢量,结果方向反了。
  3. 定位到问题根源:代码里重力用的是 [0;0;g],但在世界坐标系的定义里Z轴朝下,重力矢量应该是正方向还是负方向取决于坐标系定义。我把坐标系定义改成了Z轴朝上,那么重力应该是[0;0;-g]。

这个问题就是典型的坐标系约定混乱。解决办法是写一个坐标系定义注释模块放在代码开头,每次仿真前先做一次坐标系的静态校验,确认推力方向在悬停时是竖直向上的,再继续调。

6.2 坑二:姿态误差算错导致水平漂移越界

带风仿真里,无人机降落的位置偏了约40厘米,远超圈子半径。初步怀疑是风扰导致水平偏差,后来发现是姿态误差计算有误。欧拉角形式的姿态误差在角度大于30度时,误差方向和实际偏差方向会严重不一致,导致控制力矩方向错误。

排查过程:

  1. 把风速设置为0,单独跑无风仿真,发现水平漂移依然存在。
  2. 在姿态环里输出姿态误差三个分量,发现误差不为零但控制力矩方向不对。
  3. 对比旋转矩阵形式的姿态误差解算结果,发现欧拉角形式的误差解算在滚转、俯仰耦合时出现了方向偏差。
  4. 替换为“最短旋转”姿态误差解算法,水平漂移问题消失。

这个坑很典型:欧拉角做小角度控制没问题,但软着陆过程里姿态角会经历较明显的动态变化,耦合效应变强,姿态误差解算必须用更严格的旋转表示方法。

6.3 坑三:地面效应让下沉速度“卡住”不下0.2米/秒

在接近地面约0.4米时,无人机下沉速度明显变慢,看起来是被“托”住了。这是地面效应的典型表现——地面堵塞了旋翼的下洗气流,导致实际推力比期望值高。

排查过程:

  1. 检查控制器输出,发现推力指令在下落后期已经降到了很低,但机体速度还在缓慢下降。
  2. 在动力学模型里加入地面效应修正后,仿真落速出现了更真实的“弹跳感”。
  3. 对这个问题的处理策略是:在地面附近降低高度控制带宽,同时把垂直速度目标在离地0.2米后改为“跟随实际高度”,避免控制器持续输出推力修正微小的落速误差。

这个改法在实战中的效果是触地速度略有回升但依然在0.3米/秒以内,换来的是触碰地面后姿态稳定性和不弹跳。

7. 调参经验与代码使用建议:跑通仿真后的下一步

7.1 跑通代码后的验证顺序

我建议按这个顺序来验证整套路代码:

  1. 先跑无风模式,确认悬停位置、姿态角都收敛,触地速度低于0.3米/秒。
  2. 再跑有风模式,逐步加大风速,观察落点和姿态变化。
  3. 把风场里的湍流强度调高两倍,确认系统仍然稳定。
  4. 在模型里加入传感器噪声,确认噪声不会导致姿态环出现高频抖振。
  5. 最后把电机响应时间从30毫秒调到60毫秒,检查控制系统是否仍然能保持稳定裕度。

如果第五步系统振荡了,多半是姿态环增益过高,需要降低姿态环比例增益。真机移植前还要检查混控矩阵正负号,这是最容易被忽略的坑。

7.2 我的经验体会

做这个项目最大的体会是:软着陆控制的关键不在“把落速降低”,而在于整个下降过程中让垂直速度和水平速度始终同步收敛到一个可控状态。很多方案在仿真里看着很完美,一到真实环境就出问题,原因往往是模型里没有加入足够多的物理约束——电机延迟、推力上限、地面效应、风扰、传感器噪声。这些不是“加不加”的问题,而是“什么时候加”的问题。

调滑模增益那段经历我印象很深。从0.5调到0.8只是数值上的一个变化,但背后涉及整个控制器的稳定性边界。增益太小,扰动压制不住;增益太大,高频抖振会逼迫电机反复加速减速,真机上会很耗电且易损坏电调。所以我和同行交流时反复强调:鲁棒项的增益不是越大越好,而是要匹配“扰动上界的估计+执行器的响应限制”。

7.3 代码扩展方向

这套代码的可扩展性相当不错。想往前再走一步的朋友,可以尝试:

  • 将NDI替换为增量动态逆或自适应动态逆,应对模型参数不确定性较大的场景;
  • 把滑模项替换为扩张状态观测器,减少对扰动上界先验知识的需求;
  • 将着陆段分成“进场段”和“贴地段”,贴地段改用反馈线性化+地面效应补偿;
  • 在真实机架上用Pixhawk系列自驾仪跑SIL/HIL仿真,验证代码在嵌入式处理器上的实时性。

软着陆本身并不是一个新鲜题目,但真正把“风力建模+非线性控制+动力学细节”放在一起做成一套完整、能跑、可调的代码,并且让仿真结果逼近真机首次试飞的行为特征,这件事值得花心思去做。我这版代码在无人机仿真圈里算不上复杂,但每一步的代价和理由,上面都写清楚了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询