1. 参数不确定系统的控制困局与自适应滑模的破局思路1.1 固定增益滑模控制面对上界未知时的尴尬先聊一个很多人跑仿真时都会遇到的场景。你按教科书写了一个滑模控制器被控对象是个典型的二阶非线性系统[ \ddot{x} \theta_1 x^3 \theta_2 x \theta_3 \dot{x} b u d(t) ]其中θ是系统参数b是控制增益d(t)是外扰。你兴奋地把曲线跑出来跟踪误差确实收敛到零了——然后你仔细一看控制量u高频抖振大得吓人。如果这真的是台电机或者机械臂执行器早就烧了。为什么抖振会这么严重根源在于滑模控制里那个符号函数项。为了保证系统在参数不确定和扰动存在时仍然稳定切换增益k必须大于所有不确定项的上界。但问题来了这个上界在实际工程中很难精确知道。你说它小于1结果实际工况一变干扰幅值到了1.5系统瞬间失稳。于是你只能往大了取k取10、取20抖振自然跟着越来越大。这就是固定增益滑模控制面对参数不确定时的尴尬增益太小压不住不确定性增益太大抖振无法接受。你调了一个星期参数最后发现这个上界根本不是常数——负载变了、温度变了、机构磨损了上界就变了。1.2 自适应机制的核心思路与其盲调增益不如在线识别参数自适应滑模控制Adaptive Sliding Mode Control, ASMC的思路和上面完全不同。它不试图用一个保守的固定切换增益去砸平所有不确定性而是把系统中未知的参数当作待估计对象在控制过程中实时更新参数的估计值让控制律认识这个系统再针对估计误差做补偿。你可以把它理解成这么一件事固定增益滑模像是闭着眼睛过河只能把步子迈得特别大来防止踩空自适应滑模则是睁开眼睛一边走一边用脚探路走得又快又稳。要做到这一点被控对象里的非线性部分必须能改写成已知函数向量 × 未知参数向量的形式。这个形式在控制理论里叫线性参数化Linear in Parameters比如上面那个例子可以写成[ \ddot{x} \theta^T \phi(x, \dot{x}) b u d(t) ]其中θ [θ₁; θ₂; θ₃]是未知参数向量φ [x³; x; ẋ]是已知的回归向量。绝大多数机械系统、机电系统都能写成这种结构这也是自适应控制能够成立的前提。接下来的问题是参数怎么在线更新答案是Lyapunov稳定性分析。我们把参数估计误差也放进Lyapunov函数里通过设计自适应律让整个系统的能量单调递减这样既能保证跟踪误差收敛又能保证参数估计误差有界。下面我把这条完整的推导链路拆开讲。2. 控制器推导从滑模面到自适应律的完整链条2.1 被控对象与滑模面的构造先明确控制目标。假设期望轨迹是x_d(t)定义跟踪误差[ e x - x_d ]滑模面取标准的一阶形式[ s c e \dot{e}, \quad c 0 ]这里的c是一个非常关键的参数。当系统进入滑模面s 0时误差动态退化为一阶线性系统 ė -ce意味着跟踪误差按照指数速度收敛收敛速率直接由c决定。c越大误差收敛越快但代价是对噪声和未建模动态更敏感这个后面参数整定部分再细说。对s求导并把系统方程代进去[ \dot{s} c\dot{e} \ddot{e} c\dot{e} \ddot{x} - \ddot{x}_d c\dot{e} \theta^T \phi b u d - \ddot{x}_d ]我们的目标是让s收敛到零也就是让心里滑模面动态朝着稳定方向走。2.2 等效控制加切换控制的经典结构滑模控制律通常拆成两部分[ u u_{eq} u_{sw} ]等效控制u_eq的职责是在不知道扰动的情况下让确定性的那部分系统动态维持在滑模面上。做法是暂时忽略扰动d令s的确定性部分等于零解出控制量[ u_{eq} \frac{1}{b} \left( -c\dot{e} - \hat{\theta}^T \phi \ddot{x}_d \right) ]注意这里用的是参数估计值θ̂不是真实值θ。因为θ是未知的控制器只能拿当前时刻的估计值来计算。切换控制u_sw的职责是补偿参数估计误差和外部扰动带来的偏差把系统状态推向滑模面。写法是[ u_{sw} -\frac{1}{b} k , \text{sgn}(s) ]把两部分合在一起代入s的导数[ \dot{s} \theta^T\phi c\dot{e} b \cdot \frac{1}{b}\left(-c\dot{e} - \hat{\theta}^T\phi \ddot{x}d\right) b\cdot u{sw} d - \ddot{x}_d ]化简后cė和ẍ_d正好抵消剩下的就是[ \dot{s} \tilde{\theta}^T \phi d - k,\text{sgn}(s) ]其中θ̃ θ - θ̂是参数估计误差。这个式子非常清晰滑模面动态只受参数估计误差和扰动的影响而这两者正是切换项要对抗的对象。2.3 Lyapunov稳定性分析与自适应律推导现在进入最核心的环节怎么设计自适应律让θ̂朝着正确的方向更新选取Lyapunov函数[ V \frac{1}{2}s^2 \frac{1}{2}\tilde{\theta}^T \Gamma^{-1} \tilde{\theta} ]其中Γ是正定的自适应增益矩阵通常取对角阵。第一项代表滑模面的收敛性第二项代表参数估计误差的有界性。对V求导[ \dot{V} s\dot{s} \tilde{\theta}^T \Gamma^{-1} \dot{\tilde{\theta}} ]把前面得到的ṡ代入[ \dot{V} s\left(\tilde{\theta}^T \phi d - k,\text{sgn}(s)\right) - \tilde{\theta}^T \Gamma^{-1} \dot{\hat{\theta}} ]因为θ是常数所以的导数等于负的θ̂的导数这一项合并成[ \dot{V} \tilde{\theta}^T \left( s\phi - \Gamma^{-1}\dot{\hat{\theta}} \right) sd - k|s| ]看这个式子如果我们让括号里的部分等于零参数估计误差项就被消掉了。于是自适应律呼之欲出[ \dot{\hat{\theta}} \Gamma s \phi ]把这条自适应律代回V的导数[ \dot{V} sd - k|s| \le |s| \cdot (\bar{d} - k) ]只要切换增益k大于扰动上界d̄就有V̇ ≤ 0闭环系统在Lyapunov意义下稳定。这意味着跟踪误差s必然收敛到零参数估计误差θ̃有界。整条推导闭合没有漏洞。2.4 从符号函数到饱和函数抖振的数学根源与工程妥协严格来说上面这个控制器用的是符号函数sgn(s)。它的数学性质很好但工程上有个致命问题当s在零附近反复穿越时符号函数输出在1和-1之间跳变这会在控制量里引入极高频率的切换分量也就是抖振。实际仿真中数值求解器对这个高频切换非常敏感ode45的步长会被压得很小仿真速度慢得让人怀疑人生。更麻烦的是把这样的控制输出接到真实执行器上机械结构会很快疲劳损坏。工程上最常见的处理方式是把符号函数换成饱和函数sat函数[ \text{sat}(s) \begin{cases} s/\phi, |s| \le \phi \ \text{sgn}(s), |s| \phi \end{cases} ]当系统状态进入边界层|s| ≤ φ后切换项变成线性反馈控制量不再跳变抖振大幅缓解。代价是系统不再是严格的滑模运动跟踪误差只能收敛到边界层内而不是精确到零。这里有个典型的取舍φ太小抖振没压住φ太大稳态精度下降。具体怎么定我放到第4章结合仿真数据讲。3. Matlab代码实现被控对象、控制律与仿真主循环3.1 为什么用ode45而不是自己写递推自适应滑模控制的仿真看起来是每个时刻算一次控制量、更新一次参数估计但我的建议是直接用Matlab的ODE求解器来做整体求解。原因有两点第一被控对象加自适应律以后是一个连续时间系统状态向量由系统状态和参数估计值共同组成它们之间的耦合是连续变化的。如果用固定步长手写欧拉递推步长不好选选大了误差大选小了速度慢。ode45带自适应步长控制能够在误差允许范围内自动调整步长精度和速度都更有保障。第二和控制器相关的符号函数、饱和函数会在某些时间段内引起系统动态的快速变化ODE求解器能感知到这种变化并自动加密步长不容易出现漏掉关键动态的问题。3.2 状态向量设计把参数估计值也塞进状态里为了让ode45同时求解系统状态和参数估计我把状态向量设计成z [x; dx; theta_hat1; theta_hat2; theta_hat3]z(1)和z(2)是被控对象的位置和速度z(3)到z(5)是三个未知参数的估计值。每调用一次导数函数就先从z里解出当前的状态和参数估计值然后依次计算滑模面、控制律和自适应律最后返回完整的导数向量。这种写法和Simulink的S-Function思路类似但比S-Function轻量得多改参数、改模型都方便也更适合作为学习用的模板。3.3 可直接运行的完整代码我用的被控对象是一个带三次非线性项、线性刚度和线性阻尼的二阶系统这也是控制论文里常见的benchmark模型[ \ddot{x} \theta_1 x^3 \theta_2 x \theta_3 \dot{x} u d(t) ]其中θ真实值设为[1.2; -0.5; 0.8]扰动d 0.05sin(10t)。期望轨迹是x_d 0.5sin(t)。控制目标是让系统输出x跟踪这条正弦轨迹。先看主脚本% main_adaptive_smc.m % 参数不确定非线性系统的自适应滑模控制 - 主脚本 clear; clc; close all; %% 被控对象真实参数仿真中未知仅用于生成系统动态 theta_true [1.2; -0.5; 0.8]; b 1.0; % 控制增益标称已知 %% 控制器参数 c 5.0; % 滑模面系数 k 0.8; % 切换增益需大于扰动上界 gamma 1.5; % 自适应增益标量 phi 0.05; % 边界层厚度 %% 期望轨迹函数 xd (t) 0.5 * sin(t); xd_dot (t) 0.5 * cos(t); xd_ddot (t) -0.5 * sin(t); %% 初始状态 x0 0.3; % 初始位置 dx0 -0.2; % 初始速度 theta_hat0 [0; 0; 0]; % 参数估计初始值 z0 [x0; dx0; theta_hat0]; %% 积分求解 tspan [0 20]; options odeset(RelTol, 1e-6, AbsTol, 1e-8); [t, z] ode45((t, z) odefun_adaptive_smc(t, z, theta_true, b, c, k, gamma, phi, xd, xd_dot, xd_ddot), ... tspan, z0, options); %% 提取结果 x z(:, 1); dx z(:, 2); theta_hat z(:, 3:5); %% 重新计算控制量和滑模面用于绘图 s zeros(size(t)); u zeros(size(t)); for i 1:length(t) e x(i) - xd(t(i)); de dx(i) - xd_dot(t(i)); s(i) c * e de; phi_reg [x(i)^3; x(i); dx(i)]; u_eq (-c * de - theta_hat(i, :) * phi_reg xd_ddot(t(i))) / b; u_sw -k * sat(s(i), phi) / b; u(i) u_eq u_sw; end %% 绘图 figure(Position, [100 100 1000 800]); subplot(2, 2, 1); plot(t, x, b-, LineWidth, 1.2); hold on; plot(t, xd(t), r--, LineWidth, 1.2); xlabel(t (s)); ylabel(x); legend(实际轨迹, 期望轨迹); title(输出跟踪); grid on; subplot(2, 2, 2); plot(t, x - xd(t), b-, LineWidth, 1.2); xlabel(t (s)); ylabel(e); title(跟踪误差); grid on; subplot(2, 2, 3); plot(t, s, b-, LineWidth, 1.2); xlabel(t (s)); ylabel(s); title(滑模面); grid on; subplot(2, 2, 4); plot(t, u, b-, LineWidth, 1.2); xlabel(t (s)); ylabel(u); title(控制量); grid on; figure(Position, [100 100 1000 400]); plot(t, theta_hat(:, 1), r-, LineWidth, 1.2); hold on; plot(t, theta_hat(:, 2), g-, LineWidth, 1.2); plot(t, theta_hat(:, 3), b-, LineWidth, 1.2); plot(t, theta_true(1) * ones(size(t)), r--, LineWidth, 1); plot(t, theta_true(2) * ones(size(t)), g--, LineWidth, 1); plot(t, theta_true(3) * ones(size(t)), b--, LineWidth, 1); xlabel(t (s)); ylabel(参数估计值); legend($\hat{\theta}_1$, $\hat{\theta}_2$, $\hat{\theta}_3$, ... 真实$\theta_1$, 真实$\theta_2$, 真实$\theta_3$, Interpreter, latex); title(参数自适应估计); grid on;再看导数函数和被控对象% odefun_adaptive_smc.m % 自适应滑模控制闭环系统的导数函数 function dz odefun_adaptive_smc(t, z, theta_true, b, c, k, gamma, phi, xd, xd_dot, xd_ddot) % 从状态向量中解出当前状态与参数估计 x z(1); dx z(2); theta_hat z(3:5); % 期望轨迹 xd_val xd(t); xd_dot_val xd_dot(t); xd_ddot_val xd_ddot(t); % 跟踪误差与滑模面 e x - xd_val; de dx - xd_dot_val; s c * e de; % 回归向量对应未知参数部分 phi_reg [x^3; x; dx]; % 控制律等效控制 切换控制 u_eq (-c * de - theta_hat * phi_reg xd_ddot_val) / b; u_sw -k * sat(s, phi) / b; u u_eq u_sw; % 被控对象真实动态theta_true是未知的真实参数 d 0.05 * sin(10 * t); % 外部扰动 ddx theta_true * phi_reg b * u d; % 自适应律d(theta_hat)/dt gamma * s * phi dtheta_hat gamma * s * phi_reg; dz [dx; ddx; dtheta_hat]; end % 饱和函数 function s_out sat(s, phi) if abs(s) phi s_out s / phi; else s_out sign(s); end end跑完这段代码后会得到四个图输出跟踪曲线、跟踪误差曲线、滑模面曲线、控制量曲线以及一组参数估计值的变化曲线。第一次跑通之后建议你系统地改几个参数看看曲线是怎么变的这个手感比看十篇论文都有用。4. 仿真结果判读与三组关键参数的整定经验4.1 从仿真曲线判断控制效果的三个维度第一眼看仿真结果时不要只盯着跟踪误差。我习惯按下面三个维度来判读第一维度是跟踪误差的收敛速度和稳态精度。理想状态下可以看到误差在前1-2秒内快速衰减之后维持在零附近的小范围内波动。这个波动范围的大小直接受边界层厚度φ影响φ越小波动越小但控制量越抖两者必须平衡。第二维度是滑模面s的变化。值得关注的不是s有没有收敛到零而是s到底走了哪条路径。如果s直接顺着零线平滑前进说明等效控制起主导作用如果s在零线附近来回穿越说明切换项一直在工作系统处于典型的抖振状态。后一种情况即使跟踪误差看着还行执行器压力其实很大。第三维度是控制量u的形态。好的控制曲线不应该出现规则的锯齿状高频振荡。如果看到高频大幅振荡第一优先检查切换增益k是不是偏大第二检查φ是不是太小第三才考虑系统本身是不是有未建模的高频动态。4.2 边界层厚度与滑模面系数、切换增益的关系我整理了这三组参数还有自适应增益γ它们各自的过大和过小会造成什么现象见下面的表参数代表含义偏大时的现象偏小时的现象c滑模面收敛速率误差收敛快但控制量噪声放大可能出现高频小抖振收敛慢系统对误差反应迟钝k切换增益控制量抖动剧烈仿真速度变慢抗扰动能力下降可能无法进入滑模面φ边界层厚度抖振抑制好但稳态误差变大稳态精度高但控制量抖动明显γ自适应增益参数估计收敛快但可能引起估计振荡估计收敛慢初期跟踪误差大先说c。它的物理含义是滑模面上误差动态的阻尼频率。c取3-5在正弦跟踪这类平滑参考输入下比较常用如果参考轨迹含有快速变化的成分c可以适当调大但别超过系统能承受的带宽否则噪声带来的影响会超过收益。k的选取原则是略大于扰动上界但不需要大太多。很多人从传统滑模控制转过来的时候习惯性把k取得很大总觉得越多越稳。在自适应滑模控制里完全没有必要因为参数不确定性已经被自适应项处理掉了切换项只需要对付残差和外部扰动。我在前面例子里扰动幅度是0.05k取0.8已经非常富裕实际用的时候k取扰动上界的1.5到2倍就够。φ的经验取值在0.01到0.1之间取决于你对稳态精度的要求。有一个快速判断方法先把φ设得比较大比如0.1跑通仿真确定系统稳定后把φ逐步减半观察控制量和误差曲线的变化直到误差还在可接受范围、但控制量还没有明显抖振时这组参数就是不错的折中。4.3 参数估计曲线别指望它一定收敛到真实值这里必须泼一盆冷水。很多人第一次看到参数估计曲线后第一反应是为什么θ̂₁没有收敛到真实的1.2这是对自适应控制最常见的误解。自适应律只能保证参数估计误差有界不能保证估计值收敛到真实值。能收敛到真实值的条件是参考输入满足持续激励Persistent Excitation也就是输入信号要足够丰富包含足够的频带成分。正弦信号对三参数系统通常能提供一定的激励但可能不足以把所有参数都精确辨识出来。更实际的理解是参数估计值是控制器用来实现跟踪目标的内部变量它收敛到维持系统稳定的某个位置即可不必等于物理上的真实参数。只要跟踪误差满足要求参数估计曲线上略微的偏移不必焦虑。真正需要警惕的反而是参数估计值不发散、不振荡如果看到θ̂持续漂移大概率是自适应增益过大或者扰动超出了设计考虑。5. 常见坑与排查链路抖振、参数漂移与求解器问题5.1 抖振压不住时的完整排查路径假设你把代码跑通但控制量抖得没法看。这时候不要急着加大φ或者减小k跟着下面这条链路一步步排查第一步确认抖振是否由数值微分引起。如果你在控制器里直接对x求差分来获得速度高频噪声会被差分放大。我这个例子里状态本身就包含速度不涉及这个问题但很多实际建模会踩这个坑。解决方法是把速度当作状态量而不是用diff去估计。第二步确认符号函数或饱和函数是否有数值问题。饱和函数里我用的是if-else结构在边界层边界处虽然连续但导数不连续ODE求解器会感知到这种不连续而加密步长。如果觉得仿真速度慢可以改用双曲正切函数tanh(s/φ)做平滑逼近控制效果接近但数值特性更好。第三步检查k是否偏大。用我们前面推导的结论k只需要大于扰动上界。如果你设置了很大的扰动才有必要用大k否则多余的部分只会转化为抖振。第四步检查γ是否偏大。自适应增益过大时参数估计值会出现高频小幅振荡这种振荡会顺着控制律传导到被控对象表现为中高频的抖振。把γ从1.5降到0.5左右再看看曲线往往立竿见影。5.2 自适应律可能出现的参数漂移与处理办法前面聊过参数漂移的问题。这里给一个典型场景θ̂的控制初始值是0系统启动阶段跟踪误差较大自适应律会快速修正估计值。但如果外部扰动含有直流分量或者参考输入长期保持不变自适应律会让θ̂缓慢持续变化方向不定甚至在长时间仿真中漂移到让人怀疑系统稳定的程度。工程上最常用的修补方法是自适应律里加一个σ修正项也叫泄漏项[ \dot{\hat{\theta}} \Gamma s \phi - \sigma \Gamma \hat{\theta} ]σ是一个很小正数比如0.001。当参数估计值变大的时候泄漏项会持续把它往回拉防止参数漂移。代价是参数估计不再精确收敛到理想值但对控制系统来说工程上的稳定性比理论上的精确性重要得多。5.3 期望轨迹不光滑导致的控制量突变如果你的期望轨迹是阶跃信号或者分段常值信号ẍd会非常大控制量在切换瞬间会有一个尖峰。这种尖峰对仿真本身没有影响但对理解系统性能会产生干扰。处理办法有两种一是把期望轨迹做平滑预处理比如用斜坡或者S曲线过渡二是在控制律里对ẍd做限幅。前者更符合工程习惯因为实际执行器本来就不能承受无限大的加速度。5.4 从这套方法往后续做的三个方向跑通这套自适应滑模控制之后如果你想继续深入我建议关注下面三个方向。第一个方向是控制器结构的升级。标准滑模面只有比例加微分收敛速度是指数级的。后续可以去了解非奇异终端滑模控制它能让误差在有限时间内收敛到零收敛速度大幅提升但设计复杂度也更高。第二个方向是估计器与观测器的融合。参数不确定只是一个维度如果系统状态本身就不可测就需要设计滑模观测器或者扩展状态观测器ESO来同时估计状态和总扰动。这类方法在电机控制和飞行控制中非常常见。第三个方向是控制增益的自适应化。本文中的切换增益k是固定常数如果外部扰动幅值变化范围很大固定k还是不够理想。可以进一步的思路是设计自适应律让k随滑模面大小在线调整既保证瞬态响应速度又避免稳态时的高频切换。我个人在实际操作中还有一个体会这套代码框架稍微改改就能变成很多其他控制的模板。比如把饱和函数换成死区函数就变成带死区的准滑模控制把自适应律换成神经网络权值更新就是自适应神经网络滑模控制的雏形。先把这一套吃透后续再往其他方向扩展会顺很多。
