☰
自适应滑模控制:参数不确定非线性系统的Matlab仿真实现
2026/10/13 20:32:24 网站建设 项目流程

做控制的同行应该都有这种体会:课本上的精确模型到了现场基本靠不住。一个机械臂,空载和抓着重物时动力学参数差出一大截;一套电机驱动系统,温度上来之后摩擦系数、电阻值都会漂移。模型不准、参数不确定,恰恰是非线性系统控制里最常遇到的现实问题。自适应滑模控制正是冲着这个问题来的——滑模保证系统的鲁棒性,自适应把未知参数在线估计出来,两者配合,既不用费劲去离线辨识,又能维持可预期的跟踪性能。这篇文章用 Matlab 从零实现一套自适应滑模控制器,对象是一个带两个未知参数的二阶非线性系统,看完你能直接把这个框架搬到自己的模型上。

1. 先把问题看清楚:参数不确定到底难在哪

1.1 参数不确定性从哪来,为什么绕不开

先说个我自己的经历。某项目里给一个两轴机械臂做轨迹跟踪,实验室里标定好的模型参数,装上不同质量的末端执行器之后,原本调好的控制器突然就跟换了性格一样,跟踪误差肉眼可见地变大。后来查了一圈,问题根本不在硬件,而是模型里的转动惯量、重力矩系数这些参数跟着负载变了。

工程中这种例子太多了:

  • 电机驱动平台:定子电阻随温度升高而漂移,负载转矩随时变化
  • 机械臂、无人机:负载质量、气动阻尼在飞行或作业中不断改变
  • 液压气动系统:油温、气压变化导致等效增益漂移
  • 化工反应器:反应速率常数随温度、催化剂活性变化

这些参数不是说你不想知道,而是离线辨识成本高、环境一变又不准。所以“参数不确定”不是特例,而是常态。设计控制器时如果默认参数精确已知,现场性能就无从保障。这也是为什么鲁棒控制和自适应控制这两种路线在工程里始终有生命力。

1.2 传统滑模控制的局限

滑模控制的优点很直接:设计一个滑模面,然后用不连续控制项把状态“拉到”滑模面上,一旦进入滑模,系统动态就由滑模面方程决定,对匹配扰动和部分模型误差都有很好的抑制能力。

问题是,常规滑模控制律里有个等效控制项,它需要用到模型的解析表达式。模型里带未知参数时,等效控制只能基于名义模型去算,这个偏差会直接反映在系统动态里。为了压住它,一个朴素的办法是把切换增益调大,但增益一大,抖振就跟着来了,控制量高频切换,执行机构磨损、系统激励出未建模动态,结果就是课本里反复警告的那个“性能与抖振的拉扯”。

换句话说,传统滑模是在“不知道参数”的情况下,用一个大增益的鲁棒项硬抗。自适应滑模的思路则是先在线把参数估计出来,让等效控制更贴近真实系统,再用一个规模明显更小的鲁棒项去对付残余误差和扰动。一个靠“蛮力”,一个靠“情报”,这就是两者最本质的区别。

1.3 本文的演示对象

我挑的是一个形式简单但足够说明问题的二阶非线性系统:

x1_dot = x2 x2_dot = a * x2^2 + b * sin(x1) + u + d(t)

其中 a、b 是未知常数,d(t) 是有界时变扰动。控制目标:让 x1 跟踪期望轨迹 x1d(t)。

选它有三个原因:非线性项足够体现“非线性系统”的特点;参数以线性化形式进入方程,适合演示自适应设计;模型本身不复杂,跑 Matlab 很快就能复现。你之后换成别的对象,只要系统能写成关于未知参数线性化的形式(即所谓线性参数化结构),整个框架可以直接平移,这一点后面会重点展开。

2. 自适应滑模控制的核心设计思路

2.1 滑模面:给误差收敛定一个“节奏”

先定义跟踪误差 e = x1 - x1d,则 de = x2 - x1d_dot。我选用一阶滑模面:

s = de + lambda * e, lambda > 0

