
如果你第一次听说滑模控制算法多半会被它的核心思路震到它不追求“温柔地”把系统引导到目标而是用一组不连续的、类似“开关”的控制律强制系统状态先冲到一个事先设计好的滑模面上再沿着这个面滑向平衡点。这种看起来很“粗暴”的做法恰恰是它面对模型误差和外加扰动时依然稳如磐石的原因。我做运动控制系统和电力电子项目时但凡遇到“参数拿不准、扰动又不小”的场景第一个想到的就是滑模控制。这篇文章想用最直白的方式把滑模控制的基本原理讲清楚再配一份可以直接在 MATLAB 里跑通的完整仿真程序适合刚接触非线性控制、或者想快速拿滑模控制做验证和参考的朋友。1. 滑模控制的核心思想为什么“不连续”反而更稳1.1 一个直观类比关门与滑模想象你在暴风雨天要关一扇被风吹来吹去的门。你不会慢慢施加力去“微调”因为风的扰动太大你不知道该用多大的力才能刚好抵消风。更聪明的做法是直接朝关门的反向用力用力方向取决于门现在偏在哪一侧——如果门往左偏就往右推如果往右偏就往左拉。一开始你会果断地施加较大力气让门快速贴近目标位置然后频繁切换推拉方向让门始终被“锁”在目标附近。这个过程中门的“状态”一直被驱动向一条目标线关门线一旦到达目标线控制策略就变成在线的两侧来回切换让门不跑偏。滑模控制做的事情本质上就是同一件事设计一条目标线滑模面先用大的控制力把系统状态“打”到这条线上之后再通过高频率切换让状态始终贴着这条线滑向平衡点。这种“开关式”思路和传统 PID 有本质区别。PID 是把误差经过比例、积分、微分的连续运算后输出一个光滑的控制量它依赖“模型大致准确”和“扰动不太剧烈”这两个隐含假设。滑模控制则主动利用不连续的切换动作把“模型误差”和“外部扰动”当成一个整体去“压制”只要这个整体的大小有一个上界控制器就能保证系统状态被拉回目标线。这也是为什么它被称为“对不确定性有天然鲁棒性”的控制方法。1.2 两个阶段到达与滑动滑模控制的过程可以拆成两段看。第一阶段是“到达阶段”。系统从任意初始状态出发在控制律的作用下状态点被驱使着朝滑模面也就是目标线运动。这个阶段里控制量通常比较大目的是尽快把状态“打”到滑模面上。第二阶段是“滑动阶段”。状态到达滑模面之后如果控制律设计得当系统状态会被“粘”在滑模面上沿着滑模面滑向期望的平衡点。这个阶段的动态行为由滑模面本身决定而与被控对象的原始模型关系不大。这是滑模控制最漂亮的地方一旦滑到面上系统的动态就由你设计的滑模面“接管”了。模型参数变化、外部扰动对系统的影响会被大幅度抑制。类比来说到达阶段就像是你要把一辆失控的车拉回车道中间滑动阶段就是你沿着车道线稳稳地开回停车位这时候你已经不太关心车本身的毛病因为你走的路是预先画好的。对二阶系统来说相平面上的轨迹非常直观状态点先是快速冲向一条斜线滑模面然后贴着这条斜线滑向原点。这也是为什么很多教材都会画一张“相轨迹先冲向滑模面、再沿滑模面滑行”的图理解了这张图就理解了滑模控制的一半。1.3 滑模面的设计s 越简单越实用滑模面怎么选最常见的做法是对二阶系统取一条直线s c * x1 x2其中 x1 是位置量x2 是速度量也就是 x1 的导数c 是一个大于 0 的设计参数。当系统状态落在 s 0 上时意味着 x2 -c * x1也就是速度与位置成负比例关系。代入系统的运动关系x1 的导数等于 x2于是x1_dot -c * x1这是一个一阶线性微分方程它的解是指数衰减的。c 越大x1 衰减越快滑动阶段的收敛速度也就越快。所以滑模面参数 c 控制了“滑行阶段”的表现。但 c 也不是越大越好。在真实系统里c 过大会导致控制量过大对噪声和测量误差的敏感度也会变高。如果 x1 是光电编码器测出来的位置信号哪怕有微小的量化误差乘以大的 c 之后也会在速度通道产生明显毛刺。经验上c 通常取被控对象期望带宽的 1 到 3 倍具体要靠仿真和实测折中。对于更高阶系统滑模面可以设计成s (d/dt c)^(n-1) * e其中 e 是跟踪误差n 是系统阶数。这个式子的含义是把误差微分方程的特征根配置到 -c 处一旦 s 收敛到零误差就按指定的极点指数收敛。工程上绝大多数场景用一阶或二阶系统就够了我见过很多实际项目用的就是 s c*e e_dot 这条最基本的滑模面效果已经很好。2. 控制律构造与稳定性分析公式不是天降的2.1 等效控制项让系统“停在滑模面上”假设被控对象是这样一个二阶非线性系统x1_dot x2 x2_dot f(x) g(x) * u其中 f(x) 是已知的标称模型部分g(x) 是控制增益u 是控制输入。为了让推导简单先假装没有扰动和模型误差我们只考虑“理想情况”。滑模面 s c*x1 x2对时间求导s_dot cx1_dot x2_dot cx2 f(x) g(x)*u如果系统状态正好在滑模面上并保持滑动那就应该有 s 0 且 s_dot 0。由 s_dot 0 解出来的控制量就叫做等效控制记作 u_equ_eq ( -c*x2 - f(x) ) / g(x)等效控制的物理含义很直白它是让系统状态“正好保持”在滑模面上所需的控制力。你可以把它理解成骑自行车时为了保持直线匀速所需要的那份稳定力矩——它抵消掉系统自身的动态趋势让状态不偏离目标线。但理想是理想现实中模型 f(x) 不可能完全准确外部扰动也不可能为零只靠等效控制是不够的。所以还需要第二个分量。2.2 切换控制项克服“不确定性”的关键切换控制项的作用是在系统状态偏离滑模面时给一个“强力纠偏”u_sw -k * sign(s)这里 sign 是符号函数s 大于 0 时取 1s 小于 0 时取 -1。整个控制律写成u u_eq u_sw当 s 0 时切换项为 -k把状态往下压当 s 0 时切换项为 k把状态往上抬。只要 k 选得足够大状态就会一直被“赶”向 s 0 这条线。这个项的设计初衷不是要“精确补偿”扰动而是用幅值足够大的开关动作把一切不确定性“压制住”。这里有一个关键参数 k它必须大于系统不确定性包括模型误差和外部扰动的“总上界”。比如扰动是 d(t) 0.5sin(2pi*t)最大的绝对值是 0.5那么 k 至少要大于 0.5工程上通常是扰动上界的 1.5 到 3 倍。k 太小系统在滑模面附近会拉不回来状态会持续来回穿越甚至发散k 太大控制量幅值大抖振也会更明显执行器容易疲劳。补充一句上面假设 g(x) 是已知常数或者归一化为 1。对于 g(x) 未知但有界的系统需要对等效控制除以 g(x) 的估计值同时把 g(x) 的估计误差也纳入到切换增益 k 的设计里。原理仍然不变用足够大的切换项去覆盖不确定性。2.3 李雅普诺夫验证为什么 k 大于扰动上界就行滑模控制为什么能保证收敛这是整个理论里最核心的证明。定义李雅普诺夫候选函数V 0.5 * s^2对 V 求导注意 s 是时间函数V_dot s * s_dot考虑系统包含扰动 d(t) 的情况把控制律 u u_eq u_sw 代进去s_dot c*x2 f(x) d(t) g(x) * (u_eq u_sw)由于 u_eq 的设计正好让 c*x2 f(x) g(x)*u_eq 0所以s_dot d(t) - k * sign(s)于是V_dot s * d(t) - k * |s| ≤ |s| * (|d(t)| - k)如果 k 大于 |d(t)| 的上界 D那么括号里是负数V_dot 0只要 s 不为 0。这意味着 s 的大小会不断减小系统状态必然在有限时间内到达滑模面。这个证明最关键的结论是控制律不需要知道扰动 d(t) 的具体值只需要知道它的上界。这在实际工程里太重要了——很多扰动你测不准但可以大致评估“最大能有多大”比如负载突变、摩擦力变化、电磁干扰等只要这个上界估得合理滑模控制就能扛得住。控制项设计依据作用依赖信息u_eq 等效控制s_dot 0让状态保持在滑模面上滑动标称模型 f(x)、g(x)u_sw 切换控制V_dot 0克服扰动和不确定性把状态拉回滑模面不确定性上界 D3. MATLAB 仿真实现从零搭一个可复现的 SMC 仿真3.1 仿真模型与参数设计为了既体现滑模控制的威力又不把代码搞复杂我选了一个带扰动的二阶非线性系统x1_dot x2 x2_dot -x2 d(t) u扰动设置为 d(t) 0.5 * sin(2pit)也就是一个幅值 0.5、频率 1Hz 的正弦干扰。控制目标是在扰动存在的情况下把状态 (x1, x2) 从初始值镇定到原点。这个例子虽然简单但很有代表性-x2 是系统自身的阻尼项模拟了能量耗散d(t) 模拟外部扰动u 是我们要设计的控制输入。控制器设计时基于“标称模型”f(x) -x2同时知道扰动上界是 0.5。也就是说控制器在生成 u 时只知道模型信息并不知道扰动在每个时刻的实际值这正是实际工程里会发生的情况。选择 c 5、k 2。为什么这么选c 5 意味着滑动阶段的收敛时间常数是 0.2 秒在 5 秒仿真时长内足够看到完整的收敛过程k 2 是扰动上界 0.5 的 4 倍留了充足裕度可以清晰展示切换控制“压制扰动”的效果。如果你希望看到更剧烈的对抗感可以试着把 k 降到 0.6 附近会发现系统在滑模面附近明显“拉不住”。3.2 完整 MATLAB 程序下面这个程序在 MATLAB R2021b 和 R2023a 上都能直接运行如果你的版本比较旧也只需要确认 odefun 的调用格式和匿名函数语法没问题即可。代码分为三部分主程序、系统动态与控制律函数、饱和函数。%% 滑模控制(SMC)仿真 - 二阶非线性系统 % 被控对象: x1_dot x2, x2_dot -x2 d(t) u % 扰动: d(t) 0.5*sin(2*pi*t), |d| 0.5 % 控制目标: 在扰动存在的情况下将 (x1, x2) 镇定到原点 % 控制器: 等效控制 切换控制 clear; clc; close all; %% 参数设置 c 5; % 滑模面参数 s c*x1 x2, c 0 k 2; % 切换增益, k |d|_max 0.5 phi 0.05; % 边界层厚度(准滑模用) x0 [1; -0.5]; % 初始状态 [x1(0); x2(0)] T 5; % 仿真时长 %% 标准滑模(符号函数)仿真 [t1, x1] ode45((t,x) smc_system(t, x, c, k, sign, phi), [0 T], x0); u1 zeros(size(t1)); s1 zeros(size(t1)); for i 1:length(t1) [~, u1(i), s1(i)] smc_system(t1(i), x1(i,:), c, k, sign, phi); end %% 准滑模(饱和函数)仿真 [t2, x2] ode45((t,x) smc_system(t, x, c, k, sat, phi), [0 T], x0); u2 zeros(size(t2)); s2 zeros(size(t2)); for i 1:length(t2) [~, u2(i), s2(i)] smc_system(t2(i), x2(i,:), c, k, sat, phi); end %% 结果绘图 figure(Position, [100 100 1000 700]); % 状态响应 subplot(2,2,1); plot(t1, x1(:,1), b-, LineWidth, 1.2); hold on; plot(t1, x1(:,2), r-, LineWidth, 1.2); plot(t2, x2(:,1), b--, LineWidth, 1.2); plot(t2, x2(:,2), r--, LineWidth, 1.2); xlabel(时间 (s)); ylabel(状态); legend(x_1 (sign), x_2 (sign), x_1 (sat), x_2 (sat), Location, best); title(状态响应); grid on; % 滑模面 subplot(2,2,2); plot(t1, s1, b-, LineWidth, 1.2); hold on; plot(t2, s2, r--, LineWidth, 1.2); plot(t1, zeros(size(t1)), k:, LineWidth, 0.8); xlabel(时间 (s)); ylabel(s); legend(sign, sat, Location, best); title(滑模面收敛过程); grid on; % 控制输入 subplot(2,2,3); plot(t1, u1, b-, LineWidth, 1.2); hold on; plot(t2, u2, r--, LineWidth, 1.2); xlabel(时间 (s)); ylabel(u); legend(sign, sat, Location, best); title(控制输入); grid on; % 相轨迹 subplot(2,2,4); plot(x1(:,1), x1(:,2), b-, LineWidth, 1.2); hold on; plot(x2(:,1), x2(:,2), r--, LineWidth, 1.2); xline linspace(min(x1(:,1)), max(x1(:,1)), 100); plot(xline, -c*xline, k--, LineWidth, 1.2); xlabel(x_1); ylabel(x_2); legend(sign, sat, s0, Location, best); title(相轨迹); grid on; %% 系统动态与控制律函数 function [dx, u, s] smc_system(t, x, c, k, type, phi) x1 x(1); x2 x(2); % 滑模面 s c * x1 x2; % 扰动(只存在于被控对象中控制器完全不知道) d 0.5 * sin(2*pi*t); % 标称模型 f -x2 f -x2; % 等效控制: 使 s_dot 0 u_eq -c*x2 - f; % 等价于 u_eq -c*x2 x2 % 切换控制项 switch type case sign u_sw -k * sign(s); % 标准滑模 case sat u_sw -k * sat(s, phi); % 准滑模(边界层) end % 总控制量 u u_eq u_sw; % 被控对象动态 dx zeros(2,1); dx(1) x2; dx(2) f d u; % 实际对象包含扰动 end %% 饱和函数(边界层) function y sat(s, phi) if abs(s) phi y s / phi; % 边界层内线性过渡 else y sign(s); % 边界层外仍为符号切换 end end3.3 运行结果解读程序运行后会显示四个子图。第一个子图里x1 和 x2 都在约 1 秒内收敛到零附近sign 系列实线和 sat 系列虚线的差别在状态曲线上肉眼看起来不大这是符合预期的——两种控制器都能完成镇定任务差别主要体现在控制量上。第三个子图是核心看点sign 控制下的 u 在收敛后依然在 -3 到 3 之间高频来回跳这就是“抖振”的直观体现sat 控制下的 u 则平滑得多几乎看不到高频跳动。这个对比生动地说明了边界层方法的实际效果——状态性能几乎不损失但控制量的高频成分被大幅削减。第四个相轨迹子图可以看到状态从 (1, -0.5) 出发先快速冲向那条黑色虚线 s 0然后几乎贴着虚线滑向原点。这正好呼应了第一节讲的“到达阶段”和“滑动阶段”。你在图上还能看到实线和虚线在滑模面附近有轻微的区别准滑模的轨迹会稍微“穿透”虚线一点再回来这是因为边界层内控制力是柔性的。3.4 参数调节指南我用了一段时间之后总结了一套比较“顺手”的参数调节顺序你可以直接照做。先固定一个 c从 1 开始把 k 设成扰动上界的 2 倍仿真看状态是否收敛。如果状态明显发散首先检查 k 是不是大于扰动上界并看有没有输入饱和限制——我见过很多次仿真发散不是算法问题是控制量太大超出了自己设定的限幅。收敛没问题之后再逐步增大 c观察状态收敛速度和控制量的变化。c 每增大一倍控制量峰值大约也会翻倍。等 c 调到满意再回头微调 k。k 并不是越大越好它只影响到达阶段的速度和抖振幅值不影响滑动阶段的收敛速率。最后如果要做实物再把符号函数换成饱和函数并根据执行器的承受能力调节 phi。参数作用调大后的影响调小后的影响经验初值c滑动阶段收敛速度收敛快控制量大噪声敏感收敛慢控制量小期望带宽的 1~3 倍k克服不确定性到达快抗扰强抖振大抗扰弱可能无法到达滑模面扰动上界的 1.5~3 倍phi边界层厚度控制平滑稳态误差增大抖振增大接近符号函数控制量允许波动的 5%~10%4. 抖振问题与改进方法理论到工程落地的关键一跃4.1 抖振是怎么来的抖振是滑模控制绕不开的话题。理论分析里符号函数在 s 0 处的切换是瞬时完成的系统状态可以完美地“粘”在滑模面上但实际系统里控制器是数字采样执行的执行器有惯性传感器有延迟这些因素叠加起来会导致状态在滑模面附近来回穿越形成高频振荡。这种振荡在电机电流里表现为高频啸叫在机械系统里表现为抖动和磨损在电力电子变换器里表现为开关频率附近的纹波。我见过不少工程师第一次用滑模控制做实物结果被抖振“吓退”其实这不是算法不行而是缺少工程化处理。抖振的本质是理想切换频率和实际执行频率不匹配只要适当“软化”切换问题就能大幅改善。4.2 三种实用改进方法第一种是边界层法也就是把 sign(s) 换成 sat(s/phi)。在 |s| phi 时切换控制量从跳变变成线性比例控制律退化为一个大增益的比例控制。这样高频切换被避免但代价是滑模面附近出现了一个“边界层”状态只能在边界层内逼近原点理论上存在稳态误差。误差大小与 phi 相关phi 越小误差越小但 phi 太小又会产生近似抖振。第二种是连续化替代法符号函数替换成 s / (|s| epsilon)。这个形式不会产生硬跳变而且比饱和函数的数学形式更光滑很多在线优化算法里用起来方便。epsilon 的作用和 phi 类似但需要根据 s 的典型幅值来选太小则光顺效果有限太大则偏离滑模的本质太远。第三种是超螺旋算法Super-Twisting属于高阶滑模的一种。它不直接把切换加在 u 上而是通过一个积分项来生成连续的等效控制切换动作发生在导数层面。这样既保留了滑模对不确定性的强鲁棒性又能输出连续控制量抖振大幅减弱。代价是算法参数多、整定复杂推导也相对繁琐。我做高端运动控制项目时用过超螺旋效果确实好但如果是刚学滑模建议先把边界层法吃透。4.3 边界层法的参数整定经验边界层厚度 phi 的选择我习惯试三个点先取控制量幅值的 5%仿真看状态稳态误差是否在可接受范围如果误差太大把 phi 缩小一半如果控制量仍有明显毛刺把 phi 放大一倍。总之phi 的本质是在“抖振强度”和“稳态精度”之间取一个平衡。有一个容易被忽略的点边界层法让切换增益在边界层内变成了 k/phi 的等效比例增益这个值可能很大。比如 k 2、phi 0.05等效比例增益是 40加上 c*x2 项整个控制律在滑模面附近相当于一个高增益 PD 控制器。高增益意味着对噪声敏感所以 phi 也不是越小越好需要结合测量噪声的大小来选。实测中如果状态信号本身噪声偏大我宁愿把 phi 放宽一点让控制量干净一些也不愿意为了零点几毫米的静态误差去折腾执行器。5. 常见问题与调试技巧实录5.1 仿真发散先别急着改算法用 MATLAB 跑滑模仿真最常见的发散原因并不是算法错误而是参数组合不合理。第一个要排查的是 k 是否小于扰动上界。如果 d(t) 幅值是 0.5k 却设成 0.3理论上就不满足到达条件状态会在滑模面附近来回飞幅值可能越来越大。第二个要排查的是 c 是否过大。c 太大会让等效控制量出现很大的瞬时尖峰如果系统是刚性状态变化速率差异大ode45这种非刚性求解器容易步长收缩到极小看起来像“发散”但其实是数值问题。这时候可以换ode15s试试或者把 RelTol 从默认的 1e-3 调到 1e-6。第三个原因是限幅。很多人在仿真里给控制量加了饱和限幅但滑模控制的分析假设控制量可以无限制输出一旦限幅生效实际施加到对象上的 u 比理论要求的小就可能达不到到达条件。解决办法是把限幅值纳入设计把限幅值看作等效的 k 上限重新评估它是否大于不确定性上界不够就改动系统设计而不是硬扛。5.2 参数调整的经验法则我调整滑模控制器的顺序基本是固定的从 c 开始再 k最后 phi。先保收敛再优化动态最后处理工程细节。c 的调节可以看状态响应曲线如果 x1 收敛过慢就翻倍提高 c每次都看控制量是不是变大了很多、有没有明显毛刺k 的调节看到达阶段的时间如果 s 曲线从初始值降到 0 附近太慢就增大 k但如果控制量已经高频抖动得很严重说明 k 偏大了。phi 的调节看最后一个子图的 u 曲线如果高频波段明显就把 phi 往大调。有一个容易被忽略的细节MATLAB 里对匿名函数的使用。上面代码里(t,x) smc_system(t, x, c, k, sign, phi)是把 c、k、type、phi 这几个参数“固化”进函数句柄这样ode45在调用时就只传 t 和 x。这种写法比用全局变量干净得多推荐沿用。5.3 从仿真到实物几个保命事项过完仿真这一关真要上实物前还有几个坑。执行器是有惯性的数字控制器是离散采样的所以仿真里“理想切换”在实物里永远无法实现。建议直接用饱和函数版本做实物并且采样频率至少要达到切换频率目标值的 20 倍以上否则边界层内的高增益很容易引起自激振荡。传感器噪声如果比较大可以在滑模面计算之前对状态量做轻度低通滤波但要注意滤波会引入相位延迟c 和 k 可能要相应降低。还有一个我非常想提醒的点实物调试时要从很小的 k 开始先把名义模型下的镇定问题跑通再加入扰动/负载逐步增加 k。千万不要一上来就按仿真的 k 值全量给上因为实物里不确定性的真实上界往往比仿真估的大很多。我见过同行在电机实验台上第一次跑滑模 k 设太大结果电流直接超限报警。先小后大、逐步逼近这个原则适用于所有鲁棒控制算法的调试。另外MATLAB 代码里如果用了中文注释不同平台/编辑器可能会遇到编码问题建议统一使用 UTF-8 编码保存或者干脆用英文注释。新版 MATLABR2021b 之后对 UTF-8 的支持已经很好但老版本在 Windows 下偶尔会乱码这点留意一下就行。我个人在实际操作中的体会是滑模控制是一款“上限很高、下限也很低”的算法。如果只停留在仿真层面它看起来就是一套漂亮的开关切换真正把它用在实物上才会感受到它对参数和扰动的那种“不问原因、直接压制”的霸道效果。做控制工程的人手里多一个这样的工具遇到难缠的扰动时心里会踏实不少。以后如果你们需要我可以再把高阶滑模、离散滑模、基于观测器的滑模这几个方向分别展开讲它们都是在“基本盘”上生长出来的实用变体。