从PID到自抗扰控制:Simulink中ADRC从零搭建与参数整定实战
2026/9/9 1:26:14 网站建设 项目流程

简介:这是一份围绕自抗扰控制器(ADRC)的Simulink仿真学习资源,面向控制工程专业学生、研究人员及自动化工程师,适合希望掌握ADRC建模、参数调试与抗扰性能验证的读者。包内共36个文件,以31个m脚本为主,辅以4个mdl仿真模型和1个asv自动保存文件,m文件涵盖fal函数、扩张状态观测器(ESO)、非线性反馈、控制律及被控对象建模等核心环节,mdl文件则提供可直接运行的Simulink框图,便于对照学习与二次开发。压缩包仅37KB,内容精炼、结构清晰。资源围绕TD ESO、误差反馈滤波(SEF)以及chap6系列案例展开,包含多个典型被控对象的仿真实现与绘图脚本,能帮助读者从基础原理走向实际搭建。已有1249人学习下载,适合通过实例快速上手ADRC仿真并开展控制策略优化。 做控制的人迟早会遇到一个让人头疼的场景:模型参数明明没变,仿真一切正常,可一到现场或换了台设备,之前调好的PID全废了。增益没改,系统却开始抖。原因无非是对象特性变了、负载变了,或者干脆就是建模时候的简化误差在作祟。我当年为解决这个问题折腾了很久,最后转到自抗扰控制器(ADRC)上,配合Simulink做开发和验证,才算找到一条相对稳妥的路径。

这篇内容适合两类人来看:一类是刚接触ADRC、想搞明白它到底怎么工作的学生或工程师;另一类是在Simulink里搭过模型但调不通、总发散,甚至不知道从哪里下手的人。我会把ADRC的三大组成部分、Simulink里的具体搭建方式、参数整定的实战顺序,以及我踩过的那些仿真坑,全部摊开来讲。整个过程不依赖别人的封装库,每个模块都自己搭,这样你才能真正理解它,而不只是会点“一键应用”。

  1. 自抗扰到底比PID强在哪:一个二阶对象的现实对照

先说清楚ADRC的出现背景。PID控制之所以普及,是因为它只需要误差、误差积分和误差微分,不依赖被控对象模型。但当对象参数摄动明显、外部扰动大时,PID的增益一旦固定,性能就会退化,严重时稳定性都保不住。ADRC的思路很简单:不要把控制问题拆成“模型精确描述+扰动量级估算”,而是把系统内部动态不确定性和外部扰动合并成一个“总扰动”,再用扩张状态观测器去实时估计并补偿掉。这套思想最早由韩京清先生系统提出,后续又发展出线性化版本LADRC,在工业控制里落地得很稳。

1.1 模型不确定性和外部扰动为什么难处理

想象你控制一个伺服电机驱动的位置系统。你用二阶模型近似它:Jθ̈ = Kt·u - T_load。但实际转动惯量J会随负载变化,摩擦也不是线性的,电机参数在发热后会漂移,负载力矩更是随时可能变。这一类“模型误差+未知外力”,在传统控制框架里分别对应参数不确定性和外部扰动。PID对付这些的方式是靠积分项慢慢把稳态误差压下去,但动态过程中这些扰动仍然会直接影响系统行为,你想提高响应速度,就容易把噪声也放大。

ADRC的做法是:把“J是标称值还是真实值”“摩擦到底多大”“负载力矩多少”这些问题全部丢进总扰动项。你不需要精确知道它,只要让观测器把它估出来,再在控制量里减去这个估计值,系统就变成了一个接近纯粹的二重积分器。这时候再用很简单的PD控制,性能也相当好。这就是它不依赖精确模型却能保持鲁棒性的根本原因。

1.2 ADRC三件套各自的任务边界

一个标准的ADRC由三部分组成:跟踪微分器(TD,Tracking Differentiator)、扩张状态观测器(ESO,Extended State Observer)、非线性状态误差反馈律(NLSEF)。用一句话概括它们的分工,TD负责“安排过渡过程”,ESO负责“看穿总扰动”,NLSEF负责“给出补偿力度”。

TD解决的是超调与快速性的矛盾。传统PID里,阶跃响应想要快,误差大,微分项会猛烈动作,容易超调。TD会把目标信号处理成一条平滑的过渡轨迹,控制器在初始阶段追的是“过渡目标”而不是那个陡峭的阶跃,超调自然被压下来。

ESO是整套方法的核心。它把系统状态x1(位置)、x2(速度)以及扩张出来的x3(总扰动)同时观测出来。你不需要额外装传感器,只需要对控制量u和测量输出y做处理,x3就会实时逼近真实的扰动。