为什么用这个形式?因为当 s = 0 时,误差动态退化成 de = -lambda * e,这是一个一阶线性微分方程,它的解是指数收敛的,收敛速度由 lambda 决定。也就是说,只要系统状态被“约束”在 s = 0 这条流形上,跟踪误差就必然按指数规律衰减,根本不用管系统原方程里的非线性到底是什么样。这是滑模最漂亮的地方:滑模面上的动态是设计者自己选定的,跟被控对象无关。

lambda 选多大合适?简单说,lambda 越大误差收敛越快,但同时要求控制量有更大的带宽,而且对噪声更敏感。从离散仿真角度,lambda 还受采样步长的限制,一般建议 lambda * dt 远小于 1。我后面仿真里用的 lambda = 8,配合 dt = 0.001,两者乘积 0.008,属于安全范围。如果你的对象是三阶以上系统,滑模面会相应写成 s = (d/dt + lambda)^(n-1) e 的形式,思路完全一致,只是表达更繁琐。

2.2 线性参数化:自适应能工作的关键前提

自适应控制里有个核心假设:未知参数以线性方式进入系统方程。把被控对象写成

x2_dot = theta^T * phi(x) + u + d(t)

其中 theta 是未知参数向量,phi(x) 是已知的回归函数(由模型结构和状态量构成)。对本文这个例子:

theta = [a; b],phi(x) = [x2^2; sin(x1)]

这个形式的意义在于,控制器里的模型项可以用参数估计值 theta_hat 直接构造:f_hat = theta_hat^T * phi(x)。而估计误差对系统的影响可以写成 theta_tilde^T * phi(x),其中 theta_tilde = theta - theta_hat。后面设计 Lyapunov 函数时,这个结构让自适应律的推导变得非常干净。

很多工程系统都满足或近似满足线性参数化条件,机械臂的动力学方程在适当整理后也能写成类似形式。如果你的系统不满足,通常的做法是先做反馈线性化或引入观测器,把问题化成线性参数化结构再套用自适应框架。这一点是判断“自适应滑模能不能用”的第一道门槛,值得在动手前花时间确认。

2.3 控制律和自适应律:从 Lyapunov 函数“长”出来

这是整套设计的核心。先把滑模面的导数写出来:

s_dot = x2_dot - x1d_ddot + lambda * de = theta^T * phi(x) + u + d - x1d_ddot + lambda * de

控制律取:

u = x1d_ddot - lambda * de - theta_hat^T * phi(x) - k * sign(s)

代入后得到:

s_dot = theta_tilde^T * phi(x) + d - k * sign(s)

接下来定义 Lyapunov 函数:

V = 1/2 * s^2 + 1/2 * theta_tilde^T * Gamma^(-1) * theta_tilde

其中 Gamma 是正定对角矩阵,对角线上的元素就是各参数的自适应增益。对 V 求导,并代入 s_dot 的表达式,得到:

V_dot = s * theta_tilde^T * phi(x) + s * d - k * |s| - theta_tilde^T * Gamma^(-1) * theta_hat_dot

注意这里的处理技巧:把第一项里带 theta_tilde 的项和最后一项配对,如果令

theta_hat_dot = Gamma * s * phi(x)

那么这两项刚好抵消,Lyapunov 函数导数就只剩下:

V_dot = s * d - k * |s| <= |s| * (d_max - k)

只要切换增益 k 大于扰动的上界 d_max,V_dot <= 0 就成立,系统在 Lyapunov 意义下稳定,滑模面可达,参数估计误差有界。这个推导过程值得仔细看两遍,因为自适应律不是拍脑袋想出来的,它是为了让 V_dot 里“有害项”被抵消而被推出来的。你换一个 Lyapunov 函数,对应的自适应律往往就不同。

2.4 抖振抑制:符号函数换成饱和函数

按照理论推导,控制律里用的是 sign(s),理想情况下会在滑模面两侧高频切换,仿真里表现为控制量剧烈抖动,实际系统里还会激发未建模高频动态。工程上最常用的办法是用饱和函数 sat(s/eps) 替代符号函数:

