风扰下的多旋翼无人机软着陆:滑模控制与Matlab仿真
多旋翼无人机的软着陆听起来好像就是“飞下来”三个字真正做控制的人都知道落地那一下才是全流程里最容易出事的环节。我见过不少飞机飞得挺稳一进入降落阶段就开始姿态摇晃、触地弹跳甚至直接翻倒尤其在风天里尤为明显。这个项目解决的问题很简单也很具体面对常值风、阵风和湍流叠加的风场干扰怎么让多旋翼无人机用稳健的非线性控制手段完成一次冲击小、姿态正的软着陆。文中所有方法都配套了可运行的Matlab仿真代码适合正在做飞控算法研究、或者准备把无人机从悬停推进到自主降落的开发者作为参考。1. 先搞明白软着陆和风的问题究竟出在哪1.1 软着陆的硬指标不止一个“轻”软着陆如果只看“触地速度小”这一个指标其实不难难在多个约束同时满足。真实着陆过程里垂直速度要控制在足够小的区间比如0.2米每秒到0.3米每秒机体的滚转和俯仰角要基本对齐水平面不能带着横滚落地水平方向的地速也要收敛到接近零否则轮胎或脚架会吃侧向力。再加上螺旋桨在地面附近产生的地面效应以及阵风在低空更加剧烈的扰动这些条件叠在一起单纯靠一个降低垂直速度的指令是搞不定的。这个项目在仿真里把“软着陆”定义成三件事垂直触地速度不超过0.3米每秒、落地时姿态角小于3度、水平位置误差不超过0.2米。这三条指标互相耦合姿态角偏了会导致水平位置漂移水平速度没有抑制干净又会在触地瞬间产生侧向冲击所以整个控制器必须同时管住位置、速度和姿态三条通道而不是只盯着高度。1.2 为什么常规PID在风天降落时会失控很多飞控的内环仍然是PID悬停和匀速飞行时增益调好了问题不大。但只要进入着陆阶段控制对象的特性就会发生明显变化一方面是油门已接近悬停油门推力对增量指令的响应余量变窄控制效率下降另一方面是近地面时气动干扰增强PID按原有增益计算出的控制量可能会出现持续的相位滞后。更麻烦的是风。风不是一个固定的常值它由平均风、阵风脉冲和随机湍流叠加组成PID本质上是一个基于误差反馈的线性控制器对未知但有界的外部扰动有一定的抑制能力但扰动频率靠近系统带宽时反馈回路还没来得及修正飞机已经偏出去了。我做过对比实验在同等阵风条件下优化过的PID能把着陆垂直速度压在0.5米每秒上下姿态角误差却经常跑到8度以上这种状态落下去起落架受力是不均匀的。1.3 整体思路非线性控制加风场前馈既然问题出在“模型特性变化”和“外部扰动不可测”上方案就分两步走。第一步用非线性控制替代在小扰动假设下设计的线性控制器让控制律在大姿态、大速度偏差下依然有明确的稳定性保证第二步在控制器里显式加入风场模型把可观测或可估计的风速作为前馈补偿而不是等风把飞机吹歪了再去修正。这个项目选的是滑模控制作为核心非线性控制律配一阶风场估计器。滑模控制的优点是结构简单、对匹配扰动有天然鲁棒性写进Matlab代码只需要几十行。风场建模方面把常值风、阵风和Dryden湍流分开建模分别生成干扰量再折算到机体坐标系里加到动力学方程中。这样仿真里出现的风既是“值得对抗的扰动”也是控制器前馈可以提前消掉的一部分这种策略比单纯靠反馈硬扛更贴近实际飞控的写法。2. 系统建模无人机动力学和风力干扰怎么进仿真2.1 刚体六自由度方程与状态向量做控制器之前模型必须先立起来。多旋翼无人机在低速场景下可以简化成刚体状态向量取12维位置、速度、姿态角和三轴角速度。位置和姿态在地面坐标系里描述力和力矩则在机体坐标系里计算中间用旋转矩阵做变换。位置回路的状态方程很好写加速度等于合外力除以质量合外力包含螺旋桨推力、重力和气动阻力姿态回路则需要用欧拉角或者四元数处理由于着陆过程中姿态角变化范围不大用欧拉角加运动学方程就够用。仿真里我把阻力系数设成和速度平方相关的简单模型虽然简化但能体现低速下的气动阻尼趋势。惯量参数、质量和旋翼布局这些基础参数全部做成结构体变量方便批量调参。质量取1.2千克轴距对应的旋翼力臂0.25米转动惯量按对角线矩阵近似。这些参数和一台小型实验四旋翼差不多仿真结果可以直接对标这类平台的物理极限。2.2 风力建模的三层结构风力建模是这个项目的另一个关键点。简单地把风速设成一个常数测出来的是抗常值风性能但真实风场是分层的。项目中我用三种成分叠加平均风、阵风和湍流。平均风就是一段持续的恒定风速通常取3米每秒到6米每秒阵风用高斯脉冲或者阶跃函数模拟幅值在2米每秒左右持续1到2秒模拟起降场突发的风切变湍流则用一阶马尔可夫过程生成有色噪声让风速在平均风附近连续随机波动。这三层加起来基本覆盖了实际飞行中常见的风扰形式。在Matlab里实现时如果安装了航空工具箱可以直接调Dryden风场模型没装工具箱也没关系手工生成一阶马尔可夫过程的代码很短效果也够用。项目里我选择手工生成力求不依赖工具箱也能完整跑通仿真。2.3 地面效应模型不能省离地面越近螺旋桨推力效率越高这是地面效应。原因在仿真里不深究但它的数学模型必须放进着陆段我在高度低于0.5米时引入一个效率系数效率随高度降低迅速上升推力相同的情况下实际产生的升力显著增大。如果不建模仿真里就会出现一个让控制器“迷茫”的现象同样输出一个油门值高度越低垂直加速度反而越大PID容易超调导致触地速度超标。地面效应的处理方式可以很简单在不考虑地面效应的推力模型外面乘一个增益系数增益是高度的函数。在着陆阶段控制器把这个增益的反演算进去相当于前馈里多乘了一项补偿这样飞机在贴近地面时才不会因为效率骤增而上飘。3. 稳健非线性控制律的设计与选择逻辑3.1 为什么选滑模控制而不是反步法或自抗扰非线性控制方法并不少反步法、反馈线性化、自抗扰控制都能做。这个项目选滑模控制原因很实际结构简单且鲁棒性可直接证明。反步法需要逐步反推虚拟控制量链条长参数多反馈线性化对模型精度要求高模型失配时性能掉得快自抗扰虽然打压强但扩张状态观测器的带宽整定对新手不太友好。滑模控制的思路是把系统状态拉到设计的滑模面上然后让状态在滑模面上滑动到原点。滑模面设计好之后系统动态主要由滑模面方程决定对外界扰动和参数摄动的敏感度大幅下降。对于着陆这类要求强鲁棒性的任务滑模的思路天然契合。抖振问题用边界层饱和函数处理工程上也成熟不必刻意回避。3.2 位置环滑模面和趋近律的设计位置环需要控制三维方向的位置。以垂直通道为例定义高度误差e为当前高度减去参考高度理想情况下希望高度误差和误差变化率都收敛到零。把滑模面写成误差变化率和误差的线性组合加上增益系数这个写法相当于定义了一个误差空间里的“泛函”只要泛函收敛到零误差本身和误差导数也收敛到零。为了保证滑模面收敛使用等速加指数趋近律也就是让滑模面的变化率等于负的比例项加符号函数项。符号函数会造成高频切换这就是抖振的来源实际代码里用饱和函数代替符号函数在边界层内做连续过渡。边界层厚度就是“稳健程度”和“平顺程度”之间的一个旋钮边界层厚一点动作平顺但扰动抑制弱一点薄一点抑制强但振动大。着陆控制里我取0.2到0.3平衡得比较舒服。3.3 风扰动的估计与前馈补偿光靠滑模反馈扰动来了也能收敛但响应过程会先出现一个偏差尖峰再慢慢被拉回来。对软着陆来说这个尖峰可能就是触地姿态角超标的元凶。所以在滑模控制之外还需要一个风扰动补偿通道。风扰动作用于机体最终体现为外力作用在位置环上外力矩作用在姿态环上。位置环里我把水平风的等效加速度估计出来用低通滤波对加速度残差做在线估计垂直通道风的影响相对小主要靠滑模鲁棒性扛。水平通道加前馈后控制量里就同时包含了“反馈修正”和“扰动抵消”两部分仿真里阵风来临瞬间的横向偏差峰值明显下降。3.4 稳定性判据与控制参数守恒性滑模控制的稳定性论证不复杂用的是李雅普诺夫第二法。选取一个正定的李雅普诺夫候选函数等于滑模面平方的一半然后求它沿系统轨迹的时间导数。只要趋近律里的系数为正即使存在有界外部扰动时间导数也会在边界层外负定系统就能进入滑模面并保持。这个证明过程在文章里不用完整板书但写代码的人心里要有数如果扰动上界超过设计估计控制器还是会失稳的。所以参数设计要留裕量。我用的是“标称性能优先、然后逐步加大扰动”的办法先把滑模增益调到飞行轨迹干净流畅再逐步把风速从0加到6米每秒观察高度误差和姿态角误差是否还在约束内。如果压力太大就先增加边界层厚度再考虑提高反馈增益。这个顺序比一上来就追求强鲁棒性更稳。4. Matlab仿真全流程从风力生成到软着陆落地的完整代码4.1 工程结构与运行环境整个仿真项目不需要额外工具箱只要Matlab主程序即可运行代码分四个文件主脚本、无人机动力学函数、风场生成子函数、滑模控制器函数。主脚本负责初始化参数、跑循环和绘图其他函数按功能解耦方便替换成其他控制方法做对比研究。仿真频率设为200赫兹时间步长5毫秒总仿真时长设置为15秒其中前3秒保持悬停第3秒开始下达着陆指令第10秒左右完成触地剩余时间观察控制器收尾表现。积分采用简单的欧拉法步长足够短时精度满足需求省去用定步长ode45的复杂度。4.2 主程序与初始化参数主脚本第一步就是摆放所有参数。质量、重力加速度、惯性矩阵、旋翼力臂、推力系数这些是物理参数控制增益、边界层厚度、趋近律系数是控制参数风速均值、阵风幅值、湍流标准差是环境参数。全部放进一个结构体后续函数直接引用改参数不用到处找。初始化状态向量为零只有高度是3米。悬停阶段用控制器输出反算出合适的推力保持位置着陆指令触发后控制器切换参考高度规划器让参考高度随时间平滑下降到地面而不是直接在期望高度上猛踩一脚。4.3 风场生成函数的核心代码风场生成函数输入当前时间和参数结构体输出地面坐标系下的三维风速。常值风直接给一个固定向量阵风用高斯形状函数描述湍流用一阶马尔可夫过程产生每次调用时基于上一次状态和随机增量更新。代码里最需要注意的一点是“上一次状态”必须通过参数结构体返回否则函数多次调用之间没有记忆湍流就变成了白噪声。function wind WindField(t, params) % 平均风 wind_mean [params.wind_mean_x; params.wind_mean_y; 0]; % 阵风 gust params.wind_gust_amp * exp(-((t - params.gust_time)/params.gust_dur)^2); wind_gust [gust; 0; 0]; % 湍流一阶马尔可夫 params.turb_tx params.turb_tx params.turb_tau * (params.turb_sigma * randn - params.turb_tx); turb [params.turb_tx; params.turb_ty; 0]; wind wind_mean wind_gust turb; end注意这个函数是简化写法实际引用时要处理好副变量让湍流状态在仿真循环外部保存。我自己的版本是把湍流状态放到参数结构体里每帧调用后更新再存回结构体这样既直观又便于调试。4.4 滑模控制器函数实现控制器函数输入当前状态、参考轨迹和风估计值输出总推力、三个控制力矩和电机转速指令。总推力由垂直通道滑模面计算得到滚转和俯仰力矩由水平位置滑模面计算得到偏航则用一个简单PID保持航向。垂直通道里参考高度轨迹还要考虑地面效应补偿把补偿系数乘到控制增益上。function [u, ref] Controller_SMC(state, ref, params) % ref: 参考高度和参考速度 e_z ref.z - state.z; de_z ref.vz - state.vz; s_z params.lambda_z * e_z de_z; u_z params.k_z * s_z params.epsilon_z * sat(s_z / params.phi_z) params.m * params.g / params.k_thrust; u zeros(4,1); u(1) u_z; % 总推力 % 水平通道和姿态通道类似处理 end这段代码是最简结构正式版本会加上风估计前馈项。注意重力补偿项用的是控制器参考坐标系下的估计质量如果实际质量与模型质量偏差超过10%滑模面仍然能纠正但瞬态偏差会拉大这也是鲁棒性的边界。4.5 动力学解算与仿真循环动力学函数根据当前状态和控制量更新下一帧的状态。位置方程把螺旋桨推力的竖直分量减去重力再除以质量作为加速度姿态方程用角速度更新欧拉角角加速度由惯性矩阵和力矩解算。循环体内先读当前状态再算风场再调用控制器最后积分一步把新状态写回。为了观测结果是否达标每帧还要计算触地判定条件高度低于0.05米且垂直速度小于0.3米每秒时把电机指令强制设为0模拟熄火同时记录触地时刻的状态。这个判定逻辑要放在控制器之后、动力学积分之前否则飞机可能会在已经判定触地后再积分一段产生虚假弹跳。仿真跑完绘制六张图三维轨迹、高度随时间曲线、垂直速度随时间曲线、姿态角曲线、电机推力曲线、风场曲线。我每次调参数都靠这些图只看一个指标很容易漏掉耦合问题。5. 仿真调试中的常见坑和排查实录5.1 仿真一跑就发散先查这几处仿真发散在控制算法调试里太常见了。第一次跑通这个项目时我也遇到过高度直接冲上天速度很快变成NaN的情况。排查下来主要有三个原因一是积分步长太大控制频率200赫兹但欧拉积分不稳定把频率增加到500赫兹就好了二是滑模趋近律的符号函数在边界层外切换太剧烈导致数值解高频抖动换成饱和函数后平稳三是控制量没有限幅推力指令直接给了几千牛仿真里当然会炸。建议调试顺序是先关掉风场只用常值风让系统在无风环境下把着陆轨迹跑通然后加上阵风观察偏差峰值是否在约束内最后才加入湍流。每加一层扰动都要反复确认控制量是否出现饱和如果饱和就说明抗饱和设计还不到位。5.2 阵风模型和真实风场差在哪很多仿真里的阵风是一个高斯脉冲看着像回事但真实风场中的阵风往往伴随方向突变和垂直分量。我在仿真里产生阵风时一开始只加了水平方向的脉冲导致侧风状态下飞机往一个方向漂移。后来把阵风也拆成水平双向和垂直单向才复现出真实场景中常见的横向风切变。还有一点真实风场有空间梯度模型里如果只考虑时间变化飞机横向移动时会穿过不同的风区位置控制器需要重新收敛。这个项目里我没有做空间风场梯度算是简化处理但读者如果做更细致的仿真一定要考虑。5.3 地面效应引发的触地“上浮”现象我第一次加入地面效应模型后发现飞机在着地瞬间居然往上飘了一下再落下来触地速度反而超了0.3米每秒。原因是控制器在高度很低时仍然输出悬停级别的推力地面效应让这个推力的实际升力大于预期飞机减速过快甚至反向加速。解决方法是把地面效应补偿做进控制律里在高度低于0.5米时让推力期望值乘以一个小于1的因子。这样控制器其实是在“少推一点”避免地面效应代偿。这类问题如果不建地面效应模型仿真里永远不会暴露但实飞时一定会遇到模拟阶段千万别省。5.4 触地判定和电机停转的时序细节着陆判定逻辑看起来简单其实坑很多。如果只判断垂直速度小于阈值飞机还没真正触地也就是高度还有几厘米时就可能被判定成已着陆导致电机提前熄火飞机直接摔下去。所以判定条件必须是“高度接近地面”且“垂直速度足够小”两者同时满足。电机停转也不应该在同一个帧里完成。我的做法是检测到触地后先把推力指令每5毫秒降低百分之二十分几帧慢慢收油门这样既模拟了电调响应也避免瞬间撤力造成机身反冲。这个细节对软着陆指标的判定影响很大如果不做渐变停桨仿真里测出的触地峰值冲击会偏大和真实硬件响应也对不上。6. 从仿真到实飞落地那一下的更多心得这套仿真代码跑通之后很容易产生“算法已经可靠”的错觉。我个人体会是仿真里所有参数都收敛并不代表换到真实飞机上就能直接装桨试飞。实飞和仿真最大的差异在于传感器噪声、执行机构延迟和结构弹性滑模的抖振在真实电机上会放大边界层厚度和趋近律增益必须重新标定。做实际飞行测试时我习惯先在室外小风天做起降高度阈值放低垂直速度阈值放宽到0.5米每秒。等确认机械结构和传感器没有问题再把约束收窄到0.3米每秒。如果一个控制参数在仿真里能承受6米每秒阵风实飞时建议从2米每秒起步逐步加码每档多飞几次不要一上来就追求极致的仿真参数。这个项目还有一个值得扩展的方向把滑模控制器替换成模型预测控制或者自抗扰控制对比在同样风场下的着陆表现。我试过用自抗扰替代滑模观测器带宽调到15赫兹左右水平抗风性能接近但参数整定明显更费时间。如果读者时间有限我建议先吃透滑模这套把鲁棒性的手感建立起来再去看其他方法。最后分享一个小技巧仿真数据一定要多存几个版本尤其是风场序列。同一个控制器在两个不同随机种子生成的湍流下性能可能有明显差异这不能说明控制器有问题只能说明需要用同一条风场数据来公平对比不同算法。固定种子、固定风场、固定初始条件这是算法对比的基本功也是我在这个项目里踩过几次坑之后最想提醒别人的事。