NLSEF做的事情很直白:根据当前状态与TD给出的预期轨迹之间的误差,计算出一个反馈量,再把ESO估计的总扰动补偿进去。补偿完之后,控制器面对的“等效对象”就是一个标准二重积分器,处理起来非常顺手。

  1. Simulink里从零搭ADRC:不靠别人的封装,理解每一步

很多人习惯直接找现成的ADRC工具箱,但我强烈建议至少手搭一次。原因是ADRC的调参和排错高度依赖对内部结构的直观理解,你只有自己连过线、写过S函数,才能在发散的时候知道问题出在哪一层。下面我按TD、ESO、NLSEF三个模块,分别说明Simulink中的搭建方法。

2.1 安排过渡过程:最容易被省略却最有价值的一环

TD的用处很多人低估了。实际上,即使不加ESO,只把PID前的参考信号换成TD的平滑输出,超调都会有明显改善。TD的离散形式常采用最速控制综合函数fhan:

function [xh, xh_dot] = fhan(x1, x2, v, r, h0) d = r * h0 * h0; a0 = h0 * x2; y = x1 - v + a0; a1 = sqrt(d * (d + 8 * abs(y))); a2 = a0 + sign(y) * (a1 - d) / 2; a = (a0 + y) * (fsg(y, d)) + a2 * (1 - fsg(y, d)); fhan = -r * (a / d) * fsg(a, d) - r * sign(a) * (1 - fsg(a, d)); xh = x1 + h0 * x2; xh_dot = x2 + h0 * fhan; end function f = fsg(x, d) f = (sign(x + d) - sign(x - d)) / 2; end

在Simulink里搭建TD,推荐用MATLAB Function模块。输入是参考信号v,输出是过渡位置x1和过渡速度x2。两个输出分别通过积分器反馈回来,形成闭环过程。这里要注意,循环里不能直接用普通积分器,否则有代数环问题。最好把整个递推写进一个函数里,状态用persistent变量保存,或者用单位延迟模块(Unit Delay)形成离散状态,否则你会在仿真时报代数环错误。

如果你只是想先跑通理解流程,TD可以简化为一个低通滤波器配置的软启模块。但既然做ADRC,我建议一步到位用离散fhan,因为它在参数r(跟踪速度)和h0(滤波因子)上物理意义明确,对后续调参好处很大。

2.2 扩张状态观测器:整个控制器的灵魂

ESO的连续形式可以写成:

ẑ1 = z2 + β1·(y - z1) ẑ2 = z3 + β2·(y - z1) + b0·u ẑ3 = β3·(y - z1)

其中z1观测输出y,z2观测速度,z3观测总扰动,u是控制量,b0是控制增益的估计值。在Simulink里搭这个结构很简单:三个积分器串联,误差项(y - z1)加权后分别反馈到三个积分器输入端。实际使用时我强烈建议使用离散形式:

function [z1, z2, z3] = eso_discrete(y, u, z1, z2, z3, dt, beta1, beta2, beta3, b0) e = y - z1; z1 = z1 + dt * (z2 + beta1 * e); z2 = z2 + dt * (z3 + beta2 * e + b0 * u); z3 = z3 + dt * (beta3 * e); end

在Simulink里,用MATLAB Function模块承载这个函数,状态z1、z2、z3通过Unit Delay反馈回来。这样比连续积分器更适合后续做定点仿真和代码生成,也不容易出现连续积分器在固定步长下漂移的问题。

ESO的参数β1、β2、β3可以根据带宽ωo整定。最经典的方案是配置成观测器特征多项式s³ + β1·s² + β2·s + β3 = (s + ωo)³,于是β1 = 3ωo,β2 = 3ωo²,β3 = ωo³。后面整定部分我会细讲。在连线时务必注意,b0·u这路输入要接到第二个积分器的输入端,否则ESO对控制效果的估计就不完整。

2.3 非线性反馈律:把扰动补偿回去

NLSEF常规写法是用非线性函数fal:

function f = fal(e, alpha, delta) if abs(e) > delta f = sign(e) * abs(e)^alpha; else f = e / (delta^(1 - alpha)); end end

反馈律可以写成:u0 = k1·fal(e1, α1, δ) + k2·fal(e2, α2, δ),其中e1是TD过渡位置与观测器输出z1的差,e2是TD过渡速度与z2的差。最终的控制器输出为u = u0 - z3/b0。这里有个很关键的细节:扰动补偿方向。

实际工程中,我的建议是先用线性版本,也就是把fal换成普通放大:u0 = k1·e1 + k2·e2。非线性版本的优势在理论边界更清晰,但在强噪声环境下,α小于1的fal会把测量噪声放大,反而不如线性反馈稳定。你完全可以从LADRC(线性自抗扰)入门,跑通了再改非线性对比效果。