sat(s/eps) = sign(s),当 |s| > eps; sat(s/eps) = s / eps,当 |s| <= eps

eps 是边界层厚度。它的代价是:边界层内控制不再是切换控制,鲁棒性有所折扣,稳态误差不会严格为零,但 eps 取得足够小(比如 0.01~0.05)时,这个误差通常可以接受,换来的是控制量平滑很多。抖振抑制是滑模工程化绕不开的一步,后面仿真里我会同时保留符号函数和饱和函数,让你直观看到抖振的差异。

3. Matlab 仿真实现全流程

3.1 仿真环境与参数总览

我用的是常见的 Matlab 桌面环境,脚本编程,不需要额外工具箱。整套程序包括三部分:被控对象模拟脚本、控制器计算、绘图输出。为了可读性,我把被控对象的微分方程写成一个独立的函数文件,主脚本只管控制器和仿真循环。

先列一下关键参数表:

参数符号数值含义
仿真时长T10 s观察参数收敛和稳态跟踪
采样步长dt0.001 s数值积分步长
未知参数 aa_true1.2控制器完全不知道
未知参数 bb_true-0.6控制器完全不知道
扰动幅值d_amp0.4时变正弦扰动
滑模系数lambda8误差收敛速率
切换增益k1.5需大于扰动上界
自适应增益gamma1、gamma23a、b 估计收敛速率
边界层厚度eps0.05抖振抑制
初始状态x1(0)、x2(0)0.5、0起始位置与速度
参数初值a_hat(0)、b_hat(0)0.3、-0.2明显偏离真值

注意 k = 1.5 > d_amp = 0.4,满足稳定性条件。这个“扰动上界”在工程里很难精确知道,一般取一个偏保守的值,后面我会专门讲 k 选多大合适。

3.2 被控对象建模:写成函数文件

先写被控对象的动力学函数,输入是当前状态向量、控制量、仿真时间和真值参数,输出是状态导数:

function dx = plant_dyn(x, u, t, a_true, b_true, d_amp) % 参数不确定的非线性系统 % x(1) = x1, x(2) = x2 % dx2 = a*x2^2 + b*sin(x1) + u + d(t) dx = zeros(2,1); dx(1) = x(2); dx(2) = a_true * x(2)^2 + b_true * sin(x(1)) + u + d_amp * sin(5*t); end

用独立函数有两个好处:一是后面换系统模型时只改这个文件,主程序不用动;二是用 RK4 数值积分时四次求值调用起来很干净。

3.3 主脚本:控制器、自适应律与仿真循环

clear; clc; close all; %% 仿真参数 dt = 0.001; T = 10; t = (0:dt:T)'; n = length(t); %% 被控对象真值(仅仿真模型使用) a_true = 1.2; b_true = -0.6; d_amp = 0.4; %% 控制器参数 lambda = 8; k = 1.5; gamma1 = 3; gamma2 = 3; eps = 0.05; %% 状态与估计初值 x1 = zeros(n,1); x1(1) = 0.5; x2 = zeros(n,1); a_hat = zeros(n,1); a_hat(1) = 0.3; b_hat = zeros(n,1); b_hat(1) = -0.2; u = zeros(n,1); s = zeros(n,1); %% 期望轨迹 x1d = 0.5 * sin(2*t); x1d_dot = 1.0 * cos(2*t); x1d_ddot = -2.0 * sin(2*t); %% 仿真主循环 for i = 1:n-1 e = x1(i) - x1d(i); de = x2(i) - x1d_dot(i); s(i) = de + lambda * e; % 饱和函数替代符号函数抑制抖振 if abs(s(i)) > eps sat_s = sign(s(i)); else sat_s = s(i) / eps; end % 回归函数 phi1 = x2(i)^2; phi2 = sin(x1(i)); % 控制律 u(i) = x1d_ddot(i) - lambda * de - a_hat(i)*phi1 - b_hat(i)*phi2 - k * sat_s; % 自适应律 a_hat(i+1) = a_hat(i) + gamma1 * s(i) * phi1 * dt; b_hat(i+1) = b_hat(i) + gamma2 * s(i) * phi2 * dt; % 被控对象 RK4 数值积分 x_now = [x1(i); x2(i)]; k1 = plant_dyn(x_now, u(i), t(i), a_true, b_true, d_amp); k2 = plant_dyn(x_now + 0.5*dt*k1, u(i), t(i)+0.5*dt, a_true, b_true, d_amp); k3 = plant_dyn(x_now + 0.5*dt*k2, u(i), t(i)+0.5*dt, a_true, b_true, d_amp); k4 = plant_dyn(x_now + dt*k3, u(i), t(i)+dt, a_true, b_true, d_amp); x_new = x_now + (dt/6)*(k1 + 2*k2 + 2*k3 + k4); x1(i+1) = x_new(1); x2(i+1) = x_new(2); end s(n) = s(n-1);

