先说个我自己的经历。之前做一套小型机械臂的位置跟踪,动力学参数表上写的负载惯量是0.02,实际装上异形工件之后惯量直接翻倍,摩擦系数也跟着变。原来用固定参数设计的滑模控制器,空载时收敛得很漂亮,重载工况下一上来就剧烈抖振,电机电流噪声大得离谱。也就是从那时候起,我意识到一个问题:控制器设计时把参数不确定性“算进去”,和“假设参数是准的”,完全是两种设计哲学。这篇文章想聊的,就是针对参数不确定这类问题非常实用的一招——自适应滑模控制,以及它在Matlab里的完整落地过程。
文章会从参数不确定给传统滑模带来的核心矛盾讲起,把自适应滑模的设计推导一步步拆开,再给出可直接运行仿真的Matlab代码,最后分享调参过程中我踩过的坑。适合正在做非线性控制、机器人控制,或者刚接触滑模控制但被抖振和参数摄动折磨过的同学,也适合想把“自适应+滑模”这套组合真正跑通并读懂背后逻辑的工程师。
1. 先把问题说透:参数不确定给滑模控制出了哪些难题
1.1 名义模型与真实模型之间的“缝隙”
几乎所有基于模型的控制方法,第一步都是建模。但工程里的模型,本质上都是“名义模型”——惯量、阻尼系数、增益这些参数,要么来自离线辨识,要么来自厂家给的标称值。等你把控制器搬到真实系统上,负载变了、温度变了、部件老化了,参数就会悄悄偏离名义值。
比如电机驱动系统,电枢电阻会随温度漂移;液压系统的流量系数随油温变化;飞行器控制里的气动导数随马赫数和迎角大幅改变。这些变化如果写入控制器设计时的标称参数,那么按标称参数算出来的反馈增益,在真实系统上就不再匹配。PID还好说,鲁棒性主要靠积分项兜底;但滑模控制这类基于模型的方法,参数偏差会直接影响等效控制项的准确性。
这里的关键问题在于:参数到底是“常值未知”还是“时变未知”。常值未知可以用自适应来处理,控制器一边控制一边估计参数;时变未知且变化较快时,自适应往往跟不上,就得上扰动观测器或者鲁棒项硬扛。本文讨论的是前者——参数不确定但变化缓慢,这也是自适应滑模最典型的应用场景。
1.2 传统滑模控制的两大痛点:上界假设与抖振
滑模控制的核心思想很直观:设计一个滑模面,再用控制量把系统状态“拍”到滑模面上,之后状态就沿着滑模面滑向原点。这个“拍”的过程靠的是切换项,也就是带符号函数的非线性反馈。
传统滑模的鲁棒性,来源于切换增益必须大于系统不确定性幅值的上界。这句话说起来简单,做起来全是问题:
- 上界怎么求?参数波动范围往往只能凭经验估,估大了切换增益虚高,变成“杀鸡用牛刀”;估小了,扰动压不住,滑模面根本到达不了。
- 高增益切换的代价是什么?是抖振。切换项高频率在正负之间跳变,控制器输出不平滑,执行机构跟着高频动作,噪声、磨损、温升全来了。
我做机械臂实验时就遇到过:理论上η取0.8足够,但为了保险我取到2,结果追踪误差确实小了,但力矩指令抖得像是拿正弦波叠白噪声,驱动器过流报警直接触发。后来才明白,传统滑模设计里“为了保证鲁棒性,往大了取”这个经验,在实际系统中是要付出真金白银代价的。
1.3 自适应滑模的思路:一边控制一边辨识
自适应滑模解决这个问题的思路非常优雅:既然参数不准,那我就不假设它准,直接在控制过程中对参数做在线估计,把“不确定的部分”变成“估计值加估计误差”。估计误差项再交给切换项去处理,切换增益只需要覆盖估计误差而不需要覆盖整个参数不确定范围。
打个比方,传统滑模像是蒙着眼睛走路,每一步都准备跨很大的幅度防止踩空——所以每一步都很用力、很抖。自适应滑模相当于边走边摘下一点点眼罩,看清楚地面以后,步伐就可以越来越稳、越来越小力。切换增益的理论下界可以降到很小,抖振问题自然缓解。
这个思路的核心元器件是参数自适应律——它解决“怎么调参数”的问题,而滑模项解决“估计误差怎么处理”的问题。两者并不是简单的叠加,而是通过Lyapunov函数统一设计出来的,这一点在下一节详细展开。
2. 自适应滑模控制的设计推导:每一步都有依据
2.1 系统模型与匹配条件
用一个单关节机械臂模型来作为大家都能参照的具体对象,方程形式取:
ẋ₁ = x₂
ẋ₂ = θ₁sin(x₁) + θ₂x₂² + (1 + 0.5sin(x₁))u + d(t)
其中 x₁ 是角度,x₂ 是角速度,u 是控制力矩;θ₁、θ₂ 是未知参数,分别代表重力项系数和离心/科氏项系数;d(t) 是有界外部扰动。这里特意把控制输入前面的系数设计成带非线性项的形式,让问题更接近真实系统——很多教材里假设“输入增益是完全已知常数”,这在实际里很少见。
这里有一个非常重要的前提,叫匹配条件:不确定参数必须与控制输入出现在同一个通道中。直观理解就是:你施加的控制力,和参数不确定性造成的误差力,作用在同一个自由度上。如果系统存在未建模动态直接作用在滑模面上(非匹配不确定),自适应滑模就不够了,要考虑反步法或者动态面控制。这一点在扩展方向里再展开。
2.2 滑模面与误差动态
定义跟踪误差 e = x₁ - x_d,误差导数 de = x₂ - ẋ_d。设计滑模面为:
s = de + c·e
其中 c > 0 是滑模面斜率系数。当系统到达滑模面 s = 0 后,误差动态满足 de = -c·e,也就是说误差按指数速率收敛,收敛速度完全由 c 决定。c 越大,到达滑模面之后的误差收敛越快,但同时也会放大噪声对滑模面的影响。谁在真实系统里用过谁都知道,c 取到 10 以上的时候,传感器噪声直接让滑模面“毛边”,抖振从滑模面这里就开始酝酿了。
对 s 求导:
ṡ = dde + c·de = ẋ₂ - ẍ_d + c·de
代入系统方程:
ṡ = θ₁sin(x₁) + θ₂x₂² + (1 + 0.5sin(x₁))u + d(t) - ẍ_d + c·de
控制的目标就很清楚了:设计 u,让 ṡ 向 0 的方向收敛,并最终把 s 压到 0。
2.3 控制律与自适应律:从Lyapunov函数反推
设计Lyapunov函数,这一步是整个方法的点睛之处:
V = 0.5s² + 0.5θ̃₁²/γ₁ + 0.5θ̃₂²/γ₂
其中 θ̃₁ = θ̂₁ - θ₁、θ̃₂ = θ̂₂ - θ₂ 是参数估计误差;θ̂₁、θ̂₂ 是自适应律给出的参数估计值;γ₁、γ₂ 是自适应增益,后面仿真里再具体讨论取值。
对 V 求导:
V̇ = s·ṡ + θ̃₁·θ̂̇₁/γ₁ + θ̃₂·θ̂̇₂/γ₂
把 ṡ 的表达式代进来,并且控制律取成:
u = [ẍ_d - c·de - θ̂₁sin(x₁) - θ̂₂x₂² - k·s - η·sat(s/φ)] / (1 + 0.5sin(x₁))
先看等效控制部分——前四项的作用是:补偿期望轨迹加速度、滑模面动态、参数估计值对应的模型项。再看鲁棒项部分——-k·s 是常规线性阻尼项,负责加快滑模面收敛;-η·sat(s/φ) 是带边界层的切换项,负责压制参数估计误差和外部扰动。没有这最后一项,整个V̇就没办法保证小于等于0。
把控制律代入 V̇,经过代数化简后 V̇ 变成:
V̇ = s·[θ̃₁sin(x₁) + θ̃₂x₂² + d(t) - k·s - η·sat(s/φ)] + θ̃₁·θ̂̇₁/γ₁ + θ̃₂·θ̂̇₂/γ₂
此时为了让 θ̃₁、θ̃₂ 相关的项互相抵消,得到一个“漂亮”的负定形式,自适应律被反推出来:
θ̂̇₁ = -γ₁·s·sin(x₁)
θ̂̇₂ = -γ₂·s·x₂²
注意到 θ̂ 带的是负号,因为 V̇ 中的交叉项是 +θ̃·θ̂̇/γ,而系统返回的是 +θ̃·s·φ 项,取负号才能使两项相消。代入后:
V̇ = -k·s² - η·|s| + s·d(t) ≤ -k·s² - η·|s| + |s|·|d_max|
当 η ≥ |d_max| 时,V̇ ≤ -k·s² ≤ 0。稳定性就证完了:滑模面s最终收敛到边界层内的邻域,同时参数估计误差θ̃在持续激励条件下也收敛到0。
2.4 稳定性证明里的“故事版”解释
上面这套符号推导,很多教程都有,我这里用大白话再把几个关键点讲透:
- Lyapunov函数到底在干嘛?它构造了一个“能量函数”,包含两部分:滑模面的动能(0.5s²)和参数估计误差的“势能”(θ̃²/2γ)。如果这个函数随时间递减,系统就稳定。自适应律的设计不是凭空猜的,而是为了让这个能量函数“递减小得最顺畅”——把所有不想要的交叉项精确抵消掉。
- 为什么自适应律和滑模面s有关?因为 s 是当前控制好坏的度量。s 大说明跟踪误差大,此时参数估计应该大步修正;s 小说明系统已经接近滑模面,参数修正自然放缓。这种形式叫“梯度型自适应律”,本质上就是让参数估计沿着能量函数下降最快的方向滚动。
- η 的理论下界为什么可以比传统滑模小?传统滑模的切换增益要覆盖 θ₁、θ₂ 的全范围不确定性;自适应滑模的切换增益只需要覆盖参数估计误差加上外部扰动。参数估计误差在收敛阶段会越来越小,所以实际需要的 η 可以取小很多。这就是抖振改善的根本原因。
3. Matlab代码实现:从公式到可运行仿真
3.1 仿真场景搭建
我选择的仿真对象就是 2.1 节那个非线性系统。真实参数取 θ₁ = 2.0、θ₂ = 1.5,而控制器不知道这两个值,初始猜测都设为 0。外部扰动取 d(t) = 0.4·sin(3t),也就是说最大扰动幅值0.4,切换增益 η 取 0.5,理论上是足够的。
期望轨迹取 x_d = 0.5·sin(t),这样系统需要持续跟踪一个时变信号,更能体现控制性能。滑模面系数 c = 5,反馈增益 k = 2,边界层厚度 φ = 0.05,自适应增益 γ₁ = 15、γ₂ = 10。
系统初始状态取 x₁(0) = 0、x₂(0) = 0,即初始时刻已经位于期望轨迹起点附近,重点关注跟踪维持能力与参数辨识过程。控制器初始参数估计设 0,这样能看出自适应从“完全无知”到“逐渐学会”的全过程。
3.2 核心代码:控制器与自适应律的Matlab实现
完整的仿真用函数文件加主程序的方式组织,这样结构清晰,便于改参数做对比实验。先写系统的微分方程函数:
function [dx, u, s] = adaptive_smc_model(x, theta_hat, t, params) % x: [位置; 速度] % theta_hat: [θ1估计; θ2估计] x1 = x(1); x2 = x(2); theta1_hat = theta_hat(1); theta2_hat = theta_hat(2); theta1_true = params.theta1_true; theta2_true = params.theta2_true; % 期望轨迹与导数 xd = 0.5 * sin(t); dxd = 0.5 * cos(t); ddxd = -0.5 * sin(t); % 跟踪误差与滑模面 e = x1 - xd; de = x2 - dxd; s = de + params.c * e; % 控制律 mu = 1 + 0.5 * sin(x1); u_eq = (ddxd - params.c * de - theta1_hat * sin(x1) ... - theta2_hat * x2^2) / mu; u_sw = (-params.k * s - params.eta * sat_fun(s / params.phi)) / mu; u = u_eq + u_sw; % 自适应律 dtheta1 = -params.gamma1 * s * sin(x1); dtheta2 = -params.gamma2 * s * x2^2; % 真实系统动态(含参数摄动与扰动) dx1 = x2; d = params.d_amp * sin(params.d_freq * t); dx2 = theta1_true * sin(x1) + theta2_true * x2^2 + mu * u + d; dx = [dx1; dx2; dtheta1; dtheta2]; end function y = sat_fun(x) % 饱和函数,替代sign以抑制抖振 if abs(x) <= 1 y = x; else y = sign(x); end end饱和函数的引入非常关键。如果直接用 sign(s),控制器在滑模面附近会高频切换,ode45 为了让误差降到容差内会不断缩小步长,仿真速度慢得没法看,而且得到的结果也过度理想化——实际系统的执行机构根本不可能跟得上这种高频切换。用饱和函数,滑模面附近相当于做了一个线性过渡,控制器输出连续,仿真也稳定。
主程序负责设置参数、调用积分、绘图:
clear; clc; close all; %% 参数设置 params.theta1_true = 2.0; params.theta2_true = 1.5; params.c = 5; params.k = 2; params.eta = 0.5; params.phi = 0.05; params.gamma1 = 15; params.gamma2 = 10; params.d_amp = 0.4; params.d_freq = 3; %% 初始状态与参数估计初值 x0 = [0; 0]; % 系统状态 theta0 = [0; 0]; % 参数估计初值 init = [x0; theta0]; %% 数值积分 t_span = [0 10]; opts = odeset('RelTol', 1e-5, 'AbsTol', 1e-6, 'MaxStep', 0.01); [t, y] = ode45(@(t, y) adaptive_smc_model(y(1:2), y(3:4), t, params), ... t_span, init, opts); %% 结果提取与绘图 x1 = y(:,1); x2 = y(:,2); theta1_hat = y(:,3); theta2_hat = y(:,4); xd = 0.5 * sin(t); % 跟踪效果 figure(1); plot(t, x1, 'b-', 'LineWidth', 1.5); hold on; plot(t, xd, 'r--', 'LineWidth', 1.2); legend('实际轨迹', '期望轨迹'); xlabel('时间 (s)'); ylabel('位置'); title('轨迹跟踪效果'); grid on;3.3 仿真结果怎么读:三个信号三种信息
仿跑完主要看三组曲线,每一组都有它自己的“潜台词”:
第一组:跟踪曲线。初期有一个短暂的收敛过程,误差大约在0.3秒内压到接近0,之后稳态误差维持在边界层厚度决定的范围内。如果跟踪曲线出现周期性波动且和期望轨迹的周期同步,说明滑模面 s 偏大,检查 η 是否足够覆盖扰动。如果出现高频锯齿,十有八九是边界层厚度设太小、切换项在工作。
第二组:参数估计曲线。这是自适应控制最有意思的地方。θ̂₁ 从0开始逐渐爬升,向真实值2.0靠近;θ̂₂ 向1.5靠近。由于期望轨迹是单频正弦,激励条件未必充分,参数估计可能收敛到真实值附近但有一定偏差,这是正常现象。参数估计曲线如果出现振荡发散,基本就是自适应增益太大,或者数值积分刚度过高,需要把 γ 降下来。
第三组:控制量曲线。理想的控制量应该是光滑的、随期望轨迹周期变化。如果控制量在初始阶段有一个大的冲击尖峰,不要慌——那是因为参数估计初值离真实值太远,控制器要额外输出一大块补偿项。如果整个时间段控制量都像锯齿一样抖动,就是切换增益 η 过大或者边界层太薄,抖振没压住。
4. 调参实战:自适应增益、切换增益与抖振的权衡
4.1 γ(自适应增益)到底取多大合适
自适应增益 γ₁、γ₂ 直接决定参数估计的收敛速度。这个参数没有万能的“标准值”,但有一条核心规律:γ不能太大,太大会让参数估计系统失稳;γ可以从小慢慢往上试,只要收敛时间能接受就行。
我实测下来的参考范围:对于本文这种量级的状态变量和参数,γ₁、γ₂ 在 5~50 之间通常表现良好。把 γ 从 10 提到 100,参数估计的收敛速度确实快了,但代价是滑模面 s 在初期会出现明显的振荡,因为参数估计在快速逼近真实值的过程中,控制律里的补偿项也在剧烈变化,形成了“参数估计噪声—控制量—系统响应”之间的正反馈。严重的时候,参数估计直接发散到很大值,仿真直接报错。
判断 γ 是否过大有一个实用技巧:跑一次仿真,看参数估计曲线是否单调逼近真实值。如果曲线像弹簧一样来回震荡好几轮才稳定,说明 γ 偏大。如果参数估计到仿真结束还没逼近真实值,说明 γ 偏小,可以让仿真时间拉长确认是收敛慢还是收敛方向有误。
4.2 边界层厚度φ与抖振的“交易”
边界层是滑模控制里面最赤裸裸的一个交易:你拿一点稳态精度,换一大截执行机构的寿命。
φ 太小,等效于在滑模面附近做了一个很“尖”的切换,抖振几乎全部保留。φ 太大,滑模面附近的等效线性反馈太弱,系统状态会在边界层内游走,稳态误差增大。我常用的经验是:φ 取期望跟踪精度量级的 2~5 倍。比如你希望稳态误差在 0.01 rad 以内,φ 可以先试 0.02~0.05。把 φ 从 0.01 调大到 0.1,控制量的高频分量明显下降,但跟踪误差的稳态值从 0.005 涨到 0.02,就看你的系统更在意哪一个。
还有一个细节,很多人忽略:边界层和η是配合使用的。η 决定“最大推力”,φ 决定“推力切换的陡峭程度”。同样把 η 从 0.5 提到 0.8,φ 如果太小,边界层内的控制量仍然会剧烈波动;把 φ 同步调大,波动才能被缓和下来。所以调参不是单拧一个旋钮,而是要同时看 η、φ、k 三个参数的组合效果。
4.3 一次典型的调试全流程记录
记录一次从零开始的完整调参过程,供各位对照。第一次仿真,我把参数取为 c=5、k=1、η=0.8、φ=0.01、γ₁=10、γ₂=10,跑完看到三个现象:跟踪曲线稳态误差不错,但控制量曲线明显有锯齿状高频抖动;参数估计曲线在初期的0~0.5秒内有一个很大的过冲,θ̂₁ 一度冲到3.2。
我的逐步操作顺序是:
- 先把 φ 从 0.01 调到 0.05,锯齿明显减轻,控制量平滑了很多,稳态误差从0.008涨到0.012,还在可接受范围。
- 再把 γ₁、γ₂ 从10降到6,参数估计的过冲降到2.4附近,初期控制量尖峰也小了。
- 最后把 k 从1提到2。k 的主要作用是提供滑模面本身的线性“阻尼”,k 增大后,滑模面收敛更快,参数估计曲线的振荡频率明显下降。
调完以后,跟踪曲线、参数估计曲线、控制量曲线三个目标都达到了我预期:跟踪误差收敛到0.01以内、参数估计在5秒内收敛到真实值附近、控制量全程平滑无高频抖动。
调参顺序的经验总结:先动φ改善抖振,再动γ抑制过冲,最后动k优化整体收敛速度。反过来的话,很容易陷入“改一个坏一个”的循环。
5. 几个容易踩的坑与更好的扩展方向
5.1 仿真与实验里最常见的三个坑
第一个坑:自适应律在参数估计初值很差时,会让控制量瞬间饱和。控制器里 θ̂₁sin(x₁) + θ̂₂x₂² 这一项,在参数估计还没收敛时可能非常大,导致输出力矩远超执行机构上限。仿真软件不会管你执行机构饱和,但真实系统会。解决方案一个是给参数估计加投影映射,把 θ̂ 限制在合理范围内;另一个是在自适应律里加入σ修正(死区修正),防止参数漂移。如果只是做仿真,至少要把控制量限幅加入模型,否则结果过于理想。
第二个坑:持续激励条件决定了参数是否能收敛到真实值。期望轨迹如果是常值信号或者简单斜坡信号,系统状态提供的“信息量”不够,参数估计可能收敛到一个错误的数值,但控制效果依然不错——这是因为控制律里用的是参数估计值,误差被鲁棒项容忍了。你做仿真想看到“参数收敛到真实值”的完美画面,期望轨迹必须包含足够丰富的频率成分,比如多频正弦叠加。
第三个坑:求解器容差设置不当导致自适应律“假的发散”。我遇到过一次,把 ode45 的默认容差跑出来参数估计在高频抖动,但把 RelTol 从 1e-3 改到 1e-6 之后就平滑了。这是因为自适应律和系统动态存在刚度的差异,求解器在默认容差下没有充分分辨这段时间尺度,产生了数值伪振荡。做滑模类仿真时,一定要把 RelTol 设到 1e-5 或更小,并且关注 MaxStep 上限。
5.2 从这篇文章出发还能往哪走
自适应滑模只适合参数变化缓慢的情形,如果参数突变或者存在未建模动态,可以往这几个方向扩展:
- **扰动观测器结合:**用扩张状态观测器(ESO)把外部扰动和未建模动态一并估计出来,自适应律只管参数,分工明确,抖振进一步降低。
- **超螺旋滑模算法:**用连续的超螺旋算法替代传统符号切换,可以在不牺牲鲁棒性的前提下彻底去除抖振,很多现代滑模应用的标准选择。
- **多参数回归的扩展:**把本文两个参数的自适应律扩展到n个参数的线性参数化形式,只需要把回归量矩阵 (\phi(x)) 和参数向量写清楚,控制律结构完全不变,公式推导的骨架完全一致。
这些方向我在实际项目中都有尝试,现阶段从自适应滑模起步,把“为什么这么设计”“为什么这么调参”吃透,再往超螺旋或ESO方向走会轻松很多,因为地基是同一个。
最后再分享一个小经验:做这类仿真,我习惯先跑通一个最简版本再逐步加复杂度。第一次仿真直接上多参数、强扰动、严格轨迹,出了问题你会分不清是控制器设计错了还是参数调得不对。从本文这个单关节模型开始,把自适应滑模的每个信号都看明白、每个参数都调过一遍,再迁移到自己的系统里,会顺手很多。