搭建时,把u = u0 - z3/b0的计算放在一个单独的模块里,方便观测补偿项到底有多大。调试时我会同时把z3和b0·u的数据存到工作区,看补偿量是否和实际扰动同一数量级。如果差得太多,往往说明b0估得不对。

  1. 参数整定不是玄学:从带宽概念落地的实操顺序

ADRC给人的第一印象是参数太多了:TD有r和h0,ESO有β1β2β3,反馈有k1k2,还有b0。新手会直接被劝退。但当你把参数映射到“观测器带宽ωo”和“控制器带宽ωc”上就好办多了。

3.1 先把观测器带宽定下来,再谈控制器

ESO的核心任务是让z3足够快地逼近真实扰动。带宽ωo决定ESO跟踪速度,ωo越大,观测越快,但对噪声越敏感。一般建议ωo比期望的闭环控制带宽ωc大3到10倍。为什么?因为ESO需要在控制器动作的时间尺度内就把扰动估出来,如果ωo低于或接近ωc,则扰动补偿滞后期,等效于在回路里串了惯性环节,会拖累稳定性。

具体到离散实现,要保证采样频率fs满足fs >> ωo/(2π),经验值建议fs至少是ωo对应频率的20倍以上。这一步是很多人忽略的坑:在Simulink中默认连续仿真没问题,但一旦切到离散求解器或做代码生成,采样时间太慢,ESO表现会急剧恶化。

3.2 一组可复现的参数和调参检查表

假设被控对象是二阶系统,我给出一个可复现的初始参数思路。设期望控制带宽ωc = 20 rad/s,观测器带宽ωo选5倍,即100 rad/s,采样步长dt = 0.001 s。

表:ADRC初始参数一览(线性形式)

参数计算方式示例值说明
β13ωo300观测位置误差反馈
β23ωo²30000观测速度误差反馈
β3ωo³1000000总扰动误差反馈
k1ωc²400位置误差反馈增益
k22ωc40速度误差反馈增益
b0对象控制增益估计由对象决定需要单独辨识

b0怎么估?最简单的方式是做开环阶跃仿真:给对象一个恒定的控制输入u0,记录输出加速度稳态值a0,b0 ≈ a0/u0。对于伺服系统,如果你知道J和力矩系数Kt,b0 = Kt/J。如果你只是随手搭的仿真模型,用这种方式测量最靠谱。

调参顺序按我的经验是:先固定一个b0的初始估计,把ωo调到仿真不抖,再把ωc逐渐加上去。如果系统开始震荡,优先看观测器输出z1和实际输出y之间的误差曲线。如果误差曲线噪声很大,说明ωo太大或采样步长不够;如果误差曲线有明显滞后,说明ωo相对ωc偏低。调完这两个带宽再去动b0,b0只在小范围修正,一般不要来回动。

  1. 调不通的时候别急着改增益:我在Simulink里踩过的真实坑

这一节想重点和大家分享排查思路。仿真发散,十有八九不是ADRC算法本身的问题,而是实现方式有问题。这里列出三类最常见的坑,每一类我都给完整的排查链路。

4.1 代数环和采样步长导致的假发散

问题现象:模型一开始仿真就报“Algebraic loop detected”或者干脆结果变成NaN。

排查链路:先检查ESO中是否存在纯比例反馈构成的闭环。ESO的误差项e = y - z1经过增益后立刻又反馈到z1的积分输入,如果用的是连续积分器,有积分器隔断不会形成代数环;但如果你为了省事把z1直接连到z1的初始值上,或者用了增益模块后直接和输入相加,就会形成代数环。解决办法是在反馈回路里插一个Unit Delay,形成离散状态量。另一个常见原因是采样步长设置过大:仿真步长超过ESO离散递推的稳定性边界,也会导致数值发散。检查方式是逐步缩小固定步长,看发散是否消失。

4.2 微分信号噪声放大:一阶惯性代替纯微分的实际操作

现象:仿真结果看起来振荡,尤其当测量信号有噪声时,控制器输出特别“炸”。

原因:ESO中z2和z3本质上是微分作用的广义扩展,如果y有高频噪声,经过观测器增益放大后,会直接影响控制量。解决手段不是降低ωo到牺牲性能,而是在测量链路里加一阶惯性滤波,或者用低通滤波器处理输出反馈。具体操作:在Simulink的y信号入口串联一个Transfer Fcn 1/(Tf·s + 1),Tf取采样步长的5到10倍。也可以一步到位使用离散滤波模块。这一步看起来很简单,但在实际工程中,这个滤波器的存在几乎决定你能否把ωo调上去。

