简介:面向需要实现ADRC自抗扰控制算法的工程师、研究人员和学生,这份资源通过Simulink仿真平台完整呈现自抗扰控制器架构,细化ESO(扩张状态观测器)与TD(跟踪微分器)等核心模块,并提供Simulink模型和S函数两个可对照版本,有助于理解控制律、观测器与扰动补偿之间的关系。包内共7个文件,以m函数源码、mdl模型文件为主,搭配操作演示视频和说明文档,构成一套麻雀虽小、五脏俱全的仿真工程,压缩包仅707KB,便于快速下载部署。资源对运行环境提出明确要求(Matlab 2021a或更高版本),并提供Runme.m统一入口及操作录像,针对当前文件夹路径、子函数调用等易错点做了演示,能直接降低上手门槛。目前已有5095人学习,适合刚接触自抗扰控制、希望快速搭建仿真验证平台的读者。 我前前后后帮人搭过不少ADRC的Simulink仿真,也见过太多人在扩张状态观测器(ESO)和跟踪微分器(TD)这两个模块上卡壳。说实话,ADRC刚上手的时候确实比PID复杂不少——PID你随便给几个参数就能跑起来,ADRC要是参数没给对,仿真直接发散,或者输出抖得跟筛子一样。
但我也得说句公道话:一旦你理解了ADRC“观测扰动、补偿扰动”这套思路,再回头看它的Simulink实现,其实每一步都有章可循,比PID那种纯靠经验调试的方式“有逻辑得多”。这篇文章我就拿一个完整的ADRC控制仿真项目来拆解,从TD、ESO、NLSEF三个核心模块的原理讲起,一直讲到Simulink模型搭建、参数整定、离散化处理,最后再聊聊我实际踩过的坑。你要是正准备做ADRC仿真,或者已经在做了但结果总是不对,这篇文章应该能帮你省下不少时间。
1. 先搞明白ADRC在解决什么问题
在动手搭Simulink模型之前,我建议你先花十分钟想清楚一个问题:你到底为什么要用ADRC,而不是PID?这个问题想不清楚,后面调参你一定会调得怀疑人生。
PID的核心问题是“基于误差消除误差”——它只知道输出和目标的偏差,不知道这个偏差是怎么来的。如果系统里有外部扰动、模型不确定性、参数摄动,PID只能被动地等误差出现再去纠正,而且增益给大了容易震荡,给小了又跟不上。我们把这类扰动统称为“总扰动”,PID对总扰动的抵抗能力其实很有限。
ADRC的思路是反过来的:与其等误差出现,不如主动去估计扰动,然后把这个估计值直接补偿掉。韩京清先生当年提出自抗扰控制,核心思想就是把“不知道的系统内部动态”和“外部的未知扰动”全部看作一个总扰动,然后用扩张状态观测器把这个总扰动实时估计出来,反馈到控制量里把它抵消掉。这样,原本一个充满不确定性的复杂系统,就被ADRC“压缩”成了一个简单的串联积分型系统,再用一个很简单的控制律就能把它稳住。
ADRC的标准结构包含三个部分:
- 跟踪微分器(TD):安排过渡过程,既给出跟踪信号,又给出它的微分信号,解决“快速性”和“超调量”之间的矛盾。
- 扩张状态观测器(ESO):ADRC的心脏,实时估计系统状态和总扰动。
- 非线性状态误差反馈控制律(NLSEF):用非线性组合的方式生成控制量,配合ESO的补偿,实现对被控对象的有效控制。
在Simulink里做ADRC仿真,本质上就是把这三个模块一个个搭出来,然后用信号线连成一个闭环。
2. 核心模块原理与Simulink实现详解
2.1 跟踪微分器TD:不只是一个微分器
很多初学者对TD的作用理解得很浅,以为它就是用来求微分的。其实这是个误区。TD更重要的作用是安排过渡过程(Transient Profile)。
你想啊,如果参考输入是个阶跃信号,直接从0跳到1,你让系统去跟踪这个阶跃,起始阶段误差很大,控制器就会输出一个很大的控制量,超调几乎是不可避免的。TD的解决办法是把阶跃信号“揉成”一条平滑的曲线,让系统沿着这条曲线平滑地过渡到目标值。
TD的离散化公式是这样的:
x1(k+1) = x1(k) + h * x2(k) x2(k+1) = x2(k) + h * fhan(x1(k)-v(k), x2(k), r, h0)这里的fhan是最速控制综合函数,也就是韩京清先生提出的最速跟踪函数。它的作用是:在约束条件下,让x1以最快的速度跟上输入v,同时又不能产生超调。r是快速因子,r越大,跟踪越快,但过渡过程也就越“激进”;h0是滤波因子,适当大于仿真步长可以增强滤波效果,抑制噪声。
Simulink搭建思路:TD的实现是最直观的。用两个积分器(Integrator)串联,x1是跟踪信号,x2是x1的微分。输入端按照离散公式写好fhan函数,这个用MATLAB Function模块最方便,也可以直接用Fcn模块写表达式,但可读性会差很多。
我自己一般用MATLAB Function写fhan,代码就十几行,清爽且不容易错。
function [fh, x1_next, x2_next] = fhan_step(x1, x2, v, r, h0, h) d = r * h0^2; a0 = h0 * x2; y = x1 - v + a0; a1 = sqrt(d * (d + 8*abs(y))); a2 = a0 + sign(y) * (a1 - d) / 2; sy = sign(y + d) - sign(y - d); a = (a0 + y - a2) * sy + a2; sa = sign(a + d) - sign(a - d); fhan = -r * (a/d - sign(a)) * sa - r * sign(a); x1_next = x1 + h * x2; x2_next = x2 + h * fhan; fh = fhan; end实际调TD的时候,r和h0的配合很讲究。r太大,过渡过程太快,DP(微分峰值)会很大;r太小,系统响应迟钝。一个经验做法是先定h0(通常等于仿真步长的2~5倍),再根据你期望的调节时间反推r。
2.2 扩张状态观测器ESO:系统的“透视镜”
ESO是整个ADRC里最核心、也最不好调的部分。它的思想是:把系统模型中的未知动态和外扰合在一起,当作一个新的状态变量——“扩张状态”,然后用观测器把它估计出来。
对于一个二阶系统:
x1_dot = x2 x2_dot = f(x1, x2, w, t) + b * u y = x1扩张状态 x3 = f(...),于是系统变成三阶,ESO的离散递推公式为:
e(k) = z1(k) - y(k) z1(k+1) = z1(k) + h * (z2(k) - beta1 * e(k)) z2(k+1) = z2(k) + h * (z3(k) - beta2 * fal(e(k), 0.5, delta) + b0 * u(k)) z3(k+1) = z3(k) - h * beta3 * fal(e(k), 0.25, delta)fal是非线性函数,在原点附近是线性段,远离原点是非线性段,目的是“小误差大增益、大误差小增益”。
Simulink搭建思路:ESO的搭建比TD稍微复杂一点,因为涉及三个状态的递推和fal函数。同样推荐用MATLAB Function实现,把三个递推式写在同一个函数里,返回三个输出。
function [z1, z2, z3] = eso_update(z1, z2, z3, y, u, h, beta1, beta2, beta3, b0, alpha1, alpha2, delta) e = z1 - y; fal1 = fal(e, alpha1, delta); fal2 = fal(e, alpha2, delta); z1 = z1 + h * (z2 - beta1 * e); z2 = z2 + h * (z3 - beta2 * fal1 + b0 * u); z3 = z3 - h * beta3 * fal2; end function f = fal(e, alpha, delta) if abs(e) <= delta f = e / delta^(1 - alpha); else f = sign(e) * abs(e)^alpha; end end这里b0是控制增益的估计值,也就是你对b的建模估计。**注意:b0给错了,ESO的扰动估计就会出现偏差。**这不是说要求你精确知道b,那就不叫估计了,但b0的量级得对。否则扰动补偿的方向是对的,幅度却差很多,控制性能会明显下降。
2.3 非线性状态误差反馈控制律NLSEF
有了TD给出的参考信号v1, v2,有了ESO给出的状态估计z1, z2和扰动估计z3,控制量的生成就很自然了:
e1 = v1 - z1 e2 = v2 - z2 u0 = beta1 * fal(e1, alpha1, delta) + beta2 * fal(e2, alpha2, delta) u = u0 - z3 / b0仿真里这个模块很简单,用几个Gain和Fcn模块就能搭出来,甚至直接用一个MATLAB Function写也行。我更喜欢直接写函数,因为后边要换成线性组合、或者加前馈,改起来方便。
需要强调的是u = u0 - z3/b0这一步——扰动补偿。z3是ESO估计出来的总扰动,我们直接把控制量里减去这部分,相当于“以毒攻毒”,把扰动抵消掉。这也是ADRC抗干扰能力的根本来源。
3. 完整仿真搭建手把手实操
3.1 被控对象的选择
为了让示例有意义,我用一个带非线性摩擦和外部扰动的二阶系统作为被控对象,比如:
x1_dot = x2 x2_dot = -25*x1 - 2*x2 + 0.5*sin(3*t) + 8*u这里-25*x1 - 2*x2是线性部分,0.5*sin(3*t)是外部扰动,8*u是控制输入。实际物体可能比这复杂得多,但用它来验证ADRC足够了。
在Simulink里,被控对象可以直接用积分器加Gain搭,也可以用传递函数,甚至封装成S-Function。我建议直接用积分器搭,因为后面想看状态x1、x2比较方便,连线也直观。
3.2 模型总体架构
我搭模型喜欢分层,主层保持干净,每个模块单独一个子系统。整体结构是:
- 信号源:阶跃(Step)或者更复杂的参考输入
- TD子系统:输入参考v,输出v1、v2
- ESO子系统:输入被控对象输出y和控制量u,输出z1、z2、z3
- NLSEF子系统:输入v1、v2、z1、z2、z3,输出控制量u
- 被控对象子系统:输入u,输出y
这其实就是标准ADRC的五块结构。信号流向是:参考输入进TD,TD的跟踪信号和ESO的状态估计做差,得到误差,进NLSEF,NLSEF结合ESO的扰动补偿输出控制量,控制被控对象,被控对象的输出再反馈给ESO。
需要注意的地方是代数环。如果把ESO里的控制量u直接取当前时刻的输出,而ESO又直接连接被控对象的输入,Simulink报代数环错误的概率非常高。解决办法是在被控对象的输入端加一个Memory或Unit Delay模块,打断这个直接耦合。这个影响微乎其微,但是对仿真稳定性帮助很大。
3.3 参数初值与整定实操
参数整定是ADRC仿真里最劝退新手的一关。我直接把我常用的整定顺序分享给你:
**第一步:定仿真步长h。**这是基础。我一般用h=0.001(1ms)。如果你做的是快速系统,可能要h=0.0001,慢速系统可以h=0.01,但不管怎样,h必须先定死,后面所有参数都跟它有关。
**第二步:调TD。**先单独给TD一个阶跃输入,把h0设为2*h~5*h,然后调r,观察x1的跟踪速度和x2的峰值。r从几百开始试,不够再加,直到你得到一个“平滑加速、平滑减速、无超调”的过渡过程。
**第三步:调ESO的beta参数。**这是最容易让人崩溃的一步。我的经验是先把beta1、beta2、beta3设为1、10、100这种量级,然后看z1能否跟上y,z3能否逼近真实扰动。如果z1滞后明显,增大beta1;如果z2抖动,减小beta2;如果z3收敛太慢,增大beta3。记住,这三个参数的量级通常是“倍率递增”的,beta3往往是beta2的几十倍到上百倍。
**第四步:调NLSEF的beta1、beta2。**ESO稳定之后,再把整个闭环跑起来。先给小的NLSEF增益,比如beta1=1、beta2=2,看系统稳不稳定。稳定了再逐步增大,直到你得到满意的响应速度。这两个参数越大,系统响应越快,但过大会引起震荡。
**第五步:验证抗扰能力。**在仿真中段(比如t=3s时)加一个突加扰动,看ADRC能否快速把输出拉回设定值。如果恢复速度慢,优先增大ESO的beta3;如果恢复过程震荡,减小NLSEF的beta2,或者调大TD的r让过渡过程更平缓。
我以前整理过一次参数初值参考表,实测对大多数二阶对象都成立:
| 参数 | 初值范围 | 调整方向 | 备注 |
|---|---|---|---|
| h | 0.0001~0.01 | 固定不变 | 越小越稳定但仿真慢 |
| r | 100~5000 | 大则快、小则稳 | 受限于执行器能力 |
| h0 | 2h~5h | 大则滤波强 | 太大会导致跟踪滞后 |
| b0 | 0.5b~1.5b | 必须接近真实b | 这是ESO补偿的基准 |
| beta1 | 100~1000 | 大则跟踪快 | 过大会放大噪声 |
| beta2 | 1000~10000 | 大则速度估计快 | 过大会抖动 |
| beta3 | 10000~100000 | 大则扰动估计快 | 过大会高频振荡 |
| NLSEF beta1 | 0.1~5 | 大则响应快 | 过大会超调 |
| NLSEF beta2 | 0.5~20 | 大则阻尼强 | 过大会震荡 |
**再强调一遍:b0一定要尽量接近真实b。**这一点怎么强调都不过分。ADRC号称不依赖精确模型,但不代表b可以瞎给。b0代表你对控制增益的基本了解程度,这属于“模型的最小先验知识”,没有这个,ESO的补偿方向甚至可能是反的。
4. 仿真过程中的常见问题与排查技巧
我在帮别人看ADRC仿真问题时,遇到的典型问题翻来覆去就那么几个,这里给你整理成速查表,以后仿真不对了直接来对照。
问题一:仿真一开始就发散,数值爆炸。原因90%是ESO参数太大或者b0给错方向了。先检查b0的符号和量级,再检查beta1、beta2、beta3是否过大。有时候你把三个beta都除以10就能跑通,然后再慢慢往上加。
问题二:输出跟踪不上参考信号。如果z1能跟上y,说明ESO没问题,问题在NLSEF增益不够,把NLSEF的beta1、beta2调大。如果z1本身就滞后于y,说明ESO的beta1、beta2不够大,或者h太大导致观测延迟。
问题三:控制量高频抖振,像锯齿一样。这是fal函数在零点附近切换造成的,加上ESO高频增益放大噪声,就会这样。解决办法:增大delta,让线性段范围更宽;减小beta2、beta3;在控制量输出后加一个一阶低通滤波(Simulink里的Transfer Fcn,极点设置在50~200rad/s),但滤波器会引入相位延迟,极点不能设太低。
问题四:ESO的z3完全估不准,和实际扰动差很远。先确认你的扩张状态定义对不对。对二阶系统,扰动是通过x2通道进入的,z3估的就是x2_dot里面除去b0*u之外的所有东西。如果被控对象里面扰动是从其他通道进入的,ESO的结构就得改。另外检查b0是否准确,b0偏了,z3的估计也会偏。
问题五:仿真速度极慢。ADRC里面非线性函数多,MATLAB Function如果每个步长都调用,仿真速度会变得很难看。可以试试把fal函数用查表(Lookup Table)替代,或者用Embedded Coder把MATLAB Function转成C代码再仿真,速度提升很明显。如果你的模型最终要跑实时系统,这一步基本是必须的。
问题六:数字实现之后和仿真结果差距大。这是从仿真走到实物最经典的坑。原因主要是仿真里连续模块和离散模块混用,导致步长行为不一致。解决方法是仿真阶段就把所有的积分器都用离散形式(Forward Euler或Tustin),并且固定步长,这样生成的代码行为才和仿真一致。
5. 从仿真到代码:离散化必须注意的细节
既然标题里提到了“含代码操作演示视频”,这里再多说几句代码实现层面的东西。很多人模型搭得很顺利,一到生成C代码就发现乱套,核心原因是没有处理好离散化一致性的问题。
常用的三种离散化方法:
- Forward Euler(前向欧拉):最简单,和连续仿真直接对应,但增益高时容易不稳定。
- Backward Euler(后向欧拉):稳定性好一些,但实现起来稍微麻烦一点。
- Tustin(双线性变换):精度高,适合需要精确频率响应的场合,比如把连续控制器转成离散控制器。
ADRC的离散实现,我在实践中更推荐在一开始写MATLAB Function的时候就按离散递推式写。就像我前面给的TD和ESO代码那样,直接用x = x + h * f(x)的形式。这样在Simulink里仿真的时候是离散的,生成C代码的时候不需要额外转换,行为完全一致,省掉很多麻烦。
如果是用Simulink自带的连续积分器搭的模型,再想转离散,建议在求解器设置里选择Fixed-step discrete solver,然后手动把所有Integrator换成Discrete-Time Integrator,并把Sample time设成h。别偷懒用连续求解器去跑离散模型,最后出来的代码行为会很诡异。
6. 配套演示视频的使用建议
这个项目附带的操作演示视频,我建议你这样去用,收获最大。
先别看视频,自己根据文章把TD搭出来,单独给阶跃信号跑通,观察x1、x2的波形。这一步如果不动手,后面ESO一定会卡。然后搭ESO,注意这时候先不要闭环,直接把被控对象的输出y和输入u喂给ESO,对比z1和y的波形是否一致,对比z3和真实扰动——如果被控对象内部有明确扰动函数,你可以从被控对象里拉一个信号出来对比。
等TD和ESO都验证没问题了,再看视频里闭环连接的部分,通常是信号线的具体连法、子系统输入输出端口的设置这些细节。最后再对照视频里参数整定的过程,看人家是怎么一点点调参数的。
看参数整定那段的时候,重点看别人调整参数之后波形是怎么变化的,把这当作“参数与波形对应关系”的学习素材。这个比直接给你一组参数有用得多,因为实际系统的参数几乎不可能一模一样,你需要的不是一组数值,而是“这个参数变了波形会怎么变”的手感。
7. 我踩过的坑和最后几条心得
聊点课本上看不到的东西。
第一个坑:b0给反了。一次做仿真,系统始终发散,怎么调beta都没用,排查到半夜发现b0的符号写反了。这种低级错误你检查十遍都可能看不出来,因为它藏在某个子系统的Gain模块里。我的习惯是:所有关键参数(b0、beta1~3、r)全部定义到init脚本里,模型里不写死数值,这样检查参数的时候一目了然,改起来也方便。
第二个坑:ESO参数和NLSEF参数互相打架。你把ESO的beta调大了,z3抖动,然后你把NLSEF的增益调大去压制,结果整个系统高频振荡。正确的思路是:ESO只负责“看”,NLSEF只负责“控”。ESO让观测更准确;NLSEF决定控制力度。两边职责要分开调,每次只动一组参数。我见过太多人两个一起调,最后都不知道问题出在哪。
第三个坑:过度依赖非线性组合。韩京清先生提出的是非线性ADRC,但我在实际工程中试下来,线性ADRC(LADRC)在很多场合效果也不差,而且参数少得多、调参容易得多。LADRC就是把fal换成线性误差,ESO变成线性扩张状态观测器(LESO),NLSEF变成PD组合。如果你的应用场景不是极端追求性能,我建议先做LADRC,稳定可靠之后再考虑换成非线性版本。
第四个坑:仿真步长和实际采样周期不一致。仿真里h设0.001跑得飞起,上了真机发现采样周期只能到0.005,整个系统完全变了个样子。所以从第一天起就要想清楚你的控制器最终跑在什么平台上、采样周期大概是多少。按实际的采样周期做仿真,比做出来再改要省心得多。
最后分享一个我自己的习惯:每次做ADRC仿真,我都会同时把输出y、跟踪信号v1、ESO估计z1、扰动估计z3、控制量u这五个信号全部记录到Scope里,用Scope的多端口显示模式一屏看全。这样做的好处是,系统出问题的时候你能一眼看出是哪个环节不对——是跟踪慢了(TD问题),还是观测滞后(ESO问题),还是控制量饱和(NLSEF或执行器问题)。别省这一步,调试效率差好几倍。
ADRC不难,难的是把每个模块的职责搞清楚,并且愿意花时间一点点调试。照着这篇文章的步骤走一遍,你也会发现它不过如此。
本文还有配套的精品资源,点击获取