这段代码每一行都能跟前面的理论公式对应上。我建议你自己敲一遍而不是直接复制,因为敲的过程中你会自然去思考“这个量是什么、为什么放在这里”,理解深度跟直接跑通完全是两个档次。

3.4 数值积分与采样步长:为什么用 RK4

有人会问:控制器是连续的,仿真里直接欧拉法行不行?行,但精度差。滑模控制本身对离散化就很敏感,控制律里含有 sign 或 sat 这类不连续项,如果数值积分精度不够,误差会被积分误差进一步放大,甚至出现虚假的抖动。RK4 每步做四次函数求值,误差比欧拉小几个数量级,对于 0.001 的步长和 10 秒仿真时长,计算开销完全可以忽略。

另外提醒一点:仿真里的 dt 要跟自适应律里的积分步长保持一致,这是实现上很容易出错的地方。自适应律本质是连续系统,我代码里写的是 a_hat(i+1) = a_hat(i) + gamma1 * s(i) * phi1 * dt,相当于显式欧拉积分。如果你改用更精确的积分方法,比如用 RK4 来更新参数估计,稳定性会更好,但对绝大多数工程仿真,欧拉更新配合小步长已经足够。

注意:自适应律里的 dt 与仿真循环里的 dt 必须完全一致。我曾经见过有人把这俩搞混,结果参数估计时快时慢,误差曲线怎么看都不对劲。

3.5 绘图:结果可视化与对比

仿真结束后,画四幅图:状态跟踪对比、跟踪误差、参数估计曲线、控制量。再加一个对比实验,把自适应闭环去掉(直接把 a_hat、b_hat 固定为初值)同时把 k 调大,用来展示“纯滑模”与“自适应滑模”的差异。

%% 结果绘图 figure('Position', [100 100 900 700]); subplot(2,2,1); plot(t, x1d, '--', 'LineWidth', 1.5); hold on; plot(t, x1, '-', 'LineWidth', 1.2); xlabel('时间 (s)'); ylabel('x_1'); legend('期望轨迹 x_{1d}', '实际轨迹 x_1', 'Location', 'best'); title('轨迹跟踪对比'); grid on; subplot(2,2,2); plot(t, x1 - x1d, 'LineWidth', 1.2); xlabel('时间 (s)'); ylabel('跟踪误差 e'); title('跟踪误差曲线'); grid on; subplot(2,2,3); plot(t, a_hat, 'LineWidth', 1.2); hold on; plot(t, b_hat, 'LineWidth', 1.2); yline(a_true, '--'); yline(b_true, '--'); xlabel('时间 (s)'); legend('a 估计', 'b 估计', 'a 真值', 'b 真值', 'Location', 'best'); title('参数估计收敛过程'); grid on; subplot(2,2,4); plot(t, u, 'LineWidth', 1.2); xlabel('时间 (s)'); ylabel('控制量 u'); title('控制量输出'); grid on;

单看代码其实没多少,但每一步都对应前面的控制理论。把理论公式一行行映射到代码上,是学习这类控制方法最有效的方式。

4. 仿真结果分析与调参经验

4.1 结果怎么看:三条曲线的含义

跑完仿真,从图纸上你应该重点抓三个现象。

