滑模控制算法仿真建模这件事,门槛比想象中低,但翻车点也比想象中多。很多人刚开始接触滑模控制时,容易被那一串公式推导劝退,觉得Matlab/Simulink里的模型也一定很复杂。实际上,把滑模控制算法仿真建模拆开看,核心模块就那么几个:Gain、Sum、Sign、积分器,再加几个信号源和示波器。真正让仿真结果没法看的,往往不是控制律本身,而是求解器步长没设对、sign模块用得太理想化、参数整定顺序搞反了这些看起来很小的细节。
这篇文章我会完整记录一遍用Simulink从零搭建滑模控制算法仿真建模示例的过程,包括原理怎么落到模型上、每个模块为什么这么搭、调参时先调哪个后调哪个、以及抖振问题到底怎么治。适合刚接触滑模控制、想在Simulink里快速跑通第一个仿真的人,也适合已经能跑但发现波形抖得没法看、想找排查方向的读者。
1. 为什么要把滑模控制搬进Simulink:原理与适用场景
1.1 滑模控制到底解决的是什么问题
滑模控制(Sliding Mode Control,SMC)最吸引人的点就一个:鲁棒性。它面对模型参数不准和外部扰动时,表现出很强的"不敏感"特性,这是PID、LQR这类依赖精确模型的控制器很难做到的。
我打个比方:普通反馈控制像你开车走一条固定路线,路况好的时候没问题,一旦轮胎打滑或者有人别车,路线就会明显偏离;滑模控制则像给车加了一条看不见的滑轨,不管你在哪个位置、有没有外力干扰,控制器会先把你"狠狠拽到"滑轨附近,然后沿着滑轨把误差送到零。这种设计天然对扰动和参数偏差不敏感,所以在电机驱动、四旋翼、机械臂、导弹制导、汽车ABS这些对鲁棒性要求高的场合,滑模控制一直是被反复研究的对象。
但需要说句公道话:滑模控制不是万能的,它最大的毛病就是抖振。理想情况下状态一旦到达滑模面就贴着面滑动,实际上由于切换不连续和数字仿真的离散特性,状态会在滑模面附近来回穿越,形成高频振荡。这个问题我在后面第5节会仔细讲。
1.2 三个核心要素:滑模面、趋近律、控制律
一个完整的滑模控制器设计,绕不开三个东西。我把它们拆开说:
第一个是滑模面。滑模面是系统状态空间里的一条"目标曲线",设计的目标就是让系统的误差状态落在这条曲线上。最常用也最直观的是线性滑模面:
s = c·e + e'其中 e 是跟踪误差,e' 是误差变化率,c 是大于零的常数。这个式子其实很巧妙:当系统状态满足 s = 0 时,误差动态就退化成一阶线性系统 e' = -c·e,误差会按指数速度收敛到零。也就是说,一旦状态上了滑模面,闭环行为就被这个滑模面决定了,和被控对象的参数关系不大了——这就是鲁棒性的来源。
第二个是趋近律。滑模面只定义了"最终要去的面",但状态怎么走向这个面、走多快,由趋近律决定。最经典的是指数趋近律:
s' = -ε·sign(s) - k·s这里 ε 和 k 都大于零。等号右边有两项:-k·s 是指数项,让状态在距离滑模面较远时快速逼近;-ε·sign(s) 是切换项,确保证状态能"穿越"到滑模面上并保持,也是抖振的直接来源。
第三个是控制律。把系统模型代入趋近律,反解出控制量 u,就是完整的控制律。以我后面仿真要用的二阶系统为例,被控对象是:
J·θ'' + B·θ' = uJ 是转动惯量,B 是阻尼系数,θ 是位置输出,u 是控制力矩。通过推导可以得到控制律:
u = B·θ' - J·c·e' + J·θd'' - k1·s - k2·sign(s)这个式子看着长,拆开看就四块:第一项 B·θ' 补偿被控对象的阻尼项,第二项 -J·c·e' 补偿滑模面定义中误差变化率的耦合,第三项 J·θd'' 是期望轨迹的加速度前馈,最后两项 -k1·s - k2·sign(s) 就是趋近律部分,k1 对应指数趋近项,k2 对应切换项。前两项加第三项本质上就是在做"等效控制"——让系统状态在理想情况下保持零动态;后面两项负责"切换控制",把偏离滑模面的状态拉回来。
一句话总结原理:等效控制保证状态不离开滑模面,切换控制保证状态被强制推到滑模面上。后面的Simulink建模,其实就是把这条控制律一条一条挪到模块图上。
2. 模型框架设计:二阶机电系统与控制器结构
2.1 被控对象和仿真工况怎么选
仿真建模第一步不是急着搭Simulink模块,而是先明确被控对象模型和期望的跟踪工况。
我选的被控对象是一个二阶机电系统,物理上可以理解成一台带阻尼的旋转电机或单轴机械臂:
J·θ'' + B·θ' = u模型参数取 J = 0.01 kg·m²,B = 0.1 N·m·s。为什么选这个系统?因为它结构简单、物理意义清楚,而且和电机驱动、机器人关节这些真实工程对象强相关。把滑模控制在这个系统上摸透了,再换到四旋翼、车辆或机械臂模型上,思路是一样的。
期望跟踪指令我建议用正弦信号,而不是只用阶跃信号。阶跃信号只能看出稳态误差和超调,正弦信号能看出动态跟踪能力——比如跟踪相位滞后、幅值衰减这些问题。我用的是:
θd = 0.5·sin(2·t)幅值0.5弧度,频率2 rad/s,大约0.3Hz,对仿真系统来说比较温和,方便观察参数变化带来的影响。
2.2 整体信号流与模块划分思路
在打开Simulink之前,先在纸上画一遍信号流向。搭模型最忌讳的直接上手到处拖模块,后面一通线根本没法查问题。我这个模型的信号流是这样的:
- 参考输入 θd 和实际输出 θ 做差,得到误差 e
- e 通过增益 c 后与误差变化率 e' 相加,得到滑模面 s
- s 分别进入趋近律的两个分支:一路乘 -k1,一路经过 sign 后乘 -k2
- 两个分支加起来,再加上被控对象阻尼补偿项 B·θ'、滑模面耦合补偿项 -J·c·e'、期望加速度前馈项 J·θd''
- 合成的控制量 u 进入被控对象,被控对象输出 θ 和 θ' 反馈回去
这样的结构也决定了模块划分思路:误差模块、滑模面模块、趋近律模块、控制量合成模块、被控对象模块。每一块都是独立子系统或者至少是独立的区域,方便单独调试。我给每个区域都用大括号的Annotation标注了名字,比如"SMC Controller"和"Plant"。
特别强调一下,控制律里用到了 θ'(实际速度),这个信号我设计成从被控对象的积分器中间引出,而不是对 θ 求导,这个细节第3节会细说。
3. 分模块搭建:从被控对象到控制律的完整过程
3.1 被控对象建模:积分器串联比传递函数好用在哪
被控对象有两种常见搭法,我用的是积分器串联的方式。
第一种是用传递函数模块:直接放一个 Transfer Fcn,分子是 [1],分母是 [J, B, 0],G(s) = 1 / (J·s² + B·s)。优点是简洁、一眼看清传递函数;缺点是不方便引出中间状态,尤其是不方便直接拿到速度信号 θ'。如果后面要把速度信号引到控制器里,还得额外再搭一个 s/(J·s² + B·s) 的传递函数,这就重复了。
第二种是积分器串联:先算 θ'',通过增益 1/J 得到角加速度,然后串联两个积分器,第一个积分器输出 θ',第二个输出 θ。整个被控对象只需要三个模块:一个 Sum、一个 Gain、两个 Integrator。它的最大优势是中间状态(速度)可以直接引出,而且物理意义非常清楚:加速度积分成速度,速度积分成位置。
我把两种方式对比一下:
| 建模方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 传递函数 Transfer Fcn | 简洁,与频域分析方法衔接好 | 中间状态不好引出,做状态反馈不便 | 频率响应分析、只需关注输入输出 |
| 积分器串联 | 中间状态直接可用,物理清晰 | 模块多一些,需要手动配置初值 | 状态反馈控制、需要速度和位置信号 |
这里要提醒一个细节:积分器初值一定要设置。我习惯把位置积分器初值设为 0.2,速度积分器初值设为 0,模拟系统从偏离目标位置的状态开始运动。这样能直接看出滑模控制的"趋近过程"——状态一开始离滑模面很远,控制器把它拽回来的过程就是滑模趋近段。如果初值都是零,起始点恰好就在滑模面上,反而观察不到趋近过程了。
3.2 跟踪误差与滑模面模块:尽量别用Derivative
误差模块很简单,用两个 Sum 实现:一个 Sum 算位置误差 e = θ - θd,另一个 Sum 算速度误差 de/dt = θ' - θd'。
求 θ' 和 θd' 的方法是关键。θ' 从被控对象积分器中间引出,完全没问题。但 θd' 是期望指令的导数,很多教程会直接用 Derivative 模块对 θd 求导,我强烈不建议在滑模控制里这么干,原因是:
- Derivative 模块放大噪声。仿真中不可避免有数值误差,对带噪声的信号求导,噪声会剧烈放大。
- 滑模控制对切换项已经够敏感了,再叠加数值噪声,抖振会更吓人。
- Derivative 模块有直接馈通(direct feedthrough)特性,容易引发代数环警告。
解决办法很朴素:用数学解析式给出导数。期望轨迹是 θd = 0.5·sin(2t),那一阶导就是 θd' = 1.0·cos(2t),二阶导是 θd'' = -2.0·sin(2t)。这三个信号可以直接用三个信号源分别生成,或者用一个正弦信号源并联两个 Gain 模块变换出来。因为正弦的二次导数就是自身乘以 -ω²,我把正弦输出取反乘 4 就得到了 θd'',效率高且没有噪声。
如果期望轨迹不是解析可导的复杂信号,我建议你把期望轨迹做成一个单独的状态空间模型,把位置、速度、加速度都作为输出引出来,而不是用 Derivative 硬求。
滑模面模块就一组 Gain + Sum:s = c·e + de/dt。我把 e 通过 Gain 模块(增益设为变量 c),再和 de/dt 相加。注意这里的信号类型要保持 double,Simulink 在默认配置下不会自动处理数据类型不一致的问题,后面接 sign 模块时如果类型不对会报错。
3.3 趋近律与控制律模块:一条公式拆成三个分支
控制律这条公式,搭的时候我建议按它的物理结构拆成三个分支,最后用一个 Sum 合成,这样出了问题好定位:
- 分支1:模型补偿项,由 B·θ'、-J·c·e'、J·θd'' 三个子项构成。把 J·c 合并算成一个 Gain 系数,可以提高执行效率。
- 分支2:指数趋近项,-k1·s,一个 Gain 搞定。
- 分支3:切换项,-k2·sign(s),需要一个 Sign 模块和 Gain 模块。
把三个分支的输出接入一个 Sum,得到最终控制量 u。在这个 Sum 后面我建议加一个 Saturation 模块做限幅,把控制力矩限制在 ±10 N·m 以内。限幅有两个作用:一是模拟真实执行机构饱和特性,不让控制器输出不现实的巨大力矩;二是防止极端参数下控制量发散把仿真数值冲爆。
Sign 模块在 Simulink 的 Math Operations 库里,输入大于零输出1,小于零输出-1,等于零输出0。这个模块是理想化的符号函数,在后面抖振分析里,它就是"罪魁祸首"。
3.4 接线、示波器与数据记录的小习惯
接线之前,我建议给每个子系统的输入输出接口都起好有意义的名字,比如 r_position、theta、velocity、s_sliding、smc_u 这种,不要用默认的 In1、Out1。名字会直接显示在总线标签上,模型复杂之后排查连线问题会省很多事。
示波器我一般会分三个:
- Scope1 看位置跟踪:θd 和 θ 放在一起,直接判断跟踪效果
- Scope2 看滑模面 s:这是评价滑模控制是否"进入滑动模态"的核心指标,s 应该快速收敛到0附近,然后小幅振荡
- Scope3 看控制量 u:观察控制信号是否有剧烈抖振
数据记录建议打开模型的 Signal Logging:在相关信号线上右键,选择 Log Selected Signals,然后用 MATLAB 脚本里的logsout = sim('modelname')拿到数据,方便后续画图、做误差分析。跑完仿真后我用plot(logsout.get('theta').Values)这类命令处理数据,比在示波器里右键导数据好用得多。
还有一个很实用的小技巧:用模型初始化回调函数(Model Properties → Callbacks → InitFcn)定义参数。我把 J、B、c、k1、k2 都写成一个 initialization 脚本或 .m 文件里的变量,这样在模型里所有 Gain、积分器参数都填变量名,而不是硬编码数字。改参数的时候只需要改脚本里的值,而且方便用 for 循环批量扫参数对比。
4. 参数整定与调试:求解器、步长和参数配合
4.1 求解器选型:为什么默认ode45会翻车
这一步是我踩过最深的坑。Simulink 默认求解器是变步长 ode45,在绝大多数连续系统仿真里很稳,但在滑模控制里经常出问题。
原因是滑模控制引入了 sign(s) 这个不连续的切换项。在切换瞬间,系统方程从一个动态快速跳到另一个动态,数值上表现为刚性(stiff)行为。ode45 是显式 Runge-Kutta 法,处理刚性系统时会因为稳定性限制被迫把步长压得非常小,结果表现为:仿真进度极慢,波形在切换点附近出现肉眼可见的锯齿,甚至在切换点附近误差估算失败导致报错。
我实测下来,同样的模型和参数,ode45 跑 10 秒仿真需要一两分钟且波形明显毛糙;换 ode23t 之后,仿真速度快了不止一倍,波形平滑很多。具体建议:
- 变步长仿真时,首选ode23t。它对中等刚性系统兼顾精度和速度,适合滑模这类带非光滑项的系统。
- 如果模型更复杂、刚性更强,考虑ode15s,它作为多步法效率更高,但会引入一定数值阻尼,对抖振形态有点"掩盖"作用。
- 如果做固定步长仿真(比如为了之后做代码生成),步长上限要足够小。滑模面时间常数约 1/c,也就是 50ms 量级,但切换项的剧烈动态要求更高分辨率,我建议固定步长从 1ms 起试,必要时缩小到 0.1ms — 0.5ms。步长太大时,仿真结果看起来会像高频噪声的混叠,很多人在这里误以为控制器设计错了。
4.2 滑模参数怎么选:c、k1、k2的分工
参数整定前先搞清楚每个参数管什么事:
| 参数 | 作用 | 增大影响 | 减小影响 | 经验初值 |
|---|---|---|---|---|
| c | 滑模面斜率,决定滑动阶段的收敛速度 | 误差收敛更快,但放大噪声和高频动态,可能引起更大抖振 | 收敛变慢,对模型不确定性的敏感度增加 | 20 |
| k1 | 指数趋近项增益 | 状态更快趋近滑模面,但过大可能引起趋近段超调 | 趋近速度变慢 | 5 |
| k2 | 切换项增益 | 抗扰能力更强,系统更鲁棒,但抖振幅度显著增大 | 抗扰性变差,外界扰动可能把状态推出滑模面 | 0.5 |
我推荐的整定顺序是:
先定 c。把 k1、k2 设成比较小的值,观察误差收敛速度。c 相当于"滑模面上的收敛带宽",c 越大误差衰减越快,但太大时控制器对高频噪声特别敏感,实测中会出现本来平滑的输出变得毛糙。一般先用 c = 5 到 50 之间试,看阶跃响应的收敛速度。
再调 k1。观察 s 从初始值快速下降到 0 附近的过程。k1 决定趋近段速度,但过大的 k1 会让 s 在趋近过程中"冲过头",表现为误差先快速变小再反弹。
最后调 k2。主要看抗扰动能力和抖振幅值。我习惯从 k2 = 0.1 开始,逐步增大到刚好能看到扰动被明显压制的水平,再退一点留余量。k2 是抖振的直接能量来源,neng 小则不小。
另外一个重要的验证手段:注入扰动看鲁棒性。模型搭好后,我在被控对象输入端加一个幅值 0.5、频率 3Hz 的正弦干扰力矩,比较有扰动和无扰动的跟踪误差曲线。如果 k2 足够大,两条曲线几乎重合;如果 k2 太小,轨迹会有明显偏差。这个对比是判断滑模控制是否生效的最直观证据。
4.3 实测中比较典型的报错与排查
把我在调试过程中遇到的几个典型问题列出来,大家碰到时可以直接对照:
| 现象 | 常见原因 | 排查与解决 |
|---|---|---|
| 仿真极慢,波形锯齿状 | 求解器不匹配刚性系统 | 把求解器从 ode45 换到 ode23t / ode15s |
| 仿真发散,报 NaN 或 Inf | k1/k2 取值过大,或限幅没加、控制量爆炸 | 减小 k1、k2,加 Saturation 限幅 |
| 代数环警告 | sign 等直接馈通模块形成闭合代数回路 | 在反馈路径插入 Memory 或 Unit Delay 打断,或改善信号流布局 |
| 一波动就振荡不停 | c 太大放大高频噪声 | 减小 c,或改回 1s 限制 |
| 稳态误差不为零 | c/k 太小,或滑模控制被边界层削弱过多 | 增大 c 或 k2,检查边界层参数(见第5节) |
| 跟踪滞后明显 | 期望加速度前馈项 J·θd'' 缺失或符号不对 | 检查前馈信号是否接入,增益是否为 +J |
代数环警告需要单独说一句。滑模控制器天然容易触发这个警告,因为控制律里同时包含了状态反馈和 sign 非线性,sign 是直接馈通模块,信号从输出到输入存在瞬时路径。Simulink 会尝试迭代求解这个代数环,花费额外计算时间,严重时模型跑不动。我在反馈路径上插入一个 Memory 模块之后,警告消失,仿真也流畅了。Memory 模块引入一拍延迟,对低频跟踪影响很小,完全可接受。
5. 抖振抑制:从理想仿真到工程可用的关键一步
5.1 抖振的机理:sign在数字仿真中的"规矩"
先明确一个概念:抖振分两种来源,一种来自真实物理系统,一种来自数字仿真本身。
真实抖振的来源是 sign(s) 在 s = 0 处发生理想的不连续切换。物理执行器(电机、舵机、液压阀)不可能瞬间完成这种无限带宽的切换,必然产生有限频率的高频振动。数字仿真中的抖振则是额外的:仿真步长有限,控制器在每个仿真步长内最多切换一次,状态在滑模面附近来回穿越,就形成了和步长相关的锯齿状高频振荡。
你可以把 sign(s) 想象成一个没有死区的继电器:误差稍微为正就全力往一个方向推,稍微为负就全力往反方向推。理想情况下这个继电器能以无限频率切换,但仿真做不到,物理世界也做不到,于是状态就会像"拧麻花"一样在滑模面附近来回转。
所以抖振不是"调参偶尔会遇到的问题",而是滑模控制从原理到实现必然面临的固有矛盾。好消息是,工程界积累了很多成熟抑制手段。
5.2 四种工程上实用的抑制方案
我在实际中用过的、有效的方案有四种,按实现难度从低到高排列:
方案1:边界层法(饱和函数替代符号函数)。把 sign(s) 换成 sat(s/φ),其中 φ 是边界层厚度:
sat(s/φ) = sign(s) 当 |s| > φ sat(s/φ) = s/φ 当 |s| ≤ φ在 Simulink 里实现非常容易:把 s 经过一个增益 1/φ,再接一个上下限为 ±1 的 Saturation 模块,就得到了 sat(s/φ)。边界层内切换项变成连续线性函数,抖振被大幅削弱,代价是边界层内没有理想的滑模运动,稳态精度会打折扣。这是我最推荐的入门方案。
方案2:连续化近似。用 s/(|s| + δ) 代替 sign(s),δ 是个很小的正数。在 |s| 很小的时候,函数值平滑过渡到0附近,不会发生突然切换。Simulink 里可以直接用一个 Fcn 模块写表达式u/(abs(u)+delta),很方便。这个方案本质上和边界层法类似,但更平滑。
方案3:切换项低通滤波。在 sign 模块后面串一个一阶低通滤波器,比如 1/(0.001s + 1),把高频切换信号滤平后再参与控制量合成。这个方案和之前 Simulink 仿真中常用的一阶滤波模块思路完全一致。好处是做法简单,保留理想切换的各项特性;缺点是滤波器会引入相位滞后,边界层或滤波器时间常数太大会显著削弱鲁棒性。我一般只在边界层法效果不理想时才用滤波器,因为滤波器调参会多一点。
方案4:高阶级联滑模。比如超螺旋滑模(Super-Twisting),它把 sign 项放进积分器中,从原理上消除抖振。这个方案性能最好,但设计复杂度明显上升,需要处理二阶滑模面的稳定性证明和参数选取,适合对控制性能要求高的项目,作为从仿真走向实际工程前可以考虑的进阶方向。
四种方案放一起对比:
| 方案 | 实现难度 | 抖振抑制效果 | 对鲁棒性影响 | 适用场景 |
|---|---|---|---|---|
| 边界层饱和函数 | 很低 | 好 | 边界层内精度下降 | 绝大多数工程场景,首选 |
| 连续化近似 s/( | s | +δ) | 低 | 好 |
| 切换项低通滤波 | 中 | 较好 | 相位滞后削弱鲁棒性 | 不希望改动控制律结构时 |
| 超螺旋滑模 | 较高 | 很好 | 基本保持 | 性能要求高的军事、高速运动控制 |
5.3 边界层参数怎么选:一组实测对比
边界层法是最推荐优先试的,所以我把 φ 的选取经验单独说。φ 越大,抖振越小,但控制精度越差;φ 越小,精度越好,但抖振越明显。这是一对矛盾。
我用自己的模型做了一组对比实验,位置跟踪稳态误差和抖振幅值的大致趋势如下:
| 边界层厚度 φ | 稳态误差(量级) | 抖振幅值(量级) | 观察现象 |
|---|---|---|---|
| 0.01 | 约 0.001 | 明显,u 曲线高频毛糙 | 精度很好,控制量抖得厉害 |
| 0.05 | 约 0.005 | 轻微毛糙 | 精度仍然可接受,抖振大幅减弱 |
| 0.1 | 约 0.01 | 几乎消失 | 精度下降一个量级,波形很光滑 |
从这个趋势能明显看到:φ 从 0.01 到 0.1 之间,存在一个工程上比较好的折中点。我的经验是从 φ = 0.05 开始试(大约等于 s 幅值的 0.5% — 1% 量级),然后根据实际波形微调。如果输出曲线还抖就把 φ 调大,如果误差超了就调小。
还要提醒一点,φ 的标定依赖 s 的实际幅值范围。在固定 c 的前提下,s 的数量级可以看误差曲线估算。建议在 Scope2 里先看 s 的幅值,再定 φ,这样比盲目试数更有数。边界层法和滤波方案配合使用时,先调边界层,后调滤波器时间常数,不要同时动两个参数,否则会分不清是谁在起作用。
我在做工程复现时还发现一个现象:固定步长仿真下,即使加了饱和函数,如果步长太大,s 在边界层内的"抖动"仍然会被数值误差放大。所以说到底,抖振抑制是一个控制设计 + 数值实现的联合问题,好的参数加上合适的求解器步长,才能得到一条真正干净的输出曲线。
从Simulink模型到后续扩展的一点体会
这个模型跑通之后,我最大的感受是:滑模控制的理论推导和工程实现之间,差的就是对细节的敏感度。控制律本身半小时能推导完,但要把仿真调得又快又稳,需要把求解器、步长、边界层、限幅这些工程细节全部统筹起来。我现在拿到一个滑模控制仿真任务,优先确认的永远是三件事:模型里有没有用 Derivative 模块、求解器是不是 ode45、sign 函数有没有做平滑处理。这三个点几乎是所有滑模仿真"看起来不对"的根源。
另外,这个模型本身有很强的复用价值。把被控对象从二阶线性系统换成机电系统、车辆模型,甚至加进 Carsim 联合仿真框架,控制器结构基本不用动;把控制律写好并验证稳定之后,可以走 Simulink Coder 生成 C 代码,进入快速原型或硬件在环测试流程。四旋翼、导弹制导、VSG 这类热门方向的滑模控制,本质上都是同一个框架在不同被控对象上的应用。
最后分享一个调试技巧:如果你把模型参数都写在初始化脚本里,可以用一段 for 循环批量跑不同 k2 或 φ 的仿真,自动把每组的跟踪误差积分(ITAE)算出来做对比。用数据代替肉眼判断,选参数会客观得多。我第一次做扫参实验时发现 k2 = 0.4 和 k2 = 0.8 的误差积分差距并不大,但抖振幅值差了三倍,果断选了小 k2。这种"够用就好、留有余量"的整定思路,在从仿真走向实际硬件时能帮你避开不少麻烦。