4.3 饱和与积分器输出限制:ESO需要知道执行的边界

现象:系统一开始正常,一旦给定大幅阶跃,控制量猛烈冲顶,随后系统大幅回摆,甚至振荡不收敛。

排查链路:控制器输出u已经进入饱和区,但ESO内部还按照未饱和的u去计算b0·u,此时ESO对系统的估计会产生偏差。更麻烦的是,积分器输出可能持续累积,造成“积分饱和”。解决方法是把实际执行器的饱和限幅反馈给ESO,确保ESO里用的u和控制对象真正接收到的u一致。在Simulink里,你在Saturation模块前后分别拉一条信号线,通过开关选择饱和后的实际输出,再送进ESO的b0·u输入口。这一处修改,能让系统在大给定下稳定得多。

  1. 从Simulink模型走向实际工程:代码生成和联合仿真的要点

做ADRC最大的成就感,是模型从仿真跑通到最终能生成C代码嵌入控制器。这一路有很多细节,一步没处理好,离线仿真和高保真联合仿真结果会差很多。

5.1 模型配置与定步长设置

如果你计划做代码生成,请一定把Simulink配置为固定步长(Fixed-step)离散求解器。推荐离散求解器(如discrete),采样时间设为与ESO递推一致的时间步长。连续求解器在代码生成时会产生复杂的积分器算法,生成的代码体积大,而且可能在硬件上跑出与仿真不一致的结果。

在代码生成之前,请把模型里的“MATLAB Function”所用到的变量全部显式声明为参数,这样可以用Simulink的Parameter Optimization或External Input方式从工作区注入参数。热搜词里提到的“Simulink主模型参数导入方法详解”,其实就是指在模型里用Simulink.Parameter封装增益、带宽等参数,再通过set_param或模型参数文件批量更新。这样做的好处是当你在上层脚本里做参数扫描时,不需要打开模型编辑器,直接修改工作区参数就能批量跑仿真或生成不同配置的代码。

代码生成环境下,ADRC的时序稳定性是首要问题。ESO的递推计算必须在每个采样周期内完成,因此代码生成后要在目标硬件上测量最大任务执行时间。如果远超采样周期,系统会“跑飞”。在嵌入式环境里,我通常会把ESO递推放到高优先级定时中断里,NLSEF的计算可以放在同一个中断里连续执行。

5.2 与Carsim/AMESim等联合仿真的接口要点

联合仿真是ADRC验证的必经之路,尤其是汽车、机器人这类场景。以Carsim和Simulink联仿为例,Carsim提供车辆动力学模型,Simulink里跑你的ADRC控制器。接口上有几个关键点需要关注:

  • 时间步长匹配:Carsim通常以固定步长输出车辆状态,Simulink侧的控制器采样频率如果比Carsim高,需要把控制器输入做零阶保持;如果比Carsim低,需要做状态插值。否则会造成控制率与车辆模型更新不同步。
  • 变量单位:ADRC对量纲极其敏感。速度是km/h还是m/s,角度是deg还是rad,差一截控制效果就全变了。联仿前务必先做单位一致性检查。
  • 接口延迟:Carsim与Simulink之间数据交换存在采样保持延迟,这个延迟在高频控制下会显著降低系统稳定性。因此联仿时不宜把ωc调得过高,否则仿真结果会振荡,但实际控制器可能没问题——这个现象容易误判。

至于四旋翼、无人机方向,用Simulink搭VSG或滑模控制,思路类似:把对象模型替换成飞行器动力学,ADRC作为姿态控制内环。ESO对传感器噪声和模型不确定性的抵抗能力,在飞行器上比在电机上更明显,因为空气动力学参数不确定性非常大。

扩展一个实用小技巧:在联合仿真里,如果遇到系统振荡,先在Simulink的Scope里同时观测z3和实际外部扰动(如果有该信号可测),看ESO是否准确跟踪。如果z3噪声过大但在合理范围,优先怀疑测量信号跳变;如果z3与实际扰动相位差太大,优先怀疑ωo不足或仿真步长过大。

写到最后,我想分享一个个人体会:ADRC在Simulink里的搭建并不难,真正的门槛在于调试时的“工程直觉”。我至今保留一个习惯,任何模型都先跑开环,用Scope记录输入输出数据,确认对象增益b0大致靠谱后再闭合控制环。凡是跳过这一步直接调参的,最后基本都会绕回来。如果你也正在调ADRC,不妨从今天起把ESO的z3当作最重要的那个观测量来对待,盯住它,很多问题会提前暴露。

本文还有配套的精品资源,点击获取

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

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

立即咨询