第一,跟踪误差在初期有一小段明显波动,随后迅速收敛到零附近的一个小邻域。这个波动源于参数估计初值与真值偏差较大,自适应律还没来得及把参数拉回来。随着估计值逼近真值,误差迅速减小。你如果看到误差一直不收敛,首先怀疑方向,要么极性反了,要么回归函数符号错了。

第二,参数估计曲线最终收敛到真值附近,但会伴随小的周期性波动。这是因为扰动 d(t) 是时变的,自适应律无法彻底抵消它,参数估计会绕着真值小幅波动。要看参数收敛质量,不要只看终点值,更该看趋势和波动幅度。若波动过大,通常是自适应增益偏大或者扰动太大。

第三,控制量曲线相对平滑,没有高频毛刺,这就是饱和函数起作用的结果。你可以把边界层 eps 改成 0 重新跑一遍,控制量会变成剧烈的锯齿状,那就是教科书标准的抖振。两相对比,你就能直观体会到为什么工程里很少用纯符号函数。

4.2 参数收敛与增益的“跷跷板”

自适应增益 gamma 和切换增益 k 是这套方法里最需要揣摩的两个旋钮。

gamma 越大,参数收敛越快,但也会放大回归函数里的噪声和扰动影响,估计曲线波动更明显。gamma 太小,参数估计迟迟跟不上,跟踪误差收不进去。比如在本文对象上,gamma 取 0.5 时参数估计大约需要 5 秒以上才能接近真值,取 3 时 2 秒左右就基本到位,再取 10 时估计曲线明显出现振荡。

gamma 取值参数收敛时间估计曲线表现
0.55 秒以上平缓但有明显滞后
3约 2 秒收敛快,短期小幅波动
10约 0.5 秒振荡明显,可能诱发抖振

k 的取值原则是“尽量小但必须大于扰动上界”,这个上界是保守估计。k 如果小于扰动上界,V_dot 无法保证非正,系统可能出现稳态误差或极限环。k 如果远超扰动上界,控制量幅度变大,边界层外的切换更剧烈,抖振指标变差。工程上我习惯的做法是先给一个扰动上界的 1.5 倍左右,跑仿真看误差,再逐步往下压。

4.3 边界层厚度怎么取

eps 是抖振抑制和稳态误差之间的交易。从理论上讲,边界层内的控制变成线性反馈,滑模的强鲁棒性在边界层内是“打折扣”的,因此 eps 越大,稳态误差和扰动抑制能力越差;eps 太小,饱和函数近似符号函数,抖振又回来了。

一般来说,eps 取 0.01 到 0.1 这个量级比较常见,具体取决于你对控制量平滑度的要求和系统的扰动水平。调参时可以先从 eps = 0.01 开始,看控制量抖振,再逐步加大,直到控制量曲线肉眼可见地平滑,同时误差还能接受。注意 eps 和 k 是联动的:eps 增大后等效于降低了边界层内的鲁棒增益,如果感觉扰动抑制变差,需要同步微调 k。

4.4 参数初值与激励条件的影响

自适应律保证的是估计误差有界和系统稳定,并不保证参数一定收敛到真值。实际工程里参数收敛还跟参考信号的“激励充分程度”有关。

这里有个很实用的小经验:如果你想在仿真里验证参数辨识能力,要让期望轨迹足够“丰富”,正弦信号就比阶跃信号好,因为回归函数持续被激励。阶跃跟踪时状态量变化小,x2^2 和 sin(x1) 很快就趋于平稳,参数可能收敛不了或者收敛得很慢。这一点在做仿真验证时必须心里有数,否则你会误以为是控制器写错了,其实是激励不足。更进一步,参数初值设置为偏离真值一倍以上,能更清楚地看到自适应过程,但也不要偏离到让控制律初期的等效控制严重失配,否则系统初始误差可能过大。

5. 常见问题与避坑指南

5.1 问题速查表

