简介:自抗扰控制器(ADRC)Simulink实现资料包,面向控制理论与工程应用方向的开发者、研究生及自动化工程师,帮助理解ADRC的扩展状态观测器(ESO)、反馈控制与误差反馈滤波等核心环节,并借助Simulink模型进行仿真验证。资源共36个文件,压缩包为RAR格式,大小仅37KB,包含31个MATLAB脚本、4个Simulink模型(mdl)和1个asv备份文件。脚本覆盖fal、fst等关键非线性函数以及多种被控对象、观测器与控制律实现,mdl模型则提供完整的ADRC仿真框架,便于调整参数、观察系统响应。内容对应“自抗扰控制”相关教程的第六章,从基础ESO到TD ESO、误差反馈滤波均有涉及,每一示例均配有可运行的.m脚本与对应Simulink模型,适合逐例运行学习。目前已有1249人学习下载,适合希望快速上手ADRC仿真、验证控制算法或进行课程设计的读者。
1. 为什么我建议你在Simulink里搭自抗扰控制器
做控制的人应该都有体会,PID用了十几年,好使归好使,但遇到大惯量、强耦合、参数时变的被控对象时,调参能调到怀疑人生。我最早接触自抗扰控制器(Active Disturbance Rejection Control,ADRC)是在一份电机控制的项目文档里,当时看到"扰动观测""补偿"这几个字,第一反应是:这不就是前馈补偿换个说法嘛。后来真正在Simulink里搭了一遍才发现,ADRC的思路比前馈补偿要彻底得多——它把系统未建模动态和外部扰动统一当成"总扰动",用扩张状态观测器实时估计出来再补偿掉,本质上是把被控对象强行"改造"成积分串联型,剩下的就是简单的PD控制。
那为什么非得用Simulink来搭?我个人的实际体验是:ADRC的调试过程特别依赖"看得见"。你写一段C代码跑嵌入式,出问题了只能靠变量追踪,而Simulink里每一个信号都可以拉示波器,观测器估计的扰动值、跟踪微分器的过渡过程、控制量输出全部可视化,这对理解ADRC的工作机理帮助极大。比如我第一次跑通模型时,把实际扰动和ESO估计的扰动画在同一张图里,眼看着两条曲线几乎重合,那种"哦原来是这样"的感觉,比看十篇论文都管用。
这篇内容我按完整的落地思路来写:先是ADRC三个核心组件的原理快速梳理,然后是Simulink建模的详细步骤,接着是参数整定方法和实际调试中会遇到的坑。适合刚接触ADRC、想在Simulink里快速验证算法的工程师和学生,也适合已经跑通模型但想优化参数、提高仿真效率的进阶玩家。
2. 自抗扰控制器的三大件:TD、ESO、NLSEF
2.1 跟踪微分器:安排过渡过程的艺术
自抗扰控制器里第一个组件是跟踪微分器。它的作用有两个:一是安排过渡过程,二是提取微分信号。传统PID里的微分项D是直接对误差做微分,如果参考输入是阶跃信号,那一开始微分值会非常大,导致系统抖动。TD的灵感其实来自工程经验——你希望系统怎么运动,就让它怎么运动,人为设计一个平滑的过渡路径。
跟踪微分器的离散形式我常用的是韩京清老师提出的最速综合函数形式,核心公式如下:
fh = fhan(x1 - v, x2, r0, h0) x1 = x1 + h * x2 x2 = x2 + h * fh其中x1是过渡过程的信号,x2是它的微分值,v是参考输入,r0是速度因子,决定过渡过程的快慢,h0是滤波因子,决定对噪声的抑制效果。这个fhan函数其实就是最速控制综合函数,表达式比较长,但在Simulink里写一次后面就能复用。
2.2 扩张状态观测器:整个ADRC的灵魂
如果说TD是前菜,那扩张状态观测器才是ADRC真正的核心。ESO把系统的状态和总扰动一起估计出来,所谓"扩张",就是在原系统状态维数的基础上,额外增加一维来观测总扰动。
考虑一个二阶系统:
x1_dot = x2 x2_dot = f(x1, x2, w, t) + b0 * u y = x1其中f代表内部动态和外部扰动的总和,b0是控制增益的估计值。ESO把f当成一个新的状态x3来估计,那么扩张后的三阶系统可以写成这样:
e = z1 - y z1_dot = z2 - beta1 * fal(e, alpha1, delta) z2_dot = z3 - beta2 * fal(e, alpha2, delta) + b0 * u z3_dot = -beta3 * fal(e, alpha3, delta)你会发现,z1和z2分别估计系统的两个状态,z3估计总扰动。这里的fal函数是非线性幂次函数,表达式是:
fal(e, alpha, delta) = |e|^alpha * sign(e), 当|e| > delta fal(e, alpha, delta) = e / delta^(1-alpha), 当|e| <= delta注意线性段的设计,这个线性区间delta是为了避免在原点附近高增益带来的抖振问题。我在实际调试中踩过这个坑,delta太小会让观测器在零点附近产生高频振荡,后面会详细说。
2.3 非线性状态误差反馈:最后的控制量合成
有了TD安排的过渡过程和ESO估计的状态与扰动,下一步就是设计控制量。ADRC的控制量由两部分组成:一部分是非线性状态误差反馈,另一部分是扰动补偿项。
e1 = v1 - z1 e2 = v2 - z2 u0 = kp * fal(e1, alpha1, delta) + kd * fal(e2, alpha2, delta) u = (u0 - z3) / b0这里的逻辑很清晰:v1和v2是TD输出的期望状态,z1和z2是ESO估计的实际状态,它们的差值做非线性组合得到u0,然后把ESO估计的扰动z3除掉b0后回馈到控制量里。道理很简单——既然z3是总扰动的估计,那我直接把控制量反向补偿掉它,系统就变成了一个"干净"的积分串联型对象,这时候用PD控制就足够了。
需要提醒的是,这里的kp和kd虽然看着像PID参数,但物理含义完全不同。在ADRC中,kp和kd是根据期望的闭环带宽来设计的,而不是像PID那样根据经验试凑。具体换算关系后面参数整定部分会展开讲。
3. 手把手搭模型:Simulink实现细节全解
3.1 整体模型架构:从控制器到被控对象的完整链路
在Simulink里搭ADRC模型,我建议按模块化思路来做,控制器的三个核心组件各占一个Subsystem,被控对象单独建一个模块,便于后续替换成各种实际对象做验证。
新建模型后,整体信号流大概是这样的:参考输入v进入TD模块,TD输出v1和v2给NLSEF模块;同时,被控对象的输出y和控制器输出u共同进入ESO模块,ESO输出z1、z2、z3给NLSEF;NLSEF综合计算后得到控制量u,输入到被控对象。这个结构比PID多了一倍多的模块,但逻辑清晰,每个环节都能单独检查。
采样时间的选择在第一版模型里建议用连续仿真,求解器选ode45,变步长模式。有人一开始就用定步长离散仿真,结果无论如何参数都调不好,还以为是算法问题,其实是没搞懂连续和离散模型的行为差异。先跑通连续模型,确认算法没问题后,再切换到定步长模式验证离散化效果,这是最稳妥的路径。
3.2 TD模块的Simulink实现:用MATLAB Function写fhan
跟踪微分器的实现相对简单,因为fhan函数是纯代数计算,没有复杂的状态逻辑。我建议直接在Subsystem里放一个MATLAB Function模块,把fhan函数代码写进去,再用两个积分器连接构成完整的TD。
fhan函数在Simulink里的MATLAB Function代码可以这样写:
function fh = fhan(x1, x2, r0, h0) d = r0 * h0 * h0; a0 = h0 * x2; y = x1 + a0; a1 = sqrt(d * d + 8 * r0 * abs(y)); a2 = a0 + sign(y) * (a1 - d) / 2; a = (abs(y) <= d) ? a0 + y : a2; sy = (sign(y + d) - sign(y - d)) / 2; a = (a0 + y - a2) * sy + a2; sa = (sign(a + d) - sign(a - d)) / 2; fh = -r0 * (a/d - sign(a)) * sa - r0 * sign(a); end注意我用了一个三元运算符风格的条件判断,实际在MATLAB Function里要写成if-else结构。这段代码我是经过simplified处理的核心逻辑,真正放进Simulink时建议从韩老师的原版fhan函数照抄,确保非线性函数的切换边界处理正确。
然后TD子系统内部结构是常数v信号进外部的加和,x2经增益器反馈,核心就是两个积分器串起来,中间用fhan计算加速度。这样实现出来的TD,输入一个阶跃信号,输出的x1就是平滑的过渡过程,x2是对应的速度曲线,整体非常直观。
3.3 ESO模块搭建:一个容易踩坑的关键环节
ESO的实现要复杂一些。标准做法是用三个积分器和三个非线性反馈回路组成。在Simulink里可以在Subsystem中用MATLAB Function统一实现,也可以分成纯模块搭建,我推荐后者,理由是后续你想观察中间信号更灵活。
ESO子系统内部结构按以下路径搭建:端口y和u分别接进来,y经过和z1的做差得到e,e同时进入三个fal函数模块,各自的输出分别乘以beta1、beta2、beta3,前两个还附加u乘以b0的项,最终分别驱动z1、z2、z3三个积分器。z3比较特殊,它没有直接输入,只靠e的fal函数积分而来,这就是"扩张"的奥义。
使用Simulink基础模块时有个致命问题——代数环。如果你直接使用Gain模块把z3反馈到输入端,Simulink在连续模型里容易出现代数环错误。解决办法有两种:一种是在ESO的反馈路径上加上单位延迟,把连续ESO变成离散形式;另一种是干脆用积分器的输出直接做反馈,通过合理的模块连接顺序避免代数环。我最常用的做法是把整个ESO也做成一个MATLAB Function,用离散递推式实现,这样既能避免代数环,又方便日后把代码移植到嵌入式环境。
3.4 NLSEF与控制量合成的实现方法
NLSEF部分的搭建相对轻松,因为不需要复杂的动态过程。把TD输出的v1、v2分别和ESO输出的z1、z2做差,得到两个误差,每个误差经过各自参数下的fal函数,再乘以对应的kp、kd增益,相加得到u0,最后用(u0 - z3) / b0得到控制量。
这里有个实现细节我特别提醒:fal函数模块会重复使用多次,TD和ESO里也有类似结构。不要把fal函数逻辑复制到每个地方,正确做法是在Matlab里写一个独立的fal.m函数文件,然后在Simulink的MATLAB Function模块里通过addpath调用,或者基于S-Function Builder生成通用模块。这样后续调整fal参数只要改一处即可,不用到处找。
被控对象我建议在Simulink里用传递函数模块或状态空间模块搭建。调试初期选一个我们完全了解的线性对象,比如二阶欠阻尼系统,这样ESO的估计结果可以和真实扰动做对比,方便验证算法实现是否正确。等控制器参数调好之后,再切换到非线性、带参数变化的对象模型,才能真正体会ADRC的威力。
4. 参数整定:从发散到收敛的实战之路
4.1 初始参数设置:先让系统"动起来"
很多初学者搭好模型之后,第一时间就把参数设置成论文里的推荐值,跑出来的结果却完全不对。这里要搞清楚,论文里的参数是针对特定被控对象的,直接套用是典型的"刻舟求剑"。我的建议是先用一套保守参数让系统稳定运行,再做优化。
设被控对象为二阶线性系统,这里给一个参考的初始参数组合:
- TD速度因子r0取10,滤波因子h0取0.01;
- ESO的beta1取100,beta2取300,beta3取1000,幂次alpha1、alpha2、alpha3分别取0.5、0.25、0.125,delta取0.01;
- NLSEF的kp取20,kd取10;
- 控制增益b0先取和对象增益一致的数值。
用这组参数跑仿真,大概率不会发散,但跟踪效果也不会太好,可能有超调或响应偏慢。没关系,初始阶段的目标只是确认信号流正确、没有代数环报错、波形没有发散,这就算第一步成功。
4.2 带宽概念与参数换算法则
参数整定的理论依据是带宽法。高志强老师提出的LADRC带宽整定法把ADRC参数和闭环带宽联系起来,这套方法是我实测下来效率最高的一种。对于ADRC的ESO,β1≈3ωo,β2≈3ωo²,β3≈ωo³,其中ωo是观测器带宽;控制器的kp≈ωc²,kd≈2ωc,其中ωc是控制器带宽。
举个例子,你想让闭环系统带宽ωc为5 rad/s,那么kp应该是25,kd是10。观测器带宽一般设成控制器带宽的3到5倍,比如ωo=15到25,那么beta1约为45到75,beta2约为675到1875,beta3约为3375到15625。你会发现在带宽法框架下,ADC参数调试从"四处乱试"变成了"定性能指标、换算参数"的确定性过程。
需要注意,这个换算公式的前提是系统可以被简化成积分串联型,且b0估得比较准。如果你的对象明显偏离二阶结构,参数还需要微调。但总体思路是对的——先按带宽法计算初值,再根据仿真结果细微调整。
4.3 从仿真结果反推调整方向
有了初始参数,怎么判断该往哪个方向调?这里分享一套我在调试中总结的判别经验表,方便你快速定位问题:
| 仿真现象 | 可能原因 | 调整方向 |
|---|---|---|
| 响应太慢、跟踪滞后明显 | r0太小或kp太小 | 增大r0或增大kp |
| 超调大、振荡严重 | 过渡过程太快或kd不足 | 减小r0或增大kd |
| 系统发散 | b0远小于真实值 | 增大b0重新仿真 |
| 稳态误差明显 | ESO扰动估计偏差或b0不准 | 增大beta参数或修正b0 |
| 控制量高频抖振 | delta太小或仿真步长过大 | 增大delta或减小步长 |
| 观测器估计滞后 | beta偏小 | 增大beta系列参数 |
每次只改一个参数,改完跑一次仿真,记录波形变化。这也是为什么我强调要可视化——把参考输入、实际输出、ESO估计的扰动放在同一张图里观察,就能很直观地看出问题出在哪个环节。比如实际输出和估计的z2明显不同,那是ESO收敛的问题;z3跟踪不上真实扰动,那是ESO带宽不够的问题;输出跟踪不上过渡过程v1,那是控制器增益的问题。
4.4 b0的重要性被严重低估了
我必须单独把b0拎出来说。很多论文里都默认b0是已知的,等于系统的控制增益,可实际工程中很多对象的模型本身就不确定,b0只能给一个估计值。而这个b0恰恰是最敏感的参数之一。b0偏大,等效的闭环增益就变小,系统响应变慢;b0偏小,系统容易振荡甚至发散。
我做一个简单的线性分析来说明:如果系统真实增益是1,b0取了0.5,那么ESO需要把一半的控制输入当成"总扰动"去估计和补偿,这会迫使z3非常大,观测器压力骤增。所以在实际项目中我对b0的处理是优先基于机理估算,然后通过仿真做±30%的敏感度扫描,看哪个区间内系统表现稳定,才最终确定。
5. 常见问题与排查实战记录
5.1 代数环报错:高频问题之首
用Simulink搭ADRC,代数环是我见过最多的报错,尤其是新手第一次搭ESO时几乎必遇到。Simulink提示"Block diagram contains algebraic loop"的时候,程序里会有一条高亮的信号线,这条线就是环路的起点和终点。
排查思路是:找到从模块输出直接反馈到同一模块输入的路径,插入单位延迟或Memory模块打断环路。需要注意,打断环路会带来一拍延迟,这个延迟相当于把积分器的连续行为强制变成了离散近似。如果你使用的求解器步长比较小,这一拍延迟的影响几乎可以忽略;但如果步长很大,就会明显降低观测器的收敛速度,需要适当增大beta参数来弥补。
5.2 观测器输出振荡:别急着加滤波
ESO输出高频振荡是另一个高频问题。按我的经验,80%的情况不是算法本身的问题,而是仿真步长设置不合理。如果你用变步长仿真但把最大步长设得太大,当系统状态快速变化时,非线性函数fal在线性区和非线性区之间频繁切换,数值积分很容易引入高频分量。
先查步长配置,把最大步长设置为系统最小时间常数的1/10以下。如果步长没问题,再检查delta的设置。delta太小会加剧振荡,尤其是误差e在零附近时,fal的线性段过窄,导致增益突变。我把这个参数比作"摩擦系数"——太小就像冰面上走路,分分钟滑倒,适当增大后系统会平滑很多。
5.3 仿真速度极慢怎么办
ADRC因为是全状态反馈结构,整体计算量比PID大不少。如果你做的是复杂联合仿真或者是要跑几百秒的工况,仿真速度会成为一个实际瓶颈。我遇到过跑一次仿真要十分钟以上的情况,优化后压缩到了20秒以内。
方法就三个:一是把纯连续的模型改写成离散模型,直接用固定步长加合适的采样时间,这一步效果最大;二是用MATLAB Function替代离散模块组并再配套代码生成选项,生成的C代码执行效率提升明显;三是把非关键的信号诊测器关闭,只在必要的链条上保留scope,减少Simulink绘图带来的开销。这套组合拳实测下来,仿真加速在5倍以上。
5.4 从仿真到实物移植的准备工作
最后提一个很多人到后期才会关心的问题——Simulink模型怎么和实物对接。如果你项目最终要跑在嵌入式控制器上,建议在仿真阶段就养成两个习惯:一是所有控制器组件都用MATLAB Function或Stateflow实现,避免依赖Simulink库中的连续积分器等无法直接代码生成的模块;二是明确每个模块的离散域描述,采样时间全部使用显式声明而不是继承,这样在代码生成时系统才不会帮你"猜"采样时间。
同时注意,实际物理对象的采样周期不可能像仿真一样无限小。我的经验是,先把仿真模型的采样时间逐步从连续改到10ms再改到1ms,观察控制性能的衰减情况,找到你系统可接受的极限采样时间,这能提前暴露很多实时实现才会遇到的问题。
写在最后的实操体会
从第一次在Simulink里搭ADRC模型到现在,我的一个深刻体会是——自抗扰控制器不是一套"调好参数就万事大吉"的固定算法,它更像一种控制系统设计的方法论。仿真阶段的可视化优势、参数反复整定带来的手感、以及对每个模块物理含义的深度理解,这些能力只有真正动手搭过模型才能获得。
另外还有一个小技巧,建议把所有调好的参数单独放到一个脚本文件里,通过模型初始化回调的方式加载,而不是直接填在模块参数里。这样当你要做参数扫描或者不同被控对象对比时,不用一个个模块去改数,改一个脚本就能全模型生效,效率提升非常明显。这个习惯我一直在用,也推荐给你。
本文还有配套的精品资源,点击获取