现象可能原因排查与解决
误差发散、系统不稳定滑模增益 k 小于扰动上界增大 k,再检查符号和参数方向
误差收敛慢lambda 或 gamma 偏小增大 lambda、gamma,注意不要过大
参数估计振荡gamma 过大,或参考信号激励不足减小 gamma,更换更丰富的参考信号
控制量高频抖振eps 太小或用了 sign增大 eps 或改用饱和函数
稳态误差偏大边界层过厚、k 过小减小 eps、增大 k
自适应参数发飘、无界回归函数近似线性相关或激励不足检查回归函数线性无关性
欧拉法仿真出现虚假震荡数值积分精度不足改用 RK4,减小 dt
控制量饱和后误差增大执行器限幅生效降低期望轨迹幅值,或加入抗饱和处理

5.2 几个容易踩的坑

第一个坑是控制方向搞反。自适应律里 gamma * s * phi 这个项如果符号错了,参数估计会往错误方向叠加,系统很容易直接发散。排查方法很简单:先把参数初值设成接近真值,看系统是否稳定,再逐渐拉大初值偏差,如果初值稍微偏离就崩,那多半是自适应律符号有问题。

第二个坑是回归函数的数值尺度。x2^2 和 sin(x1) 的幅值可能差一个数量级,如果正则项尺度差异大,建议对回归函数做归一化处理,或者各参数分别设置不同的 gamma。简单说就是“哪个回归项幅值大,对应自适应增益就适当小一点”。我在代码里给两个参数都设了 3,实际使用中经常需要分别调。

第三个坑是自适应增益和采样步长相互制约。离散实现的自适应律本质是显式积分,gamma * phi^2 * dt 这个量决定了参数更新的“步长”,太大时自适应律本身会数值发散,表现为参数估计高频振荡并不断放大。这个往往和参考信号导致的大回归值有关,解决方法是缩短 dt 或减小 gamma。

第四个坑是扰动估计过于乐观。仿真时你可以任意设定扰动上界,但实际系统里扰动上界往往只能靠试验估。如果你按仿真里的理想值去设置 k,到现场大概率不够用。我一般会在离线上预留 30% 的裕量,同时配合观测器直接估计扰动,把常数上界变成在线扰动补偿,效果会好很多。

调试技巧:先开一个小扰动甚至零扰动,确认自适应收敛正常,再逐步把扰动加上去。这个方法能帮你快速定位“控制器问题”和“数值问题”,比上来就全套参数硬调高效得多。

5.3 这套框架还能往哪扩展

这套基于线性参数化的自适应滑模框架,可以往几个方向扩展。

一是把一阶滑模面扩展为高阶滑模或积分滑模面,以进一步削弱抖振,同时改善跟踪精度。积分滑模尤其在存在常值扰动时能显著减小稳态误差,代价是引入了积分项的饱和和超调问题,需要搭配抗积分饱和逻辑。

二是在参数估计中加入 σ 修正或参数投影,以改善极端工况下的鲁棒性。σ 修正本质是在自适应律里加一个阻尼项,防止参数估计漂移过大;参数投影则把估计值限制在事先设定的合理区间内,两者都对参数发飘问题有立竿见影的效果。

三是用扰动观测器估计时变扰动,代替保守的常数上界。此时滑模增益可以取得很小,抖振问题从根上得到缓解。这个方向是现在工程应用里很常见的组合拳,值得深入研究。

无论哪个方向,核心思想都一致:滑模提供鲁棒性骨架,自适应提供模型修正,两者缺一不可。

最后分享一点实操体会

我个人这些年用下来的体会是:自适应滑模这类方法,最大的价值不在于某个公式多精妙,而在于它逼着你把“模型里哪些参数可信、哪些不可信”这个问题想清楚。写代码之前先在纸上把 Lyapunov 推导走一遍,把控制律、自适应律、回归函数都标清楚,Matlab 里跑通就是水到渠成的事。另外一个屡试不爽的小经验:当你觉得“仿真结果莫名其妙”时,先别急着改控制器参数,把数值积分精度、步长、符号方向这三样查一遍,大多数问题都出在这里。说白了,控制理论落地到代码,考验的不是你背了多少公式,而是你能不能把每个公式变成一行的赋值语句,并且清楚地知道它在闭环回路里干的是什么。

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

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

立即咨询